Skip to main content
These are the API changelog entries from September 23, 2026, newest first. The newest changes are on the API changelog.
Trader P&L reads accept date bounds and selected sections
GET /api/v1/trader/{address}/pnl now accepts from, to, and sections. You can request a shorter daily series and omit sections your client does not use.Before this change, every request received all 5 sections over the full stored history.
  • from and to are inclusive UTC dates in YYYY-MM-DD form. They limit entries and drawdown; stats keeps its published time windows, and monthly and year_totals keep their whole-period totals.
  • sections accepts entries, stats, monthly, year_totals, and drawdown, using repeated or comma-separated values. sections[] is an accepted alias; an unselected section is absent from data.
  • A selected or bounded response adds data.view, with the effective bounds, selected sections, full and window entry counts, and anchor, the last daily point before from. anchor is null without a lower bound or an earlier point; its unavailable values stay null.
  • Cumulative values keep their full-history baseline. A malformed date, reversed date range, or unknown section returns 400 with error.param naming the parameter.
  • Requests that omit these parameters keep their previous body. Each selected representation has its own ETag.
Backward compatible. These parameters and the selected-response metadata are additive.What to change: Nothing for an unfiltered request. Guard optional sections in a typed client, and use the anchor when calculating a change relative to the requested window.
The forecasting axis covers nearly every graded wallet
GET /api/v1/trader/{address} now returns forecast_score and forecast_evidence for nearly every graded wallet that has a calibration record. Before this change, a wallet’s forecast was written only when its grade was recomputed in the same hourly pass, so most wallets returned no forecast. A new day’s ranking row now also keeps the previous score instead of returning none until the next pass.The fields and their meaning are unchanged: forecast_score is an independent forecasting percentile from 0 to 100, and forecast_evidence describes support from the observed record from 0 to 1. A wallet with no calibration record still returns no forecast.Backward compatible. Fewer responses omit the fields; nothing is removed or renamed.What to change: Nothing. If you treated a missing forecast_score as rare, expect it on almost every graded wallet now.
Python SDK adds an async client with bounded concurrency and resumable streams
oxinsider.AsyncClient (0xinsider 0.6.0 on PyPI) provides the synchronous Client operations as async/await methods using httpx.AsyncClient. max_concurrency limits simultaneous requests and defaults to 10.AsyncClient.stream() reads GET /api/v1/stream resumably: it reconnects after a disconnect with Last-Event-ID set to the last frame’s id, backs off exponentially between attempts, and raises oxinsider.StreamClosedError on a terminal event: error frame that says the credential was permanently refused instead of reconnecting.Before this change, an async caller needed a hand-built thread-pool wrapper around the synchronous Client, and a disconnected stream had to track its own resume cursor and reconnect logic. Cancelling a call (asyncio.CancelledError, an asyncio.wait_for timeout) now closes the in-flight connection and propagates uninterrupted, rather than leaking a thread.Backward compatible. The synchronous Client is unchanged; AsyncClient is a new, additive class.What to change: Nothing is required. Install 0xinsider>=0.6.0 and use oxinsider.AsyncClient in place of Client for an async caller.
One freshness check for data_quality in every SDK and MCP
The TypeScript, Python and Go SDKs add one helper that turns a response’s data_quality into a decision against your own limit: assessDataQuality (@0xinsider/sdk 0.13.0), assess_data_quality (0xinsider 0.5.0 on PyPI) and AssessDataQuality (0xinsider-go). A group passes only when its status is fresh, it has an as_of, and that clock is within your limit. A group with unknown status fails, and untracked groups are left out and listed.The MCP tools get_positions, get_large_trades and the large-trade history tools now return the page’s top-level data_quality. Before this change, the tool result dropped it. The trader tools already returned it inside the trader, and their descriptions now say what it answers. The copy-trade recipe checks it before it uses a grade.Backward compatible. The helpers and the MCP field are additive.What to change: Nothing is required. Call the helper, or read status and as_of yourself, before you act on a body. Never treat unknown as recent.
Coverage gets its own route; /platforms becomes a deprecated alias
GET /api/v1/coverage returns which V1 reads the API serves for Polymarket. It needs no key, and the body is the one /api/v1/platforms already returns. The SDK adds getCoverage (@0xinsider/sdk 0.13.0) and MCP adds get_coverage (@0xinsider/mcp 2.8.0).Before this change, the document was only at /api/v1/platforms. That path stays live with the same body and now answers with a Deprecation header and a Link header naming /api/v1/coverage. getPlatforms and get_platforms keep working.Backward compatible. The new route, method and tool are additive.What to change: Nothing is required now. Read /api/v1/coverage, getCoverage or get_coverage before the /platforms alias is retired under the deprecation policy.
Market flow gets canonical paths; the intel paths become deprecated aliases
GET /api/v1/market/{condition_id}/flow and POST /api/v1/markets/flow/batch now return a market’s flow and top positions, with object set to market_flow and market_flow_batch and the MarketFlow and BatchMarketFlowItem schemas. The SDK adds getMarketFlow and batchGetMarketFlow (@0xinsider/sdk 0.12.0), MCP adds get_market_flow and batch_get_market_flow (@0xinsider/mcp 2.7.0), and the CLI reads 0xinsider market flow <id>.Before this change, the same data was only at /intel and /markets/intel/batch. Those paths stay live with the same parameters and the same body, with object still market_intel and market_intel_batch, and now answer with a Deprecation header and a Link header naming the successor path. The get_market_intel and batch_get_market_intel MCP tools keep working.Backward compatible. The new paths, methods and tools are additive; nothing existing is removed.What to change: Nothing is required now. Move reads to the /flow paths, or to getMarketFlow and get_market_flow, before the /intel aliases are retired under the deprecation policy.
Wallet positions offer a five-minute snapshot for repeatable pagination
GET /api/v1/positions now accepts consistency=snapshot when you pass at least one wallet. It returns up to 500 positions and 2 MB from one ordered result for up to five minutes. The response adds snapshot.as_of, snapshot.expires_at, snapshot.row_count, and a cursor that requires the same filters on each page. A new first page replaces the API key’s previous snapshot.Before this change, every page read live positions, so a price change could move a position across a page boundary. The default consistency=live behavior is unchanged. A snapshot freezes the positions this API returned; it does not prove that every provider position was available.Backward compatible. The new mode and response field are additive.What to change: Add consistency=snapshot and wallet for a repeatable multi-page read. Keep the same filters on each page. If the cursor expires or another first page replaces it, request the first page again. Narrow your filters or use the live mode if the snapshot exceeds either limit.
MCP get_positions adds wallet and consistency, matching the REST route
The get_positions MCP tool, on both the remote server and @0xinsider/mcp 2.9.0, now accepts wallet (a wallet or a book of up to 25) and consistency (live or snapshot), the same parameters GET /api/v1/positions already takes.Before this change, an MCP client could only read the global positions board and could not use consistency=snapshot; reaching a single wallet’s book or a repeatable multi-page walk needed a direct REST call instead of the tool. wallet is passed as a repeated query parameter on both transports, matching the REST route’s wire shape.Backward compatible. The new arguments are additive; a call that omits them behaves exactly as before.What to change: Nothing is required. Pass wallet and consistency: "snapshot" in a get_positions tool call to scope to a wallet or start a repeatable multi-page walk over MCP.
Large-trade subscriptions add individual payloads and filters
GET /api/v1/stream now emits LargeTradeInsertedV2 with one trade’s market, wallet, grade snapshot, size, side, price, and durable event_id. POST /api/v1/webhooks can subscribe to large_trade_inserted_v2 with trade_filters for condition_id, wallet, min_grade, and min_size_usd.Before this change, WhaleTradesInserted and whale_trades_inserted carried only a count, so clients had to fetch the trades after a pulse. Those count-only events still arrive unchanged. The stream also accepts wallet and min_size filters for individual trade frames.Backward compatible. The new events and filters are additive; existing subscriptions keep their behavior.What to change: Nothing for existing clients. Subscribe to the versioned event when you need each trade without a follow-up request. Use the SSE sequence for reconnects; use the payload’s event_id for durable trade identity.
Trader grade at a past decision time
GET /api/v1/trader/{address}/grade-at now takes as_of and returns a grade proven visible at that time. Before this change, the API offered only current grades; a historical backtest could not query the grade known at its decision time.
  • status is graded, ungraded, or unknown. Before the first recorded observation or during an unconfirmed transition, it is unknown.
  • available_from is the first confirmed observation for the wallet. observation.observed_at and published_by bound publication, and previous_observation_id links retained revisions.
  • model_version, model_build_sha, and source_observed_by are present when recorded. Older observations leave unproven fields null.
