Skip to main content
These are the API changelog entries from November 7, 2026, newest first. The newest changes are on the API changelog.
Settled markets report PGA Tour golf as league pga, and the league filter selects it
GET /api/v1/markets/settled, the market_settled webhook, and the MarketSettled frame on Stream now set league to pga on a PGA Tour market. league=pga on the list and "leagues": ["pga"] in settlement_filters select those markets.Before this change, a PGA Tour market had league: null, because its event slug starts with a year (2026-world-wide-technology-championship-winner) instead of a league. No league filter could select it. On November 7, 453 of the 767 moneyline states recorded in one hour were these markets.
  • Other golf tours keep their leagues from the event slug: lpga, dpwt, kft, and liv.
  • The list derives league when you read it, so PGA Tour rows recorded before this change also read pga now.
  • A market whose slug names no league and is not on the PGA Tour still has league: null.
Backward compatible. No field is added or removed. league on PGA Tour markets changes from null to pga, and a subscription that names leagues still leaves them out unless it names pga.What to change: Nothing to keep your current results. To receive PGA Tour golf, add pga to league or leagues. If you grouped league: null rows as golf, read pga instead.
Game market settlements name the winner of a resolved oracle question
GET /api/v1/games/{event_slug}/markets now sets settlement.result and settlement.winning_outcome_index on a settled market whose settlement is a resolved oracle question. We read the winner from the payout Polymarket’s ConditionalTokens contract recorded on Polygon, once per market. payouts stays null there, because Polymarket’s resolution row for the question carries none.Before this change, both fields were read from payouts only, so a resolved oracle question read settled with no winner. On November 7, 61 of the 100 markets of one golf tournament read that way.The market_settled webhook, the MarketSettled frame on Stream, and List settled markets carry the same states:
  • A question recorded before the contract paid, with a null winner, is followed by a state that names the winner, with its own settlement_id.
  • A later state that names a winner already sent, such as Polymarket’s row with payouts, is not sent by the webhook or Stream. List settled markets still returns it, at its own observed_at.
  • A state we first see more than 6 hours after the market resolved is still not sent, and still listed.
Backward compatible. No field is added or removed. Two fields that were null now carry the winner, and a market can have one more settled state.What to change: Nothing if you read the winner from result and winning_outcome_index. If you derived it from payouts, read those two fields instead, because payouts stays null on a resolved oracle question. If you act once per winner on List settled markets rows, skip a row whose market and winner you already handled.
Edge Hunter most-held markets keep an untraded holder read for up to 30 minutes
GET /api/v1/edge-hunter with admission=most_held_48h or most_held_48h_by_type: a market only the most-held selection takes, which kicks off more than an hour later and has had no trade since its last holder read, now keeps that read for up to 30 minutes, with no new read. The default selection already did this.Before this change such a market kept its last read for only 15 minutes. When a busy kickoff window left no time to read it again, it then dropped as holder_deadline_unavailable and the snapshot was marked degraded. The same market in the default selection kept its row.Backward compatible. The response shape is unchanged. holder_snapshot_at on such a row can now be up to 30 minutes old, and provider_read_source is cached.What to change: Nothing. To judge a row’s age, read holder_snapshot_at.
Edge Hunter most-held selections skip stopped games' side markets and keep live underdogs
GET /api/v1/edge-hunter with admission=most_held_48h or most_held_48h_by_type now skips every market of a game Polymarket marks postponed, cancelled, suspended, or final, including its exact score, half-time, and other side markets. A market on the game’s main event, such as a team-win market, is skipped as decided only at a Yes price of 0.02 or lower, or 0.98 or higher. A skipped market counts as provider_excluded in funnel, coverage, and departures[].reason.Before this change:
  • The decided bounds were 0.05 and 0.95, so a live underdog priced at 0.048 lost its place in the Soccer selection.
  • A postponed game’s exact score and half-time markets could still take places, because only the game’s main markets were matched.
  • A skipped market was reported as capacity_limited, which reads as losing its place to a market with more graded holders.
