Skip to main content
These are the API changelog entries from November 3, 2026, newest first. The newest changes are on the API changelog.
Leaderboard adds summary: ranked wallets, the S, A and B split, and 30-day active count
GET /api/v1/leaderboard now sends a top-level summary with the whole board’s totals, the counts the leaderboard page shows. The get_leaderboard MCP tool returns it too.Before this change, the only way to count the board was to walk every page.
  • ranked_total: ranked wallets on the board.
  • by_grade: the S, A and B counts, which sum to ranked_total.
  • active_30d: wallets whose last trade was 30 days or less before as_of, the wallets activity=active lists.
  • as_of: when the totals were counted.
  • Sent on every page with no category and no strategy, whatever the sort, dir and activity. Left out on a category or strategy page, which lists a subset these totals do not describe, and while the ranking has no totals for the page’s generation.
Backward compatible. Additive field; the entries, cursor and meta are unchanged.What to change: Nothing. To read the board’s size or grade split, read summary from any unfiltered page, for example ?limit=1.
Webhooks now send an event about a second after it happens, not up to 10 seconds later
Webhooks now start delivering an event as soon as it is recorded. In a local measurement of 7 events, an event reached the receiver a median of 0.35 seconds after it was recorded, and at most 1.5 seconds.Before this change, a webhook sender with nothing to send checked for new events every 10 seconds. On November 3, large_trade_inserted_v2 deliveries were created 5.5 to 9.3 seconds after the same trade reached GET /api/v1/stream.Backward compatible. Payloads, headers, signatures and retries are unchanged.What to change: Nothing.
Pick of the Day reads take view=summary, pick_id, archive paging and stats sections
GET /api/v1/pick-of-the-day, /archive and /stats now take optional parameters that return a smaller body. The remote MCP tools get_pick_of_the_day, get_pick_of_the_day_archive and get_pick_of_the_day_stats now send view=summary unless you name a view.Before this change, each read returned the whole record. On November 3, 2026 that was 198,807 bytes for one day’s 14 picks, 1,124,089 for the archive and 151,774 for the stats. Claude Code saves an MCP result over 50,000 characters to a file instead of showing it, so an agent got no numbers.
  • view=summary on the pick returns each pick’s identity, side, price, result, return and grade, about 600 bytes a pick. pick_id returns one pick, and the MCP tool reads it in full by default.
  • view=summary on the archive returns hit_rate without series and 50 short rows. limit (1 to 200) and cursor (from page.next_cursor) page the rows in either view and add page.
  • view=summary on the stats leaves out series, days, categories, months and voids, the sections that grow with the record. sections returns only the sections you name, with series as its own key.
  • An unknown view or sections name or a cursor the route did not return is 400 bad_request with error.param naming it. limit behaves like every list limit: a value outside 1 to 200 is clamped, and one that is not an integer is 400 invalid_query.
@0xinsider/mcp 3.12.0 has the same tools and defaults. Its release is pending; 3.11.0 and earlier still send no view.@0xinsider/sdk 0.38.0 types the new parameters. It types the archive and stats data as the full body or the smaller one, so TypeScript code that reads them checks "view" in data first; the full body has no view key. Its release is also pending.Backward compatible for REST. A request without the new parameters gets the same body and ETag. Breaking for remote MCP clients that read fields the summary leaves out, such as display_holders or entry_authorization.What to change: Over MCP, send view: "full", or call get_pick_of_the_day with pick_id for one pick in full. Over REST, nothing.
Edge Hunter reads category=sports as kind=sports in every view
GET /api/v1/edge-hunter now reads category=sports, in any casing, as kind=sports (every sport except Esports) in the board, signals and observations views, and in the MCP get_edge_hunter tool. The response’s selection carries kind: "sports" beside category: "sports". This matches the meaning category=sports has on every other route.Before this change, sports was not a sport name, so it answered an empty result with selection.category set to non_sport.
  • category=sports together with kind=esports now returns 400 with param: "category", because the two select nothing in common.
  • Any other category that is not a sport still returns an empty result with category: "non_sport".