Backward compatible. This is an additive route; current-grade responses are unchanged.What to change: Existing clients need no change. For backtests, pass the decision time as as_of and use grade only when status is graded.
Developer API adds named integration keys with scopes and isolated rotation
POST /api/keys/integrations now lets a signed-in Pro account create a named key with read, webhooks, export, or usage scopes and a 1 to 90 day expiry. Before this change, the account held one unrestricted developer key; rotating it immediately stopped every consumer that shared it.
  • GET /api/keys/integrations lists key metadata, expiry, last use, and monthly requests. It never returns a key’s secret.
  • POST /api/keys/integrations/{integration_id}/rotate issues a new key for one integration and keeps its previous key valid for at most 15 minutes. DELETE /api/keys/integrations/{integration_id} revokes every key in that integration immediately.
  • A request outside an integration key’s scopes returns 403 insufficient_scope and names the needed scope. Existing default keys keep unrestricted access, and every credential uses the same account quota and subscription.
Backward compatible. The new routes and key type are additive; existing keys and requests keep working.What to change: Create one scoped key per integration when you want isolated rotation. Continue using your existing key if you do not need it.
Game API markets now include provider moneyline prices and availability states
GET /api/v1/games and GET /api/v1/games/{event_slug} now include markets[].prices when the board has classified a moneyline projection. Before this change, a game named its linked markets and token ids but a client needed a separate market snapshot to read a price.
  • prices.provider.state is paired, incomplete or invalid. A paired state carries prices bound to the two competitors, their identity binding and each leg’s price provenance. Incomplete and invalid states carry a reason instead of a guessed pair.
  • prices.observed_at is the board cache vintage or the older CLOB leg clock, identified by prices.observation_source. It is null when no reliable clock exists; it is not a Gamma-authored timestamp.
  • Markets with no classified moneyline projection omit prices. Existing game and market fields keep their values and positions.
Backward compatible. The prices block is additive, and existing fields are unchanged.What to change: Nothing for existing clients. Read prices.provider.state before using a price, and accept new provider reason values as the source vocabulary grows.
List reads add stored data_quality
GET /api/v1/positions, GET /api/v1/large-trades, GET /api/v1/large-trades/history and their /api/v1/whale-trades aliases now return top-level data_quality beside data. It reports the stored age and coverage of the fields in the page.Before this change, these list envelopes exposed data, pagination fields and meta, but no shared age and coverage summary.
  • Positions group their position, trader and market fields under wallet_positions.last_reconciled_at|updated_at, traders.last_synced and market_canonical.last_refreshed_at.
  • Large-trade pages group alert, trade, trader, ranking, market and volume fields. whale_alerts.inserted_xid is a transaction identifier, so the alert group is unknown.
  • The field is part of the response representation and ETag. meta.cached and meta.cache_age_s continue to describe transport cache state.
Backward compatible. The field is additive and existing fields keep their names and values.What to change: Nothing. Read data_quality.status, data_quality.as_of and data_quality.field_groups when you need a page-level freshness decision.
Trader data quality dates rank and open-position P&L
GET /api/v1/trader/{address}, POST /api/v1/traders/batch and GET /api/v1/trader/{address}/context now carry real freshness times for the existing data_quality and expand=trust metadata. leaderboard_rank uses the latest completed global rank refresh, and positions uses the latest successful /positions snapshot or the last completed trader sync.Before this change, leaderboard_rank and positions returned unknown because the API did not carry a clock for either value.
  • leaderboard_rank dates the stored global rank with the completion time of the latest refresh that finished for every candidate.
  • positions dates open-position unrealized P&L with the latest successful position snapshot and falls back to the last completed trader sync when no position snapshot exists. This clock does not date closed or native accounting values.
Backward compatible. The response shape is unchanged, and the new timestamps make existing metadata more useful.What to change: Nothing. Clients that use freshness can read the new as_of values; clients that do not use them keep working.
Trader reads can require a freshness ceiling
GET /api/v1/trader/{address} accepts max_age_s and returns 409 with error.reason=freshness_ceiling_unsatisfied when the requested whole-response age cannot be met. error.freshness reports the requested ceiling and the measured age when available.Before this change, the endpoint could return 200 without a caller-supplied bound on the stored data’s age.Backward compatible.What to change: Nothing.
The webhook event catalog lists all 14 subscribable types
GET /api/v1/webhooks/events now lists large_trades_inserted and trader_synced, the successor spellings of whale_trades_inserted and whale_trader_synced, and the OpenAPI WebhookEventType enum includes them. The catalog now holds all 14 types, including the four export_job_* events.Before this change, Create a webhook accepted both successor spellings, but the catalog and the enum omitted them, so an SDK type rejected a name the API accepts.Backward compatible. Existing subscriptions and deliveries are unchanged; each spelling receives the same events under the name it registered.What to change: Nothing required. Prefer large_trades_inserted and trader_synced in new subscriptions (@0xinsider/sdk 0.11.1 types them).
Sharp money names become available alongside their smart money aliases
“Sharp money” is the product term for graded money on a side. The API now spells every remaining “smart money” name that way, and keeps each old name on the wire with the same value.
  • Webhooks add the event type sharp_money_flow_detected. It is the same event as smart_money_flow_detected, which stays subscribable. An endpoint that registered the old name keeps receiving type: smart_money_flow_detected; a new endpoint should register the new one. Either spelling also works in the ?event= filter on GET /api/v1/stream.
  • GET /api/v1/pick-of-the-day returns sharp_wallet_count and sharp_usd beside smart_wallet_count and smart_usd, with the same values.
  • GET /api/v1/markets/explore returns score_components.sharp_money_signal beside score_components.smart_money_signal, with the same value.
  • The OpenAPI spec marks smart_money on market flow and ranked flows, and the four fields above, deprecated.
  • @0xinsider/mcp 2.6.0 lists get_sharp_money_flows first and keeps get_smart_money_flows as its deprecated alias, still calling the deprecated /api/v1/markets/smart-money-flows path. get_market_intel, get_pick_of_the_day and the market_report prompt say “sharp money”. No tool was added or removed.
  • @0xinsider/sdk 0.11.0 adds listSharpMoneyFlows, the SharpMoneyFlowDetectedEvent envelope and SharpMoneyFlowDetectedData. listSmartMoneyFlows, SmartMoneyFlowDetectedEvent and SmartMoneyFlowDetectedData stay exported and are marked @deprecated.
