Skip to main content
These are the API changelog entries from October 29 to November 1, 2026, newest first. The newest changes are on the API changelog.
Market holders totals split each side's USD by grade
GET /api/v1/market/{condition_id}/holders now returns totals.yes_grade_usd and totals.no_grade_usd, each with s, a and b. They split yes_usd and no_usd by wallet grade, over the whole roster before your outcome and min_grade filters. The get_market_holders MCP tool returns the same fields, and @0xinsider/sdk types them as MarketHoldersSideGradeUsd.Before this change, totals had the wallet count per grade on each side but no USD per grade. To learn where the S-grade money sat, you had to page the whole roster and add up current_value_usd yourself.Each value is rounded to the cent after summing, so s + a + b can differ from the side total by up to 2 cents.Backward compatible. The fields are additive.What to change: Nothing is required. Read totals.yes_grade_usd.s instead of summing pages. A strict schema that rejects unknown keys must accept the new fields.
CLI 3.7.0 adds api list, game and holder commands, and suggests a mistyped operation id
The CLI in @0xinsider/mcp 3.7.0 adds api list, which prints every operation api call runs with its method, path, and path and query parameter names. It works offline without a credential, as JSON or with --table.
  • New commands: games list, games get <event_slug>, market holders <condition_id>, market candles <condition_id>, markets sharp-flows, traders context <wallet>, traders categories <wallet>, traders grade-at <wallet>, large-positions list, edge-hunter, and pick archive. Each one prints the API response unchanged.
  • An unknown api call operation id now names the closest registered ids and points to api list. 0xinsider api call getGames suggests listGames and getGame.
  • api call now runs the batch lookups batchGetTraders and batchGetMarketFlow, with the request body in --body '<json>'. It also runs listWebhooks, getWebhook, and getTraderExportSnapshot, the same reads the MCP tools serve. Neither webhook read returns a signing secret.
  • Shell completions now complete each command’s second word and the operation id after api call.
Before this change, those reads worked only through api call <operation-id>, nothing listed the ids, and a near-miss printed This is not a registered non-administrative JSON read operation. with no suggestion.Backward compatible. Every existing command, alias, option, and exit code is unchanged. api call still refuses every write, the other webhook routes, streams, MCP, other exports, and Markdown routes.What to change: Nothing. Update to @0xinsider/mcp 3.7.0 once it is published, then run 0xinsider api list --table to see what api call accepts. Scripts that match the old refusal message should match exit code 2 instead.
Team logos carry logo_mark_measured, and provider-hosted logos are now measured on white
Team logos now carry logo_mark_measured, a boolean beside logo_mark_dark and logo_mark_light. It is true when both verdicts are measurements of this exact logo URL, so their false means the mark is legible on that background. It is false when the logo has not been measured yet, so a false verdict beside it can still mean “not measured”.
  • GET /api/v1/pick-of-the-day: each team in sports_context (yes_team, no_team and event_subject_team). Like logo_mark_light there, it is always sent.
  • GET /api/v1/edge-hunter: each entry in a row’s participants. Like logo_mark_light there, it is sent only when true.
Every vendored crest is measured. Provider-hosted logos are now measured on a white background as well as a dark one, so logo_mark_light can be true for them too. A provider logo becomes measured within a few hourly cycles of first appearing.Backward compatible. The field is additive. logo_mark_light can now be true for a provider-hosted logo.What to change: Nothing is required. If you draw an outline behind a logo whose verdicts are unknown, skip it when logo_mark_measured is true. A strict schema that rejects unknown keys must accept the new field.
Team logos carry logo_mark_light, for crests that vanish on a white background
Team logos now carry logo_mark_light, a boolean beside logo_mark_dark. It is true when measurement shows the crest’s artwork disappears on a white background, so it needs a dark plate there. It is false when no current measurement marks the logo light, which includes every provider-hosted logo.
  • GET /api/v1/pick-of-the-day: each team in sports_context (yes_team, no_team and event_subject_team). Like logo_mark_dark there, it is always sent.
  • GET /api/v1/edge-hunter: each entry in a row’s participants. Like logo_mark_dark there, it is sent only when true.