Breaking only for clients that send category=sports with kind=esports, which now get 400 instead of an empty result. Every other request that answered empty now answers the sports rows.What to change: Nothing. Use kind=sports or category=sports for every sport except Esports, and do not send category=sports with kind=esports.
category=sports now matches every sport but Esports, not just the catch-all Sports
category=sports, in any casing, now matches every sport except Esports on GET /api/v1/large-trades, GET /api/v1/large-trades/history, their whale-trades aliases, GET /api/v1/leaderboard, GET /api/v1/markets/explore, GET /api/v1/markets/sharp-money-flows and its smart-money-flows alias, and GET /api/v1/trader/{address}/categories. The MCP tools on those routes match the same way. The set includes Sports, the category Polymarket uses for sports with no category of their own, such as cycling and lacrosse.Before this change, sports matched only that Sports category. Its newest large trade was from July 9, 2026, so category=sports on large trades answered with July trades as the newest, and history and leaderboard reads answered an empty page.
  • GET /api/v1/positions, GET /api/v1/large-positions and GET /api/v1/markets/search filter by one category, so sports now returns 400 with param: "category" there. Before this change it returned an empty list, and Sports returned that category alone.
  • GET /api/v1/leaderboard now matches every sport name in any casing. Before this change, cricket returned an empty page and only Cricket matched.
  • A leaderboard cursor taken with category=Sports now returns 400 cursor_expired, because the filter means a different set of wallets.
  • On market explore, the Sports value in facets.categories still counts the catch-all category alone.
Breaking for clients that send category=Sports to read the catch-all category alone. That category can no longer be selected on its own.What to change: Send sports for every sport except Esports, esports for Esports, or omit category for both. On positions, large positions and market search, omit category or name one sport, and handle 400 on the parameter.
A filtered stream resumes with Last-Event-ID after a short restart or a backend release instead of resyncing
GET /api/v1/stream now keeps a filtered connection’s position on the server while it drops frames its filter excludes. A reconnect that sends the last id you received replays from where the connection stopped.Before this change, a filtered connection usually answered event: resync with completeness.status: "truncated" on reconnect, even after a one-second restart. The replay window holds the newest 4,096 feed events of every type, about 15 to 30 seconds in the evening, and the last id a filtered client receives (for example event=LargeTradeInsertedV2 with min_size) is often minutes older. Every backend release closes open streams, so a filtered client resynced on every release.
  • The window itself is unchanged and is now stated in the endpoint description: it is counted in all feed events, not in the frames you receive.
  • A client away longer than the window still gets a truncated resync. Catch up large trades with GET /api/v1/large-trades?since= or GET /api/v1/events/feed/since.
  • The wire format does not change: no new frame type, header or parameter.
Backward compatible. Every client benefits as it is, including @0xinsider/sdk 0.36 and a browser EventSource.What to change: Nothing. Keep sending Last-Event-ID with the last id you processed.
SDK 0.37.0 sends the stream's wallet and min_size filters
@0xinsider/sdk 0.37.0 adds wallet and min_size to StreamFilters, so streamFeed, streamFeedResilient and consumeStreamCheckpointed can send the GET /api/v1/stream filters of the same names.Before this release, the SDK could send only event, condition_id and min_grade. A whale-alert consumer received every large trade and dropped small ones itself, and could not follow one wallet on the stream.
  • wallet is a 0x address in any case. min_size is a USD notional floor, number | string; pass a string such as "25000" to send it exactly.
  • Both narrow LargeTradeInsertedV2 frames. With either set, every other frame type is excluded, as on the endpoint.
  • A malformed value is refused with 400 bad_request and param naming the filter; the resilient consumers do not retry it.
Backward compatible. Both fields are optional, and no existing filter changes.What to change: Upgrade to 0.37.0 and pass wallet or min_size instead of filtering large trades in your handler.
Trader breakdowns state which settled positions the entry-price buckets leave out
entry_price_analysis in GET /api/v1/trader/{address} with expand=breakdowns now carries coverage, and the first entry-price band starts at $0.Before this change, the buckets left out every settled position with no single entry price and said nothing about it. On one wallet they held 9,121 of 12,811 settled markets, and $2.99M of the $5.79M those markets made, and a client reading them beside pnl.in_scope could take them for the whole record.
  • coverage carries settled_markets and settled_pnl (every settled sports and esports position the section reads), bucketed_markets and bucketed_pnl (the buckets’ sums), and unbucketed, one row per reason with markets and total_pnl. The bucketed and unbucketed markets add up to settled_markets.
  • reason is two_sided (both outcomes bought, a hedged position with no single side), held_side_unpriced (the outcome the wallet held has no recorded price), no_entry_price (no outcome has one), or freshness_unknown (a bucket whose data has no freshness time yet).
  • settled_pnl adds up the wallet’s per-market realized P&L. It differs from pnl.in_scope.realized_pnl, which is Polymarket’s published sports and esports P&L with a share of the wallet’s rebates.
  • The first band is $0.00-$0.19 instead of $0.01-$0.19, and now holds positions bought below $0.01, which had no bucket before.
  • The MCP get_trader tool, the OpenAPI document and the SDK types describe coverage.