/api/v1/markets/smart-money-flows itself is unchanged. It has been a deprecated alias of /api/v1/markets/sharp-money-flows since July 5, 2026, and it still answers with Deprecation and a Link to its successor. No path is retired.Backward compatible. Every new name is additive, and no old name, field, or event type was removed or changed value.What to change: Nothing. Prefer the sharp_ spellings in new code. If you switch on a webhook type, note that an existing subscription keeps delivering the spelling it registered, so nothing changes until you register the new one.
Export jobs can be cancelled; the job adds cancel_requested and cancelled
POST /api/v1/trader/{address}/export/cancel cancels an export job you own and answers the job resource. A queued job reads cancelled at once. A running job reads cancel_requested until the worker stops at its next safe point, then cancelled.Before this change, an export could not be stopped: a job ran to ready or failed and its file was built either way.
  • The job resource gains the status values cancel_requested (not terminal, next_action poll) and cancelled (terminal, next_action resubmit), and the nullable timestamps cancel_requested_at and cancelled_at. GET .../export/status and POST .../export return them too.
  • A job whose file is already being published, or that is already ready, reconcile_required, failed, expired, or cancelled, comes back unchanged with 200. A cancel never deletes a ready file.
  • Cancelling returns no quota: the submit keeps counting toward the daily and per-wallet hourly caps. A later submit never reuses a job you asked to cancel, so it queues a new one.
  • Webhooks add the owner-scoped event export_job_cancelled with job_id, status, format, and next_action.
  • @0xinsider/sdk 0.11.0 adds cancelTraderExport(address, jobId), retries it on a network error or a 5xx because a repeat is safe, and types the new statuses, fields, and event.
  • An unknown job, another account’s job, or a job under another wallet’s path answers 404, the same as the status route.
Backward compatible. Every field is additive, and the two new status values appear only on a job its owner cancelled.What to change: Nothing unless you cancel. If your code switches on status, treat cancel_requested like running and cancelled like failed.
Trader reads always return data_quality, and trust names the real owner of each clock
GET /api/v1/trader/{address}, POST /api/v1/traders/batch and GET /api/v1/trader/{address}/context now always return data_quality. It tells you how old a trader body is and which part of it is weak, without asking for expand=trust.Before this change, the only answer to “how old is this” was expand=trust, a per-field object about twenty entries long, and half of those entries reported freshness unknown even where a real timestamp existed.
  • data_quality.status is fresh, partial, unknown, untracked or unavailable. It reads fresh only when every group does, unavailable only when every group does, and partial otherwise.
  • data_quality.as_of is the oldest clock the body rests on. It is omitted when no group has one.
  • data_quality.field_groups holds one entry per group, each with group, owner (the table and column that writes it), status, an as_of where one exists, and a reason where the status is not fresh.
  • The five groups are sync (traders.last_synced: total and realized P&L, markets traded, both win rates, last_active, synced_at, sync_status), ranking (trader_rankings.computed_at: grade, score, streak_tier, forecast_score, forecast_evidence), leaderboard_rank (traders.leaderboard_rank), volume (trader_usd_volume.observed_at: stats.total_volume) and positions (trader_markets: pnl.unrealized).
  • leaderboard_rank and positions always read unknown rather than carrying a time. That column and that aggregate store no write timestamp, so the API says it does not know instead of borrowing another group’s clock.
  • Every value comes from a stored timestamp, so a cached body reports the same ages a freshly computed one does. meta.cached and meta.cache_age_s are still the only fields that describe the response itself.
  • Under expand=trust, grade, score, streak_tier, forecast_score and forecast_evidence now name trader_rankings as their source.owner and carry its freshness.as_of; they named 0xinsider_trader_read_model and reported unknown. total_volume now carries the volume observation time. rank now names traders.leaderboard_rank and keeps freshness unknown.
  • @0xinsider/sdk 0.11.0 types DataQuality and DataQualityGroup and adds data_quality to Trader.
Backward compatible. data_quality is a new field, and no field or value was removed. Under expand=trust some source.owner strings and freshness values changed, because they were wrong before.What to change: Nothing. Read data_quality.status and data_quality.as_of instead of expand=trust when you only need to know whether a body is current enough for you.
Large trades add a market-significance filter and sort
GET /api/v1/large-trades, GET /api/v1/large-trades/history and their /api/v1/whale-trades aliases add two query parameters: min_market_volume_share and sort.Before this change, market_volume_share was returned on every large trade but nothing could order or narrow a page by it. A $10,000 fill that is 0.03% of a presidential market and one that is 40% of a small esports market came back side by side, in the same order.
  • min_market_volume_share is a fraction, not a percent: 0.01 is one percent of the market’s traded volume. Valid range is 0 to 1; anything else answers 400 with param: "min_market_volume_share". A non-zero value never returns a trade whose market_volume_share is absent, because an unavailable share cannot be said to clear a floor.
  • sort takes recent (the default, and the order every caller got before this) or market_volume_share, which ranks biggest-share first and puts a trade with no share last. An unrecognized value answers 400 with param: "sort".
  • sort=market_volume_share reads a bounded window: the last 30 days on the list route, and from (or the last 30 days when from is omitted) on /history. The share is computed for each request against a markets volume observation, so an unbounded ranking cannot be served inside the API latency budget.
  • A next_cursor is bound to the order it was minted in. A cursor from sort=recent is refused by sort=market_volume_share and the other way round, with 400 and param: "cursor", so a walk cannot silently cross rankings and skip or repeat rows.
  • @0xinsider/sdk types both parameters on listLargeTrades, listLargeTradesHistory and the two deprecated whale-trade operations.
Backward compatible. Both parameters are additive and omitting them serves exactly what it served before: newest first, no share predicate.What to change: Nothing. To rank by how large a trade was for its own market, send sort=market_volume_share; to drop the trades that were rounding error in a deep market, send min_market_volume_share.
Trader and positions responses add exact decimal atoms
GET /api/v1/trader/{address} adds pnl.exact.realized and stats.exact.total_volume. GET /api/v1/positions adds an exact object to each valued position. These atoms carry the full-precision decimal value plus unit, scale, and basis.The existing numeric fields keep their names and display-safe values. An exact block or member is omitted when its verified source is unavailable, so clients must handle absence rather than treating it as zero.Backward compatible. The new fields are additive.What to change: Use a decimal-safe type for value when exact arithmetic matters. Keep the numeric twins for display, and use unit, scale, and basis to interpret an atom.
The sandbox lists all 44 MCP tools and answers tools/call with a tool result
The sandbox at https://0xinsider.com/sandbox now answers tools/list on POST /api/v1/mcp with the same 44 tools the live API lists, each carrying the live description, inputSchema, annotations and _meta. tools/call now answers a tool result instead of that list. The live API is unchanged.Before this change, the sandbox answered both methods with the endpoint’s documented example, which is a tools/list result holding 2 tools. A client could not read the catalog it would get, and every call came back as a list of tools.
  • A call that succeeds answers content (the tool’s data as text) and structuredContent (the same data as an object), with isError: false. The data is the sandbox’s own answer for the route the tool reads, projected the way the live server projects it: a list tool carries data, has_more, next_cursor and the route’s page fields, a single-object tool carries the object’s own fields, and both carry meta.
  • Arguments are checked against the inputSchema the tool advertises before anything runs. A wrong type, a value out of range, a value outside an enum, an unknown key, a missing required argument or an argument combination the schema forbids answers isError: true, with the text naming each field by its JSON Pointer inside arguments and structuredContent.error carrying status, code and param.
  • An unknown tool name answers JSON-RPC error -32601, and a tools/call with no name answers -32602, so a client tells those apart from a tool that ran and failed.
  • A call whose route answers an error gets that error as the tool error: the text names the status, code and reason, and structuredContent.error carries code, reason, param, doc_url, retry_at and request_id as the live server sends them.