The verdict is tied to the exact bytes of a vendored crest. On November 1, 2026, 458 of 19,955 vendored crests measured light.Backward compatible. The field is additive.What to change: Nothing is required. To keep a light crest legible on a light background, draw it on a dark plate when logo_mark_light is true. A strict schema that rejects unknown keys must accept the new field.
Edge Hunter keeps a quiet far-from-kickoff market's holder page for up to 30 minutes
On GET /api/v1/edge-hunter with view=observations, a pregame market more than an hour before kickoff still has its holders read again once its page is 10 minutes old, but only if the market has traded since that page. While it has had no trade, the snapshot keeps reusing the page until it is 30 minutes old. Such a row carries the page’s original time in holder_snapshot_at and provider_read_source cached, as any reuse does.Before this change the page was read again every 10 minutes whether or not anything had traded. Markets within an hour of kickoff, in_play rows, and the markets only admission=most_held_48h takes are read as before.Backward compatible. No field is added, removed, or renamed. holder_snapshot_at on a quiet market more than an hour before kickoff can now be up to 30 minutes old instead of 10.What to change: If you check holder_snapshot_at against a 10- or 15-minute bound on these rows, allow 30 minutes for a market more than an hour before kickoff.
A holder's category average price is counted once a day, with its win record
category_avg_price on each holder of GET /api/v1/market/{condition_id}/holders, and on each display_holders entry of GET /api/v1/pick-of-the-day, now comes from the same daily count as category_win_rate and category_win_record beside it. Before this change the price moved as markets settled while the record moved once a day, so a holder could carry a price that counted markets its record did not count yet.The definition does not change: the wallet’s average entry price over its markets in the category that resolved to a winning side, of $20 or more where it bought one side at a known price.Backward compatible. No field is added, removed, or renamed. The price can now trail a settlement by up to a day, as the record already did.What to change: Nothing.
Category win rates count every settled market, including ones a wallet sold out of early
A wallet’s category win rate and record now count every market where its position is settled: the wallet sold out of it, or the market resolved. Wins and losses are still the sign of realized P&L. Before this change a market counted only once it resolved to a winning side, so a sale out of a market still trading waited for that market, and a void or unresolvable market never counted. The wallet’s profile already counted every settled market, so the two now agree.Backward compatible. No field is added, removed, or renamed. Counts and rates move for wallets that sold out of markets still trading or held positions on void markets. Calibration fields, such as edge and average entry price, still count only markets with a winning side.What to change: Nothing. If you label these counts “resolved markets”, “settled markets” is the accurate name.
Browser CORS stops listing Mcp-Session-Id and X-Mcp-Error-Code as readable headers
Public /api/v1 responses no longer name Mcp-Session-Id or X-Mcp-Error-Code in Access-Control-Expose-Headers. No response has sent either header since remote MCP dropped sessions earlier the same day, so the list now names only headers the API sends.
  • Before this change, every public API response listed both names among the headers browser JavaScript may read.
  • Every other readable header is unchanged. The Browser CORS table lists them.
  • Mcp-Session-Id stays allowed on the request, so an older client’s preflight still passes.
Backward compatible. A browser read of either header returned null before this change and still does.What to change: Nothing.
OpenAPI response schemas now match what each endpoint sends
The response schemas in the OpenAPI document are now checked against the server’s own response types, so each one lists what its endpoint sends. Before this change, they were kept by hand, and a few said less or more than the server sends. No response changes.Breaking for a client generated from the document that references one of the three removed properties: the generated type no longer has it. No response byte changes, and those properties were never present.What to change: Nothing at runtime. If generated code references total, proof_pending_picks, or meta.cache_age_s on those bodies, delete the reference.
Sandbox MCP answers the transport contract the live endpoint now serves
The sandbox POST /api/v1/mcp at https://0xinsider.com/sandbox now gives the same status codes, headers, and JSON-RPC errors as the live endpoint after the October 31 move to stateless MCP. Before this change, the sandbox still answered the old transport contract.
  • The sandbox sends no Mcp-Session-Id and no X-Mcp-Error-Code, and it ignores an Mcp-Session-Id you send. Browser preflight now allows Mcp-Method and Mcp-Name.
  • Revision 2026-07-28 is served. A request at that revision needs the Mcp-Method header, and Mcp-Name on tools/call. Without them it returns -32020. Its _meta must carry protocolVersion and clientCapabilities, or it returns -32602.
  • An unsupported MCP-Protocol-Version on a POST returns 400 with the REST error body, error.code bad_request. GET, PUT, PATCH, and DELETE return 405 with Allow: POST and error.reason method_not_allowed.
  • A message without an id returns 202. A response message returns 400 with -32600.
  • These all return 400 with -32600: a null, fractional, or boolean id; params that is not an object; a JSON array body; a repeated method or id key; or jsonrpc other than 2.0.
  • A tools/call whose arguments is not an object returns -32602. An unknown tool returns -32601.
  • A method the server does not implement, such as resources/list, returns -32601 with the bare method name as its message. An unknown method keeps the Method not found: prefix.