Backward compatible, except for the first band’s label. coverage is a new field, and every other band keeps its label.What to change: If you match the band label $0.01-$0.19, match $0.00-$0.19 instead. Read coverage.unbucketed before you compare the buckets’ totals with pnl.in_scope.
Rust crate oxinsider 0.5.0 adds game sharp money, game holders, and 10 more operations
The Rust crate oxinsider 0.5.0 covers all 106 operations in the OpenAPI document. It adds get_game_sharp_money, get_game_holders, list_game_markets, list_game_trades, and 8 more methods.Before this change, oxinsider 0.4.0 covered 94 operations. ListGamesParams had no expand, Game had no sharp_money or volume, and a 200 response did not return its ETag.
  • New methods: get_game_sharp_money, get_game_holders, list_game_markets, list_game_trades, get_market_top_holders, get_market_wallets_in_out, get_pick_of_the_day_stats, get_resolution_report, get_sport_leaderboard, list_category_leaders, get_category_leaderboard, and list_leaderboard_leagues.
  • ListGamesParams::expand takes sharp_money, and Game has sharp_money and volume.
  • Each of the 55 operations whose 200 carries an ETag has a ..._with_etag method, such as list_games_with_etag. It returns Tagged { data, etag }. Send etag as if_none_match on the next read.
  • get_game_holders takes condition_ids as a &[&str] argument.
Breaking for code that reads fields the document now types as enums, such as LeaderboardEntry.grade, which is Grade instead of String. The other methods keep their signatures.What to change: Run cargo add oxinsider@0.5.0. Where a field is now an enum, read it with as_str().
Game holders returns a market's last holder list, marked stale, while a fresh one computes
GET /api/v1/games/{event_slug}/holders and the MCP get_game_holders tool now answer a market whose fresh holder list is not ready within the request with the last list computed for it in the past 15 minutes. That item carries stale: true and its own computed_at.Before this change, such a market was a per-item error with reason read_model_warming, even when GET /api/v1/market/{condition_id}/holders had served it minutes earlier. A cold holder scan takes longer than the batch can wait: on November 3 it took a median of 0.8 seconds and up to 13 seconds.
  • Every item now has stale. It is false on a fresh list and true on the last list, which can be up to 15 minutes old.
  • The fresh list keeps computing, so a read a few seconds after a stale item normally returns stale: false.
  • A market refused over the account’s cold-read share is served its last list the same way. It is a per-item cold_read_limited error only when it has none.
  • A market with no list in the past 15 minutes is still a per-item error with reason read_model_warming and retry_at. Its code stays rate_limit_unavailable for compatibility, so branch on reason.
Backward compatible. stale is a new field, and no existing field changes meaning.What to change: Nothing. Read stale and computed_at if your client needs holders newer than 15 minutes, and read the market again a few seconds later for the fresh list.
Go client v0.6.0 decodes every number as float64, so money keeps its cents
The Go client github.com/0xinsider/0xinsider-go v0.6.0 decodes every JSON number as float64. Money and price fields such as LargeTrade.SizeUsd, LargeTrade.Price, and GameSharpMoney.TotalUsd keep the precision the API sends.Before this change, 478 fields decoded as float32. Above $262,144 a `float32` cannot hold every cent: a trade the API sent as `351389.57` decoded as `351389.5625` and printed as $351,389.56.
  • Regenerated from the current OpenAPI document, 106 operations.
  • The README and examples read OXINSIDER_API_KEY, the variable the TypeScript and Rust clients read. They read OXI_API_KEY before.
  • oxinsider.Version and the User-Agent header say 0.6.0. v0.5.0 through v0.5.2 sent 0.4.0.
Breaking for Go code that declares float32 variables or parameters for these fields. It no longer compiles.What to change: Run go get github.com/0xinsider/0xinsider-go@v0.6.0 and change those float32 declarations to float64.
Sandbox games list sharp money and game market lists match the per-game routes
In the sandbox, GET /api/v1/games with expand=sharp_money now gives each game the same sharp_money body that GET /api/v1/games/{event_slug}/sharp-money returns for it. GET /api/v1/games/{event_slug}/markets lists only that game’s markets.Before this change, the first game in the sandbox list carried sharp_money: null and every other game a placeholder with object: "sandbox". A game’s market list also included markets from other sandbox games.
  • Without expand=sharp_money, sandbox games carry no sharp_money field, as in the live API. With it, meta.cost rises by 3.
  • The sport field of a sandbox game carries a sport, such as Football or Basketball, and no longer a league, such as NFL.
  • The live API is unchanged.
