Skip to main content
These are the API changelog entries from October 17 to 20, 2026, newest first. The newest changes are on the API changelog.
Schema Feed Article lines no longer carry a speakable selector
GET /feeds/research.jsonl, /feeds/learn.jsonl, and /feeds/news.jsonl now serve each Article without a speakable property. The research and learn results of GET /ask and each page’s JSON-LD drop it too.Before this change, every Article carried speakable.cssSelector ["h1", "[data-speakable]"]. No research, learn, or news page renders a data-speakable element, so validator.schema.org reported NO_MATCHES_FOUND on each of those pages.Backward compatible. schema.org makes speakable optional, and headline carries the same text as the page’s h1.What to change: Nothing. A consumer that read speakable reads headline instead.
Explore's change_pct_24h measures a full 24 hours, or is null
GET /api/v1/markets/explore now computes change_pct_24h and no_change_pct_24h from the price exactly 24 hours ago, to within 2 minutes. price_points and no_price_points start at that price, so the first point and the change describe the same window.Before this change, the series started at the first whole hour after that moment, so each change covered 23 to 24 hours. Measured on October 20, 2026 across every market with a published change, the starting price was off by more than 2 cents in about 1 in 150 reads, and by up to 39.5 cents on a fast-moving market.
  • A market younger than 24 hours now has change_pct_24h: null instead of a change over its shorter life. Its price_points still return.
  • A resolved market whose price series stopped when trading closed also has change_pct_24h: null. Its old figure measured the last in-play price, not the resolution, and on October 20, 2026 it was up to 80 cents away from Polymarket’s own 24-hour change.
  • score_components.price_move_signal is null whenever both changes are, so discover_score gives such a market no credit for a price move.
  • The explore_markets MCP tool returns the same values.
Backward compatible. No field is added, removed, or retyped. Both fields were already nullable.What to change: Nothing. If you computed a change from price_points[0], it now matches change_pct_24h.
Remote MCP refuses a bad Origin or protocol header before it reads the body
POST /api/v1/mcp with an API key or OAuth token again checks the Origin and MCP-Protocol-Version headers before it reads the request body. A disallowed Origin answers 403, and an unsupported MCP-Protocol-Version answers 400 with JSON-RPC error -32600, whatever the size of the body. Neither refusal counts against your request limits.Before this change, from October 18, a keyed POST read up to 1 MiB of its body before these checks:
  • A body over 1 MiB got the Failed to read request body error instead of the 403 or 400.
  • A slow body delayed the refusal until it arrived.
  • A refused tools/call counted against the per-minute allowance, the monthly quota, and pay-as-you-go usage.
Backward compatible. The refusals are the ones this endpoint documents; only the order of the checks changes back.What to change: Nothing.
Edge Hunter observations keep a same-event market when the 168-market limit is full
GET /api/v1/edge-hunter with view=observations now keeps a place for a most-held wider_holder market inside its event’s limit of 4 only when the limit of 168 markets in all has room for it. When the soonest kickoffs and in-play markets fill the 168, the most-held market gives up that place, and another market of the same event can take it.Before this change, the most-held market kept the place even when the 168 were full. A market of the same event among the soonest kickoffs was then left out and counted in coverage.capacity_limited_markets, and the most-held market was left out as well.Backward compatible. No field, parameter, or enum value changes. The limits stay 8 markets per group and sport, 4 per event, and 168 in all.What to change: Nothing.
Holder win rates carry the wallet's average price paid, category_avg_price
GET /api/v1/market/{condition_id}/holders and the display_holders entries of GET /api/v1/pick-of-the-day now return category_avg_price beside a measured category_win_rate. It is the wallet’s average entry price in the same category, from 0 to 1, so 0.98 is 98 cents.Before this change, a holder carried only the win rate and its record. A wallet that buys outcomes that are already near-certain wins almost every time, so a 99% win rate with no price could read as skill. One MLB holder won 1,532 of 1,543 markets, and 1,361 of them were bought at 98 cents or more.
  • The sample is the wallet’s settled markets in the category of $20 or more where it bought one side at a known price. It is the sample behind the Edge column on the sport leaderboards, and it can differ from the category_win_record sample, which counts every trade size.
  • The field is present only when category_win_rate_status is measured and that sample has at least 5 markets. It is absent otherwise, never null.
  • On an esports market or pick, the price is the Esports category’s, also when category_win_rate_game is present. There is no per-game price.
  • The MCP tools get_market_holders and get_pick_of_the_day return the same field.