The sandbox still ignores credentials, so it never returns the live endpoint’s 401. Parse-error messages for malformed JSON use the sandbox’s own wording.Breaking for sandbox clients that read Mcp-Session-Id, X-Mcp-Error-Code, or the old JSON-RPC body on GET or on an unsupported MCP-Protocol-Version.What to change: Nothing if your client works against the live endpoint. Otherwise make the same changes as the live endpoint’s October 31 entry below.
Remote MCP drops sessions, serves revision 2026-07-28, and reshapes a few transport errors
POST /api/v1/mcp now runs on the official Rust MCP SDK in stateless mode. Tools, their input schemas, their results, credentials, and quota counting do not change. A few transport details do.
  • Sessions: the server no longer sends Mcp-Session-Id, and it ignores one you send. Before this change, initialize minted one and every response echoed it, though nothing depended on it.
  • Revisions: 2026-07-28 is now served, alongside 2025-11-25, 2025-06-18, 2025-03-26, and 2024-11-05. A 2026-07-28 request needs the Mcp-Method header and gets -32020 without it. Browser preflight now allows Mcp-Method and Mcp-Name.
  • An unsupported MCP-Protocol-Version on a POST returns 400 in the REST error envelope, error.code bad_request, instead of JSON-RPC -32600. A GET ignores the header. An initialize whose MCP-Protocol-Version header names a different revision from its params.protocolVersion returns 400 with -32600; send the header only after initialize, or send the same value.
  • GET /api/v1/mcp with a credential still returns 405 with Allow: POST. The body is now the REST error envelope with error.reason method_not_allowed, not JSON-RPC -32004. DELETE and PUT return the same 405.
  • No response carries X-Mcp-Error-Code. Read the code from the JSON-RPC body.
  • A message without an id returns 202 whatever its method. Before this change, an unknown method without an id returned 400.
  • A method the server does not implement, such as resources/list or prompts/list, still returns -32601, but the message is now the bare method name rather than Method not found: <method>. An unknown method keeps the prefix. Match on the code, not the text.
  • A notification method sent with an id returns 200 with -32601, not 400 with -32600. A response message returns 400 with -32600 in the body, not an empty 400.
  • A tools/call whose arguments is not an object returns -32602, not a tool result with isError. An arguments of null is read as the empty object.
  • A request id of null, an object id, params that is not an object, a JSON array body, or a body with two method keys returns 400 with -32600. Before this change, a null id was echoed and non-object params on initialize were ignored.
Breaking for clients that read X-Mcp-Error-Code, the -32004 body on GET, or the -32600 body for an unsupported MCP-Protocol-Version.What to change: Read transport errors from the JSON-RPC body, and stop sending Mcp-Session-Id. Conforming MCP clients need no change.
Esports markets Polymarket files under no series now count toward their game
Polymarket files some esports events under no series. A market in one of those events used to count toward the Esports record alone. It now counts toward the game its event slug names, such as cs2- or valorant-.
  • games on GET /api/v1/trader/{address}/categories can show higher wins and decided, and a game the wallet did not have before. The counts update at the next daily rebuild.
  • category_win_rate_game on GET /api/v1/market/{condition_id}/holders can now name a game on such a market.
  • A market with a series keeps that series. A market whose slug names no game, or names a league that another sport shares, still counts toward Esports alone.
  • The Esports record itself does not change.
