Leaderboard adds summary: ranked wallets, the S, A and B split, and 30-day active count
GET /api/v1/leaderboard now sends a top-level summary with the whole board’s totals, the counts the leaderboard page shows. The get_leaderboard MCP tool returns it too.Before this change, the only way to count the board was to walk every page.ranked_total: ranked wallets on the board.by_grade: theS,AandBcounts, which sum toranked_total.active_30d: wallets whose last trade was 30 days or less beforeas_of, the walletsactivity=activelists.as_of: when the totals were counted.- Sent on every page with no
categoryand nostrategy, whatever thesort,dirandactivity. Left out on acategoryorstrategypage, which lists a subset these totals do not describe, and while the ranking has no totals for the page’s generation.
meta are unchanged.What to change: Nothing. To read the board’s size or grade split, read summary from any unfiltered page, for example ?limit=1.Webhooks now send an event about a second after it happens, not up to 10 seconds later
Webhooks now start delivering an event as soon as it is recorded. In a local measurement of 7 events, an event reached the receiver a median of 0.35 seconds after it was recorded, and at most 1.5 seconds.Before this change, a webhook sender with nothing to send checked for new events every 10 seconds. On November 3,
large_trade_inserted_v2 deliveries were created 5.5 to 9.3 seconds after the same trade reached GET /api/v1/stream.- A redelivery from
POST /api/v1/webhooks/{id}/deliveries/{delivery_id}/redeliverstarts at once too. - Retries keep their schedule, and the 10-second check stays as a backstop.
Pick of the Day reads take view=summary, pick_id, archive paging and stats sections
GET /api/v1/pick-of-the-day, /archive and /stats now take optional parameters that return a smaller body. The remote MCP tools get_pick_of_the_day, get_pick_of_the_day_archive and get_pick_of_the_day_stats now send view=summary unless you name a view.Before this change, each read returned the whole record. On November 3, 2026 that was 198,807 bytes for one day’s 14 picks, 1,124,089 for the archive and 151,774 for the stats. Claude Code saves an MCP result over 50,000 characters to a file instead of showing it, so an agent got no numbers.view=summaryon the pick returns each pick’s identity, side, price, result, return and grade, about 600 bytes a pick.pick_idreturns one pick, and the MCP tool reads it in full by default.view=summaryon the archive returnshit_ratewithoutseriesand 50 short rows.limit(1 to 200) andcursor(frompage.next_cursor) page the rows in either view and addpage.view=summaryon the stats leaves outseries,days,categories,monthsandvoids, the sections that grow with the record.sectionsreturns only the sections you name, withseriesas its own key.- An unknown
vieworsectionsname or acursorthe route did not return is400 bad_requestwitherror.paramnaming it.limitbehaves like every list limit: a value outside 1 to 200 is clamped, and one that is not an integer is400 invalid_query.
@0xinsider/mcp 3.12.0 has the same tools and defaults. Its release is pending; 3.11.0 and earlier still send no view.@0xinsider/sdk 0.38.0 types the new parameters. It types the archive and stats data as the full body or the smaller one, so TypeScript code that reads them checks "view" in data first; the full body has no view key. Its release is also pending.Backward compatible for REST. A request without the new parameters gets the same body and ETag. Breaking for remote MCP clients that read fields the summary leaves out, such as display_holders or entry_authorization.What to change: Over MCP, send view: "full", or call get_pick_of_the_day with pick_id for one pick in full. Over REST, nothing.Edge Hunter reads category=sports as kind=sports in every view
GET /api/v1/edge-hunter now reads category=sports, in any casing, as kind=sports (every sport except Esports) in the board, signals and observations views, and in the MCP get_edge_hunter tool. The response’s selection carries kind: "sports" beside category: "sports". This matches the meaning category=sports has on every other route.Before this change, sports was not a sport name, so it answered an empty result with selection.category set to non_sport.category=sportstogether withkind=esportsnow returns400withparam: "category", because the two select nothing in common.- Any other category that is not a sport still returns an empty result with
category: "non_sport".
category=sports with kind=esports, which now get 400 instead of an empty result. Every other request that answered empty now answers the sports rows.What to change: Nothing. Use kind=sports or category=sports for every sport except Esports, and do not send category=sports with kind=esports.category=sports now matches every sport but Esports, not just the catch-all Sports
category=sports, in any casing, now matches every sport except Esports on GET /api/v1/large-trades, GET /api/v1/large-trades/history, their whale-trades aliases, GET /api/v1/leaderboard, GET /api/v1/markets/explore, GET /api/v1/markets/sharp-money-flows and its smart-money-flows alias, and GET /api/v1/trader/{address}/categories. The MCP tools on those routes match the same way. The set includes Sports, the category Polymarket uses for sports with no category of their own, such as cycling and lacrosse.Before this change, sports matched only that Sports category. Its newest large trade was from July 9, 2026, so category=sports on large trades answered with July trades as the newest, and history and leaderboard reads answered an empty page.GET /api/v1/positions,GET /api/v1/large-positionsandGET /api/v1/markets/searchfilter by one category, sosportsnow returns400withparam: "category"there. Before this change it returned an empty list, andSportsreturned that category alone.GET /api/v1/leaderboardnow matches every sport name in any casing. Before this change,cricketreturned an empty page and onlyCricketmatched.- A leaderboard cursor taken with
category=Sportsnow returns400 cursor_expired, because the filter means a different set of wallets. - On market explore, the
Sportsvalue infacets.categoriesstill counts the catch-all category alone.
category=Sports to read the catch-all category alone. That category can no longer be selected on its own.What to change: Send sports for every sport except Esports, esports for Esports, or omit category for both. On positions, large positions and market search, omit category or name one sport, and handle 400 on the parameter.A filtered stream resumes with Last-Event-ID after a short restart or a backend release instead of resyncing
GET /api/v1/stream now keeps a filtered connection’s position on the server while it drops frames its filter excludes. A reconnect that sends the last id you received replays from where the connection stopped.Before this change, a filtered connection usually answered event: resync with completeness.status: "truncated" on reconnect, even after a one-second restart. The replay window holds the newest 4,096 feed events of every type, about 15 to 30 seconds in the evening, and the last id a filtered client receives (for example event=LargeTradeInsertedV2 with min_size) is often minutes older. Every backend release closes open streams, so a filtered client resynced on every release.- The window itself is unchanged and is now stated in the endpoint description: it is counted in all feed events, not in the frames you receive.
- A client away longer than the window still gets a
truncatedresync. Catch up large trades withGET /api/v1/large-trades?since=orGET /api/v1/events/feed/since. - The wire format does not change: no new frame type, header or parameter.
@0xinsider/sdk 0.36 and a browser EventSource.What to change: Nothing. Keep sending Last-Event-ID with the last id you processed.SDK 0.37.0 sends the stream's wallet and min_size filters
@0xinsider/sdk 0.37.0 adds wallet and min_size to StreamFilters, so streamFeed, streamFeedResilient and consumeStreamCheckpointed can send the GET /api/v1/stream filters of the same names.Before this release, the SDK could send only event, condition_id and min_grade. A whale-alert consumer received every large trade and dropped small ones itself, and could not follow one wallet on the stream.walletis a0xaddress in any case.min_sizeis a USD notional floor,number | string; pass a string such as"25000"to send it exactly.- Both narrow
LargeTradeInsertedV2frames. With either set, every other frame type is excluded, as on the endpoint. - A malformed value is refused with
400bad_requestandparamnaming the filter; the resilient consumers do not retry it.
wallet or min_size instead of filtering large trades in your handler.Trader breakdowns state which settled positions the entry-price buckets leave out
entry_price_analysis in GET /api/v1/trader/{address} with expand=breakdowns now carries coverage, and the first entry-price band starts at $0.Before this change, the buckets left out every settled position with no single entry price and said nothing about it. On one wallet they held 9,121 of 12,811 settled markets, and $2.99M of the $5.79M those markets made, and a client reading them beside pnl.in_scope could take them for the whole record.coveragecarriessettled_marketsandsettled_pnl(every settled sports and esports position the section reads),bucketed_marketsandbucketed_pnl(the buckets’ sums), andunbucketed, one row per reason withmarketsandtotal_pnl. The bucketed and unbucketed markets add up tosettled_markets.reasonistwo_sided(both outcomes bought, a hedged position with no single side),held_side_unpriced(the outcome the wallet held has no recorded price),no_entry_price(no outcome has one), orfreshness_unknown(a bucket whose data has no freshness time yet).settled_pnladds up the wallet’s per-market realized P&L. It differs frompnl.in_scope.realized_pnl, which is Polymarket’s published sports and esports P&L with a share of the wallet’s rebates.- The first band is
$0.00-$0.19instead of$0.01-$0.19, and now holds positions bought below $0.01, which had no bucket before. - The MCP
get_tradertool, the OpenAPI document and the SDK types describecoverage.
coverage is a new field, and every other band keeps its label.What to change: If you match the band label $0.01-$0.19, match $0.00-$0.19 instead. Read coverage.unbucketed before you compare the buckets’ totals with pnl.in_scope.Rust crate oxinsider 0.5.0 adds game sharp money, game holders, and 10 more operations
The Rust crate
oxinsider 0.5.0 covers all 106 operations in the OpenAPI document. It adds get_game_sharp_money, get_game_holders, list_game_markets, list_game_trades, and 8 more methods.Before this change, oxinsider 0.4.0 covered 94 operations. ListGamesParams had no expand, Game had no sharp_money or volume, and a 200 response did not return its ETag.- New methods:
get_game_sharp_money,get_game_holders,list_game_markets,list_game_trades,get_market_top_holders,get_market_wallets_in_out,get_pick_of_the_day_stats,get_resolution_report,get_sport_leaderboard,list_category_leaders,get_category_leaderboard, andlist_leaderboard_leagues. ListGamesParams::expandtakessharp_money, andGamehassharp_moneyandvolume.- Each of the 55 operations whose
200carries anETaghas a..._with_etagmethod, such aslist_games_with_etag. It returnsTagged { data, etag }. Sendetagasif_none_matchon the next read. get_game_holderstakescondition_idsas a&[&str]argument.
LeaderboardEntry.grade, which is Grade instead of String. The other methods keep their signatures.What to change: Run cargo add oxinsider@0.5.0. Where a field is now an enum, read it with as_str().Game holders returns a market's last holder list, marked stale, while a fresh one computes
GET /api/v1/games/{event_slug}/holders and the MCP get_game_holders tool now answer a market whose fresh holder list is not ready within the request with the last list computed for it in the past 15 minutes. That item carries stale: true and its own computed_at.Before this change, such a market was a per-item error with reason read_model_warming, even when GET /api/v1/market/{condition_id}/holders had served it minutes earlier. A cold holder scan takes longer than the batch can wait: on November 3 it took a median of 0.8 seconds and up to 13 seconds.- Every item now has
stale. It isfalseon a fresh list andtrueon the last list, which can be up to 15 minutes old. - The fresh list keeps computing, so a read a few seconds after a
staleitem normally returnsstale: false. - A market refused over the account’s cold-read share is served its last list the same way. It is a per-item
cold_read_limitederror only when it has none. - A market with no list in the past 15 minutes is still a per-item error with
reasonread_model_warmingandretry_at. Itscodestaysrate_limit_unavailablefor compatibility, so branch onreason.
stale is a new field, and no existing field changes meaning.What to change: Nothing. Read stale and computed_at if your client needs holders newer than 15 minutes, and read the market again a few seconds later for the fresh list.Go client v0.6.0 decodes every number as float64, so money keeps its cents
The Go client
github.com/0xinsider/0xinsider-go v0.6.0 decodes every JSON number as float64. Money and price fields such as LargeTrade.SizeUsd, LargeTrade.Price, and GameSharpMoney.TotalUsd keep the precision the API sends.Before this change, 478 fields decoded as float32. Above $262,144 a `float32` cannot hold every cent: a trade the API sent as `351389.57` decoded as `351389.5625` and printed as $351,389.56.- Regenerated from the current OpenAPI document, 106 operations.
- The README and examples read
OXINSIDER_API_KEY, the variable the TypeScript and Rust clients read. They readOXI_API_KEYbefore. oxinsider.Versionand theUser-Agentheader say0.6.0. v0.5.0 through v0.5.2 sent0.4.0.
float32 variables or parameters for these fields. It no longer compiles.What to change: Run go get github.com/0xinsider/0xinsider-go@v0.6.0 and change those float32 declarations to float64.Sandbox games list sharp money and game market lists match the per-game routes
In the sandbox,
GET /api/v1/games with expand=sharp_money now gives each game the same sharp_money body that GET /api/v1/games/{event_slug}/sharp-money returns for it. GET /api/v1/games/{event_slug}/markets lists only that game’s markets.Before this change, the first game in the sandbox list carried sharp_money: null and every other game a placeholder with object: "sandbox". A game’s market list also included markets from other sandbox games.- Without
expand=sharp_money, sandbox games carry nosharp_moneyfield, as in the live API. With it,meta.costrises by 3. - The
sportfield of a sandbox game carries a sport, such asFootballorBasketball, and no longer a league, such asNFL. - The live API is unchanged.
Sandbox game routes answer about the game named in the path
In the sandbox,
GET /api/v1/games/{event_slug} and its markets, sharp-money, trades, and holders routes now describe the game that GET /api/v1/games lists under that slug. A slug the sandbox list does not include returns 404 not_found with the live API’s message.Before this change, each route returned its documented example for every slug. listGames() followed by getGameSharpMoney() on the listed Bills vs. Jets game returned Celtics vs. Magic outcomes.- Sharp money uses the side labels from the game’s own winner market. For
nfl-buf-nyj-2026-09-22, that is “Buffalo Bills” and “New York Jets”. - Market slugs, outcome labels, and market titles name the requested game.
- A slug from a live example, such as
nba-bos-orl-2026-11-01, is not a sandbox game and returns404. Use a slug from the sandbox’sGET /api/v1/games. - The live API is unchanged.
404 instead of a sample 200.What to change: In the sandbox, take event_slug from GET /api/v1/games.Trader adds the profile's Edge badge and its strategy breakdowns with expand=breakdowns
GET /api/v1/trader/{address} now returns price_edge, the wallet’s edge over the price it paid, and takes expand=breakdowns for the measures on the wallet profile’s Insights tab.Before this change, neither was in the API: the Edge badge and these measures were on the profile page only.price_edgecarriesmarkets,hit_rate,avg_price,edgeandstatus, as on Leaderboard. Whenstatusisranked,shrunk_edgeis the number the badge shows and the leaderboard ranks, withfield_average, the average it is pulled toward. It is left out for a wallet with no settled sports or esports market of $20 or more.breakdownscarrieshourly_performance(by UTC hour),time_breakdowns(hold-time and four-hour bands),risk_metrics(position sizes and categoryconcentration) andentry_price_analysis(five entry-price bands). It is computed on each request, so a wallet with a long history can take several seconds, and a read past 30 seconds answers503withRetry-After.POST /api/v1/traders/batchrefusesexpand=breakdownswith400andparam: "expand".- The MCP
get_tradertool takesbreakdownsinexpand, and the OpenAPI document and SDK types describe both fields.
breakdowns is sent only when requested, and no existing field changes.What to change: Nothing. Read price_edge for the edge, and request expand=breakdowns on single-wallet reads that need the strategy measures.Webhooks dead-letter paid event deliveries while the account has an unpaid invoice
Webhook deliveries of a paid event type now move to
dead_letter while the owning account has an unpaid invoice, with last_error set to delivery owner has an overdue invoice (payment_overdue); pay it, then redeliver this delivery. Free event types still deliver.Before this change, those deliveries kept going out while the same account’s API requests answered 402 with error.reason payment_overdue.- Each refused delivery stays on Webhook deliveries for 7 days, so nothing is dropped without a record.
- After the invoice is paid, requeue each one with Redeliver a webhook delivery, or catch up from Event replay.
- Endpoint verification is refused for the account until it pays.
status and last_error keep their meaning; the message is new.What to change: If you match on last_error, handle the new message, and redeliver the refused deliveries once the invoice is paid.@0xinsider/mcp 3.11.0 and @0xinsider/sdk 0.36.0 are published with the payment_overdue 402
@0xinsider/mcp 3.11.0 and @0xinsider/sdk 0.36.0 are on npm. They carry the November 2 and 3 entries below.- On a
402 payment_overdue, the local MCP server’s tool error ends “Pay the overdue invoice at https://0xinsider.com/billing.” and its error detail carriesbilling_url. On a lapsed plan it ends “Reactivate at https://0xinsider.com/billing.” - The SDK’s
SubscriptionRequiredErroraddsbillingUrlandpaymentOverdue, andAPI_ERROR_REASONSlistspayment_overdue. get_edge_hunterand the SDK takeadmission: "most_held_48h_by_type"withmarket_types.list_gamesandget_gamedescribe a tennis game’stournament, and the SDK exportsGameTournamentandGameTournamentLocation.- Tool descriptions and
max_age_saccept thestaledata quality status. The SDK’sassessDataQualitypasses astalegroup that is within yourmaxAgeMs. 0xinsider api call batchGetTradersandbatchGetMarketFlowreload a stored session once on a401, as every GET already did.
@0xinsider/mcp@3.11.0.Backward compatible. Both releases add fields, values, types and messages. Nothing is removed or renamed. One behavior changes: SDK 0.35.0 failed a stale group in assessDataQuality as an unrecognized status, and 0.36.0 passes it when it is within maxAgeMs.What to change: Run npm install --global @0xinsider/mcp@3.11.0, or change a client’s @0xinsider/mcp@3.10.0 argument to @0xinsider/mcp@3.11.0. Run npm install @0xinsider/sdk@0.36.0 to read billingUrl and paymentOverdue. 0xinsider --version prints 3.11.0 once the update is in place.A key whose account has an unpaid invoice answers 402 payment_overdue
Every
/api/v1 request made with a key whose account has an unpaid invoice now answers 402 with error.code subscription_required, the new error.reason payment_overdue, and the new error.billing_url. The remote MCP endpoint answers the same 402, and GET /api/v1/me and GET /api/v1/usage still answer 200.Before this change, a key kept full Pro or Max access while Stripe retried a failed renewal payment, which can take weeks.payment_overduemeans the latest subscription ispast_dueorunpaid. Paying the invoice atbilling_urlrestores the same key with its settings; no new key is needed.billing_urlishttps://0xinsider.com/billing. It is present on everysubscription_requirederror, includingsubscription_inactive.@0xinsider/sdk0.36.0 addsbillingUrlandpaymentOverduetoSubscriptionRequiredError.@0xinsider/mcp3.11.0 names the billing link in the tool error and the CLI message.- A retry does not clear the error. Only a person paying the invoice does.
payment_overdue and show billing_url to the account owner. If you already handle subscription_required, nothing.Edge Hunter admission=most_held_48h_by_type ranks markets within each type
GET /api/v1/edge-hunter with view=observations&cohort=wider_holder now takes admission=most_held_48h_by_type with market_types. It runs the most_held_48h rule within each type you name: per sport and per market_context.market_type, up to 8 markets with the most graded holders, at most 4 per event. The get_edge_hunter MCP tool takes the same value, and the SDK’s admission and admitted_by types gain it in the next @0xinsider/sdk release.Before this change, admission=most_held_48h ranked every market type together and market_types filtered the rows afterwards. A type whose markets hold fewer wallets than team-win markets could return no rows: Soccer totals returned none on November 3, 2026, from 286 candidates.- Every row has
admitted_by: "most_held_48h_by_type", the response echoesadmission, and it has nodepartures. market_typesis required, each value 1 to 64 lowercase letters, digits or underscores. A missing or malformed value returns400witherror.param: "market_types".- A sport and type pair nobody asked for in the last 30 minutes returns
503read_model_warmingwithRetry-After: 45once, while the next snapshot computes it. - The default response and
admission=most_held_48hdo not change.
admission=most_held_48h_by_type&market_types=totals&category=Soccer and retry once after a 503. See Most-held selection by market type.Tennis games carry their tournament: tier, city, venue time zone, dates and draw
GET /api/v1/games and GET /api/v1/games/{event_slug} now return tournament on a tennis game: the tournament edition its game page states, from 0xinsider’s registry curated from official ATP, WTA and ITF pages. The list_games and get_game MCP tools and the CLI’s api call listGames and api call getGame return it too.Before this change, no V1 route, MCP tool or CLI command named a tennis game’s tournament tier, place or local time. A client had only the competition label in league.tournamentholdslabel(the provider competition label, verbatim),tour(atp,wtaoritf),draw,tier(for exampleATP Masters 1000,ATP Challenger 50,ITF M15),location(city,country,country_code),timezone(the venue’s IANA zone),edition_year,starts_on,ends_on,source_urlsandchecked_on.drawisqualificationwhen the label ends, Qualificationandmainfor an unmarked ATP or WTA label. It is omitted for an unmarked ITF label, because Polymarket gives an ITF qualifying match the main draw’s label.tier,locationandtimezoneare omitted when no official page states them.tournamentis omitted outside tennis, when the registry holds no single edition for the game, and on a finished game read after it leaves the live board.- The SDK types gain
GameTournamentandGameTournamentLocationin the next@0xinsider/sdkrelease.
scheduled_at with tournament.timezone. See Tennis tournament.