Breaking for a sandbox client that read the 2-tool example, or that expected a tools/call to answer a tool list. The live API is unchanged.What to change: Nothing if your client already works against the live API. A client built against the sandbox should read result.tools for the catalog and result.content or result.structuredContent for a call, and treat isError: true as a failed call.
Large trades return review_score beside signal_score; MCP adds market_review
GET /api/v1/large-trades, GET /api/v1/large-trades/{id}, GET /api/v1/large-trades/history, their /api/v1/whale-trades aliases, and expand=trade on GET /api/v1/events/feed/since now return review_score and recorded_review_score. They carry the same values as signal_score and recorded_signal_score.Before this change, the per-trade 0 to 1 score was named only signal_score, and the point-in-time copy only recorded_signal_score. The number ranks trades to read first. It does not predict how a market resolves, so the name now says what it does.
  • signal_score and recorded_signal_score stay in every response with unchanged values. The OpenAPI spec marks them deprecated and names the new keys.
  • @0xinsider/sdk 0.10.0 types review_score and recorded_review_score on LargeTrade, and tags every property the spec marks deprecated with @deprecated.
  • @0xinsider/mcp 2.5.0 adds the market_review prompt: “Review a market: profitable wallets’ side, large trades, suspicious trades”, with optional market and category arguments. trading_signals stays registered as its deprecated alias with the same arguments and messages. The market_report prompt now ends in a “Review summary”. Prompts ship in the stdio package only; the remote MCP endpoint serves tools.
Backward compatible. Every change is additive. No field, value, or prompt id was removed.What to change: Nothing now. New code should read review_score and recorded_review_score, and MCP clients should call market_review instead of trading_signals.
Export status adds immutable artifact manifests and checksums
GET /api/v1/trader/{address}/export/status now returns artifact.artifact_id and an immutable artifact.manifest for newly generated ready files. The manifest carries format, schema_version, coverage, the source generation and watermarks, row_count, exact content_size_bytes and compressed_size_bytes, and separate content_sha256 and compressed_sha256 values.Before this change, a ready artifact exposed only its storage etag, compressed size, content type and content encoding. The download URL stays temporary and is still returned by GET /api/v1/trader/{address}/export/download.
  • content_sha256 covers the decompressed bytes a client reads. compressed_sha256 covers the stored gzip bytes. The multipart etag is not a checksum.
  • Historical artifacts can return manifest: null because they were written before this contract.
  • @0xinsider/sdk can read the status first and verify the streamed decompressed file with downloadTraderExport(..., { verifyChecksum: true }). A mismatch throws ExportIntegrityError.
Backward compatible. The fields are additive, and existing download calls keep working.What to change: If you need file integrity, read the manifest and enable verifyChecksum in the TypeScript SDK, or compare the decompressed stream with content_sha256 and content_size_bytes yourself. Treat a null manifest as unavailable verification and request a newly generated export when verification is required.
A large trade must now be at least 0.1% of its market's traded volume
A Polymarket fill is admitted to the large-trade record only when it clears the existing absolute floor and is at least 0.1% of its market’s recorded traded volume. GET /api/v1/whale-trades, GET /api/v1/whale-trades/history, the live feed and the MCP large-trade tools all read that one record, so all of them see the same rows.Before this change, eligibility was one fixed dollar amount: 10,000 USD, or 1,000 USD in an earnings market, priced below 0.97 (0.99 in earnings markets). Those bounds are unchanged and still apply first. A market’s liquidity spans four orders of magnitude, so the same 10,000 USD fill was the whole story in one market and rounding error in another, and the record could not tell them apart.
  • The denominator is the market’s recorded traded volume, which Polymarket reports as a cumulative one-side taker count in shares, and the numerator is the fill’s own share count. Both sides are the same measure.
  • A market with no stored volume figure keeps the fill. The floor only ever withholds a fill a known denominator proves to be rounding error.
  • At 0.1% a fill can only be withheld by a market 1,000 times its size, so the rule cannot reach a thin market. Measured over the 14 days to September 23, 2026, it would have withheld 351 of the 6,499 admitted fills carrying a denominator (5.4%), every one of them in a politics or geopolitics market, and no sports or esports fill at all.
  • It is not the same number as the market_volume_share field on a trade, which is computed at read time against a volume observation that post-dates the fill and answers a different question.
Not backward compatible in what is returned, additive in shape. No field changed, no field was added or removed, and no stored row was rewritten: rows captured before September 23, 2026 were not re-filtered. Going forward, fewer rows are captured.What to change: Nothing, unless you count large trades per market per day and compare across September 23, 2026. A window that crosses that date spans two capture rules, and the counts on either side are not comparable for very large markets. The endpoint descriptions and /llms-full.txt state the rule and the date.
The sandbox answers the MCP handshake the way the live API does
The sandbox at https://0xinsider.com/sandbox now answers initialize on POST /api/v1/mcp with the same result the live API returns: serverInfo (name, title, version), capabilities, instructions, and a protocolVersion. The answer carries Mcp-Session-Id. The live API is unchanged.Before this change, the sandbox answered initialize with the endpoint’s documented example, which is a tools/list result. A client that read result.protocolVersion or result.serverInfo found neither, and the reference MCP SDK refuses that body when it connects.
  • protocolVersion is negotiated the way the live server negotiates it: a client that asks for 2025-11-25, 2025-06-18, 2025-03-26 or 2024-11-05 gets that revision back, and any other value, or none, gets 2025-11-25.
  • Mcp-Session-Id echoes the session the request claimed, or a0b1c2d3e4f5a6b7c8d9eafbecfda0b1 when it claimed none. The sandbox stores no session, and neither does the live API.
  • A MCP-Protocol-Version request header naming a revision outside those four now answers 400 with JSON-RPC error -32600 and X-Mcp-Error-Code, on POST and GET, before the body is read. The sandbox served the request before.
  • The sandbox preflight now allows the Mcp-Session-Id and MCP-Protocol-Version request headers and exposes Mcp-Session-Id, so a browser client can complete the handshake.
  • ping, tools/list and tools/call still echo the request id, and carry Mcp-Session-Id when the request sent one.
Breaking for a sandbox client that read the tools/list result initialize used to return, or that sent an unsupported MCP-Protocol-Version header. The live API is unchanged.What to change: Nothing if your client already works against the live API. A client built against the sandbox should read serverInfo, capabilities and protocolVersion from the initialize result, and send one of the four supported revisions in MCP-Protocol-Version or omit the header.
Export jobs can notify their owner when ready, failed or expired
POST /api/v1/webhooks can now subscribe to export_job_ready, export_job_failed, and export_job_expired; GET /api/v1/webhooks/events describes them. The events are delivered only to webhook endpoints owned by the API-key account that created the export.Before this change, an export client had to poll GET /api/v1/trader/{address}/export/status to learn whether its job was ready or had failed.
  • export_job_ready carries job_id, status: "ready", format, next_action: "download", total_trades, processed_trades, file_size, and data_as_of.
  • export_job_failed carries job_id, status: "failed", format, next_action: "resubmit", and failure_reason.
  • export_job_expired carries job_id, status: "expired", format, and next_action: "resubmit".
  • Each event has a stable event id for that job transition. No event contains a download URL. Use the authorized status and download routes with the same API key, and keep polling as the recovery path when delivery is unavailable.