Backward compatible. No field, type, or series_slug value changes.What to change: Nothing. A client that stores per-game counts should expect them to rise once.
Market snapshots date a settlement by Polymarket's own settlement time
market.resolved_at on GET /api/v1/market/{condition_id}/snapshot now holds the time Polymarket settled the market, for a market that settles from this release on. Before this change, it held the time 0xinsider recorded the settlement. That ran about 2 minutes after Polymarket’s on a typical market, and days or weeks after it on a market 0xinsider settled late.
  • The value is Polymarket’s close time for the market, or its resolution time for an archived market that Polymarket’s market listing no longer returns. It is never later than the time 0xinsider recorded the settlement.
  • A market settled without a Polymarket time keeps the time 0xinsider recorded, as before.
  • A market settled before this release keeps the value it has.
  • market.status and market.closed do not change.
  • The MCP tool get_market_snapshot returns the same value.
Backward compatible. The field and its type do not change; for a new settlement the time it reports is earlier.What to change: Nothing. A client that read resolved_at as the time 0xinsider learned of a settlement should read it as the time the market settled.
Live tennis doubles carry API-Tennis set scores in tennis_points.set_games
sports.live_score.tennis_points on GET /api/v1/market/{condition_id}/snapshot, and live_score.tennis_points in LiveScoreChanged frames on GET /api/v1/stream, now reach live ATP and WTA doubles matches and can carry a new optional field, set_games. It lists API-Tennis’s games in every set played so far, first set first, each as [first, second] in live_score.scores order, such as [[6, 1], [4, 6], [9, 5]]. A deciding match tie-break is listed as games, so [9, 5] is a match tie-break at 9-5.Polymarket publishes no score for doubles: live_score.scores reads one set at 0-0 for the whole match. Before this change, tennis_points was only sent when its current-set games matched that score, so a doubles match lost the group after its first game. A doubles group is now sent when both pairs’ names, the tour, the date, and API-Tennis’s live flag match one match.
  • set_games is present only while live_score carries no published score. Show it as the set score, with points and serving_side for the current game.
  • With set_games present, set_number and games still describe the current set, but they are not checked against live_score.
  • A singles match is unchanged and never carries set_games. If Polymarket ever publishes a nonzero game for a doubles match, the group is dropped and singles checking applies again.
  • live_score.scores is unchanged. It is still Polymarket’s value, 0-0.
Backward compatible. set_games is new and optional, and no existing field changes type or meaning for singles.What to change: Nothing. To show a live doubles score, read tennis_points.set_games when it is present, and keep enforcing expires_at.
Live tennis doubles scores on the stream name both pairs
LiveScoreChanged frames from GET /api/v1/stream for a tennis doubles match now name the two pairs in live_score.scores[]. Before, a frame from Polymarket’s live feed could name two players of the same pair as the two sides, such as Facundo Bagnis against Emilio Gomez for Bagnis/Gomez against Drzewiecki/Matuszewski, and one side could carry a singles ranking.Each side’s team is now the pair’s code from the event slug, in slug order, such as BAGNGOM and DRZEMAT for atp-doubles-bagngom-drzemat-2026-10-30, and full_name is the pair’s name, such as Bagnis/Gomez. A doubles side carries no singles ranking. This applies while Polymarket’s doubles score reads 0-0, which it does for every doubles match today.Backward compatible. No field or type changes.What to change: Nothing. A client that matched a doubles side to a player by team should match the pair by full_name.
Holder entries name the league on every sport, not only soccer
Holder entries on GET /api/v1/pick-of-the-day and GET /api/v1/market/{condition_id}/holders now carry category_win_rate_game on the other sports too. It names the league that category_win_rate and category_win_record were measured in: NBA, WNBA, NFL, College Football, NHL, MLB, UFC, ATP, WTA, and the other registered leagues.Before this change, the field was sent only on esports and soccer markets, and every NBA rate was the wallet’s whole Basketball record.
  • It is present when the wallet has at least 5 resolved markets in that league. The rate and record are then the league’s rather than the whole category bucket’s, so the counts in category_win_record can be smaller than before.
  • It is absent when the rate is the wallet’s whole sport record, on a competition without its own league record (NBA Summer League, the World Baseball Classic, and others), and beside every status that is not measured.
  • category_avg_price stays the sport’s price, and the payload-level category stays the sport, such as Basketball.
  • The MCP tools get_market_holders and get_pick_of_the_day return the same value.
