Skip to main content
These are the API changelog entries from November 2, 2026, newest first. The newest changes are on the API changelog.
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_s is 1800 for the positions group on positions, the age at which each row’s own freshness reads stale; 604800 (7 days) for sync, ranking, volume and positions on a trader and for trader and ranking on positions and large trades; and 22620 for leaderboard_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_s and is never stale: sports_record, market, trade and the large-trade volume.
  • The top-level status is stale when every group is fresh or stale and at least one is stale. unavailable and partial keep 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_s ceiling on GET /api/v1/trader/{address} and the get_trader MCP tool accepts fresh or stale, so every request answers as it did before. error.freshness.data_quality_status can now be stale.
  • In the next @0xinsider/sdk release, the types gain "stale" and max_age_s, and assessDataQuality passes a stale group inside your maxAgeMs, as it passed that group before. Python 0xinsider 0.8.1 (assess_data_quality) and Go github.com/0xinsider/0xinsider-go v0.5.1 (AssessDataQuality) pass a stale group inside your max_age / MaxAge the same way; earlier releases fail stale as an unrecognized status, which errs toward refusing a body.
Backward compatible under the published rule to treat a status you do not recognize as 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_trader and get_trader_context MCP tools, the CLI’s traders get and traders context, and the SDK’s getTrader return the corrected figure with no update.
  • The OpenAPI descriptions of pnl.unrealized and position_summary.unrealized_mtm state the definition.