Backward compatible. The event types and payload fields are additive. Existing polling, download authorization, webhook signing, retries, and redelivery behavior stay the same.What to change: Subscribe to the event types you need, then fetch the job through the authorized export routes when a delivery arrives. Treat duplicate deliveries as safe to replay by event id.
Trader profiles publish a capital-normalized forecasting axis
GET /api/v1/trader/{address} now returns forecast_score and forecast_evidence beside grade when forecasting data is available. forecast_score is a 0-100 score and forecast_evidence is a 0-1 value.Before this change, the response exposed the grade but no separate forecasting axis.Backward compatible. The fields are additive and omitted when the forecasting signal is unavailable. Existing response fields keep their names and meanings.What to change: Nothing. Read the fields when present and treat omitted values as unavailable.
Sandbox responses align with MCP errors, grades, holders, batches, and webhooks
The sandbox at https://0xinsider.com/sandbox now answers six more requests the way the live API does. The live API is unchanged.
  • POST /api/v1/mcp answers in the MCP transport’s own shapes. A malformed message (unparseable JSON, not an object, a bad id, a missing or non-string jsonrpc or method, jsonrpc other than 2.0, a notification with an id, a body over 1 MiB) is 400 with a JSON-RPC error object and X-Mcp-Error-Code (-32700 or -32600). An unknown method with an id is 200 with a -32601 error. An id-less ping, notifications/initialized or notifications/cancelled is 202 with no body. Content-Type is not checked, and a response’s id echoes the request’s. Before this change, the sandbox refused these with the V1 error envelope (invalid_body, 415, 413) and answered id-less notifications with 200.
  • Grade fields carry each sample wallet’s own grade (S, A, A, B, B, C, D across the seven rows), so min_grade drops rows the way it does live: GET /api/v1/positions?min_grade=A returns 3 rows. Before, every enumerated grade read S and min_grade dropped nothing.
  • GET /api/v1/market/{condition_id}/holders lists only S, A and B holders when min_grade is omitted, as the live roster does. Before, it listed C and D wallets as well.
  • POST /api/v1/traders/batch: a sandbox wallet (0x51ab...), whether posted by address, trd_ id or username, returns its own profile and username. Any other address returns no username, and any other username gets its own address. Before, a posted address carried another sandbox wallet’s username. "expand": null now gets 400 invalid_body with error.param expand, as it does live.
  • GET /api/v1/market/{condition_id}/snapshot, /intel, /candles, /holders and /context.md describe the condition_id in the path (mkt_ prefix stripped), in market.condition_id, market.id, data.condition_id and each holder’s pos_ id. Before, they described the documented sample market whatever id you asked for.
  • GET /api/v1/webhooks/events gives all eight event types the description, payload_shape and status the live API serves. Before, seven read “Sandbox example value.” and “sandbox”.
Not backward compatible for sandbox callers. A sandbox MCP client that read the V1 envelope on a malformed message now gets a JSON-RPC error object, and sandbox bodies change as described. The live API is unchanged.What to change: Nothing if your client already works against the live API. An MCP client should read error.code from the JSON-RPC object, or X-Mcp-Error-Code, on a 400.
Market intel names the age of the oldest position behind top_positions
GET /api/v1/market/{condition_id}/intel and POST /api/v1/markets/intel/batch now return sharp_money.oldest_snapshot_as_of, and the same field on the deprecated smart_money alias. It is the timestamp of the oldest position behind top_positions.Before this change, the response said nothing about how current top_positions was. The list is a stored read of tracked wallets’ positions, not a live one, and tracked wallets refresh on different schedules, so a roster carried by a slowly refreshed wallet could be hours old and looked identical to one built a minute ago.
  • The value is the oldest refresh time among the positions the body actually returns, after the top-5 selection, so the whole list is at least that current and no more.
  • It is null when top_positions is empty or no returned position has a recorded refresh time.
  • It does not date net_flow_usd, buy_volume_usd, sell_volume_usd or the trade counts. Those come from large-trade event times.
Backward compatible. The field is additive and present on both sharp_money and the deprecated smart_money alias. No existing field changed.What to change: Nothing. To show or check freshness, read sharp_money.oldest_snapshot_as_of and treat null as unknown rather than current. @0xinsider/sdk types it from 0.9.0.
The sandbox aligns 12 more request checks and response shapes with the live API
The sandbox at https://0xinsider.com/sandbox now matches the live API on request checks it skipped and on response details it got wrong. The live API is unchanged.Requests:
  • A body over 1 MiB answers 413 with error.reason payload_too_large and error.param body. Before this change, the sandbox read the whole body and answered 200 or 400.
  • null is refused where the schema does not allow it: a body of null, or {"traders": null}, on POST /api/v1/traders/batch answers 400 invalid_body. An optional field the live API reads as absent, such as name on PATCH /api/v1/webhooks/{id}, still accepts null.
  • Body fields are held to their const, length, pattern, bound and format rules: jsonrpc: "1.0" on POST /api/v1/mcp, or a webhook name over 100 characters, answers 400 invalid_body naming the field.
  • Every occurrence of a repeated query name is checked, and wallet and wallet[] count together: 26 wallet values on GET /api/v1/positions answer 400 naming wallet, and sections=stats&sections=bogus answers 400. Before, only the first occurrence was checked.
  • A date-time query value is checked: from=not-a-date on GET /api/v1/whale-trades/history answers 400 invalid_query.
  • An unknown expand value is ignored, on the query and in the trader batch body, as the live API ignores it. Before, the sandbox answered 400.
  • A mkt_, wt_ or rf_ value posted to POST /api/v1/traders/batch is a per-item error with error.param address, not a synthetic trader.
Responses:
  • Ids take their live forms: pos_<wallet>:<condition_id>:<outcome_index> for positions and holders, wt_<n> for large trades, rf_<n> for radar flags, pe_<n> for position-timeline events, an opaque base64 id for replay events, and mkt_<condition_id> for every market. Before, most rows read sbx_01 and markets read a bare condition id.
  • POST /api/v1/markets/intel/batch returns the posted condition id in data.market.condition_id and, prefixed, in data.market.id. Before, every item described the same sample market.
  • Opt-in sections follow expand: a trader from POST /api/v1/traders/batch or GET /api/v1/trader/{address} carries strategy, category_strengths, category_records, category_skill_model, quant_metrics and trust only when its expansion is asked for; the market snapshot carries trust only with expand=trust; a replay event carries trade only with expand=trade.
  • GET /api/v1/webhooks/events lists each of the eight event types once, with has_more: false and no next_cursor or total. Before, it repeated one event type seven times beside total: 7. GET /api/v1/webhooks and GET /api/v1/content/search also answer one page.
  • min_grade drops lower-graded rows before the page is cut, an enumerated filter whose value a row carries (strategy, side, timeframe, severity, cohort, status) sets that value on every row, and X-Effective-Query names only the parameters the sandbox applied. Before, ?strategy=market_maker was reported as applied beside rows of every strategy.
Not backward compatible for sandbox callers. A sandbox request that sent one of the shapes above now gets the error the live API returns, sandbox ids change form, and a trader body read without expand no longer carries the opt-in sections. The live API is unchanged.What to change: Nothing if your client already works against the live API. If a sandbox call starts returning 400 or 413, read error.param and error.reason: the live API refuses the same request. Pass expand for every trader section you read, and treat sandbox ids as opaque beyond their prefix.
Games: both sides, schedules and linked Polymarket markets in one response
Two new operations return a game instead of a market. GET /api/v1/games lists what 0xinsider covers and GET /api/v1/games/{event_slug} reads one of them. Before this, building a game view meant calling markets/search, then market/{condition_id}/snapshot per market, then matching team-name strings to decide which markets belonged to the same fixture.Each game carries:
  • event_slug, the identity. It is the same key the live_sports_updated webhook pulse carries, so a receiver can read the full game straight from a pulse. game_id is the provider’s Gamma gameId, and it is absent when the canonical owner has no single value for the slug rather than guessed.
  • competitors, both sides in the provider’s own order, each with provider_id, logo, score and record. For a team league the provider lists the home side first. coverage.competitors reads provider_ids, labels or unavailable, so you never have to guess whether joining on a name is safe.
  • markets, every linked Polymarket market with its condition_id, slug, sports_market_type, outcome_yes, outcome_no and outcome_token_ids in the provider’s own index order. side says which of home, away, draw or other the YES leg pays, and draw_offered says whether the game has a draw leg at all.
  • status.state, one of scheduled, live, paused, ended, postponed, cancelled, suspended, delayed or unknown. A postponement, a cancellation and a suspension keep their own state instead of folding into “not live”, and a kickoff in the past is never read as live on its own.
  • freshness and coverage, per source. freshness.delayed applies the board half’s own servable-age bar (15 seconds live, 120 seconds upcoming), which is not the same as freshness.source_freshness.
  • series_format for esports, for example Bo3.