Backward compatible. One optional field is added.What to change: Nothing. To show the context the 0xinsider chips show, print the price beside the win rate when it is 0.90 or more.
Games no longer report a period past the map that decided an esports series
GET /api/v1/games and GET /api/v1/games/{event_slug} now keep an esports series’ status.period on the map that decided it. When one side has won the majority of a best-of series, period reads that map, for example 2/3 for a best-of-3 won 2-0.Before this change, period could read the next map for about 20 seconds, for example 3/3 beside a 2-0 score in a best-of-3, before the game ended and it returned to 2/3. No such map is ever played.Backward compatible. No field, parameter, or enum value changes. The scores and every other status field are unchanged.What to change: Nothing.
Combo fills: a result filter lists a combo only when recorded and current results agree
With result set, GET /api/v1/combos/fills now lists a combo only when its recorded result and its current result are the same. Before, it checked only the current result. A combo whose two results differ still appears when you send no result filter, under its current status.On October 18, 2026, no listed fill had results that differed (0 of 1,541,021). The same change makes filtered pages faster: deep pages under result were taking up to 14 seconds and could return 503.Backward compatible. Response fields and their meaning are unchanged.What to change: Nothing.
Edge Hunter observations name their selection, and most-held wider_holder markets show a day ahead and stay to kickoff
GET /api/v1/edge-hunter with view=observations adds admitted_by to every row. most_held marks the group of up to 8 markets per sport with the most graded holders, and every in_play row. soonest_kickoff marks the 8 soonest wider_holder kickoffs per sport. An emerging_pile row carries the value of its wider_holder row.The most-held wider_holder group added earlier on October 18 now looks 24 hours ahead instead of 12, and a market in it keeps its place until its game starts. Before this change, a market with more graded holders could take the place of one already shown, which then returned only among the 8 soonest kickoffs.
  • A market gives its place up when its holder read shows no observation: its graded holders split, no current graded holder is found, or its price is unavailable. A holder read that does not finish in time does not cost it its place.
  • The limits stay the same: 8 markets per group and sport, 4 per event, and 168 in all. Markets beyond them still count in coverage.capacity_limited_markets.
Backward compatible. admitted_by is a new required field on observation rows; no field, parameter, or enum value is removed or renamed.What to change: Nothing. To compare with the most-held selection the feed made before October 17, keep the rows with admitted_by most_held. Expect those rows from up to a day before kickoff, each staying until its kickoff.
Max allows 200 requests per minute
A Max account now allows 200 authenticated requests per minute, up from 100. Its batch item budget follows: 5,000 item units per minute, up from 2,500. Pro stays at 100 requests and 2,500 item units. See Rate limits.
  • RateLimit-Limit and X-RateLimit-Limit report your plan’s minute allowance: 200 on Max, 100 on Pro.
  • On batch routes, X-Batch-RateLimit-Limit and meta.rate_limit.limit report 5000 on Max and 2500 on Pro.
  • GET /api/v1/usage reports the same minute allowance in data.rate_limit.limit.
  • The monthly quota does not change: 2,000,000 requests on Max and 500,000 on Pro. The usage and identity reads keep their shared allowance of 100 reads per minute, and the per-IP allowance stays 1,200 requests per minute.
Backward compatible. No request or response field changes. Only the numbers in these headers and fields change for Max.What to change: Nothing. A client that paces from RateLimit-Limit and RateLimit-Remaining speeds up on its own. A client that hard-codes 100 a minute can raise it to 200 on Max.
Remote MCP counts only tools/call against your request limits
POST /api/v1/mcp now counts only tools/call against the per-minute request allowance, the monthly quota, and pay-as-you-go usage. Every other message is free, with or without a key.Before this change, every message that carried a key counted as one request. A client that opens a session for each tool call spent 5 requests on it, so the 100-request minute allowance held about 20 tool calls.
  • Free now: initialize, ping, tools/list, notifications/initialized, notifications/cancelled, and the 405 answers to GET, HEAD, and DELETE.
  • A free message still checks the key. A wrong key answers 401, a lapsed plan 402, and a locked account 423, as before.
  • On a free message, RateLimit-* describes the per-IP allowance of 1,200 requests per minute, the same as a message without a key. It carries no X-Monthly-Quota-* or X-Usage-Accounting header.
  • Free messages no longer appear in your usage counts or on a pay-as-you-go invoice.