League records fill in from the first daily rebuild after this release. Until then these entries carry the sport’s record, as before.Backward compatible. The field already exists and stays optional; it now appears on more entries.What to change: Nothing, if you already label the rate with category_win_rate_game when it is present and with category when it is not. A client that reads an NBA rate as a Basketball rate should check the field first.
Team names read in full on picks, and live scores carry a display name
full_name and matchup_title in sports_context on GET /api/v1/pick-of-the-day now hold the name to show for a team, the same one the website shows. College football joins the school and the mascot, so UCF reads UCF Knights. NBA and NHL teams read their club name, so Thunder reads Oklahoma City Thunder. Every other league keeps the name it already had.label does not change. It stays the spelling on the market outcome, such as UCF, so use label to match a team to an outcome. pick_outcome_label, matchup, and every field of a published pick stay frozen, and a settled pick keeps the names it settled with.live_score.scores[] under sports on GET /api/v1/market/{condition_id}/snapshot can now carry an optional display_name. It is the same name to show, and it is omitted when it equals the entry’s full_name, or its team when that entry has no full_name. team and full_name keep their values on a score entry.Backward compatible for clients that show these fields. The types are unchanged and display_name is new and optional. A client that compares full_name or matchup_title to a stored value sees a new value on games not yet settled.What to change: To match a team to an outcome, read label, not full_name. To show a live score, read display_name, then full_name, then team.
MarketPriceChanged stream frames carry each outcome's display price
MarketPriceChanged frames on GET /api/v1/stream can now carry two optional fields, display_price (the Yes outcome) and no_display_price (the No outcome). Each is the price Polymarket displays for that outcome token, as { "price": "0.565", "basis": "midpoint", "ts": "1793386388438" }:
  • price is a decimal string between 0 and 1.
  • basis is midpoint when the spread is at most $0.10, otherwise last_trade.
  • ts is the provider time of that price, in epoch milliseconds, as a string.
Each outcome is priced from its own token. no_display_price is never computed as 1 minus the Yes price. A field is omitted when no priced frame for that token landed in the frame’s window, so an omitted field says nothing about the token; keep the price you hold. A frame can now carry only these fields, with last_price, no_last_price, best_bid and best_ask all null.Backward compatible. Both fields are new and optional, and the existing fields keep their meaning.What to change: Nothing. To show the same price as Polymarket, read display_price and no_display_price, and keep a newer ts over an older one for each outcome. A client that skipped frames whose four existing price fields were all null should accept them now.
Soccer holder entries name the league their win rate was measured in
Holder entries on GET /api/v1/pick-of-the-day and GET /api/v1/market/{condition_id}/holders now carry category_win_rate_game on a soccer market too. It names the league that category_win_rate and category_win_record were measured in, by the name Pick of the Day prints: LaLiga, Premier League, Serie A, Bundesliga, Ligue 1, UEFA Champions League, UEFA Europa League, MLS, and the other leagues a pick names.Before this change, the field was sent only on esports markets, and every soccer rate was the wallet’s whole Soccer record.
  • It is present when the wallet has at least 5 resolved markets in that league. The rate and record are then the league’s rather than the whole Soccer bucket’s.
  • It is absent when the rate is the wallet’s whole Soccer record, on a competition without its own league record (UEFA Nations League, international friendlies, Eredivisie, and others), and beside every status that is not measured.
  • category_avg_price stays the Soccer price, and the payload-level category stays Soccer.
  • The MCP tools get_market_holders and get_pick_of_the_day return the same value.