Backward compatible. The response shape is unchanged, and provider_excluded is an existing reason. Rows change: a market priced between 0.02 and 0.05, or between 0.95 and 0.98, can now take a place.What to change: Nothing. To tell a skipped market from one that lost its place, read provider_excluded apart from capacity_limited.
New GET /api/v1/markets/settled lists game market settlements since a time
The new GET /api/v1/markets/settled lists the sports and esports game markets that settled since a time, oldest first. Each row is the data object the market_settled webhook sends. Every plan with an API key can read it, at a cost of 1.Before this change, a missed market_settled delivery could be replayed only from the delivery log, and a client without a webhook learned of a settlement only by reading each game’s markets until its settlement changed.
  • since (RFC 3339) starts the list; next_cursor continues it and is always returned. has_more: false means you have read up to complete_through, 2 minutes before the read. Keep the cursor and read it again later for newer rows.
  • sports_market_type (default moneyline, * for every type) and league filter the rows as settlement_filters does. Filters sent with a cursor must match the cursor’s, or the read is a 400 with error.reason cursor_expired.
  • Unlike the webhook, it lists a state first seen more than 6 hours after its market resolved.
  • A first page that misses the 200 ms read deadline answers 503 with error.reason read_model_warming and Retry-After: 1.
The remote MCP server adds the list_settled_markets tool with the same parameters; complete_through is kept beside data. The TypeScript SDK adds listSettledMarkets() in its next release. The settlement notifier recipe uses the endpoint to catch up after downtime.Backward compatible. This adds an endpoint and an MCP tool.What to change: Nothing. To catch up on settlements after your receiver was down, or to follow them without a webhook, read this endpoint from a since time and keep its next_cursor.
market_settled's default filter includes every golf player market; webhook signatures can carry two candidates at any time
The default settlement_filters of the market_settled webhook, {"sports_market_types": ["moneyline"]}, was described as each game’s winner market. It matches every market Polymarket labels moneyline. That is each head-to-head game’s winner market, but in golf and other field events Polymarket labels every player market moneyline: the winner, make the cut, and round leader markets. One golf tournament sent 452 states to an endpoint with the default filter in about two minutes.Narrow it. Set leagues to the leagues you want, or name markets with condition_ids. A golf event slug that starts with a year, such as 2026-world-wide-technology-championship, has a null league, and no leagues list matches it.Signatures. x-0xinsider-signature can carry more than one comma-separated candidate while your endpoint’s secret_rotation.status is idle, because a rotation of 0xinsider’s own signing key adds one to every delivery. The Webhooks guide said this happened only during a staged rotation. Accept the request when any candidate matches.Backward compatible. Filtering and signing are unchanged; only their descriptions changed, in the OpenAPI spec and on this site. An endpoint subscribed without settlement_filters keeps the same default.What to change: If your endpoint uses the default filter and should not receive golf player markets, set leagues or condition_ids. If your receiver checks only the first signature candidate, check every one.
Pick of the Day display holders carry the price edge the pick card prints
GET /api/v1/pick-of-the-day now returns category_price_edge and category_scope_price_edge on display_holders[] entries. Each is a PickHolderPriceEdge object: markets, hit_rate, avg_price, and edge, which is hit_rate minus avg_price, all fractions. It shows how often the side the wallet bought won, against the average price it paid, over its settled markets of $20 or more at a known entry price.Before this change, only the pick card on 0xinsider.com printed this edge, for example -17.1 pp beside a Premier League win rate. The API returned the win rate and the average price, but not the edge.
  • category_price_edge covers the pick’s whole category, such as Soccer, beside a measured category_win_rate. It stays the whole category’s edge when the rate was measured in a game or league.
  • category_scope_price_edge covers only the game or league that category_win_rate_game names, and appears only beside it. It is never filled from the whole category’s edge.
  • An edge appears only when it has 30 or more counted markets, a sample that agrees with the win rate within 10 points, and a realized profit over those markets.
  • Otherwise category_price_edge_hold or category_scope_price_edge_hold says why: hold is category_loss, too_few_markets, sample_mismatch, or no_markets, and record carries the four figures when they exist. A hold never sits beside the edge it explains.
  • Recomputed when served from the daily record rebuild, never frozen with the pick. Full picks only: a locked or teaser pick carries no holders, and the holders[] compatibility list never carries these fields.