Backward compatible. No request or response body changes.What to change: Nothing. To pace tool calls, read RateLimit-* from a tools/call response, not from the handshake.
Edge Hunter wider_holder observations show the most-held markets hours before kickoff
GET /api/v1/edge-hunter with view=observations&cohort=wider_holder now evaluates up to 8 more markets per sport: those with the most graded holders among the rest that kick off within 12 hours. The 8 soonest kickoffs per sport are evaluated as before, so a market about to start stays evaluated until kickoff.Before this change, each sport evaluated only its 8 soonest kickoffs. In soccer they reached about 13 minutes ahead, so a soccer row first appeared a median of 14 minutes before kickoff.
  • The limits of 4 markets per event and 168 in all do not change. Markets beyond them still count in coverage.capacity_limited_markets.
  • A market in the new group can drop out before its kickoff when a market with more graded holders takes its place. It returns once it is among the sport’s 8 soonest kickoffs.
  • A wider_holder or emerging_pile market that kicks off more than an hour later is read again only when its last read is 10 minutes old. Until then its row carries that read, with the read’s own holder_snapshot_at and provider_read_source: "cached". The 15-minute limit on an older read does not change.
  • in_play does not change.
Backward compatible. No field, parameter, or enum value changes.What to change: Nothing. Expect more wider_holder rows, many of them hours before kickoff. Use holder_snapshot_at for the age of a row’s holder read.
Pick of the Day moves the free selection when the free pick's game is cancelled
GET /api/v1/pick-of-the-day now keeps a free selection when the day’s free pick is withdrawn because its game was cancelled, or is never published because its game ended or was cancelled before its release time. Another pick of that day becomes the free selection, and its is_free_selection changes from false to true.Before this change, the day then had no free selection. This replaces the line of the earlier October 18 entry that said a withdrawn free selection keeps the designation alone.
  • The new free selection is the first pick in publication_order that is still scheduled or published, is not withdrawn, and whose game has not started. It can be a pick you already read with is_free_selection: false.
  • If no pick qualifies, the next addition to that day becomes the free selection, provided its game has not started.
  • The withdrawn pick keeps is_free_selection: true. The current day and GET /api/v1/pick-of-the-day/archive leave withdrawn picks out, so they still show at most one free selection a day. GET /api/v1/pick-of-the-day/ledger keeps every published pick, so a ledger day can show two picks with is_free_selection: true: the withdrawn pick and the one that replaced it.
  • Pro still opens the free selection plus the first 4 nonfree picks, so on such a day Pro opens up to 5 live picks where before it opened up to 4.
  • is_free_selection never changes from true to false while the API serves the pick. A scheduled pick that is set aside and later restored to the schedule can return with false, when another pick holds the free selection by then. The Pick of the Day MCP tools return the same is_free_selection. Ledger proofs do not change, because a sealed pick does not include is_free_selection.
Backward compatible. No field, parameter, or enum value changes.What to change: Nothing for the current day and the archive. If you take the day’s free selection from the ledger, expect up to two picks with is_free_selection: true and use the one that was not withdrawn.
Pick of the Day moves the free selection when the designated pick is not released
GET /api/v1/pick-of-the-day now keeps a free selection when the day’s designated free pick is held back at its release time. Another pick of that day becomes the free selection, and its is_free_selection changes from false to true.Before this change, the day then had no free selection. On October 18, 2026, signed-in free accounts could open none of the day’s 16 published picks until we corrected it at 09:15 UTC.
  • The new free selection is the first pick in publication_order that is still scheduled or published, is not withdrawn, and whose game has not started. It can be a pick you already read with is_free_selection: false.
  • is_free_selection never changes from true to false on a pick the API serves.
  • Pro opens the free selection plus the first 4 nonfree picks, so on such a day Pro opens up to 5 picks where before it opened up to 4.
  • If every remaining game has already started, the day has no free selection.
  • A free selection that is withdrawn, rather than held back, keeps the designation.
  • GET /api/v1/pick-of-the-day/archive, GET /api/v1/pick-of-the-day/ledger, and the Pick of the Day MCP tools return the same is_free_selection. Ledger proofs do not change, because a sealed pick does not include is_free_selection.