League records fill in from the first daily rebuild after this release. Until then every soccer entry carries the Soccer record, as before.Backward compatible. The field already exists and stays optional; it now appears on more entries.What to change: Nothing, if you already label the rate with category_win_rate_game when it is present and with category when it is not. A client that assumed the field means an esports game should read it as a game or a league.
A tennis match frozen in play for 3 hours reads as suspended, not live
GET /api/v1/games and GET /api/v1/games/{event_slug} now report a tennis match as suspended when Polymarket still lists it in play but nothing has changed for 3 hours. That means no live score feed for the match, and no change to its period or score. Such a match reads status.state suspended, status.match_status suspended, and status.live false.Before this change, these endpoints reported such a match as live. Polymarket does not always mark a suspended tennis match SUS. On October 30, 2026, two doubles matches suspended the evening before still read live at 0-0, more than 19 hours after their listed start.
  • status.period and the score stay as Polymarket sent them.
  • The match reads live again when the live score feed sends it a state, or when its period or score changes.
  • Suspended games whose period reads SUS are unchanged.
  • The status.state and status.live descriptions now name this case.
Backward compatible. No field is added, removed, or retyped, and suspended was already a documented value of status.state and status.match_status.What to change: Nothing, if your client already handles suspended. If you list live games with status=live, also request status=suspended to see tennis matches that stopped during play.
Account identity reports integration keys as integration_key, and their scope errors say how to fix them
GET /api/v1/me now returns credential_kind: "integration_key" for a named integration key created on Developers. Before this change, an integration key reported oauth_grant, so 0xinsider whoami showed it as an OAuth grant, and so did any client that read the field.A request outside an integration key’s scopes still returns 403 insufficient_scope with the same param and WWW-Authenticate challenge. Its error.message now says that an integration key’s scopes are fixed and tells you to create a key with the missing scope on Developers. Before, it said to re-authorize, which an integration key cannot do. The remote MCP tools and the GET /api/v1/stream closing frame use the same wording.
  • api_key and oauth_grant keep their meaning, and OAuth grants keep the re-authorize message.
  • scopes is unchanged: null for a developer key, the scope array for an OAuth grant or integration key.
Additive. credential_kind gains one value; no field is added, removed, or retyped. The TypeScript SDK’s credential_kind type adds "integration_key".What to change: If your client switches on credential_kind, handle integration_key. Do not match on error.message; branch on error.code and param.
Explore markets keeps open_interest current for 30 minutes after a call
GET /api/v1/markets/explore and the MCP explore_markets tool now keep each listed market’s open_interest current for 30 minutes after a call. The first call after 30 minutes with no call can return open_interest: null for markets whose value has not been read yet. The values fill in over the next few minutes, and later calls in the window carry them.Before this change, the server read open interest for these markets every few minutes all day, whether or not anyone called the route.
  • oi_change_pct is null whenever open_interest is, as before.
  • Every other field is unchanged.
Backward compatible. open_interest was already nullable, and no field is added, removed, or retyped.What to change: Nothing, if you already handle null. If your first call needs open interest, call the route again a few minutes later.
A grade needs 5 sports or esports markets
A wallet now needs at least 5 sports or esports markets to hold a grade. Below that, grade is null.Before this change, the floor counted markets in every category, so a wallet with 1 to 4 sports or esports markets could still hold a letter.
  • grade changes wherever it is returned, including large trades, positions and their min_grade filters.
  • Letters are shares of the graded wallets, so a smaller group moves other letters. On a copy of production from October 28, 27,534 of 164,786 graded wallets lost their letter, all of them C, D or F. Another 7,760 changed letter, including 35 S and 180 A wallets that went down one.
  • In production, wallets graded S or A went from 1,475 on October 29 to 1,289 on October 30.
Backward compatible. Field names, types, allowed values and presence rules are unchanged; what the values measure changes.What to change: If your logic reads a list of S or A wallets, refresh it. If you store grades, expect a one-time change for some wallets.
Pick of the Day keeps a team's full name in sports_context after kickoff
GET /api/v1/pick-of-the-day now names each team in a pick’s sports_context by its full Polymarket name before, during, and after the game. This applies to a pick on a market whose outcomes are “Yes” and “No”, such as “Will US Lecce win?”, where yes_team and no_team are the game’s two teams.Before this change, once the game had a score, label and full_name on yes_team and no_team, and matchup_title, switched to the team’s short name. “US Lecce” read “Lecce”, and “ACF Fiorentina” read “Fiorentina”. event_subject_team could also go missing for such a team.
  • A team with no short name is unchanged.
  • A pick on a market whose outcomes are the team names keeps those names, as before.