The SDK adds the four fields to PickHolder, with the new PickHolderPriceEdge and PickHolderPriceEdgeHold types. The get_pick_of_the_day MCP tool’s output schema carries them.Backward compatible. Four optional fields are added.What to change: Nothing. To show the edge the pick card prints, read category_scope_price_edge beside category_win_rate_game, and category_price_edge otherwise.
New market_settled webhook and MarketSettled stream frame for settled game markets
The new market_settled webhook sends a sports or esports game market’s settlement once per settled state, usually within a few minutes of Polymarket resolving it. GET /api/v1/stream carries the same fields as MarketSettled frames. Both are Pro and Max only.Before this change, you learned that a game market had settled only by reading GET /api/v1/games/{event_slug}/markets until its settlement changed.Once per settled state. A market can settle twice: first as a resolved oracle question without payouts, then with them. Each state is sent once, with its own settlement_id, and the event id is evt_market_settled_{condition_id}_{settlement_id}. A state we first see more than 6 hours after it resolved is not sent.Subscribe. POST /api/v1/webhooks and PATCH /api/v1/webhooks/{id} take a new optional settlement_filters object. It is refused with 400 (param: "settlement_filters") when event_types does not include market_settled. Every field you set must match:
  • leagues: slug prefixes such as nba or epl, at most 32.
  • sports_market_types: Polymarket market types such as moneyline, spreads, or totals, at most 64, where * is every type. Omitted, it is ["moneyline"], or ["*"] when condition_ids is set.
  • event_slugs and condition_ids: at most 100 each.
Subscribing without settlement_filters stores the moneyline default. A PATCH without it keeps the stored settings, and removing the event clears them. The webhook object now carries settlement_filters, which is null unless the event is subscribed.Payload. data carries:
  • The market: condition_id, event_slug, game_id, league, sports_market_type, and outcomes, each outcome name at its index.
  • settlement, the object the game markets routes serve: payouts, result, winning_outcome_index, was_disputed, resolved_at, and observed_at.
  • copy_of_event_slug, set on a Polymarket polyv2 copy of a game to the original game’s slug. A copy is sent only to a filter that names its condition ID or its own event slug.
Stream. MarketSettled frames are sent for every game market of every type, with no settlement_filters. Select them with event=MarketSettled. condition_id matches the frame’s market.The MCP tools list_webhooks and get_webhook return settlement_filters. The TypeScript SDK adds MarketSettledEvent and MarketSettledData in its next release.Backward compatible. This adds an event type, a request field, a response field, and a stream frame type. Existing webhook subscriptions are unchanged. A stream opened without event now also receives MarketSettled frames.What to change: Nothing, unless you want settlement alerts: subscribe an endpoint to market_settled, or open the stream with event=MarketSettled. If you read the stream without event, skip frame types you do not handle.
Pick of the Day display holders carry category_scope_avg_price, the league's own price
GET /api/v1/pick-of-the-day now returns category_scope_avg_price on a display_holders[] entry whose win rate was measured in a game or league (category_win_rate_game present). It is the wallet’s average entry price in that game or league alone, a 0..1 fraction of $1, under the same sample rules and 5-market floor as category_avg_price.Before this change, a holder row named the league its rate came from but carried only category_avg_price, the whole sport’s price. A Premier League rate read against the Soccer price.
  • Present only beside category_win_rate_game, and only when the wallet’s sample there clears the floor. Absent otherwise.
  • Recomputed when served, never frozen with the pick, like the win rate beside it.
  • Full picks only: a locked or teaser pick carries no holders. The holders[] compatibility list never carries it.
The SDK’s PickHolder type and the get_pick_of_the_day MCP tool’s output schema carry the field.Backward compatible. One optional field is added.What to change: Nothing. To read a league rate against its own price, use category_scope_avg_price when present and category_avg_price otherwise.
A settled game market answers its settlement on the first read, with a fixed observed_at
GET /api/v1/games/{event_slug}/markets now answers a settled game market with settlement.status observed on the first read of its game. We record each game market’s settlement a few minutes after it resolves, whether or not anyone reads the game. GET /api/v1/games/{event_slug}, GET /api/v1/games with expand=settlement and GET /api/v1/market/{condition_id}/snapshot serve the same record.Before this change, a game no read had asked about lately answered unavailable on its first read, even when its markets had settled days earlier. On a settled market, observed_at moved to the time of the latest Polymarket read, about every 10 minutes.
  • On a settled market, observed_at is now when we first saw that settlement. It is the same on every read.
  • A market that resolved before this change carries the time we first saw it after the change.
  • On a market that has not settled, observed_at is unchanged: when Polymarket last answered.
  • unavailable now means no read is in hand and no settlement of the market is recorded. Ask again shortly, and never treat it as unsettled.