Backward compatible. No field, parameter, or enum value changes.What to change: Nothing. If you cache picks by pick_id, read is_free_selection again instead of keeping the first value you saw.
Edge Hunter in-play observations never serve a cached holder read from before kickoff
GET /api/v1/edge-hunter with view=observations no longer serves an in_play row from a cached holder read taken before that market’s own kickoff, once that kickoff has passed. Such a market now gets a new holder read, or its last in-play read from up to 10 minutes earlier.Before this change, a holder read cached in the last minutes before kickoff could be served for up to 3 minutes after the game went live. That row’s position values were priced against the pre-game market, and its holder_snapshot_at was earlier than its game_start_time.
  • The check uses the market’s scheduled kickoff, game_start_time. If a game starts late, a read cached between the scheduled and the real start can still be served.
  • A market can be in_play because its parent event is live while its own game_start_time is still ahead. Its cached read is still served, because every read it can have is from before its own kickoff.
  • A row with provider_read_source: "live" is a new read and is not affected.
Backward compatible. No field, parameter, or enum value changes. An in_play row with provider_read_source: "cached" whose game_start_time has passed now always has a holder_snapshot_at at or after that game_start_time.What to change: Nothing.
Edge Hunter in-play observations can reuse a holder read up to 10 minutes old
GET /api/v1/edge-hunter with view=observations now keeps an in_play market when a refresh cannot finish its new holder read. The row serves that market’s last in-play holder read for up to 10 minutes, with that read’s original holder_snapshot_at and provider_read_source: "cached".Before this change, an in_play market whose holder read did not finish ended as holder_deadline_unavailable and was missing from that snapshot. On October 18, 2026, from 05:25 to 05:55 UTC, 172 of the 175 markets that ended that way were in_play.
  • The reused read is always one taken after the game went live and after that market’s own kickoff. A read from before kickoff is never reused for an in_play row.
  • The 10 minutes are counted when the snapshot is computed. After 10 minutes without a new read, the market ends as holder_deadline_unavailable again.
  • A provider error still ends the market as unavailable. Only a read that ran out of time falls back.
  • in_play rows still need a holder_snapshot_at no older than 30 minutes.
  • wider_holder and emerging_pile rows keep their 15-minute limit.
Backward compatible. No field, parameter, or enum value changes. An in_play row’s holder_snapshot_at can now be up to 10 minutes old, where before it was at most about 3 minutes old.What to change: Nothing. If you need a fresher in-play holder read, compare holder_snapshot_at with your own limit.
GET /api/v1/usage sends monthly_quota.unavailable_reason, and SDK 0.29.0 types it
GET /api/v1/usage now sends monthly_quota.unavailable_reason on every response that carries monthly_quota. It is "usage_price_reconciliation_required" when billing must reconcile the account’s usage price before pay as you go can be used, and null otherwise. In that state pay_as_you_go is false.Before this change, the route never sent the field. The entry of October 1, 2026 announced it, and the entry of October 14, 2026 removed it from the document because the route did not send it. The OpenAPI document lists it as always sent, and the TypeScript SDK 0.29.0 types it as "usage_price_reconciliation_required" | null.Backward compatible. The field is added; no field is removed or retyped.What to change: Nothing. To tell a billing problem from pay as you go being off, read unavailable_reason beside pay_as_you_go.
Edge Hunter observations no longer mark a snapshot degraded for the 100-holder page
GET /api/v1/edge-hunter with view=observations no longer adds holder_scan_incomplete to degradation_reasons. A market with more than 100 holders on an outcome token still shows holder_scan_complete: false and still counts in coverage.incomplete_holder_markets, but it no longer sets degraded to true.Before this change, nearly every snapshot listed holder_scan_incomplete, because each market’s holders come from 1 page of up to 100 per outcome token and busy markets have more. On October 17, 2026, all 1,028 snapshots from 09:00 to 22:00 UTC were degraded, so the flag could not separate a failed refresh from a healthy one.
  • degraded is now true only for an operational cause, such as holder_deadline_unavailable, provider_unavailable or live_source_stale. Snapshots can still be degraded while holder reads miss their refresh deadline.
  • holder_scan_incomplete stays in the reason enum, retired, and stays a funnel terminal.
  • The get_edge_hunter MCP tool and the SDK’s edgeHunter.observations() return the same fields and change the same way.