Backward compatible. The field, its type and the response envelope are unchanged. Its value is corrected.What to change: If you subtracted partial-sell profit from 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_of on 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_holders and the CLI’s market top-holders return the field too, and the SDK type MarketTopHolders gains source_stale: boolean.
Backward compatible. The field is new and every other field keeps its meaning.What to change: Nothing. If you need positions no more than about 150 seconds old, check 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_revision as 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.
Backward compatible. The fields and their types are unchanged; they are now filled during play.What to change: If you order score frames by 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_day tool takes the optional date argument the remote tool already takes. The tool count stays at 69.
  • The SDK adds getPickOfTheDayForDate(date) and getMarketTopHolders(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, getResolutionReport and getPickOfTheDayStats. listLeaderboard accepts sort, dir and activity.
The CLI, MCP and introduction pages now install @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.code not_found, error.param date, with no Retry-After. Do not retry it.
  • A date after today, or a malformed one, returns 400, error.code bad_request, error.param date.
  • @0xinsider/sdk 0.35.0 adds getPickOfTheDayForDate(date), and the 0xinsider CLI in @0xinsider/mcp 3.10.0 adds 0xinsider pick date <YYYY-MM-DD>. Both were published on November 2, 2026.
Backward compatible. The parameter is optional, and no field or type changes.What to change: Nothing. To read a past date’s picks, send 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_active now matches last_active on GET /api/v1/leaderboard, which refreshes it hourly.
  • A cached response can be up to 60 seconds behind a new trade.
  • last_active is 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’s sync group no longer covers last_active, and no other group does, because the value is itself the time of a trade. With expand=trust, trust.last_active names trader_last_activity.last_activity_at as its source, with freshness unknown.
Backward compatible. The field name and type are unchanged, and the value now matches the published description. A client that used 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[].usd on the game markets list.
  • data.outcomes[].usd, data.total_usd and data.unallocated_usd on game sharp money.
  • Only the sign of a zero changes. Every other value keeps its full precision.
Backward compatible. -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> and leaderboard categories.
  • market top-holders <condition_id> and market wallets-in-out <condition_id>.
  • games sharp-money <event_slug> and games trades <event_slug>.
  • pick stats and reports resolutions <date>.
The CLI, MCP and introduction pages now install @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_active now agrees with the activity filter: every wallet activity=inactive lists 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 its last_active as the sync time, the same value as synced_at. A later change will correct it.
Backward compatible. The value now matches the published description. A client that used 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_holders tool.
Backward compatible. This adds an operation. Existing operations are unchanged.What to change: Nothing. To list every large holder of a market, call 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_edge holds markets, hit_rate, avg_price, edge (hit_rate minus avg_price), status, and hold when 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.
  • status is ranked, too_few_markets or sample_mismatch. hold is record_incomplete and appears only on a ranked edge.
  • sort=edge lists wallets with a ranked edge and no hold, highest edge first. 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.
Backward compatible. 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, ?, -, and a?.
  • Still searched: ny, g2, t1, -7.5, and every longer query.
Before this change, such a query returned 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.
  • sort is rank (the default), grade, volume, predictions, pnl or win_rate. win_rate ranks a wallet’s rate once 5 of its markets are decided.
  • dir is asc or desc. It defaults to the column’s natural direction: rank 1 first, and the largest value first for every other column.
  • activity is all (the default), active (traded in the last 30 days) or inactive.
  • next_cursor is tied to the sort, dir and activity of its page. A cursor sent with another sort or dir returns 400 with error.param cursor; one sent with another activity returns 400 with error.reason cursor_expired.
  • A sorted page can return 503 with error.reason read_model_warming and Retry-After while its ranking copy is more than 5 minutes old.
  • A sort changes the order only: the same S, A, and B wallets are listed. activity is the one new filter.
Backward compatible. Without the new parameters, the order, the rows and the cursor are unchanged.What to change: Nothing is required. To sort, send 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_computed is 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_money fields are unchanged.
Backward compatible. More rows read 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/categories returns 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, and as_of.
  • A cursor issued before the hourly ranking refresh returns 400 with error.reason cursor_expired.
The MCP server adds 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 402 from the MCP endpoint, so it reads both over REST.
  • The stdio server in @0xinsider/mcp 3.9.0 lists both tools.
Before this change, the quota was readable only from the 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. Its desc is cached, refreshing (a newer copy was building and the current one served) or rebuilt.
  • slice, legs, metadata and game_ids: the stored reads behind status=ended, cancelled and postponed, which pick the page’s games, then read their markets, their team and league details, and their provider game ids.
  • sharp_money: the expand=sharp_money work, when you ask for it.
  • respond: building the response.
A part that ran more than once on a page carries 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/leagues lists every league with its own board, with the sport and slug values 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=totals drops the market list.
  • GET /api/v1/pick-of-the-day/stats returns 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.
The MCP server adds 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.
Backward compatible. The response shape is unchanged. A cursor issued before the change, for a query that is a market’s exact title, can skip that market’s event; start again from the first page.What to change: Nothing. To find a market you already know, search its full title; it is the first result.
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_money carries status (computed, not_computed or unavailable), trader_count, outcomes[] with index, usd and the s, a and b wallet counts, and money_as_of.
  • Each ok game holders item is what market holders returns for that market, unfiltered and cut to limit. A market from another game is that item’s not_found. The route spends the batch item budget, one unit per id.
  • The list_game_markets MCP tool takes expand, the new get_game_holders MCP tool calls the holders route, and the SDK adds getGameHolders in its next release.
Backward compatible. The route, the 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_label is unchanged.
Backward compatible. The field and its type are unchanged; only the text of these labels is shorter.What to change: Nothing. If you matched the text 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=now keeps every game Polymarket reports live or paused, 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, postponed and cancelled games, 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 returns 400 invalid_query with error.param set to window.
Backward compatible. A request without 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 window other than 24h or 7d answers 400, and an unknown market answers 404.
  • New MCP tool get_market_wallets_in_out, CLI command market wallets-in-out <condition_id>, and TypeScript client method getMarketWalletsInOut.
Before this change, these numbers were only on the market page.Backward compatible. This adds a route and changes no existing one.What to change: Nothing.
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:
  • volume on each game: value, unit (taker_shares: one side of each trade, counted in shares, not dollars), basis (game_lines for the game’s own lines, or event_total for every market in the Polymarket event), source (polymarket_data or gamma), observed_at and fresh_for_seconds.
  • liquidity on each entry in markets: value, the liquidity Polymarket’s market API (Gamma) reports for that market, source (always gamma) and observed_at.
Before this change neither route returned volume or liquidity, so ranking games by activity or skipping thin markets took a separate read per market.A figure Polymarket does not report is left out, never sent as 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_slug is a new filter on GET /api/v1/large-trades, GET /api/v1/large-trades/history, and GET /api/v1/markets/sharp-money-flows. It keeps only the game’s markets, and an unknown game is a 404 with error.param set to event_slug.
  • condition_id is a new filter on GET /api/v1/large-trades and GET /api/v1/markets/sharp-money-flows. History already had it.
  • condition_id and event_slug cannot be sent together. On GET /api/v1/large-trades, neither works with since, and event_slug works with sort=recent only. Each of these answers 400.
  • The deprecated whale-trades and smart-money-flows paths take the same filters.
  • New MCP tool get_game_trades. get_large_trades, get_large_trades_history, and get_sharp_money_flows take event_slug, and the first and last take condition_id.
  • New CLI command games trades <event_slug>, and TypeScript client methods listGameTrades and listGameTradesConditional(), which returns a typed 304 for polling.
Before this change, a game’s fills were only on the game page, and a list could not be narrowed to one game.Backward compatible. Requests without the new parameters return what they did before, and sharp-money-flows cursors from earlier pages stay valid.What to change: Nothing. To follow a game, poll 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 league keeps it.
  • Finished tennis and cricket games read Polymarket’s league code, such as itf or cricranji. An open game in the same tournament can read the tournament name instead.
  • league stays absent when Polymarket’s sports list names no league for the game’s series.
Backward compatible. The response shape is unchanged; more finished games carry 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 returns 404. 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.
Backward compatible. The response shape is unchanged. The list returns one row per game instead of two.What to change: Nothing. If you removed 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-observations includes upcoming women’s college basketball games in its basketball supply.
  • funnel.sports[].board_upcoming_unavailable_scopes can contain cwbb when that upcoming source is unavailable. The OpenAPI enum and generated SDK types include the value.
Backward compatible. Coverage and the unavailable-scope value are additive. Request parameters and response fields are unchanged.What to change: Nothing unless you validate the unavailable-scope values against a fixed list; add 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.
Backward compatible. The response shape is unchanged; queries return more open games.What to change: Nothing. If you worked around missing games by reading 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.
  • competitors stays empty and unavailable when no team record exists for the game.
  • coverage.scores is unchanged: a game no live-score feed reported still has no score.
Backward compatible. The response shape is unchanged; more finished games carry 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 by kind, plus total_usd, allocation, unallocated_usd, trader_count and holders_partial.
  • status, lifecycle, computed_at and money_as_of.
The same read is the 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-observations includes upcoming college basketball games in its basketball supply.
  • funnel.sports[].board_upcoming_unavailable_scopes can contain ncaa-basketball when that upcoming source is unavailable. The OpenAPI enum and generated SDK types include the value.
  • Women’s college basketball is not included.
Backward compatible. Coverage and the unavailable-scope value are additive. Request parameters and response fields are unchanged.What to change: Nothing unless you validate the unavailable-scope values against a fixed list; add 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_status was Scheduled, or Started, a string no provider sent.
  • status.match_status was scheduled and status.live was false.
  • coverage.scores was available with no score and no freshness.scores_observed_at.
A game read from Polymarket’s event data, with no status from the live-score feed, also stops sending 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_after to starts_before. Without starts_after the window reaches back 7 days from starts_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 limit games, or none, while has_more is true.
  • A stored game reads as it does on the detail: status.state ended, cancelled or postponed, the final score when one was recorded, moneyline markets[] without prices, and no title.
  • as_of is the time of the read for these statuses. Without a finished status, the list is unchanged.
Backward compatible. The response shape is unchanged; a finished 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.
  • families counts 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_slug names its own event.
  • GET /api/v1/markets/search results now carry event_slug, so you can go from a search result to the game’s full market list.
  • The descriptions of list_games, get_game and search_markets now say what they return: the winner markets, and one market per event.
Backward compatible. The endpoint, the MCP tool and the 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.source adds the value stored. freshness.source_freshness is not_applicable and freshness.source_observed_at is the later of the last live-score write and the last leg settlement.
  • status.state reads ended, cancelled or postponed. competitors[].score carries the final score when a live-score frame recorded one.
  • markets[] lists the moneyline markets without prices, and title is omitted.
  • A game the board dropped while it was still in play still returns 404. GET /api/v1/games is unchanged and lists only live and upcoming games.
Backward compatible. Live and upcoming games read exactly as before.What to change: If you switch on 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.