The MCP tools list_game_markets, get_game, list_games and get_market_snapshot serve the same block.Backward compatible. No field is added or removed.What to change: Nothing. To time a settlement, read observed_at on a settled market once: it does not move.
Games carry status.ended_at and status.ended_at_source, the observed end time
GET /api/v1/games and GET /api/v1/games/{event_slug} now return status.ended_at, when the game ended as first observed, and status.ended_at_source, who observed it. Both are on every game and are null unless the game is ended and an end was observed.Before this change, a game said whether it had ended but not when.
  • sports_ws: the first Polymarket Sports WebSocket update that reported the game final, at the time we received it.
  • gamma_finished: Polymarket’s event finish time, for a game no such update ended. It is served only when it is earlier than the market’s resolution, and never for an MMA or boxing card.
  • We never infer an end from the kickoff, the game clock or the resolution. A game that ended before this change has null.
The SDK’s GameStatus type and the list_games and get_game MCP tools carry both fields.Backward compatible. Two fields are added to status.What to change: Nothing. To time a game’s end, read status.ended_at and treat null as unknown, not as still in play.
Leaderboard value-sort cursors report a ranking that moved within one generation
GET /api/v1/leaderboard now sets meta.continued_from_generation on a sort=volume, predictions, pnl, win_rate or edge page whenever the board was refreshed after your cursor was issued. The board refreshes about once a minute, and nearly every refresh changes some trader’s volume, P&L, win rate or edge, so the field can now equal meta.ranking_generation.Before this change, these sorts set the field only when the ranking generation changed. A page read after a refresh inside one generation came back without it, although its order could have moved and it could repeat or miss a trader.
  • On these sorts, a page is exact only when it is read from the same refresh as the page that issued its cursor. If you wait a minute between pages, expect the field to be set.
  • The default order, sort=rank and sort=grade keep the rule of the earlier November 7 entry on leaderboard cursors: they report a move only when a wallet’s rank, grade or membership changed.
  • A value-sort cursor issued before this change is reported as moved on its next page.
  • next_cursor on these sorts now changes with each refresh, so the page’s ETag does too.
Backward compatible. No field is added or removed. The field is now set on more pages, in the cases where it was missing.What to change: Nothing, if you already handle meta.continued_from_generation: de-duplicate rows by id, or restart from the first page when it is set. For an exact walk of a value sort, read the pages without long pauses.
List props book filters skip markets we do not serve before they check books
GET /api/v1/props with max_spread_c or min_book_usd_within_2c now leaves out the markets we do not serve before it checks any order book. A game’s event can hold markets outside sports and esports, which no page lists, and those markets no longer change what a filtered page returns.Before this change, such a market was checked like any prop, so one market we do not serve could change a filtered page:
  • When we could not read its book, the page ended before it with has_more: true, or, as the page’s first prop, the request answered 503 with error.reason read_model_warming.
  • When we held no book for it, it was counted on coverage.rows_without_book.
Pages without either filter are unchanged.Backward compatible. A filtered page can now hold more props, and coverage.rows_without_book counts only props the page could list.What to change: Nothing.
Leaderboard activity filter counts 30 days from the ranking's date, not your request's
GET /api/v1/leaderboard with activity=active or activity=inactive now counts the 30 days of activity up to the UTC date of the ranking it serves. That is the date summary.active_30d is counted at, so the list and the count always agree.Before this change, the filter counted up to the date of your request. For up to 3 minutes after UTC midnight, before the ranking refreshed, activity=active could leave out a trader that summary.active_30d still counted, and a next_cursor walk could report an exact continuation although that trader had left the list.Backward compatible. No parameter or field changes. Outside those 3 minutes after midnight, every response is the same.What to change: Nothing.
Quotes carry order book depth, and list props filters on it with min_book_usd_within_2c
Every quote from GET /api/v1/games/{event_slug}/markets and GET /api/v1/props with expand=quotes now carries book_depth_within_2c: the dollars resting on each side of the book within 2 cents of its midpoint. List props takes a new min_book_usd_within_2c parameter that keeps only props with that much on each side, so ?league=nfl&min_book_usd_within_2c=500 lists the NFL props you can trade $500 against near the price.Before this change, no quote carried depth, and Market snapshot never filled its bid_depth_usdc and ask_depth_usdc.
  • book_depth_within_2c is {bid_usd, ask_usd}: price times size of bids at or above the midpoint minus 2 cents, and of asks at or below the midpoint plus 2 cents, over every level of the book. It is the measure the market_price_moved webhook already sends under the same name, here as numbers. It is null when we hold no depth for the book; read again after a few seconds.
  • Market snapshot’s order_book.bid_depth_usdc and ask_depth_usdc now carry the same two sums, rounded to the cent.
  • min_book_usd_within_2c is a whole number of dollars from 1 to 10,000,000, the range the webhook filter takes. Any other value is a 400 with error.reason invalid_query. With max_spread_c, one outcome must pass both.
  • A prop whose book or depth we do not hold yet is left out of data and counted on coverage.rows_without_book, as with max_spread_c.
  • The list_props MCP tool takes min_book_usd_within_2c. The SDK’s types carry both fields in the next @0xinsider/sdk release.