Backward compatible. No field, parameter or enum value is removed. degraded is false more often.What to change: Nothing. To check holder coverage, read each row’s holder_scan_complete or coverage.incomplete_holder_markets, not degraded.
Pick of the Day leaves a void pick out of every money figure
GET /api/v1/pick-of-the-day/archive now leaves a pick that settled void out of every money figure. A void is a pick whose market settled with no winner, such as a cricket match with no result. It stays in picks and in the void counts, and it counts as neither a win nor a loss.Before this change, the archive valued a void at Polymarket’s 50-50 payout, stake_usd * 0.5 / backed_price (the October 17, 2026 entry below). A favorite bought at 0.655 then read as a loss of about $237 on a $1,000 stake, for a game that had no winner.
  • picks[].stake_usd, return_usd, return_per_100, payout_display, and unit_score are omitted for a void, as they already were for a pending pick. picks[].backed_price stays on a void.
  • hit_rate.net_profit_usd, hit_rate.staked_usd, hit_rate.roi_pct, hit_rate.unit_score, and days[].unit_score leave voids out.
  • hit_rate.series[] adds no point for a void. hit_rate.pct and decided do not change, because a void never counted in them.
  • The return_usd on GET /api/v1/pick-of-the-day is omitted for a void pick in the same way.
To match the site’s money from the ledger at a flat stake, a win returns stake / backed_price, a loss returns 0, and a void or a withdrawn pick is left out.Backward compatible. No field is added, removed, or retyped. Each omitted field was already optional.What to change: Nothing, unless you value a void yourself. Then leave it out of profit, total stake, ROI, and units.
The public picks ledger leaves withdrawn picks out of its record, as the site does
The index.json record in github.com/0xinsider/picks now leaves out every pick the site withdrew. Those picks are left out of wins, losses, voids, decided, and hit_rate, as well as the money figures. A new record.withdrawn and record.proven_record.withdrawn count them instead. The record now matches the site’s and the rule this changelog gave on October 17: skip every entry whose withdrawal_reason is not null.Before this change, the record counted a withdrawn pick in its wins, losses, and voids and left it out of the money only. On October 18, 2026 it read 348 wins, 213 losses, and 561 decided against the site’s 346, 212, and 558.
  • The partition is now decided + voids + withdrawn = opened.
  • The withdrawn picks stay in ledger/ with their commitments and real outcomes, and verify.py still verifies them. It prints their outcomes on a separate withdrawn line.
  • The ledger API, GET /api/v1/pick-of-the-day/ledger, is unchanged.
Backward compatible. Two keys are added, and no key is removed or retyped. wins, losses, voids, decided, and hit_rate drop by the withdrawn picks.What to change: If you check decided + voids == opened on index.json, add withdrawn to the left side.
A trader's sync_status no longer reads synced while a sync runs
GET /api/v1/trader/{address}, POST /api/v1/traders/batch, GET /api/v1/trader/{address}/context and the MCP get_trader tool now return the sync_status and synced_at the trader has at the time of the request.Before this change, a cached trader body could return sync_status: "synced" for up to 60 seconds after a sync had started, and the previous synced_at for up to 60 seconds after it had finished.
  • A cached body is still served while the trader’s sync_status and synced_at are unchanged, with meta.cached and meta.cache_age_s as before.
  • When either has changed, the body is rebuilt from current data and meta.cached is false.
Backward compatible. No field, parameter, or limit changes.What to change: Nothing. If you waited out the cache before trusting sync_status, you can read it directly.
Large trades before April 2, 2026 can carry a verified outcome and token_id
GET /api/v1/large-trades, GET /api/v1/large-trades/history, GET /api/v1/large-trades/{id}, their whale-trades aliases, and the whale dataset export can now return outcome, token_id and outcome_index for a trade recorded before April 2, 2026.Before this change, all three were null for every trade before that date. Before April 2, 2026, 0xinsider stored the wrong outcome for a large share of trades on markets whose outcomes are names, such as two teams. Those trades have since been corrected. A trade before that date now serves its outcome only when the outcome was checked against the fill’s own trade records and every record agreed. Any other trade from that period still serves null.
  • The verified outcomes are being recorded in one batch. Until that batch runs, every trade before April 2, 2026 still serves null.
  • About 93% of the 394,000 large trades before April 2, 2026 qualify. The rest stay null: their fill’s records show both outcomes, no record exists, or the records disagree with the stored outcome.
  • Trades from April 2, 2026 on are unchanged.