Backward compatible. Response shapes are unchanged. Sandbox values now agree across routes.What to change: Nothing.
Sandbox game routes answer about the game named in the path
In the sandbox, GET /api/v1/games/{event_slug} and its markets, sharp-money, trades, and holders routes now describe the game that GET /api/v1/games lists under that slug. A slug the sandbox list does not include returns 404 not_found with the live API’s message.Before this change, each route returned its documented example for every slug. listGames() followed by getGameSharpMoney() on the listed Bills vs. Jets game returned Celtics vs. Magic outcomes.
  • Sharp money uses the side labels from the game’s own winner market. For nfl-buf-nyj-2026-09-22, that is “Buffalo Bills” and “New York Jets”.
  • Market slugs, outcome labels, and market titles name the requested game.
  • A slug from a live example, such as nba-bos-orl-2026-11-01, is not a sandbox game and returns 404. Use a slug from the sandbox’s GET /api/v1/games.
  • The live API is unchanged.
Breaking for sandbox clients that send a slug the sandbox list does not include. Those requests now get 404 instead of a sample 200.What to change: In the sandbox, take event_slug from GET /api/v1/games.
Trader adds the profile's Edge badge and its strategy breakdowns with expand=breakdowns
GET /api/v1/trader/{address} now returns price_edge, the wallet’s edge over the price it paid, and takes expand=breakdowns for the measures on the wallet profile’s Insights tab.Before this change, neither was in the API: the Edge badge and these measures were on the profile page only.
  • price_edge carries markets, hit_rate, avg_price, edge and status, as on Leaderboard. When status is ranked, shrunk_edge is the number the badge shows and the leaderboard ranks, with field_average, the average it is pulled toward. It is left out for a wallet with no settled sports or esports market of $20 or more.
  • breakdowns carries hourly_performance (by UTC hour), time_breakdowns (hold-time and four-hour bands), risk_metrics (position sizes and category concentration) and entry_price_analysis (five entry-price bands). It is computed on each request, so a wallet with a long history can take several seconds, and a read past 30 seconds answers 503 with Retry-After.
  • POST /api/v1/traders/batch refuses expand=breakdowns with 400 and param: "expand".
  • The MCP get_trader tool takes breakdowns in expand, and the OpenAPI document and SDK types describe both fields.
Backward compatible. Both fields are new, breakdowns is sent only when requested, and no existing field changes.What to change: Nothing. Read price_edge for the edge, and request expand=breakdowns on single-wallet reads that need the strategy measures.
Webhooks dead-letter paid event deliveries while the account has an unpaid invoice
Webhook deliveries of a paid event type now move to dead_letter while the owning account has an unpaid invoice, with last_error set to delivery owner has an overdue invoice (payment_overdue); pay it, then redeliver this delivery. Free event types still deliver.Before this change, those deliveries kept going out while the same account’s API requests answered 402 with error.reason payment_overdue.Backward compatible. status and last_error keep their meaning; the message is new.What to change: If you match on last_error, handle the new message, and redeliver the refused deliveries once the invoice is paid.
@0xinsider/mcp 3.11.0 and @0xinsider/sdk 0.36.0 are published with the payment_overdue 402
@0xinsider/mcp 3.11.0 and @0xinsider/sdk 0.36.0 are on npm. They carry the November 2 and 3 entries below.
  • On a 402 payment_overdue, the local MCP server’s tool error ends “Pay the overdue invoice at https://0xinsider.com/billing.” and its error detail carries billing_url. On a lapsed plan it ends “Reactivate at https://0xinsider.com/billing.”
  • The SDK’s SubscriptionRequiredError adds billingUrl and paymentOverdue, and API_ERROR_REASONS lists payment_overdue.
  • get_edge_hunter and the SDK take admission: "most_held_48h_by_type" with market_types.
  • list_games and get_game describe a tennis game’s tournament, and the SDK exports GameTournament and GameTournamentLocation.
  • Tool descriptions and max_age_s accept the stale data quality status. The SDK’s assessDataQuality passes a stale group that is within your maxAgeMs.
  • 0xinsider api call batchGetTraders and batchGetMarketFlow reload a stored session once on a 401, as every GET already did.