Backward compatible. One optional parameter and one quote field are added, and two snapshot fields that were always absent now appear; nothing is removed.What to change: Nothing. To list props with room to trade, send min_book_usd_within_2c, and read coverage.rows_without_book before you treat a short page as the whole board.
List props filters on the order book spread with max_spread_c
GET /api/v1/props takes a new max_spread_c parameter. It keeps only props where one outcome’s best ask minus its best bid is at most that many cents, so ?league=nfl&max_spread_c=2 lists the NFL props with a market at most 2 cents wide. It reads the order books we already hold, the ones expand=quotes serves, and makes no call to Polymarket while you wait.Before this change, you read every page of a league and compared best_bid and best_ask yourself.
  • The value is in cents, above 0 and at most 100, in steps of 0.01. A book exactly at the cap is kept. Any other value is a 400 with error.reason invalid_query.
  • A prop whose book we do not hold yet is left out of data and counted on the new coverage.rows_without_book, which is null without the filter. The read starts a fetch of the league’s books, so read again after a few seconds.
  • The filter decides which props are on a page, so next_cursor stays exact. A page can hold fewer than limit props while has_more is true.
  • Each page checks the books as we hold them when you read it, and next_cursor never comes back to a prop a page passed over. When coverage.rows_without_book is above 0, read from the first page again after a few seconds to check those props.
  • If we cannot read the order book of the first prop a page has to check, the read answers 503 with error.reason read_model_warming and a Retry-After, instead of an empty page whose next_cursor is the one you sent.
  • The list_props MCP tool takes max_spread_c. The SDK’s types carry it in the next @0xinsider/sdk release.
  • This change adds no depth filter. min_book_usd_within_2c, in the entry above, does.
Backward compatible. One optional parameter and one response field are added; nothing is removed.What to change: Nothing. To list tight markets, send max_spread_c. If coverage.rows_without_book is above 0, read from the first page again before you treat the result as the whole board.
Leaderboard cursors survive a ranking refresh and say when the ranking moved
GET /api/v1/leaderboard now continues a next_cursor from an earlier ranking generation instead of refusing it. The next page follows the cursor’s position in the current ranking, and a new meta.continued_from_generation names the cursor’s generation when the ranking moved under it.Before this change, a cursor answered 400 with error.reason cursor_expired whenever the ranking generation moved, about once a minute, even when no rank had changed. A caller that paused between pages had to restart its walk.
  • meta.continued_from_generation is absent on a first page and on an exact continuation. The default order, sort=rank and sort=grade are exact while no wallet’s rank, grade or membership moved; every other sort reports any new generation.
  • When it is set, a trader who crossed the cursor’s boundary can repeat a row you already hold or be missing from the walk.
  • A cursor sent with another category, strategy or activity still answers 400 cursor_expired.
  • The get_leaderboard MCP tool’s output schema and the @0xinsider/sdk types carry the new field in their next release.
Backward compatible. One optional field is added, and a request that used to fail now succeeds.What to change: Nothing. For an exact walk, de-duplicate rows by id, or restart from the first page when meta.continued_from_generation is set.
List games refuses an unknown status and names an uncovered sport in a header
GET /api/v1/games now answers a status outside its published values with 400, error.reason invalid_query and error.param status, and the message lists the accepted values. A sport we do not cover still returns an empty list, and the response now names sport in the new X-Query-Unmatched header.Before this change, both returned 200 with an empty list and nothing said why, so status=upcoming read as “no games”. Upcoming games are scheduled.
  • status accepts scheduled, live, paused, ended, postponed, cancelled, suspended, delayed and unknown, the values the OpenAPI spec and the list_games MCP tool have always listed.
  • X-Query-Unmatched is readable from browser JavaScript, and @0xinsider/sdk lifts it into meta.queryUnmatched in its next release.