Backward compatible. No field is added, removed or retyped. The fields were already nullable.What to change: Nothing. If you excluded trades before April 2, 2026 because they had no outcome, you can include the ones that now carry one. A null outcome still means the side is unknown, not a default.
Edge Hunter pre-game observations can reuse a holder read up to 15 minutes old
GET /api/v1/edge-hunter with view=observations now keeps a pre-game market in wider_holder and emerging_pile when a refresh cannot finish its new holder read. The row serves that market’s last holder read for up to 15 minutes, with that read’s original holder_snapshot_at and provider_read_source: "cached".Before this change, a holder read that did not finish ended the market as holder_deadline_unavailable until a later refresh read it again. On October 17, 2026, 35 markets a refresh ended that way on average, and wider_holder ranged from 5 to 44 rows between refreshes.
  • in_play rows never reuse an older read.
  • A provider error still ends the market as unavailable. Only a read that ran out of time falls back.
  • After 15 minutes without a new read, the market ends as holder_deadline_unavailable again.
  • Each market’s holders come from 1 page of up to 100 holders per outcome token. A token with more holders leaves holder_scan_complete as false. The extra page reads that tried to complete such scans are removed; none of them ran in the previous 7 days.
Backward compatible. No field, parameter, or limit changes. Pre-game holder_snapshot_at values can be up to 15 minutes old.What to change: Nothing. If you need a fresher holder read, compare holder_snapshot_at with your own limit.
Pick of the Day ledger adds the published_after_kickoff withdrawal reason
GET /api/v1/pick-of-the-day/ledger and GET /api/v1/pick-of-the-day/ledger/{pick_id} can now return a second value in withdrawal_reason: published_after_kickoff.It marks a pick that was published at or after its game started, so it was never a pre-game pick. That includes a suspended match that resumed later, where the scheduled start moved to the resumption. The site’s record leaves such a pick out of every count and money figure, the same as a provider_game_cancelled pick.
  • The field’s values are now provider_game_cancelled, published_after_kickoff or null.
  • A provider_game_cancelled withdrawal happens while the pick is pending. A published_after_kickoff withdrawal can reach a pick that has already settled, so an entry’s withdrawal_reason can change from null to published_after_kickoff after you first read it.
  • The withdrawn pick keeps its commitment and its real outcome in the ledger. The field is not part of the hashed payload, so no commitment_hash changes.
Backward compatible for most clients. No field is added, removed or retyped. A client that validates withdrawal_reason against a closed list must accept the new value.What to change: If you rebuild the track record from the ledger, keep skipping every entry whose withdrawal_reason is not null, and re-read settled entries instead of caching their withdrawal_reason at settlement.
Edge Hunter wider_holder observations follow the kickoff order, soonest first
GET /api/v1/edge-hunter with view=observations&cohort=wider_holder now evaluates each sport’s markets in kickoff order, soonest first. Among markets with the same kickoff, the market with the most graded holders comes first. A pre-game market that is evaluated stays evaluated until its game starts.Before this change, each sport’s 8 wider_holder slots went to the markets with the most graded holders in the whole 48-hour window. A market seen a day before kickoff could drop out when a later game with more holders entered the window, and be missing at its own kickoff.
  • The limits stay the same: at most 8 markets per sport and cohort, 4 per event, and 168 in all. Markets beyond them still count in coverage.capacity_limited_markets.
  • in_play keeps its order, most graded holders first.
  • A market that enters the signals view’s admission leaves wider_holder, as before. view=signals lists it only while its holder pile leans to one side, its backed side has a holder at or above min_grade, and its kickoff is within horizon_hours. The cohort description now says so.
Backward compatible. Fields, parameters, and limits do not change.What to change: Nothing. To follow a market after it leaves wider_holder before kickoff, also read view=signals with horizon_hours=48.
Pick of the Day ledger says which settled picks the site withdrew
GET /api/v1/pick-of-the-day/ledger and GET /api/v1/pick-of-the-day/ledger/{pick_id} now add withdrawal_reason to every settled entry. It reads provider_game_cancelled when the site withdrew the pick because Polymarket marked its game Cancelled, and null when the pick counts in the record.Before this change, the ledger did not say whether a pick was withdrawn. A withdrawn void and a counted void looked the same, so a client could not reproduce the site’s record from the ledger.
  • opened entries always carry withdrawal_reason.
  • uncommitted entries carry it once the pick settles, and omit it while the pick is pending, the same way they omit matchup.
  • sealed entries do not carry it.
  • A withdrawn pick keeps its commitment and its real outcome in the ledger. The site’s record leaves it out of every count and money figure.
  • The field is not part of the hashed payload, so no commitment_hash changes.