Backward compatible. No field is added, removed, or retyped.What to change: Nothing. If you matched a pick’s team by the short name it showed during a game, match by provider_id instead.
A wallet that trades only combos says it has no sports record, not a pending one
GET /api/v1/trader/{address} and POST /api/v1/traders/batch now say why a wallet that trades only combos has no sports and esports record. The record counts single markets, so a wallet whose sports and esports trading is all in combos never gets one. A wallet counts as one when its last full trader sync found no single sports or esports market, 0xinsider stores none of its markets, and 0xinsider holds at least one of its combos.Before this change, these wallets read as if their record were still being computed, and they never got one.
  • The sports_record group in data_quality is partial, with owner traders.last_synced, as_of set to that sync, and a reason that names combos. Before, it was unavailable.
  • pnl.in_scope, stats.markets_traded, and stats.win_rate stay absent. The API serves no other P&L figure in their place.
  • With expand=trust, trust.markets_traded and trust.win_rate stay unavailable, and their reason names combos.
  • GET /api/v1/trader/{address}/pnl returns series_status: "ready" with empty sections, realized: null, and freshness_at set to that sync. Before, it returned pending.
Backward compatible. No field is added, removed, or retyped.What to change: Nothing. If you retried such a wallet until pnl.in_scope appeared, stop when sports_record is partial with owner traders.last_synced: the record will not appear while the wallet trades only combos.
A wallet that traded only other categories reads 0 sports markets, not pending
GET /api/v1/trader/{address} and POST /api/v1/traders/batch now return stats.markets_traded as 0 for a wallet that traded only markets outside sports and esports. GET /api/v1/trader/{address}/pnl and GET /api/v1/trader/{address}/export return ready with no day for it. Two earlier entries of October 29 covered a wallet that traded no market at all; a wallet now also counts when its last completed sync read its Polymarket positions and found no sports or esports market.Before this change, these wallets read as if their record were still being computed, and they never got one. On October 29, 2026, 17 wallets matched.
  • Each field reads as those entries describe: stats.markets_traded is 0, pnl.in_scope and stats.win_rate are absent, and the sports_record group is partial with owner traders.last_synced.
  • /pnl returns series_status: "ready" with empty sections and realized: null, and the export returns pnl_series_status: "ready" with no day.
  • With expand=trust, trust.markets_traded.source.field is now last_synced for every wallet that traded no sports or esports market, including the wallets those entries cover. Before, it was markets_traded, Polymarket’s count, which is above 0 for these 17.
  • A wallet with Polymarket combo trades is not counted here.
  • A wallet whose sports and esports record is not computed yet is unchanged: it still omits stats.markets_traded and reads pending.
Backward compatible. No field is added, removed, or retyped.What to change: Nothing, unless you read trust.markets_traded.source.field: for a wallet with no record it is now last_synced.
A P&L correction no longer moves a trader's synced_at or sends trader_synced
GET /api/v1/trader/{address} and POST /api/v1/traders/batch now keep synced_at at the wallet’s last full sync when 0xinsider corrects the wallet’s settled P&L. A correction re-reads the wallet’s closed positions from Polymarket and recomputes its statistics. It does not read the wallet’s trades or open positions.Before this change, a correction set synced_at to the time it finished. A wallet could then read as synced minutes ago while its trades were last read days before.
  • synced_at and last_active, and the as_of of the sync and volume groups in data_quality, keep the time of the last full sync.
  • The sports_record group in data_quality still moves when a correction changes the wallet’s sports and esports record.
  • The trader_synced webhook and its deprecated spelling whale_trader_synced no longer fire for a correction. They fire when a full sync finishes.