Breaking for clients that send a status outside the published values: they get 400 instead of an empty list. Every other request is unchanged.What to change: Send one of the published status values. To read an empty page, check X-Query-Unmatched or coverage.sports.
Props quotes carry a book for props we do not stream, and quotes name the book's source
GET /api/v1/props with expand=quotes now carries the best bid and best ask for props we are not streaming. Each read starts a fetch of the league’s order books from Polymarket after the response, at most every 10 seconds, so the next read of any page of that league has them.Before this change, every prop we were not already streaming read book_unavailable_reason: not_streamed with no book. On November 7, 2026, all 200 outcomes of the first 100 NFL props read that way.
  • A new quote.book_source field names who read the book: clob_stream (Polymarket’s live market feed) or clob_rest (a fetch of Polymarket’s order books). It is null when there is no book. It is on GET /api/v1/games/{event_slug}/markets too.
  • A prop with no book yet reads cache_miss, which means read again after a few seconds. not_streamed stays in the enum, but no endpoint returns it any more.
  • book_observed_at is when the book last changed, from either source.
  • The list_props and list_game_markets MCP tools describe the change. The SDK’s types carry book_source in the next @0xinsider/sdk release.
Backward compatible. One field is added; no field or value is removed.What to change: Read best_bid and best_ask on props as you do on game markets. If you skipped not_streamed books, read again on cache_miss instead.
Market snapshot top_of_book reasons add cache_unavailable when we could not read a book from our cache
GET /api/v1/market/{condition_id}/snapshot now reads reason: cache_unavailable on an outcome’s top_of_book, and on freshness.top_of_book, when we could not read that outcome’s book from our cache: the read failed, ran out of time, or found an entry we could not decode. Nothing is known about the book then, and a read a few seconds later may answer. The game markets and props quotes name the same failure the same way.Before this change, the snapshot served cache_miss for that failure, or stream_capacity when we were not streaming the market, which says reading again does not help.
  • cache_miss now means only that our cache answered and holds no book for the outcome yet.
  • stream_capacity keeps its meaning, and is served only when the cache read succeeded.
  • freshness.top_of_book.reason reads cache_unavailable when no outcome has a book with a side and the read did not answer for at least one of them.
Backward compatible. The reason string gains one value; no field changes.What to change: Treat cache_unavailable as retryable and read again after a few seconds. A client that refuses unknown reason values must accept it.
Quote book reasons add cache_unavailable when we could not read a book from our cache
The quote object on GET /api/v1/games/{event_slug}/markets and GET /api/v1/props with expand=quotes now reads book_unavailable_reason: cache_unavailable when we could not read that outcome’s book from our cache: the read failed, ran out of time, or found an entry we could not decode. Nothing is known about the book then, and a read a few seconds later may answer.Before this change, the same failure read cache_miss or stream_capacity on game markets, and not_streamed on props, which says reading again does not help.
  • cache_miss now means only that our cache answered and holds no book for the outcome yet.
  • not_streamed and stream_capacity keep their meaning, and are served only when the cache read succeeded.
  • The list_game_markets and list_props MCP tools name the new value. The SDK’s types carry it in the next @0xinsider/sdk release.
Backward compatible. The book_unavailable_reason enum gains one value; no field changes.What to change: Treat cache_unavailable as retryable and read again after a few seconds. A client that refuses unknown enum values must accept it.
Game markets answers first reads within 250 ms; a slow expand reads unavailable sooner
GET /api/v1/games/{event_slug}/markets now answers the first read of a game within its 250 ms budget, with every expand. When listing the game’s markets takes long, the endpoint waits less for settlement, sharp_money, quote, and trading_terms, and a block it cannot read in that time is unavailable.Before this change, each block waited its full time after the markets were listed. On November 7, 2026, 6 of 13 first reads of NFL games with every expand took longer than 250 ms, up to 279 ms.
  • A first read can return unavailable for a block that it used to wait for and return. The read keeps running, so the next read a few seconds later carries it.
  • A read of a game that was read recently is unchanged.