To match the site’s money at a flat stake, value each counted pick this way: a win returns stake / backed_price, a loss returns 0, and a void returns stake * 0.5 / backed_price. Leave a withdrawn pick out.Backward compatible. One field is added. No field is removed or retyped.What to change: Nothing, unless you rebuild the track record from the ledger. Then skip every entry whose withdrawal_reason is not null.
Explore markets states how old each sharp-money split is, as sharp_money_as_of
GET /api/v1/markets/explore now sends sharp_money_as_of on every market: when the oldest stored smart-money wallet behind the split in smart_score, smart_count and smart_label last finished syncing, and null when there is no split or a wallet’s sync time is unknown. Some holdings in the split can come from a Polymarket holder read cached for up to 3 minutes, which this time does not describe.Before this change the split carried no age, so it read as current. Graded wallets sync on different schedules, and most splits rest on positions that are days old: on October 17, 2026 the oldest position behind a market’s split had a median age of 145 hours.Backward compatible. The field is new, and the split itself is unchanged.What to change: Nothing. To treat the split as recent, check sharp_money_as_of against your own bound; null never means current.
Pick of the Day values a void pick at the 50-50 payout, not a refund
GET /api/v1/pick-of-the-day/archive now values a pick that settled void at Polymarket’s real payout. Polymarket settles a void market 50-50 and pays $0.50 a share, so a void returns stake_usd * 0.5 / backed_price.Before this change, the archive valued a void as if the stake were refunded. A pick bought at 0.64 then read as $0 profit, but the market paid $781.25 on a $1,000 stake, a loss of $218.75.
  • picks[].return_usd is stake_usd * 0.5 / backed_price for a void, and picks[].return_per_100 is 50 / backed_price. Both are omitted for a void with no frozen price.
  • hit_rate.net_profit_usd, hit_rate.staked_usd, hit_rate.roi_pct, and days[].unit_score now include a priced void. A void still counts in void, never in wins, losses, or decided, so hit_rate.pct does not change.
  • hit_rate.series[] adds one point for a priced void. The point moves the profit and leaves the win rate unchanged.
No pick in the record was counted as a void when this shipped, so no value served today changes.Backward compatible. No field is added, removed, or retyped. The values for a future void are corrected.What to change: Nothing, unless your client computes its own return for a void. Value it at stake * 0.5 / backed_price.
Games that Polymarket has suspended now read as suspended, not live
GET /api/v1/games and GET /api/v1/games/{event_slug} now report a game that Polymarket has suspended, whose period reads SUS, with status.state suspended, status.match_status suspended, and status.live false. A request with status=live no longer returns such a game, and status=suspended does.Before this change, these endpoints reported a suspended game with status.state live and status.live true, because Polymarket keeps a suspended game’s in-play flag set. On October 17, 2026, two soccer games suspended on October 10 still read live.
  • status.period still reads SUS, and the score is the last one Polymarket sent.
  • The game reads live again when play resumes and its period moves on.
  • freshness.source reads upcoming for a suspended game.
  • The status.live description now names the two cases where the field differs from Polymarket’s own flag: a suspended game, and a game rescheduled to a later kickoff, both read false.
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 games that stopped during play.
Market holders keep an empty answer for up to an hour while the market has no trades
GET /api/v1/market/{condition_id}/holders now keeps an answer whose scan found no wallet at all (scan.wallet_count is 0), on an open market before its end date, for up to an hour while no trade in that market is seen after the scan. A trade, the market closing, resolving or passing its end date, or the hour ending stops the hold, and the next request scans again. The kept answer carries meta.cached: true, and meta.cache_age_s can now reach 3,600 seconds for these markets.Before this change, every answer was recomputed at least once a minute, so meta.cache_age_s stayed under 60 seconds, over a Polymarket scan at most 180 seconds old.
  • Shares that reach an empty market without a trade (a split, a merge or a transfer) can show up to an hour late.
  • An answer whose scan saw any wallet, graded or not, keeps the one-minute refresh.
Backward compatible. No field is added, removed, or retyped.What to change: Nothing. Read meta.cache_age_s for the age of an empty answer; the first trade in the market refreshes it.