Backward compatible. No field is added, removed, or retyped.What to change: Nothing. To notice a corrected P&L, compare the sports_record group’s as_of, not synced_at.
Trader export reads ready, not pending, for a wallet that traded no market
GET /api/v1/trader/{address}/export now returns source_range.pnl_series_status as ready for a wallet that traded no market. Such a wallet never gets a sports and esports daily series. The export file’s export_metadata.pnl_series_status follows, with pnl_freshness_at set to the sync that found no market.Before this change, these wallets read pending for good, as if the series were still being computed. On October 29, 2026, 1,274 wallets matched. A wallet counts when GET /api/v1/trader/{address} returns stats.markets_traded as 0 for it, the same rule that endpoint and GET /api/v1/trader/{address}/pnl use since October 29, 2026.
  • The dates stay null, counts.pnl_days stays 0, and the file’s pnl section stays empty.
  • With nothing synced, completeness.status is empty, as before.
  • A wallet with markets whose series is not computed yet is unchanged: it still reads pending.
  • A file built before this change keeps its old value until it expires, one day after it was built.
Backward compatible. No field is added, removed, or retyped, and pnl_series_status keeps its two values.What to change: Nothing. If you retried a wallet until pnl_series_status became ready, the retry now ends with an empty series.
Pick of the Day chooses a pick only inside the 4 hours before kickoff
GET /api/v1/pick-of-the-day now chooses a pick only when its game starts within the next 4 hours, on the wallets and the price at that moment. A game further off is not chosen early, so scheduled_picks no longer lists a slot for it hours ahead.Before this change, a pick could be chosen as early as the start of the product day and wait in scheduled_picks until 4 hours before kickoff. A slot chosen that way before this change stays in scheduled_picks and publishes at its release_at, after the usual publication checks.
  • scheduled_picks still lists a slot whose release is held: one chosen before this change, one chosen before 3:00 AM Eastern, or one whose kickoff the provider moves later.
  • Publication timing is unchanged: no earlier than 4 hours and no later than 30 minutes before kickoff.
Backward compatible. No field is added, removed, or retyped. 404 pick_not_released can last until the day’s first game is within 4 hours of kickoff.What to change: Nothing, if you schedule reads from retry_at and release_at. Do not treat an empty scheduled_picks as a day with no picks to come.
A synced wallet that traded no market reads 0 markets, not a pending record
GET /api/v1/trader/{address} and POST /api/v1/traders/batch now return stats.markets_traded as 0 for a wallet that traded no market. Such a wallet never gets a sports and esports record. A wallet counts as one when Polymarket counted no market for it at its last completed sync, 0xinsider stores none of its markets, and no Polymarket sports or esports board lists it.Before this change, these wallets read as if their record were still being computed, and they never got one. On October 29, 2026, 1,275 wallets matched.
  • stats.markets_traded is 0. pnl.in_scope and stats.win_rate stay absent, because the wallet has no record.
  • The sports_record group in data_quality is partial, with owner traders.last_synced and as_of set to the sync that found no market. Before, it was unavailable.
  • With expand=trust, trust.markets_traded is computed from traders.markets_traded and dated by that sync.
  • GET /api/v1/trader/{address}/pnl returns series_status: "ready" with empty sections, realized: null, and freshness_at set to that sync. Before, it returned pending for these wallets.
  • A wallet with markets whose record is not computed yet is unchanged: it still omits stats.markets_traded and reads pending.
Backward compatible. No field is added, removed, or retyped.What to change: Nothing. If you retried a wallet until stats.markets_traded appeared, the retry now ends with 0.
Trader export provider_lifetime_volume is always null and deprecated
GET /api/v1/trader/{address}/export now returns reconciliation.provider_lifetime_volume as null for every wallet. The export file’s summary.volume_reconciliation.provider_lifetime_volume is null too, and the reference marks the field deprecated.Before this change, the field held Polymarket’s volume for the wallet across every market, often observed weeks earlier. Since October 25, 2026, the volume beside it counts sports and esports markets only, so the two could not be compared.
  • The key stays, with the same type, so existing clients keep parsing the object.
  • exported_activity_volume, exported_market_cost_basis, and every other field are unchanged.
  • A file built before this change keeps its old value until it expires, one day after it was built.
  • The TypeScript SDK marks ExportVolumeReconciliation.provider_lifetime_volume @deprecated, and the get_trader_export_snapshot MCP output schema marks it deprecated.
Breaking for clients that read provider_lifetime_volume as a number. Its name and type are unchanged.What to change: Stop reading provider_lifetime_volume. Read exported_activity_volume for the wallet’s sports and esports volume.