Backward compatible. No field or status changes.What to change: Nothing, if you already read again when a block is unavailable, as the endpoint page says.
ITF live state skips a match when an abbreviated name fits two players in the draw
ITF singles live state now skips a match when API-Tennis abbreviates a player’s name and that name fits two players Polymarket listed in the same tournament over the last 48 hours. “J. Smith” fits both James Smith and John Smith. Brothers often play the same ITF draw.Before this change, such a match could take another match’s state. One brother’s finished match could set the final score of the other brother’s next match against the same opponent.
  • A skipped match keeps phase: unknown on the market_price_moved webhook and the MarketPriceMoved stream frame. On GET /api/v1/games it carries no live score, the same as a match API-Tennis does not list.
  • A full name, or an abbreviation that fits one player only (“Ja.” against “Je.”), binds as before.
Backward compatible. No field changes; a few ITF matches carry no live state rather than a wrong one.What to change: Nothing.
New GET /api/v1/props lists one league's player props with player, stat and line
GET /api/v1/props lists the player props of one league’s games in one paged list, each row with the player’s name, the stat and the line as separate fields. It covers the games GET /api/v1/games serves for the league, and reads both places Polymarket files props: an NFL game’s -player-props and -first-td-scorer events, and the NBA game event.Before this change, reading a league’s props took one GET /api/v1/games call and then one GET /api/v1/games/{event_slug}/markets call per game, and the player’s name had to be split out of group_item_title.
  • Filters: league (required), starts_after and starts_before, player (part of the name, ignoring case and accents), stat (Polymarket market types), status, limit (1 to 200, default 50) and cursor.
  • player.name is Polymarket’s market label before its first : , the name the event page shows. When Polymarket gives no label, player is null and player_unavailable_reason is missing_provider_title. line is null on a yes/no prop.
  • expand=quotes adds each outcome’s quote, the object GET /api/v1/games/{event_slug}/markets returns. A prop we are not already streaming carries no book, with the new book_unavailable_reason value not_streamed, which only this endpoint returns.
  • coverage.sources_unavailable names any game source for the league that did not answer for the read, in the <sport>:<half> form GET /api/v1/games uses, so a short page is never read as the whole board.
  • The request costs 2, plus 1 with expand=quotes.
  • The MCP tool list_props calls it. The SDK’s listProps() and its types ship in the next @0xinsider/sdk release.
Backward compatible. A new endpoint and MCP tool. The quote object’s book_unavailable_reason enum gains not_streamed, which GET /api/v1/games/{event_slug}/markets never returns.What to change: Nothing. To read a league’s props, call GET /api/v1/props?league=nfl instead of walking each game’s markets.
market_price_moved no longer sends a second event inside one window during a release
The market_price_moved webhook and the MarketPriceMoved frame on GET /api/v1/stream now keep their delivery rule while 0xinsider releases a new server version. Inside one window, only a larger move in the same direction fires again, and it carries previous_level_cents.Before this change, a release could send a second event for a market inside a window that was already open. That event carried previous_level_cents: 0, and some reported the opposite direction. Three releases on November 7, 2026 sent 9 such events.Backward compatible. No field changes; fewer duplicate events arrive.What to change: Nothing.
Position timeline returns every stored fill, not only large trades since July 17
GET /api/v1/trader/{address}/position-timeline and GET /api/v1/traders/{trader}/position-timeline now return every fill 0xinsider stores for the wallet in the market, at any date. running_amount and running_avg_price count all of them.Before this change, both routes returned just the large-trade fills recorded from July 17, 2026. A wallet whose fills were smaller or older got 200 with an empty list, which read as “no fills”.
  • tx_hash can be null. The hash comes from the large-trade alert for the fill, and a smaller fill has none.
  • id is an opaque fill_... value. Ids from earlier responses (pe_...) do not match any row now.
  • A cursor from an earlier response returns 400 with error.reason cursor_expired.
  • A wallet with more than 20,000 fills in the market before the requested page returns 404 with error.reason fill_history_too_large. The API refuses instead of serving running totals it could not finish. Like trader_not_tracked, it is final; do not retry.
  • The MCP tools get_position_timeline and get_position_timeline_by_id and the SDK types describe the same contract. The SDK adds FillHistoryTooLargeError from the next @0xinsider/sdk release.