The CLI, MCP and introduction pages now install @0xinsider/mcp@3.11.0.Backward compatible. Both releases add fields, values, types and messages. Nothing is removed or renamed. One behavior changes: SDK 0.35.0 failed a stale group in assessDataQuality as an unrecognized status, and 0.36.0 passes it when it is within maxAgeMs.What to change: Run npm install --global @0xinsider/mcp@3.11.0, or change a client’s @0xinsider/mcp@3.10.0 argument to @0xinsider/mcp@3.11.0. Run npm install @0xinsider/sdk@0.36.0 to read billingUrl and paymentOverdue. 0xinsider --version prints 3.11.0 once the update is in place.
A key whose account has an unpaid invoice answers 402 payment_overdue
Every /api/v1 request made with a key whose account has an unpaid invoice now answers 402 with error.code subscription_required, the new error.reason payment_overdue, and the new error.billing_url. The remote MCP endpoint answers the same 402, and GET /api/v1/me and GET /api/v1/usage still answer 200.Before this change, a key kept full Pro or Max access while Stripe retried a failed renewal payment, which can take weeks.
  • payment_overdue means the latest subscription is past_due or unpaid. Paying the invoice at billing_url restores the same key with its settings; no new key is needed.
  • billing_url is https://0xinsider.com/billing. It is present on every subscription_required error, including subscription_inactive.
  • @0xinsider/sdk 0.36.0 adds billingUrl and paymentOverdue to SubscriptionRequiredError. @0xinsider/mcp 3.11.0 names the billing link in the tool error and the CLI message.
  • A retry does not clear the error. Only a person paying the invoice does.
Backward compatible. The code and status are unchanged; the reason and field are additive.What to change: Stop scheduled retries on payment_overdue and show billing_url to the account owner. If you already handle subscription_required, nothing.
Edge Hunter admission=most_held_48h_by_type ranks markets within each type
GET /api/v1/edge-hunter with view=observations&cohort=wider_holder now takes admission=most_held_48h_by_type with market_types. It runs the most_held_48h rule within each type you name: per sport and per market_context.market_type, up to 8 markets with the most graded holders, at most 4 per event. The get_edge_hunter MCP tool takes the same value, and the SDK’s admission and admitted_by types gain it in the next @0xinsider/sdk release.Before this change, admission=most_held_48h ranked every market type together and market_types filtered the rows afterwards. A type whose markets hold fewer wallets than team-win markets could return no rows: Soccer totals returned none on November 3, 2026, from 286 candidates.
  • Every row has admitted_by: "most_held_48h_by_type", the response echoes admission, and it has no departures.
  • market_types is required, each value 1 to 64 lowercase letters, digits or underscores. A missing or malformed value returns 400 with error.param: "market_types".
  • A sport and type pair nobody asked for in the last 30 minutes returns 503 read_model_warming with Retry-After: 45 once, while the next snapshot computes it.
  • The default response and admission=most_held_48h do not change.
Backward compatible. The value is new and opt-in.What to change: Nothing. To get Soccer totals, call admission=most_held_48h_by_type&market_types=totals&category=Soccer and retry once after a 503. See Most-held selection by market type.
Tennis games carry their tournament: tier, city, venue time zone, dates and draw
GET /api/v1/games and GET /api/v1/games/{event_slug} now return tournament on a tennis game: the tournament edition its game page states, from 0xinsider’s registry curated from official ATP, WTA and ITF pages. The list_games and get_game MCP tools and the CLI’s api call listGames and api call getGame return it too.Before this change, no V1 route, MCP tool or CLI command named a tennis game’s tournament tier, place or local time. A client had only the competition label in league.
  • tournament holds label (the provider competition label, verbatim), tour (atp, wta or itf), draw, tier (for example ATP Masters 1000, ATP Challenger 50, ITF M15), location (city, country, country_code), timezone (the venue’s IANA zone), edition_year, starts_on, ends_on, source_urls and checked_on.
  • draw is qualification when the label ends , Qualification and main for an unmarked ATP or WTA label. It is omitted for an unmarked ITF label, because Polymarket gives an ITF qualifying match the main draw’s label.
  • tier, location and timezone are omitted when no official page states them. tournament is omitted outside tennis, when the registry holds no single edition for the game, and on a finished game read after it leaves the live board.
  • The SDK types gain GameTournament and GameTournamentLocation in the next @0xinsider/sdk release.
Backward compatible. The field is new and optional.What to change: Nothing. To show a tennis match in venue time, format scheduled_at with tournament.timezone. See Tennis tournament.