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, andpick archive. Each one prints the API response unchanged. - An unknown
api calloperation id now names the closest registered ids and points toapi list.0xinsider api call getGamessuggestslistGamesandgetGame. api callnow runs the batch lookupsbatchGetTradersandbatchGetMarketFlow, with the request body in--body '<json>'. It also runslistWebhooks,getWebhook, andgetTraderExportSnapshot, 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.
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 insports_context(yes_team,no_teamandevent_subject_team). Likelogo_mark_lightthere, it is always sent.GET /api/v1/edge-hunter: each entry in a row’sparticipants. Likelogo_mark_lightthere, it is sent only whentrue.
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 insports_context(yes_team,no_teamandevent_subject_team). Likelogo_mark_darkthere, it is always sent.GET /api/v1/edge-hunter: each entry in a row’sparticipants. Likelogo_mark_darkthere, it is sent only whentrue.
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.
GET /api/v1/trader/{address}/categories: every category and game record.GET /api/v1/market/{condition_id}/holders:category_win_rateandcategory_win_recordon each holder, and the 5-market floor behindcategory_win_rate_status.GET /api/v1/pick-of-the-day: the same fields ondisplay_holdersentries, andqualifying_expert.win_rateandn_resolved.- The descriptions of
markets_resolvedand theresolved_*win-rate counts onGET /api/v1/trader/{address}/contextandGET /api/v1/trader/{address}/pnlnow say “settled markets”. Their values do not change.
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-Idstays allowed on the request, so an older client’s preflight still passes.
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.
- Grades list their values,
S,A,B,C,DandF, where the schema said only “string”:gradeonGET /api/v1/leaderboard,trader.gradeonGET /api/v1/large-tradesandGET /api/v1/events/feed/since,gradeintop_positionsonGET /api/v1/market/{condition_id}/flow, andtrader_gradeandgrade_distribution[].gradeonGET /api/v1/reportsand its snapshots. - Objects that were untyped now name their keys: report
categories[](category,large_trade_count,whale_trade_count,category_volume) andgrade_distribution[](grade,count), anddirect_streaming,async_jobandrate_limitinlarge_export_policyonGET /api/v1/trader/{address}/export. - Three properties the server never sent leave the schema:
totalonGET /api/v1/combos/fills,proof_pending_pickson the no-entitled-picks body ofGET /api/v1/pick-of-the-day, andmeta.cache_age_sonGET /api/v1/events/feed/since. - Where an endpoint sends fewer values than its type allows, such as a webhook’s own
status, the schema node carries"x-narrowed": true. Validators and code generators ignorex-extensions.
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-Idand noX-Mcp-Error-Code, and it ignores anMcp-Session-Idyou send. Browser preflight now allowsMcp-MethodandMcp-Name. - Revision
2026-07-28is served. A request at that revision needs theMcp-Methodheader, andMcp-Nameontools/call. Without them it returns-32020. Its_metamust carryprotocolVersionandclientCapabilities, or it returns-32602. - An unsupported
MCP-Protocol-Versionon a POST returns400with the REST error body,error.codebad_request.GET,PUT,PATCH, andDELETEreturn405withAllow: POSTanderror.reasonmethod_not_allowed. - A message without an
idreturns202. A response message returns400with-32600. - These all return
400with-32600: anull, fractional, or booleanid;paramsthat is not an object; a JSON array body; a repeatedmethodoridkey; orjsonrpcother than2.0. - A
tools/callwhoseargumentsis not an object returns-32602. An unknown tool returns-32601. - A method the server does not implement, such as
resources/list, returns-32601with the bare method name as its message. An unknown method keeps theMethod not found:prefix.
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,initializeminted one and every response echoed it, though nothing depended on it. - Revisions:
2026-07-28is now served, alongside2025-11-25,2025-06-18,2025-03-26, and2024-11-05. A2026-07-28request needs theMcp-Methodheader and gets-32020without it. Browser preflight now allowsMcp-MethodandMcp-Name. - An unsupported
MCP-Protocol-Versionon a POST returns400in the REST error envelope,error.codebad_request, instead of JSON-RPC-32600. A GET ignores the header. AninitializewhoseMCP-Protocol-Versionheader names a different revision from itsparams.protocolVersionreturns400with-32600; send the header only afterinitialize, or send the same value. GET /api/v1/mcpwith a credential still returns405withAllow: POST. The body is now the REST error envelope witherror.reasonmethod_not_allowed, not JSON-RPC-32004.DELETEandPUTreturn the same405.- No response carries
X-Mcp-Error-Code. Read the code from the JSON-RPC body. - A message without an
idreturns202whatever its method. Before this change, an unknown method without anidreturned400. - A method the server does not implement, such as
resources/listorprompts/list, still returns-32601, but the message is now the bare method name rather thanMethod not found: <method>. An unknown method keeps the prefix. Match on the code, not the text. - A notification method sent with an
idreturns200with-32601, not400with-32600. A response message returns400with-32600in the body, not an empty400. - A
tools/callwhoseargumentsis not an object returns-32602, not a tool result withisError. Anargumentsofnullis read as the empty object. - A request
idofnull, an objectid,paramsthat is not an object, a JSON array body, or a body with twomethodkeys returns400with-32600. Before this change, anullidwas echoed and non-objectparamsoninitializewere ignored.
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-.gamesonGET /api/v1/trader/{address}/categoriescan show higherwinsanddecided, and a game the wallet did not have before. The counts update at the next daily rebuild.category_win_rate_gameonGET /api/v1/market/{condition_id}/holderscan 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
Esportsalone. - The
Esportsrecord itself does not change.
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.statusandmarket.closeddo not change.- The MCP tool
get_market_snapshotreturns the same value.
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_gamesis present only whilelive_scorecarries no published score. Show it as the set score, withpointsandserving_sidefor the current game.- With
set_gamespresent,set_numberandgamesstill describe the current set, but they are not checked againstlive_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.scoresis unchanged. It is still Polymarket’s value, 0-0.
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
categorybucket’s, so the counts incategory_win_recordcan 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_pricestays the sport’s price, and the payload-levelcategorystays the sport, such asBasketball.- The MCP tools
get_market_holdersandget_pick_of_the_dayreturn the same value.
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" }:priceis a decimal string between 0 and 1.basisismidpointwhen the spread is at most $0.10, otherwiselast_trade.tsis the provider time of that price, in epoch milliseconds, as a string.
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
Soccerbucket’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_pricestays theSoccerprice, and the payload-levelcategorystaysSoccer.- The MCP tools
get_market_holdersandget_pick_of_the_dayreturn the same value.
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.periodand the score stay as Polymarket sent them.- The match reads
liveagain when the live score feed sends it a state, or when its period or score changes. - Suspended games whose period reads
SUSare unchanged. - The
status.stateandstatus.livedescriptions now name this case.
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_keyandoauth_grantkeep their meaning, and OAuth grants keep the re-authorize message.scopesis unchanged:nullfor a developer key, the scope array for an OAuth grant or integration key.
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_pctisnullwheneveropen_interestis, as before.- Every other field is unchanged.
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.gradechanges wherever it is returned, including large trades, positions and theirmin_gradefilters.- 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,DorF. Another 7,760 changed letter, including 35Sand 180Awallets that went down one. - In production, wallets graded
SorAwent from 1,475 on October 29 to 1,289 on October 30.
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.
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_recordgroup indata_qualityispartial, with ownertraders.last_synced,as_ofset to that sync, and a reason that names combos. Before, it wasunavailable. pnl.in_scope,stats.markets_traded, andstats.win_ratestay absent. The API serves no other P&L figure in their place.- With
expand=trust,trust.markets_tradedandtrust.win_ratestay unavailable, and their reason names combos. GET /api/v1/trader/{address}/pnlreturnsseries_status: "ready"with empty sections,realized: null, andfreshness_atset to that sync. Before, it returnedpending.
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_tradedis0,pnl.in_scopeandstats.win_rateare absent, and thesports_recordgroup ispartialwith ownertraders.last_synced. /pnlreturnsseries_status: "ready"with empty sections andrealized: null, and the export returnspnl_series_status: "ready"with no day.- With
expand=trust,trust.markets_traded.source.fieldis nowlast_syncedfor every wallet that traded no sports or esports market, including the wallets those entries cover. Before, it wasmarkets_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_tradedand readspending.
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_atandlast_active, and theas_ofof thesyncandvolumegroups indata_quality, keep the time of the last full sync.- The
sports_recordgroup indata_qualitystill moves when a correction changes the wallet’s sports and esports record. - The
trader_syncedwebhook and its deprecated spellingwhale_trader_syncedno longer fire for a correction. They fire when a full sync finishes.
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_daysstays0, and the file’spnlsection stays empty. - With nothing synced,
completeness.statusisempty, 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.
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_picksstill 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.
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_tradedis0.pnl.in_scopeandstats.win_ratestay absent, because the wallet has no record.- The
sports_recordgroup indata_qualityispartial, with ownertraders.last_syncedandas_ofset to the sync that found no market. Before, it wasunavailable. - With
expand=trust,trust.markets_tradediscomputedfromtraders.markets_tradedand dated by that sync. GET /api/v1/trader/{address}/pnlreturnsseries_status: "ready"with empty sections,realized: null, andfreshness_atset to that sync. Before, it returnedpendingfor these wallets.- A wallet with markets whose record is not computed yet is unchanged: it still omits
stats.markets_tradedand readspending.
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 theget_trader_export_snapshotMCP output schema marks it deprecated.
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.