Filters are sport, league, status, starts_after and starts_before, with limit (1 to 100, default 20) and an opaque gms_v1_ cursor. The list is ordered by kickoff, then by event_slug, with games the provider has published no kickoff for last.A sport or status outside the published vocabulary returns an empty page, never a 400. Every page carries a top-level coverage naming the sports and leagues this deployment serves and any scope whose source was unavailable for that read, so an empty page is never ambiguous.Both operations return a weak semantic ETag; send If-None-Match for a 304. request_id, cost and as_of are excluded from the validator.The response carries no price, no sharp money split and no holder identity. Those stay on GET /api/v1/market/{condition_id}/snapshot, GET /api/v1/markets/sharp-money-flows and GET /api/v1/market/{condition_id}/holders under their own access rules.Additive. Nothing existing changed. @0xinsider/sdk 0.7.0 adds client.listGames(params) and client.getGame(eventSlug) and the Game type.What to change: Nothing. If you assemble games from markets/search and team names today, these two operations replace that walk.
SDK and CLI add keyless MCP setup, export cancellation, and safer config writes
  • @0xinsider/sdk: mcp() sends initialize, ping, tools/list and notifications/* without an API key, as POST /api/v1/mcp allows; tools/call still needs one. Before, every mcp() call without a key failed locally. mcp() also always sends jsonrpc: "2.0", even when a request passes jsonrpc: undefined, which before produced a 400.
  • @0xinsider/sdk: in a runtime without AbortSignal.any, downloadTraderExport keeps signal and downloadTimeoutMs connected until the body is read, closed or cancelled. Before, they stopped reaching the transfer once the headers arrived.
  • @0xinsider/mcp init: a config path that is a dangling symlink is written at its target and stays a link, instead of being replaced by a regular file. An edit to ~/.claude.json is merged again if Claude Code saved the file while init was running, instead of overwriting that save.
Backward compatible. Calls that worked before behave the same.What to change: Nothing. To run the MCP discovery handshake before you have a key, call client.mcp({ method: "initialize", ... }) with no key configured.
Export jobs report lifecycle details; expired downloads return 410
POST /api/v1/trader/{address}/export and GET /api/v1/trader/{address}/export/status return these new job fields:
  • terminal says whether the job can no longer change.
  • next_action tells you to poll, download, or resubmit; poll_after_s provides polling timing.
  • created_at, started_at, ready_at, failed_at, and expired_at record lifecycle times.
  • expires_at is the download deadline, 24 hours after submission.
  • data_as_of identifies the time represented by the file, matching its export_metadata.
  • attempt and max_attempts report attempt counts.
  • Once ready, artifact includes etag, compressed_size_bytes, content_type, and content_encoding.
status gains expired: a ready job past expires_at becomes expired, while a queued or reconciling job becomes failed. Failed and expired jobs remain readable for 48 hours, then return 404.The file’s export_metadata also names its job_id and expires_at.GET /api/v1/trader/{address}/export/download answers 410 once the retention window has passed, with error.code not_found and the new error.reason export_expired; submit a new export. The submit route takes fresh=true to skip reusing a finished, running or reconciling job (only a queued one is reused); the default keeps today’s reuse.Additive: every field, reason and parameter is new; status values a client does not know should be treated as terminal only when terminal is true. A client that retried a download on 404 after expiry now gets 410 and should resubmit.
Large trades use /api/v1/large-trades paths and retain whale-trade aliases
The large-trade feed now has canonical paths: GET /api/v1/large-trades, /api/v1/large-trades/{id}, /api/v1/large-trades/history, and the two counterparties reads under /api/v1/large-trades/{id}. Their rows use the LargeTrade schema.Before this change, the only spelling was whale. The /api/v1/whale-trades paths still answer the same bodies, and each response now carries Deprecation and a Link header naming its large-trades successor.
  • Responses that named a whale field carry the large spelling beside it, with the same value: top_large_trades, total_large_trades and total_large_trade_volume on reports; rep_large_trades, large_trade_count, large_trade_distinct_wallets, large_trade_total_usd, large_trade_last_at and large_trade_signal on GET /api/v1/markets/explore; large_trades in platform capabilities.
  • Webhooks and the event stream accept large_trades_inserted and LargeTradesInserted wherever you name an event. A webhook endpoint registered under large_trades_inserted receives that type; an endpoint registered under whale_trades_inserted keeps receiving the original. SSE frames keep the original WhaleTradesInserted name.
  • @0xinsider/sdk 0.6.0 adds listLargeTrades, getLargeTrade and listLargeTradeHistory. The whale methods stay and are marked deprecated.
  • @0xinsider/mcp 2.2.0 and the remote MCP server add get_large_trades, get_large_trade and get_large_trades_history; the CLI adds large-trades list. The whale tools and whales list stay as deprecated aliases.
Backward compatible. Every whale path, field, event name, SDK method and MCP tool still works with the same values.What to change: Nothing now. In new code, call the large-trades paths and read the large fields, and plan to move existing calls before a future version removes the whale spelling.
Insider Radar is now Suspicious trades, and the old routes stay live as aliases
GET /api/v1/suspicious-trades and GET /api/v1/suspicious-trades/{id} are the canonical reads for flagged Polymarket trades, with the same parameters, the same rows, the same rf_ ids, and the same cursors as before. Before this change, the only routes were GET /api/v1/insider-radar and GET /api/v1/insider-radar/{id}.
  • The two insider-radar routes stay live as deprecated aliases with no retirement date. Every response on them now carries Deprecation: @1790121600, which is 2026-09-23T00:00:00Z, and a Link header naming the canonical route with rel="successor-version" and the versioning policy with rel="deprecation". No Sunset header is sent. The alias for one id links to the canonical route for that same id.
  • The new detail read answers object: "suspicious_trade". GET /api/v1/insider-radar/{id} keeps answering object: "radar_flag", so a client that branches on the envelope is unaffected on the old path. Both list routes answer object: "list".
  • The OpenAPI component schema RadarFlag is renamed to SuspiciousTrade. Every wire field is identical.
  • GET /api/v1/webhooks/events adds suspicious_trade_flagged, the canonical spelling of insider_radar_flag_raised, which stays. They are one event under two spellings: either one subscribes, listing both on one endpoint stores one, and an endpoint receives every delivery under the spelling it registered, in the x-0xinsider-event-type header and in the payload’s type. A subscription made before today keeps receiving insider_radar_flag_raised, and the payload fields are identical.
  • GET /api/v1/stream accepts suspicious_trade_flagged in the event filter as well as insider_radar_flag_raised. The frame itself still carries type: insider_radar_flag_raised.
  • GET /api/v1/platforms adds platforms.polymarket.suspicious_trades beside insider_radar, holding the same value.
  • MCP adds the tools get_suspicious_trades and get_suspicious_trade. get_insider_radar and get_insider_radar_flag stay as deprecated aliases that call the deprecated routes, so tools/list returns 37 tools.
  • The next release of @0xinsider/sdk adds listSuspiciousTrades(), getSuspiciousTrade(), and the types SuspiciousTrade, SuspiciousTradesListParams, SuspiciousTradeFlaggedData and SuspiciousTradeFlaggedEvent. listInsiderRadar(), getInsiderRadarFlag(), InsiderRadarListParams, InsiderRadarFlagRaisedData, InsiderRadarFlagRaisedEvent and RadarFlag all remain as deprecated aliases, so no import breaks, and InsiderRadarFlagRaisedEvent keeps its own "insider_radar_flag_raised" discriminant.
Backward compatible. Everything here is added. No route, field, id, cursor, envelope or event spelling was removed or changed.What to change: Nothing. Move to the suspicious-trades paths when it suits you, because the insider-radar paths now answer with a Deprecation header.
The live_sports_updated webhook now delivers a bounded pulse for each live game
The live_sports_updated webhook event now sends deliveries. Subscribe to it on POST /api/v1/webhooks and each live game sends one pulse when its scores, status, period, live, or ended state moves. GET /api/v1/webhooks/events now lists it as active. It needs no Pro subscription.Before this change, the event was in the catalog with status dormant: you could subscribe to it and it sent nothing.Two rules bound the volume, and Live game pulses has the full contract:
  • An ordinary change waits out 20 seconds since that game’s last pulse, then goes out on the first frame after that. A game going live or final ignores the wait, and so does a game’s first pulse.
  • A game that stops sending frames stops sending pulses. The frame that ends a game is exempt from the wait, so a finished game always sends its last pulse.
  • A change inside those 20 seconds is not dropped. The next pulse lists it in changed and carries the current state, so you see every field that moved, but not every value it passed through.
  • The game clock moving on its own never sends a pulse. clock still rides every pulse.
data carries event_slug, game_id, league, version, changed, observed_at, published_at, status, period, clock, live, ended, scores, series_format, and snapshot_url. version is a counter for that game that goes up by one per pulse: deliveries are not ordered, so drop a pulse whose version you already have. snapshot_url is the game’s page, to read after a gap instead of replaying pulses.Backward compatible. What to change: Nothing, unless you want the event. A client that already subscribed to live_sports_updated starts receiving deliveries, so make sure its handler reads the payload rather than assuming an empty one. @0xinsider/sdk 0.5.1 types the payload as LiveSportsUpdatedData and parseWebhookEvent narrows it on type.
TypeScript SDK: a terminal stream error frame throws instead of arriving as an event
When GET /api/v1/stream ends with an event: error frame, @0xinsider/sdk now throws the typed error the frame carries, such as SubscriptionRequiredError for a lapsed account or InvalidApiKeyError for a revoked key. The frame is never passed to onEvent, and it does not move cursor.seq or a checkpoint. A frame with retry: false is permanent, so streamFeedResilient and consumeStreamCheckpointed do not reconnect; retry: true follows the normal reconnect path.Before this change, the SDK delivered the frame as a feed event with type: "error" and then ended the stream normally, so consumeStream resolved without an error.Backward compatible for normal frames. Feed events and resync markers are unchanged.What to change: Catch OxinsiderApiError around your stream consumer and stop on SubscriptionRequiredError or InvalidApiKeyError. Remove any handler code that checked for envelope.type === "error".
MCP: two tools now match their routes
Two MCP tools now advertise and return what their routes do.
  • The stdio server’s get_sports_edge_signals returns the route’s meta, including directional_source (degraded means the ranking fell back to raw conviction) and request_id. Before, the stdio tool dropped meta; the remote server already carried it.
  • get_leaderboard lists the ten strategy values (accumulator, algo_trader, arbitrageur, directional, event_driven, market_maker, momentum, scalper, speculator, swing_trader) on the stdio and remote servers. An unknown value is refused by the tool’s input schema; before, it passed the schema and the route answered 400 bad_request.
Backward compatible. Valid calls are unchanged; the only newly refused input is a strategy the route already rejected.What to change: Nothing. Read meta.directional_source from get_sports_edge_signals.
TypeScript calls require options for operations with path parameters or a body
In @0xinsider/sdk, call, list, text, paginatePages, paginate and collect now require their options argument when the operation has path parameters or a request body. client.call("createWebhook") and paginate(client, "listWebhookDeliveries") are compile errors.Before this change, those calls type-checked. The first reached the API with no body and was refused; the second threw Missing path parameter: id at runtime.Backward compatible at runtime. Requests are unchanged. A TypeScript project that omitted required options now gets a compile error where it previously failed at runtime.What to change: Pass path and body for operations that declare them. For an operation id chosen at runtime, type the value as ApiOperationId to use the untyped form.
TypeScript SDK: streamFeed enforces maxFrameBytes on every frame
streamFeed in @0xinsider/sdk now rejects any frame larger than maxFrameBytes (default 1 MiB), including a frame whose blank-line delimiter arrives in the same chunk. It throws StreamProtocolError with reason frame_too_large before the frame is parsed, delivered, or moves cursor.seq, and lastSeq stays the last delivered event.Before this change, the limit applied only to a frame still waiting for its delimiter, so an oversized frame that arrived complete was parsed and delivered.Backward compatible. Frames under the limit, keep-alive comments, and LF and CRLF framing behave as before.What to change: Nothing, unless you raised maxFrameBytes expecting complete frames to bypass it. Handle frame_too_large as you already do for an incomplete frame.
TypeScript SDK: error.reason carries every documented reason
In @0xinsider/sdk, error.reason on an OxinsiderApiError now holds the response’s error.reason whenever it is a documented reason, whichever error class is thrown. SubscriptionRequiredError carries subscription_inactive; RateLimitedError carries monthly_quota_exceeded, ip_rate_limited or ip_throttled; BadRequestError carries invalid_query, invalid_path, invalid_body, payload_too_large, unsupported_media_type or method_not_allowed.Before this change, 12 of the 24 documented reasons read null, so a check such as e.reason === "subscription_inactive" never matched and a lapsed key kept retrying.Backward compatible. An unrecognized reason still reads null, and the raw string stays on error.error.reason.What to change: Nothing required. Branch on e.reason to stop on subscription_inactive and to tell a monthly quota or an IP cooldown apart from a per-minute rate limit.
Market holders answers 200 with an empty roster for a market no graded wallet holds
GET /api/v1/market/{condition_id}/holders now answers 200 with holders: [] and zero totals when the complete holder scan finds no S, A or B wallet on either outcome. It answered 503 with error.reason read_model_warming and Retry-After, a retry that could never succeed for such a market.What a client must change: nothing, if it already handles an empty page; a client that retried on 503 stops retrying. Every other 503 (incomplete, unstable or failed scan) is unchanged.
Pre-game sides get canonical paths, and four canonical row field names
GET /api/v1/sports/pre-game-sides and GET /api/v1/sports/pre-game-side-observations are now the canonical paths for the two sports routes, and every row carries side, ranked_at, backing_score, and side_share.Before this change, the only paths were /api/v1/sports-edge-signals and /api/v1/sports-edge-observations, and the same four values were spelled piled_side, signal_created_at, conviction_score, and smart_score.
  • The old paths stay live as deprecated aliases. They take the same parameters, answer the same body, and now send Deprecation and a Link header naming the successor path.
  • The old field names stay in every response with the same values. Nothing is removed.
  • Response schemas are renamed: SportsEdgeSignal is now PreGameSide, and SportsEdgeObservation is now PreGameSideObservation.
  • @0xinsider/sdk 0.8.0 adds listPreGameSides, listPreGameSideObservations, and listPreGameSideObservationsConditional. The listSportsEdge* methods stay as deprecated aliases on the deprecated paths.
  • @0xinsider/mcp 2.3.0 adds the get_pre_game_sides and get_pre_game_side_observations tools. get_sports_edge_signals and get_sports_edge_observations stay as deprecated aliases.
Backward compatible. Every existing path, field, SDK method, and MCP tool keeps working.What to change: Nothing today. In new code, call the /api/v1/sports/pre-game-* paths and read side, ranked_at, backing_score, and side_share.
MCP adds list_games and get_game for the game catalog
The MCP servers add two tools: list_games, which calls GET /api/v1/games, and get_game, which calls GET /api/v1/games/{event_slug}. Both the hosted server at https://api.0xinsider.com/api/v1/mcp and @0xinsider/mcp 2.4.0 expose them, and both return the same page for the same arguments.Before this change, no tool called the game resource, so an agent on MCP had to assemble a game from search_markets, per-market snapshots and team-name matching.
  • list_games takes sport, league, status, starts_after, starts_before, limit and cursor, and returns data, has_more, next_cursor, as_of, coverage and meta. Read coverage: it names the sports and leagues served, so an empty page is never ambiguous.
  • get_game takes event_slug and returns the game object with meta beside it.
  • status is restricted to the route’s nine values, so a typo is refused by the tool schema. An unknown sport or league still returns an empty page, as the route does.
  • Neither tool returns a price, a sharp-money split or a holder identity.
Backward compatible. Existing tools are unchanged. The tool count moves from 42 to 44.What to change: Nothing. To use the new tools on the local server, install @0xinsider/mcp 2.4.0 or later.
V1 query diagnostics decode form values and reject malformed unknown names
/api/v1 now decodes X-Effective-Query with form-urlencoded rules, so + is a space, and strict validation rejects an unknown query name even when its percent escape is incomplete.Before this change, X-Effective-Query did not document the form-decoded value and strict validation could let an unknown name with an incomplete percent escape reach the handler.Backward compatible. The default still ignores unknown names and reports them in X-Query-Ignored; strict validation is opt in.What to change: Nothing for default callers. If you send X-Query-Validation: strict, handle unknown_query_parameter and use each operation’s documented query names.
@0xinsider/mcp 2.1.3: init configures Claude Code in the file Claude Code reads
npx -y @0xinsider/mcp init now writes the Claude Code server to ~/.claude.json, where Claude Code keeps user-scoped MCP servers. It also moves an entry an earlier version left in ~/.claude/settings.json.Before this change, init wrote the server to ~/.claude/settings.json, which Claude Code does not read for MCP servers. The server never appeared in claude mcp list, and a copy of your key sat in a file nothing read.
  • init writes each config through a temporary file and a rename, so a crash cannot leave ~/.claude.json half-written, and a symlinked config stays a symlink.
  • A ~/.claude/settings.json that init cannot parse is reported and left untouched.
  • 2.1.3 also ships the unpublished 2.1.1 and 2.1.2 changes: REST provenance and error details in MCP tool results, and pageable search_markets on the stdio server.
Backward compatible. Cursor, Codex, and Gemini CLI setup is unchanged.What to change: If you ran init for Claude Code before, run npx -y @0xinsider/mcp@2.1.3 init again, open a new Claude Code session, and check claude mcp list.
The sandbox answers the two Markdown routes, the export download and the MCP GET
The sandbox server answers four operations it used to refuse with 400 bad_request. A caller holding only a sandbox key from POST /api/v1/agents/register could not reach any of them, while the registration response told that caller every documented operation is answered.
  • GET /api/v1/trader/{address}/context.md and GET /api/v1/market/{condition_id}/context.md answer 200 with content-type: text/markdown. Each document is rendered from the body the sandbox serves for the JSON route beside it, so the Markdown and the JSON describe the same sandbox trader and the same sandbox market.
  • GET /api/v1/trader/{address}/export/download answers the 302 it documents. Location points at a CSV the sandbox serves itself, with the same twelve columns as a live CSV export. No object store is involved and no row is real.
  • GET /api/v1/mcp answers 405 with Allow: POST and X-Mcp-Error-Code: -32004, the same as the live API.
  • GET /api/v1/stream is still refused with 400, because a Server-Sent Events stream is a live connection rather than a body. It is now named as the one exclusion in the OpenAPI server description, in the registration response and in the TypeScript SDK.
The OpenAPI document also corrects the body documented for the 405 on GET /api/v1/mcp: it is a JSON-RPC error object, not the REST error envelope. The 401, 403 and 429 on that route are unchanged, because those come from the credential and rate-limit layers in front of the MCP transport.Backward compatible. No live API behavior changed.What to change: Nothing. If you skipped these operations while building against the sandbox, they now answer there.
Pay as you go stays available while a payment retry is in progress
API key quota admission and /api/keys/usage now treat a pay-as-you-go account in past_due status as available while payment retries continue.Before this change, both accepted only active. The account could be refused past its included quota while its metered usage was still sent for billing, and the usage response showed pay_as_you_go: false.
  • canceled and unpaid are still refused past the included quota, and they are not metered.
  • This changes behavior only. No response shape changed.
What to change: Nothing. If you display pay_as_you_go, treat true as available during a payment retry.
Report grade distributions now describe the traders in the report period
GET /api/v1/reports and the daily, weekly, and monthly routes now define report.grade_distribution as the current grade mix of the distinct Polymarket traders with a large trade in that report’s source date range.Before this change, it counted every currently ranked trader site-wide. Unrelated periods showed the same census, and a closed report froze that census.
  • Traders without a current grade are left out.
  • An entry with grade: null means the active trader’s current grade is unavailable.
Breaking for a client that treated grade_distribution as a site-wide census. The field name and shape are unchanged, but the values now describe the report period.What to change: Stop reading grade_distribution as a site-wide census. A client that wants the report period’s mix needs no request change.
potd-trader 0.3.0 adds shared stopping, atomic reservations, and a UTC daily budget
The POTD auto-trader 0.3.0 checks an authoritative stop before every submission. live off now stops new submissions from running watchers that share the folder, and waits for a submission already in progress before it acknowledges. It cannot cancel an order already sent.Before this change, live off only changed the next process’s configuration. Concurrent processes or duplicate feed rows could submit twice, and DAILY_CAP_USD used dates the feed controlled.
  • New commands: live status, run --dry-run, and watch --dry-run. The setup preview is forced dry, even with an inherited LIVE=yes.
  • The shared local JSON ledger reserves each pick and its spending under an inter-process lock before it posts.
  • DAILY_CAP_USD now counts locally recorded UTC submission times, and an unresolved earlier order keeps its reservation across midnight. The cap bounds order principal, not exchange fees.
  • A duplicate or inconsistent slate, incomplete safety data, a mismatched or expired entry_authorization, or an expired quote cannot authorize a buy.
  • The watcher follows proof_pending_picks[].retry_at.
  • DAILY_CAP_USD=0 is rejected, OXINSIDER_API_BASE must be https://api.0xinsider.com, and live trading requires an active .env control file.
  • A watcher started dry stays dry until it restarts. live on can resume one that started live.
Breaking for a setup with DAILY_CAP_USD=0, another API base, or no control file. The new CLI flags are additive, and the REST API response is unchanged.What to change: Stop every old watcher process before you upgrade, and keep the existing ledger. Set a positive cap, mount the control file for an environment-only deployment, and install the versioned release with uv sync --locked. Run with --dry-run before you restart live.
Trader exports identify their database generation and source coverage
A new file from POST /api/v1/trader/{address}/export now reads every section from one database generation before delivery.Before this change, the sections could read different database states during a refresh.
  • JSON and NDJSON add export_metadata.generation, with id, selected_at, consistency (repeatable_read), and source_watermarks for positions, native P&L, categories, and all matching trades.
  • They also carry position_generation, provider_observations, category_data_as_of, pnl_date_from, pnl_date_to, pnl_rows, pnl_row_limit, markets_rows, markets_row_limit, summary_basis, and trades_basis.
  • Provider observations keep their own clocks. One database generation does not mean every provider fact was observed at the same moment.
  • Summary counts cover all stored markets. The section limits are now explicit: markets holds at most 50,000 rows ordered by latest activity, and pnl holds at most the earliest 3,650 daily rows.
  • Provider lifetime P&L is not rebuilt from the exported trade cash flows, and a missing source observation stays unavailable.
  • CSV appends export_generation, export_selected_at, and export_consistency.
  • The downloaded object’s x-amz-meta-export-generation, x-amz-meta-export-selected-at, x-amz-meta-export-consistency, x-amz-meta-export-trades, and x-amz-meta-export-provenance headers describe the generation and the section watermarks, even for an empty CSV.
  • Preparation fails, rather than returning a partial file, when it runs past 600 seconds or 2 GiB of uncompressed output.
Backward compatible. The fields and columns are additive, and existing files stay as they are.What to change: Nothing for JSON and NDJSON, which can ignore the new metadata. CSV clients should select columns by name and allow the three new ones. Job submission, polling, and download URLs are unchanged.