Breaking for clients that type tx_hash as a string that is always set, store fill ids, or keep a timeline cursor across this change.What to change: Treat tx_hash as nullable. Request the first page again without a cursor. Handle 404 fill_history_too_large as final.
Trader category records now list each league and esports title in scopes
GET /api/v1/trader/{address}/categories now returns a scopes array on each record: the wallet’s record in each soccer league, each registered league of another sport, and each esports title it has a settled market in. A wallet’s UEFA Champions League record is now one call: request ?category=soccer and read the ucl entry.Before this change, only the Esports record broke down further, through games. The Soccer, Basketball, Tennis and other records carried the bucket total alone, though Pick of the Day and the holder chips already printed league records.
  • Each scopes entry is { scope_key, display_name, wins, decided, win_rate, status }, busiest first, with the same 5-market floor and the same measured or not_enough_data status as its parent record.
  • display_name is the name Pick of the Day and category_win_rate_game on the holder chips print, such as UEFA Champions League, LaLiga, NBA, or LoL. scope_key is the stable key, such as ucl, la-liga, nba, or league-of-legends.
  • A scope’s decided is a subset of its parent record’s. The parent record stays the whole category.
  • scopes is absent, never null, when the record has no scope with a settled market. A league is not a filter of its own: ask for its category and read scopes.
  • On the Esports record, scopes lists the same titles as games, with the same counts. games stays unchanged.
  • The MCP tool get_trader_categories returns scopes now. The SDK type TraderCategoryScopeRecord and the scopes field on TraderCategoryRecord ship in the next @0xinsider/sdk release.
Backward compatible. One optional field is added to each record; no field changes or goes away.What to change: Nothing. To read a wallet’s record in one league, read its entry in scopes by scope_key.
Large trades and dataset rows: clv now carries the fill's fee and the CLV after it
The clv object on every large trade now carries the fee the trade’s fill paid and the closing line value after that fee. That covers GET /api/v1/large-trades, GET /api/v1/large-trades/history, GET /api/v1/large-trades/{id}, the trades /api/v1/events/feed/since expands, and every row of a whale dataset file.Before this change, clv_cents was the only figure, gross of fees, and the API carried no fee for a fill. Polymarket charges only the taker of a fill, and the wallet behind a large trade is its taker. So on a market with fees, the fee lowers that trade’s CLV by the same amount per share, whether it bought or sold.
  • taker_fee_cents: the fee the fill paid, in cents per share, from the exchange’s on-chain record of the fill. It includes any builder fee an app added and excludes the maker and taker rebates Polymarket pays later. It is set whatever status is, and null until that record is decoded (usually within a minute of the trade) and for trades before August 16, 2026.
  • clv_after_fees_cents: clv_cents minus taker_fee_cents, set when both are set.
  • clv_cents and GET /api/v1/trader/{address}/clv stay gross of fees.
  • The MCP tools get_large_trades, get_large_trade and get_large_trades_history describe both fields. The SDK’s clv types carry them from the next @0xinsider/sdk release.
  • A whale dataset submitted after this release is written with both fields. An identical submit builds a new file instead of reusing one written before.
Backward compatible. Two fields are added to the clv object; no field changes or goes away.What to change: Nothing. To judge whether a trade beat the close after its cost, read clv_after_fees_cents instead of clv_cents.
ITF singles games now carry live state: phase in_play on line moves, and final scores on ended games
ITF World Tennis Tour singles matches now have live game state. Polymarket publishes no score for them, so 0xinsider takes it from API-Tennis, the provider that already supplies tennis point detail, and states it only when one API-Tennis match names both players, the same tournament, and a date within a day of the kickoff.
  • The market_price_moved webhook and the MarketPriceMoved stream frame carry phase: in_play and a game_state (status inprogress, period such as S2, each player’s sets such as 6, 2) for a move on an ITF singles match in play. Before this change they carried phase: unknown with game_state_unavailable_reason: no_game_state from kickoff on.
  • GET /api/v1/stream sends LiveScoreChanged frames for these matches, with the same score and, while a game is in play, the tennis_points group. The TennisPoints object description now says so.
  • A move after the match ends is no longer delivered, as for every other covered league: it is a settlement move.
  • An ended ITF singles game on GET /api/v1/games carries its final set scores, and coverage.scores reads available.
  • A match API-Tennis does not list, or that more than one match could be, keeps phase: unknown. ITF doubles are not covered.
Backward compatible. No field is added or removed, and the values are the ones other tennis games already carry.What to change: Nothing. A subscription with phase: in_play now receives ITF singles moves, and one that filtered out ITF moves because they read unknown can stop.