data_quality reads stale when a clock is older than its writer's refresh budget
Every
data_quality block now judges each dated group against its writer’s refresh budget: fresh inside it, the new stale past it, with the budget in seconds as the new max_age_s. This covers GET /api/v1/positions, GET /api/v1/trader/{address}, POST /api/v1/traders/batch, GET /api/v1/trader/{address}/context, GET /api/v1/large-trades and GET /api/v1/large-trades/history.Before this change, a group read fresh whenever it had a timestamp, however old. One wallet’s positions page, reconciled 5 days earlier, said fresh while each of its rows said stale.max_age_sis 1800 for thepositionsgroup on positions, the age at which each row’s ownfreshnessreadsstale; 604800 (7 days) forsync,ranking,volumeandpositionson a trader and fortraderandrankingon positions and large trades; and 22620 forleaderboard_rank.- A group whose timestamp is written once, moves only when its value changes, or is written when a reader asks carries no
max_age_sand is neverstale:sports_record,market,tradeand the large-tradevolume. - The top-level
statusisstalewhen every group isfreshorstaleand at least one isstale.unavailableandpartialkeep their rules. - A cached trader response is judged again when it is served. A positions snapshot page (
consistency=snapshot) serves the block stored with its rows. - The
max_age_sceiling onGET /api/v1/trader/{address}and theget_traderMCP tool acceptsfreshorstale, so every request answers as it did before.error.freshness.data_quality_statuscan now bestale. - In the next
@0xinsider/sdkrelease, the types gain"stale"andmax_age_s, andassessDataQualitypasses astalegroup inside yourmaxAgeMs, as it passed that group before. Python0xinsider0.8.1 (assess_data_quality) and Gogithub.com/0xinsider/0xinsider-gov0.5.1 (AssessDataQuality) pass astalegroup inside yourmax_age/MaxAgethe same way; earlier releases failstaleas an unrecognized status, which errs toward refusing a body.
unknown. A client that accepts a body only when status is fresh now refuses one whose writer has missed a refresh.What to change: Treat stale as dated but overdue, and compare as_of with your own limit, as you did for fresh. Read max_age_s for each writer’s budget. If you only send max_age_s to the trader route, nothing.A trader's pnl.unrealized counts open positions only, without the profit of partial sells
GET /api/v1/trader/{address} pnl.unrealized is now the unrealized P&L of the wallet’s open sports and esports positions and nothing else: Polymarket’s cashPnl, current value minus cost, summed over the positions still held. It is the same figure as position_summary.unrealized_mtm on GET /api/v1/trader/{address}/context, and trader.pnl.unrealized in that body now equals it.Before this change, pnl.unrealized also added the realized P&L of partial sells on markets the wallet still held. pnl.in_scope.realized_pnl already counts those sells, so adding the two fields counted them twice, and one context body carried two different unrealized figures. On one wallet whose open positions were down about $2,500 on Polymarket, `pnl.unrealized` read $3,278.76 while unrealized_mtm read -$2,700.34.- The value changes only for a wallet that sold part of a position it still holds. Other wallets read the same figure as before.
- The
get_traderandget_trader_contextMCP tools, the CLI’straders getandtraders context, and the SDK’sgetTraderreturn the corrected figure with no update. - The OpenAPI descriptions of
pnl.unrealizedandposition_summary.unrealized_mtmstate the definition.
pnl.unrealized, or reconciled it against unrealized_mtm, remove that step. pnl.in_scope.realized_pnl plus pnl.unrealized now counts each sale once.Top holders send the last sample, marked source_stale, while a new one builds
GET /api/v1/market/{condition_id}/top-holders now answers a request that arrives after the sample’s 30 seconds are up with the market’s last sample and a new field, source_stale: true, while 0xinsider builds a new one. It does so only while that sample’s holder board is at most 300 seconds old.Before this change, such a request waited for the whole build, which took 0.4 to 2.5 seconds, mostly the read of Polymarket’s holder board. source_stale is false on every other response, including every response for a resolved market.positions_as_ofon a stale sample is at most 300 seconds old, where a fresh sample’s is usually up to about 150.- A request a few seconds later gets the new sample, with
source_stale: false. - A market with no recent sample still waits for the build.
- The MCP tool
get_market_top_holdersand the CLI’smarket top-holdersreturn the field too, and the SDK typeMarketTopHoldersgainssource_stale: boolean.
source_stale and request again a few seconds later when it is true.Top holders mark a wallet 0xinsider has no record of as untracked, not under the floor
GET /api/v1/market/{condition_id}/top-holders now sends category_win_rate_status: "untracked" for a holder 0xinsider has no record of. Such a holder also has grade, in_scope_realized_pnl and in_scope_volume set to null. not_enough_data now means only that the wallet is on file and has fewer than 5 decided markets in the category.Before this change, the route sent not_enough_data for both cases, so a wallet nobody had measured read as a small sample. On one live tennis market, 37 of the 44 holders marked not_enough_data had no record. When the wallet record read fails, an unmeasured holder now reads unavailable instead of not_enough_data.The OpenAPI enum on MarketHolder and PickHolder lists the new value too, because they share the type. GET /api/v1/market/{condition_id}/holders and Pick of the Day never send it: every wallet they list is graded and on file.Backward compatible for clients that treat an unknown status as “no rate”. The SDK type category_win_rate_status gains "untracked", so an exhaustive TypeScript switch on it needs a new case.What to change: Read untracked as “no record, nothing measured”, not as a small sample. Where you branch on the status, add the case.Esports score frames on the stream carry each finished map's winner and round score
GET /api/v1/stream LiveScoreChanged frames for an esports series now carry the same score as the market page: each finished map’s winner in live_score.scores[].map_results, each map’s round score in live_score.scores[].maps, and a game that Polymarket’s series state finished before the live feed counted it.Before this change these frames carried only the live feed’s series totals and current map, so a stream client saw map_results and maps empty during play.- 0xinsider composes each live esports series every 5 seconds or more, and sends a new frame when the composed score shows something the last frame did not. Such a frame can have the same
source_revisionas the frame before it. - Frames continue for up to 2 minutes after the series’ ended frame, so the final map’s winner and round score arrive after
ended: true. - Other sports are unchanged.
source_revision and drop a frame whose revision is not newer, accept an equal revision for an esports series, or you keep the earlier frame without the map history. Keep applying frames for a series after it ends.@0xinsider/mcp 3.10.0 and @0xinsider/sdk 0.35.0 are published with the past-date Pick of the Day read
@0xinsider/mcp 3.10.0 and @0xinsider/sdk 0.35.0 are on npm.- The CLI adds
0xinsider pick date <YYYY-MM-DD>, which reads a past date’s picks. - The local MCP server’s
get_pick_of_the_daytool takes the optionaldateargument the remote tool already takes. The tool count stays at 69. - The SDK adds
getPickOfTheDayForDate(date)andgetMarketTopHolders(conditionId, { limit }). - SDK 0.35.0 is the first SDK release since 0.32.0. It also adds the methods the entries below say arrive in a later release:
getGameSharpMoney,getGameHolders,listGameMarkets,listGameTrades,getMarketWalletsInOut,getSportLeaderboard,listLeaderboardLeagues,getCategoryLeaderboard,listCategoryLeaders,getResolutionReportandgetPickOfTheDayStats.listLeaderboardacceptssort,dirandactivity.
@0xinsider/mcp@3.10.0.Backward compatible. Both releases add commands, arguments and methods. Nothing is removed or renamed.What to change: Run npm install --global @0xinsider/mcp@3.10.0, or change a client’s @0xinsider/mcp@3.9.0 argument to @0xinsider/mcp@3.10.0. Run npm install @0xinsider/sdk@0.35.0 to use the new SDK methods. 0xinsider --version prints 3.10.0 once the update is in place.GET /api/v1/pick-of-the-day takes a date to read a past day's picks
GET /api/v1/pick-of-the-day now takes an optional date query parameter (YYYY-MM-DD, on the America/New_York calendar). With a past date, it returns that date’s picks as the website’s page for that date shows them, under the same plan access. The get_pick_of_the_day MCP tool takes the same date argument.Before this change, the endpoint served only today’s picks. A past date’s picks were readable only on the website, and the internal dated route refused an API key with 401.- Without
date, or with today’s date, the response is unchanged. - A past date returns no
scheduled_picks. - A past date with no published pick returns
404,error.codenot_found,error.paramdate, with noRetry-After. Do not retry it. - A date after today, or a malformed one, returns
400,error.codebad_request,error.paramdate. @0xinsider/sdk0.35.0 addsgetPickOfTheDayForDate(date), and the0xinsiderCLI in@0xinsider/mcp3.10.0 adds0xinsider pick date <YYYY-MM-DD>. Both were published on November 2, 2026.
date; the archive still lists every published pick with its pick_date.GET /api/v1/trader last_active is the wallet's last trade, not its sync time
GET /api/v1/trader/{address} now sends last_active as the time the wallet last traded, as the field was documented. POST /api/v1/traders/batch, GET /api/v1/trader/{address}/context, and the get_trader MCP tool send the same value.Before this change, last_active carried the time 0xinsider last synced the wallet, the same value as synced_at. On November 2, 2026, a wallet that last traded on August 26 showed a last_active from that morning.last_activenow matcheslast_activeonGET /api/v1/leaderboard, which refreshes it hourly.- A cached response can be up to 60 seconds behind a new trade.
last_activeis left out when 0xinsider has recorded no trade for the wallet, even if the wallet has synced. About 1 in 5 synced wallets had no recorded trade on November 2, 2026.data_quality’ssyncgroup no longer coverslast_active, and no other group does, because the value is itself the time of a trade. Withexpand=trust,trust.last_activenamestrader_last_activity.last_activity_atas its source, with freshnessunknown.
last_active to see whether a wallet still trades now gets the right answer.What to change: If you read last_active as the time 0xinsider last synced the wallet, read synced_at instead. Handle last_active being absent for a synced wallet.Game sharp money dollar fields serve a zero as 0, never -0.0
GET /api/v1/games/{event_slug}/markets with expand=sharp_money and GET /api/v1/games/{event_slug}/sharp-money now serve a zero dollar amount as 0.0, never -0.0. The list_game_markets and get_game_sharp_money MCP tools change the same way.Before this change, a side with no S, A or B holder could serve "usd": -0.0 on a line’s sharp_money.outcomes. On November 2, 2026, 2 of 14 computed lines on one NBA game did. These fields are sent unrounded, so the October 27 fix for rounded fields did not reach them.data[].sharp_money.outcomes[].usdon the game markets list.data.outcomes[].usd,data.total_usdanddata.unallocated_usdon game sharp money.- Only the sign of a zero changes. Every other value keeps its full precision.
-0.0 and 0.0 are equal numbers, and no field or type changes.What to change: Nothing. A guard that hid -$0.00 in your client is no longer needed.@0xinsider/mcp 3.9.0 is published with every CLI and MCP addition since 3.7.0
@0xinsider/mcp 3.9.0 is on npm. It is the first release after 3.7.0 and carries every CLI command and MCP tool added since. Version 3.8.0 was never published, so the sport leaderboard entry below now names 3.9.0 for its commands.The local MCP server goes from 55 tools in 3.7.0 to 69. The new tools are get_account, get_usage, get_sport_leaderboard, get_leaderboard_leagues, get_category_leaderboard, list_category_leaders, get_market_top_holders, get_market_wallets_in_out, get_game_holders, get_game_sharp_money, get_game_trades, list_game_markets, get_resolution_report and get_pick_of_the_day_stats. The CLI adds these commands:leaderboard sport <sport>,leaderboard leagues,leaderboard category <slug>andleaderboard categories.market top-holders <condition_id>andmarket wallets-in-out <condition_id>.games sharp-money <event_slug>andgames trades <event_slug>.pick statsandreports resolutions <date>.
@0xinsider/mcp@3.9.0.Backward compatible. This adds commands and tools. No command or tool is removed or renamed.What to change: Run npm install --global @0xinsider/mcp@3.9.0, or change a client’s @0xinsider/mcp@3.6.1 or @0xinsider/mcp@3.7.0 argument to @0xinsider/mcp@3.9.0. Refresh a cached tools/list. 0xinsider --version prints 3.9.0 once the update is in place.GET /api/v1/leaderboard last_active is the wallet's last trade, not its sync time
GET /api/v1/leaderboard entries now send last_active as the time the wallet last traded, as the field was documented. The get_leaderboard MCP tool returns the same value.Before this change, last_active carried the time 0xinsider last synced the wallet, which is usually within the past week whether or not the wallet traded. On November 2, 2026, all 100 wallets on an activity=inactive page showed a last_active from the past 7 days, though none had traded in 30 days.last_activenow agrees with theactivityfilter: every walletactivity=inactivelists last traded more than 30 days ago.- The value is refreshed hourly, so a trade in the last hour may not show yet.
- The field name, its type, and when it is left out are unchanged.
GET /api/v1/trader/{address}still sends itslast_activeas the sync time, the same value assynced_at. A later change will correct it.
last_active to see whether a wallet still trades now gets the right answer.What to change: Nothing. If you read last_active as the time 0xinsider last synced the wallet, read synced_at on GET /api/v1/trader/{address} instead.Market top holders lists a market's largest holders of every grade
GET /api/v1/market/{condition_id}/top-holders is a new operation. It lists up to 100 holders per side, largest shares first, with C, D, F and ungraded wallets included. This is the Top holders table on the market’s event page. Each row carries the wallet’s grade, its position (shares, current_value_usd, avg_entry_price, position P&L), and totals over the whole sample. On an open market a position can be up to about 150 seconds old: positions_as_of says when Polymarket’s holder board was fetched, and computed_at when the sample was built.GET /api/v1/market/{condition_id}/holders is unchanged. It still returns S, A and B holders only, from a complete scan with exact counts. Use it for the profitable-wallet roster, and use top holders for the biggest positions whatever their grade.- The SDK adds
getMarketTopHolders(conditionId, { limit }). - The CLI adds
market top-holders <condition_id>. - MCP adds the
get_market_top_holderstool.
top-holders with limit=100.GET /api/v1/leaderboard entries carry price_edge, and sort=edge orders by it
GET /api/v1/leaderboard entries now carry price_edge, the wallet’s edge over the price it paid, and sort=edge orders the list by it. The get_leaderboard MCP tool, the SDK’s listLeaderboard method and the CLI accept sort=edge too.Before this change, the entry had no edge field and sort=edge returned 400 with error.param sort.price_edgeholdsmarkets,hit_rate,avg_price,edge(hit_rateminusavg_price),status, andholdwhen one applies. It is left out when the wallet has no settled sports or esports market of $20 or more at a known entry price.statusisranked,too_few_marketsorsample_mismatch.holdisrecord_incompleteand appears only on arankededge.sort=edgelists wallets with arankededge and nohold, highestedgefirst. Every other wallet sorts as having no edge.- The leaderboard page ranks those wallets by their edge pulled toward the sports and esports average, which the API does not send, so the two orders can differ.
price_edge is a new optional field, and sort=edge was refused before.What to change: Nothing is required. To rank by edge, send sort=edge and keep it on every page.GET /api/v1/markets/search refuses a q with fewer than two letters or digits
GET /api/v1/markets/search now answers 400 bad_request with error.param q when q holds fewer than two letters or digits after trimming. The MCP tool search_markets returns the same error, and its q schema now has minLength 2.- Refused:
a,7,?,-, anda?. - Still searched:
ny,g2,t1,-7.5, and every longer query.
200 with a page that matched almost every market and ranked them on no real signal: a listed draw markets, and ? listed markets in an arbitrary order. It also took 300 to 550 ms to answer.Breaking for clients that send a one-character q, such as a search box that calls on the first keystroke.What to change: Send at least two letters or digits. In a search box, wait for the second character before you call.The leaderboard answers 503 with Retry-After while its rankings are refreshing
GET /api/v1/leaderboard now answers 503 with error.reason: "read_model_warming" and Retry-After: 30 when its precomputed rankings are missing or were last checked 180 seconds ago or more. Every 200 page comes from those rankings, and meta.cached and meta.cache_age_s say how old they are.Before this change that state answered 200 from a slower read of the live ranking tables, which could take several seconds. We measured no request in that state over the week before the change, so most clients will never see the 503.Breaking for clients that treat every 503 from this route as a rate-limiter outage or do not retry it.What to change: On a 503 whose error.reason is read_model_warming, retry this route after Retry-After (or error.retry_at). Keep it out of any backoff you share with other routes. A 503 with no error.reason is still the rate-limiter outage.GET /api/v1/leaderboard sorts by P&L, volume, predictions, win rate or grade
GET /api/v1/leaderboard now takes sort, dir and activity, the same orders and 30-day filter as the leaderboard page. The get_leaderboard MCP tool, the SDK’s listLeaderboard method and the CLI pass them through.Before this change, the endpoint listed wallets only in the board’s own order, highest score first, with no way to sort by P&L or filter out inactive wallets.sortisrank(the default),grade,volume,predictions,pnlorwin_rate.win_rateranks a wallet’s rate once 5 of its markets are decided.dirisascordesc. It defaults to the column’s natural direction: rank 1 first, and the largest value first for every other column.activityisall(the default),active(traded in the last 30 days) orinactive.next_cursoris tied to thesort,dirandactivityof its page. A cursor sent with anothersortordirreturns400witherror.paramcursor; one sent with anotheractivityreturns400witherror.reasoncursor_expired.- A sorted page can return
503witherror.reasonread_model_warmingandRetry-Afterwhile its ranking copy is more than 5 minutes old. - A sort changes the order only: the same S, A, and B wallets are listed.
activityis the one new filter.
sort and keep the same sort, dir and activity on every page. Retry a sorted 503 after Retry-After.Per-line sharp money keeps filling a game's lines for two minutes after a read
GET /api/v1/games/{event_slug}/markets with expand=sharp_money now keeps the game’s split refilling for two minutes after each read, so a later read in that time answers more of the lines the game page splits. A read that arrives while the game’s lines are being listed serves the last split held for the game, dated by money_as_of, instead of unavailable on every row.Before this change, refilling stopped 30 seconds after a read, so a game read once kept most of its lines unavailable. A read more than about two and a half minutes after the last one could answer every row unavailable.not_computedis unchanged, and the reference now names what it covers: player props, the lines beyond the seven around each family’s main line, and closed or settled lines. The game page splits none of them.- The status values and the
sharp_moneyfields are unchanged.
computed; no field, value or status was added or removed.What to change: Nothing. To fill a game, read it, then read it again after a minute or two for the rows that were unavailable.Two new v1 reads rank each sport's wallets by how surely they beat the price
GET /api/v1/leaderboard/categories/{slug} returns one sport’s or esports’ edge board, the board the site’s category pages show. It ranks wallets by edge_lower_bound, the 95% lower bound of their edge over the prices they paid, and lists only wallets whose lower bound is above zero. It takes active_within (all, 7d, 30d, 90d), min_events, limit, and cursor, Pro and Max:GET /api/v1/leaderboard/categoriesreturns every category’s top three in one read, the site’s categories index, in the same row shape.- Each row carries
rank,category,id,address,username,platform,grade,edge_lower_bound,independent_events,unresolved_events,n_resolved,win_rate,pnl, andas_of. - A cursor issued before the hourly ranking refresh returns
400witherror.reasoncursor_expired.
get_category_leaderboard and list_category_leaders, and the CLI in @0xinsider/mcp 3.9.0 adds leaderboard category <slug> and leaderboard categories. SDK 0.34.0 adds getCategoryLeaderboard and listCategoryLeaders.Before this change, the category boards were on the website only. The strategy pages’ top traders were already readable as GET /api/v1/leaderboard?strategy=, and that is unchanged.Backward compatible. Two new operations; every existing route, field, and tool is unchanged.What to change: Nothing.MCP get_usage reads the minute budget and monthly quota left, at no quota cost
The MCP tool
get_usage returns what GET /api/v1/usage does: rate_limit (the minute window’s used, limit, remaining and reset_at), monthly_quota (the month’s limit, used, remaining, reset_at and pay-as-you-go state) and daily_usage. get_account returns what GET /api/v1/me does: the credential kind, scopes and whether paid data access is active. Both are on remote MCP and in @0xinsider/mcp.- Neither call counts against the minute budget or the monthly quota. They share the 100-per-minute allowance of the two REST reads.
- A lapsed account still gets
402from the MCP endpoint, so it reads both over REST. - The stdio server in
@0xinsider/mcp3.9.0 lists both tools.
X-Monthly-Quota-* and X-RateLimit-* response headers, which MCP clients do not show to the model, or over REST.Backward compatible. Two new tools; every existing tool and its cost is unchanged.What to change: Nothing. An agent that paces itself can call get_usage before a long run instead of waiting for a 429.GET /api/v1/games names where a page's time went in Server-Timing
GET /api/v1/games now sends the parts of its processing time in Server-Timing, after api;dur. Before this change, the header carried only api;dur, the total.catalog: how long the page waited for the current list of games. Itsdesciscached,refreshing(a newer copy was building and the current one served) orrebuilt.slice,legs,metadataandgame_ids: the stored reads behindstatus=ended,cancelledandpostponed, which pick the page’s games, then read their markets, their team and league details, and their provider game ids.sharp_money: theexpand=sharp_moneywork, when you ask for it.respond: building the response.
desc="N runs" and its summed time. Parts can overlap, so they need not add up to api;dur: on a finished-status list the game list loads while the stored reads run. For example: Server-Timing: api;dur=72.656, slice;dur=18.407;desc="2 runs", legs;dur=35.200;desc="3 runs", metadata;dur=1.131;desc="3 runs", catalog;dur=1.732;desc="cached", respond;dur=10.796.Backward compatible. api;dur is unchanged and stays the first value. The part names help you report a slow page; they are not a stable schema and can change.What to change: Nothing. If you parse Server-Timing yourself, read the api metric by name. When you report a slow page, include the whole header.New v1 reads rank a sport or league and serve the resolution report and pick stats
GET /api/v1/leaderboard/sports/{sport} returns the skill board of one sport, or with league= one league’s, the board the site’s sport pages show. sort=edge (the default) ranks the edge over the price paid, sort=pnl realized P&L and sort=win_rate the Wilson lower bound of the win rate; limit takes 1-100 rows. Three more reads ship with it, Pro and Max:GET /api/v1/leaderboard/leagueslists every league with its own board, with thesportandslugvalues the board route takes.GET /api/v1/reports/resolutions?date=returns one UTC day of resolved sports and esports markets: how often the side the profitable (S, A and B) wallets held won, beside every qualifying wallet’s side.view=totalsdrops the market list.GET /api/v1/pick-of-the-day/statsreturns the Pick of the Day record broken down by day, sport, league, access group, month, entry-price band, market type and totals side, with streaks, records and every void pick. It is computed from the same rows the archive publishes.
get_sport_leaderboard, get_leaderboard_leagues, get_resolution_report and get_pick_of_the_day_stats, and the CLI in @0xinsider/mcp 3.9.0 adds leaderboard sport <sport>, leaderboard leagues, reports resolutions <date> and pick stats. SDK 0.33.0 adds getSportLeaderboard, listLeaderboardLeagues, getResolutionReport and getPickOfTheDayStats.Before this change, GET /api/v1/leaderboard ranked every wallet across all sports with no league filter, and the resolution report and the stats breakdown were on the website only.Backward compatible. Four new operations; every existing route, field and tool is unchanged.What to change: Nothing. To rank one league, read its sport and slug from GET /api/v1/leaderboard/leagues and call GET /api/v1/leaderboard/sports/{sport}?league={slug}.Market search puts a market first when you search its exact title
GET /api/v1/markets/search now ranks an event first when one of its markets has the exact title you searched for, whatever the game’s date. That market is the one returned for the event, even when it is a spread or a total. The match ignores case, spaces at either end, and a closing ?, . or !, so a title pasted with or without its question mark counts. The search_markets MCP tool calls the same route and returns the same results.Before this change, ranking was only how closely a title matched, multiplied by how close the game is to today. An exact title for a game a few days out could lose to partial matches dated today. On November 2, 2026, Will Stade Rennais FC 1901 win on 2026-11-05? returned its own game 82nd, and Will Atlanta United FC win on 2026-11-07? did not return its game in 100 results.- Every event with an exact-title market ranks ahead of every event without one. Among those events, and among all others, ranking is unchanged: title match times how close the game is to today.
- A matchup title that repeats across games, such as
Red Wings vs. Panthers, now lists every game with that title first, the nearest date first. - A query that matches no title exactly, such as
lakers, returns the same results in the same order as before.
Per-line sharp money on list game markets, and a game holders batch
GET /api/v1/games/{event_slug}/markets takes expand=sharp_money. Each row then carries sharp_money: how much S, A and B wallet money holds each side of that line, the split the game page shows for every spread, total and prop. GET /api/v1/games/{event_slug}/holders is new: it returns the graded holders of up to 25 of a game’s markets in one request. Both need a Pro or Max key.Before this change you had to call market holders once per line and add the rows yourself.sharp_moneycarriesstatus(computed,not_computedorunavailable),trader_count,outcomes[]withindex,usdand thes,aandbwallet counts, andmoney_as_of.- Each
okgame holders item is what market holders returns for that market, unfiltered and cut tolimit. A market from another game is that item’snot_found. The route spends the batch item budget, one unit per id. - The
list_game_marketsMCP tool takesexpand, the newget_game_holdersMCP tool calls the holders route, and the SDK addsgetGameHoldersin its next release.
expand parameter and the sharp_money field are additive. Without expand, game market rows are unchanged.What to change: Nothing is required. A strict schema that rejects unknown keys must accept sharp_money on a game market when you send expand.Game trades name a main spread line once: Spread -3.5, not Spread: Spread -3.5
GET /api/v1/games/{event_slug}/trades now sends a main spread line’s market.line_label as Polymarket names it, such as Spread -3.5. Before this change, a game that also had half or quarter spreads at the same line put a second Spread: in front, so the label read Spread: Spread -3.5.- Only a label that already starts with its line type changes, such as a main spread line that starts with
Spread. Before, it got that same word in front a second time when another line type in the game shared its number. - Half and quarter spread lines keep their prefix, such as
1st Half Spread: 1H Spread -3.5, so each spread line still reads differently from the others. - Every other
line_labelis unchanged.
Spread: Spread, match Spread - at the start of the label instead.GET /api/v1/games adds window=now, which lists the games on now
GET /api/v1/games accepts window=now, which returns what is on now. The remote MCP tool list_games takes the same window argument, and the stdio server, the TypeScript client and the CLI help in the next @0xinsider/mcp and @0xinsider/sdk releases name it.Before this change, a request with no filters returned the whole catalog in kickoff order. Its first page could hold only games from earlier days: on November 1 at 21:37 UTC it ran from October 10 to the afternoon of November 1, and no live or upcoming game was on it.window=nowkeeps every game Polymarket reportsliveorpaused, and every other game whose kickoff was at most 3 hours ago or is still to come. Games with no published kickoff stay and sort last.- It leaves out
ended,postponedandcancelledgames, and any game not in play that kicked off more than 3 hours ago. window=all, the default, is the catalog as before. Any other value returns400invalid_querywitherror.paramset towindow.
window returns the same games in the same order, and the cursor format is unchanged.What to change: Nothing is required. To read tonight’s slate, add window=now, or --query window=now on the CLI’s games list.One market's biggest net buyers and sellers over 24 hours or 7 days
GET /api/v1/market/{condition_id}/wallets-in-out is new. It returns the 25 wallets that put the most money into one market and the 25 that took the most out, over the last 24 hours (window=24h, the default) or 7 days (window=7d), and how many wallets were on each side in total. These are the numbers in the Wallets In / Out panel that the 0xinsider market page shows a Pro viewer, and they are live.- Each wallet carries its display name, today’s grade, its net in USD, and its fill count in the window.
- A net is buys minus sells across both outcomes, not a side. Use Market holders to see which outcome a wallet holds.
- Any
windowother than24hor7danswers400, and an unknown market answers404. - New MCP tool
get_market_wallets_in_out, CLI commandmarket wallets-in-out <condition_id>, and TypeScript client methodgetMarketWalletsInOut.
Games show how much each game has traded and how deep each winner market is
GET /api/v1/games and GET /api/v1/games/{event_slug} now return Polymarket’s traded volume for each game and its reported liquidity for each winner market, each with where it came from and when we read it:volumeon each game:value,unit(taker_shares: one side of each trade, counted in shares, not dollars),basis(game_linesfor the game’s own lines, orevent_totalfor every market in the Polymarket event),source(polymarket_dataorgamma),observed_atandfresh_for_seconds.liquidityon each entry inmarkets:value, the liquidity Polymarket’s market API (Gamma) reports for that market,source(alwaysgamma) andobserved_at.
0. A finished game read after it leaves the live board carries neither field. Per-market volume is not included: the catalog holds no sourced, dated figure for each market. The TypeScript client types (GameVolume, GameMarketLiquidity) and the list_games and get_game MCP output schemas describe the same fields.Backward compatible. Both fields are optional additions; nothing else in the response changed.What to change: Nothing. To rank games by activity, read volume.value and check volume.basis: event_total counts every market in the event, not only the game lines. Treat volume as shares, not dollars.A game's live fills in one read, and event_slug on large trades and sharp-money flows
GET /api/v1/games/{event_slug}/trades is new. It returns a game’s newest fills across every market in it, newest first and live, the same rows the 0xinsider game page shows a Pro viewer. Each fill carries the wallet’s display name and today’s grade, and the market’s line_label, its name within the game.event_slugis a new filter onGET /api/v1/large-trades,GET /api/v1/large-trades/history, andGET /api/v1/markets/sharp-money-flows. It keeps only the game’s markets, and an unknown game is a404witherror.paramset toevent_slug.condition_idis a new filter onGET /api/v1/large-tradesandGET /api/v1/markets/sharp-money-flows. History already had it.condition_idandevent_slugcannot be sent together. OnGET /api/v1/large-trades, neither works withsince, andevent_slugworks withsort=recentonly. Each of these answers400.- The deprecated
whale-tradesandsmart-money-flowspaths take the same filters. - New MCP tool
get_game_trades.get_large_trades,get_large_trades_history, andget_sharp_money_flowstakeevent_slug, and the first and last takecondition_id. - New CLI command
games trades <event_slug>, and TypeScript client methodslistGameTradesandlistGameTradesConditional(), which returns a typed304for polling.
GET /api/v1/games/{event_slug}/trades with If-None-Match and drop fills whose id you have already seen.Finished games carry their league, so a league filter on the ended list returns them
GET /api/v1/games with status=ended, cancelled or postponed, and GET /api/v1/games/{event_slug} for a finished game, now set league on games from leagues without their own board, such as acle, bl3 and es3. The value is the league code Polymarket’s sports list gives the game’s series, the code coverage.leagues lists, so sport=Soccer&status=ended&league=acle now returns the finished AFC Champions League Elite games.Before this change, these games returned no league, and a league filter on the ended list returned no rows for them. On November 2, 5,326 of the 7,169 finished games in the 7-day window had no league, among them 488 of 1,075 soccer, 219 of 291 basketball, and 153 of 199 hockey games.- A finished game that already had a
leaguekeeps it. - Finished tennis and cricket games read Polymarket’s league code, such as
itforcricranji. An open game in the same tournament can read the tournament name instead. leaguestays absent when Polymarket’s sports list names no league for the game’s series.
league.What to change: Nothing. If you matched finished games to a league by their slug, filter with league= instead.Finished games no longer appear twice when Polymarket copies a game
GET /api/v1/games with status=ended, cancelled or postponed now lists each finished game once. Polymarket sometimes posts a Protocol V2 copy of a game as a second event, whose slug is the game’s slug with a polyv2 prefix. The list now leaves that copy out and keeps the original game, as the live games list already did.Before this change, a finished game with a copy appeared twice. On November 2, the 7-day window listed six such pairs, for example nba-det-nyk-2026-10-27 and polyv2-nba-det-nyk-2026-10-27. The copy had league: null and no score.GET /api/v1/games/{event_slug}with a copy’s slug now returns404. Read the original game’s slug instead.- A copy stays in the list, under its own slug, when 0xinsider has no record of the original game. It is then the only record of that game.
polyv2 copies yourself, you can stop.The games catalog adds NCAA women's college basketball
GET /api/v1/games now includes NCAA women’s college basketball games as league=cwbb, under sport=Basketball. coverage.leagues lists cwbb. Use sport=Basketball&league=cwbb to list them, then GET /api/v1/games/{event_slug} for one game’s linked markets.Before this change, an open cwbb-* game was missing from the list while it was upcoming, and GET /api/v1/games/{event_slug} returned 404 for it. Most of these games are thinly traded: the median open game had $30 of liquidity on November 2, 2026.GET /api/v1/sports/pre-game-side-observationsincludes upcoming women’s college basketball games in its basketball supply.funnel.sports[].board_upcoming_unavailable_scopescan containcwbbwhen that upcoming source is unavailable. The OpenAPI enum and generated SDK types include the value.
cwbb. Use league=cwbb when you want only women’s college basketball games.Market search finds every open game, not only games with a $10,000 trade
GET /api/v1/markets/search now searches every unresolved game market (a game’s winner market, or an outright) whether or not it has traded, together with every market that has had a single trade of $10,000 or more. The search_markets MCP tool and the TypeScript client searchMarkets call the same route and return the same results.Before this change the search covered only markets with a $10,000 trade, so an open game nobody had traded that large returned nothing for any query, its exact title included. On November 2, 2026 that hid 31,150 of 31,231 open game markets, among them almost every college basketball game.- A game’s winner market and its larger-traded spreads, totals and props still collapse into one result per event.
- Ranking is unchanged: title match times how close the game date is to today. Open games without large trades now rank beside the ones with them.
- A resolved market without a $10,000 trade is still not returned.
GET /api/v1/games, search now finds them too.Finished soccer games name both teams, with provider IDs
GET /api/v1/games/{event_slug} and GET /api/v1/games with status=ended, cancelled or postponed now name both sides of a finished game that has no live score and no moneyline named by team. A soccer game’s three 1X2 legs read Yes and No, so this covers most soccer leagues without a live-score feed. These games now carry competitors[].name, provider_id, and logo from Polymarket’s teams for the event, and coverage.competitors reads provider_ids.Before this change, these games returned competitors: [] with coverage.competitors unavailable. On November 2, 492 of the 1,068 finished soccer games in the 7-day window read this way, for example acle-nef-itj-2026-10-26 in the AFC Champions League Elite.- A finished game that already had names keeps them, from its final score or its moneyline’s outcome labels.
competitorsstays empty andunavailablewhen no team record exists for the game.coverage.scoresis unchanged: a game no live-score feed reported still has no score.
competitors.What to change: Nothing. If you looked up a second source to name these games, read competitors instead.New game sharp money read, also on list_games with expand=sharp_money
GET /api/v1/games/{event_slug}/sharp-money returns how much S, A and B grade money backs each side of one game, and the verdict the game page shows for it. GET /api/v1/games attaches the same object to every game on a page when you send expand=sharp_money. Both need a Pro or Max key.Before this change you had to page market holders for every market of a game and add the rows yourself. The new object carries:outcomes[]: per side,usd,pct,is_leader, and the S, A and B wallet counts.verdict, keyed bykind, plustotal_usd,allocation,unallocated_usd,trader_countandholders_partial.status,lifecycle,computed_atandmoney_as_of.
get_game_sharp_money MCP tool, list_games takes expand, and the CLI adds 0xinsider games sharp-money <slug> in its next release. The SDK adds getGameSharpMoney in its next release.Backward compatible. The route, the expand parameter and the sharp_money field are additive. Without expand, list_games rows are unchanged.What to change: Nothing is required. A strict schema that rejects unknown keys must accept sharp_money on a game when you send expand.The games catalog adds NCAA men's college basketball
GET /api/v1/games now includes NCAA men’s college basketball games as league=cbb, under sport=Basketball. coverage.leagues lists cbb. Use sport=Basketball&league=cbb to list them, then GET /api/v1/games/{event_slug} for one game’s linked markets.Before this change, no scope covered college basketball. An open game such as cbb-warwil-ncat-2026-11-03 was missing from the list while it was upcoming, and GET /api/v1/games/{event_slug} returned 404 for it.GET /api/v1/sports/pre-game-side-observationsincludes upcoming college basketball games in its basketball supply.funnel.sports[].board_upcoming_unavailable_scopescan containncaa-basketballwhen that upcoming source is unavailable. The OpenAPI enum and generated SDK types include the value.- Women’s college basketball is not included.
ncaa-basketball. Use league=cbb when you want only college basketball games.Game status fields come only from a live-score feed, never from labels of our own
GET /api/v1/games and GET /api/v1/games/{event_slug} now send status.match_status, status.provider_status, and status.live only when a live-score feed has reported the game. coverage.scores reads available only when that feed supplied a score.Before this change, a listed game that no live-score feed had reported carried labels the API derived itself. On November 1, 1,986 of 2,023 listed games read this way:status.provider_statuswasScheduled, orStarted, a string no provider sent.status.match_statuswasscheduledandstatus.livewasfalse.coverage.scoreswasavailablewith no score and nofreshness.scores_observed_at.
status.provider_status: its Live, Finished, or Scheduled was a label of our own. status.state is unchanged. A game that Polymarket lists as open, neither in play nor ended, still reads scheduled when no feed has reported it, also after its kickoff.Breaking for clients that read status.provider_status Scheduled or status.live false to find games that have not started. Every one of these fields was already optional and documented as omitted when no live-score frame exists.What to change: Read status.state to tell a scheduled game from a live one. Treat a missing status.live as unknown, not as false, and check coverage.scores before you read competitors[].score.GET /api/v1/games lists finished games with status=ended, cancelled or postponed
GET /api/v1/games with status=ended, cancelled or postponed now lists finished games that have left the live board. They come from the same stored records GET /api/v1/games/{event_slug} serves, with freshness.source stored, merged with any finished game still on the board in the usual scheduled_at, then event_slug order.Before this change the list drew only on the live and upcoming board, so status=ended returned only the few games that had not left it yet.- Stored games cover kickoffs from
starts_aftertostarts_before. Withoutstarts_afterthe window reaches back 7 days fromstarts_before, which defaults to, and is capped at, the time of the read. - Each read scans a bounded slice of that window, so a page can hold fewer than
limitgames, or none, whilehas_moreistrue. - A stored game reads as it does on the detail:
status.stateended,cancelledorpostponed, the final score when one was recorded, moneylinemarkets[]withoutprices, and notitle. as_ofis the time of the read for these statuses. Without a finishedstatus, the list is unchanged.
status returns more games.What to change: Page on has_more and next_cursor, never on a short or empty page. If you switch on freshness.source exhaustively, handle stored.New GET /api/v1/games/{event_slug}/markets lists every market of a game
GET /api/v1/games/{event_slug}/markets is new. It lists every stored Polymarket market of one game, including spreads, totals, team totals, period lines and player props, with each market’s condition_id, sports_market_type, line and outcome token ids. The new list_game_markets MCP tool and the SDK’s listGameMarkets method call it.Before this change, no endpoint listed a game’s spreads, totals or props. The game object’s markets holds only the winner markets, and the docs said it held every linked market. Search markets returns one market per game.familiescounts each market type the game has, whatever the filters.- A soccer game’s child events (exact score, halftime result, more markets) are included, and each row’s
event_slugnames its own event. GET /api/v1/markets/searchresults now carryevent_slug, so you can go from a search result to the game’s full market list.- The descriptions of
list_games,get_gameandsearch_marketsnow say what they return: the winner markets, and one market per event.
event_slug field are additive. Existing responses are unchanged.What to change: Nothing is required. To find a game’s props, call the new endpoint with the game’s event_slug. A strict schema that rejects unknown keys must accept event_slug on search results.A finished game stays readable by its slug, served from its stored record
GET /api/v1/games/{event_slug} now returns a finished game after it leaves GET /api/v1/games. Once the provider reports the game final, awarded, forfeited, cancelled or postponed, or every moneyline leg has resolved, it is served from its stored record with freshness.source stored.Before this change the detail drew only on the live and upcoming board, so a game returned 404 within minutes of its final whistle.freshness.sourceadds the valuestored.freshness.source_freshnessisnot_applicableandfreshness.source_observed_atis the later of the last live-score write and the last leg settlement.status.statereadsended,cancelledorpostponed.competitors[].scorecarries the final score when a live-score frame recorded one.markets[]lists the moneyline markets withoutprices, andtitleis omitted.- A game the board dropped while it was still in play still returns
404.GET /api/v1/gamesis unchanged and lists only live and upcoming games.
freshness.source exhaustively, handle stored. To read a result after the game ends, call the detail by its event_slug instead of looking for it in the list.