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.fromandtoare inclusive UTC dates inYYYY-MM-DDform. They limitentriesanddrawdown;statskeeps its published time windows, andmonthlyandyear_totalskeep their whole-period totals.sectionsacceptsentries,stats,monthly,year_totals, anddrawdown, using repeated or comma-separated values.sections[]is an accepted alias; an unselected section is absent fromdata.- A selected or bounded response adds
data.view, with the effective bounds, selected sections, full and window entry counts, andanchor, the last daily point beforefrom.anchorisnullwithout a lower bound or an earlier point; its unavailable values staynull. - Cumulative values keep their full-history baseline. A malformed date, reversed date range, or unknown section returns
400witherror.paramnaming the parameter. - Requests that omit these parameters keep their previous body. Each selected representation has its own
ETag.
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.statusisgraded,ungraded, orunknown. Before the first recorded observation or during an unconfirmed transition, it isunknown.available_fromis the first confirmed observation for the wallet.observation.observed_atandpublished_bybound publication, andprevious_observation_idlinks retained revisions.model_version,model_build_sha, andsource_observed_byare present when recorded. Older observations leave unproven fieldsnull.
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/integrationslists key metadata, expiry, last use, and monthly requests. It never returns a key’s secret.POST /api/keys/integrations/{integration_id}/rotateissues 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_scopeand names the needed scope. Existing default keys keep unrestricted access, and every credential uses the same account quota and subscription.
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.stateispaired,incompleteorinvalid. 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_atis the board cache vintage or the older CLOB leg clock, identified byprices.observation_source. It isnullwhen 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.
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_syncedandmarket_canonical.last_refreshed_at. - Large-trade pages group alert, trade, trader, ranking, market and volume fields.
whale_alerts.inserted_xidis a transaction identifier, so the alert group isunknown. - The field is part of the response representation and
ETag.meta.cachedandmeta.cache_age_scontinue to describe transport cache state.
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_rankdates the stored global rank with the completion time of the latest refresh that finished for every candidate.positionsdates 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.
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 assmart_money_flow_detected, which stays subscribable. An endpoint that registered the old name keeps receivingtype: smart_money_flow_detected; a new endpoint should register the new one. Either spelling also works in the?event=filter onGET /api/v1/stream. GET /api/v1/pick-of-the-dayreturnssharp_wallet_countandsharp_usdbesidesmart_wallet_countandsmart_usd, with the same values.GET /api/v1/markets/explorereturnsscore_components.sharp_money_signalbesidescore_components.smart_money_signal, with the same value.- The OpenAPI spec marks
smart_moneyon market flow and ranked flows, and the four fields above,deprecated. @0xinsider/mcp2.6.0 listsget_sharp_money_flowsfirst and keepsget_smart_money_flowsas its deprecated alias, still calling the deprecated/api/v1/markets/smart-money-flowspath.get_market_intel,get_pick_of_the_dayand themarket_reportprompt say “sharp money”. No tool was added or removed.@0xinsider/sdk0.11.0 addslistSharpMoneyFlows, theSharpMoneyFlowDetectedEventenvelope andSharpMoneyFlowDetectedData.listSmartMoneyFlows,SmartMoneyFlowDetectedEventandSmartMoneyFlowDetectedDatastay 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
statusvaluescancel_requested(not terminal,next_actionpoll) andcancelled(terminal,next_actionresubmit), and the nullable timestampscancel_requested_atandcancelled_at.GET .../export/statusandPOST .../exportreturn them too. - A job whose file is already being published, or that is already
ready,reconcile_required,failed,expired, orcancelled, comes back unchanged with200. 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_cancelledwithjob_id,status,format, andnext_action. @0xinsider/sdk0.11.0 addscancelTraderExport(address, jobId), retries it on a network error or a5xxbecause 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.
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.statusisfresh,partial,unknown,untrackedorunavailable. It readsfreshonly when every group does,unavailableonly when every group does, andpartialotherwise.data_quality.as_ofis the oldest clock the body rests on. It is omitted when no group has one.data_quality.field_groupsholds one entry per group, each withgroup,owner(the table and column that writes it),status, anas_ofwhere one exists, and areasonwhere the status is notfresh.- 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) andpositions(trader_markets:pnl.unrealized). leaderboard_rankandpositionsalways readunknownrather 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.cachedandmeta.cache_age_sare still the only fields that describe the response itself. - Under
expand=trust,grade,score,streak_tier,forecast_scoreandforecast_evidencenow nametrader_rankingsas theirsource.ownerand carry itsfreshness.as_of; they named0xinsider_trader_read_modeland reportedunknown.total_volumenow carries the volume observation time.ranknow namestraders.leaderboard_rankand keeps freshnessunknown. @0xinsider/sdk0.11.0 typesDataQualityandDataQualityGroupand addsdata_qualitytoTrader.
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_shareis a fraction, not a percent:0.01is one percent of the market’s traded volume. Valid range is0to1; anything else answers400withparam: "min_market_volume_share". A non-zero value never returns a trade whosemarket_volume_shareis absent, because an unavailable share cannot be said to clear a floor.sorttakesrecent(the default, and the order every caller got before this) ormarket_volume_share, which ranks biggest-share first and puts a trade with no share last. An unrecognized value answers400withparam: "sort".sort=market_volume_sharereads a bounded window: the last 30 days on the list route, andfrom(or the last 30 days whenfromis omitted) on/history. The share is computed for each request against amarketsvolume observation, so an unbounded ranking cannot be served inside the API latency budget.- A
next_cursoris bound to the order it was minted in. A cursor fromsort=recentis refused bysort=market_volume_shareand the other way round, with400andparam: "cursor", so a walk cannot silently cross rankings and skip or repeat rows. @0xinsider/sdktypes both parameters onlistLargeTrades,listLargeTradesHistoryand the two deprecated whale-trade operations.
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) andstructuredContent(the same data as an object), withisError: 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 carriesdata,has_more,next_cursorand the route’s page fields, a single-object tool carries the object’s own fields, and both carrymeta. - Arguments are checked against the
inputSchemathe 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 answersisError: true, with the text naming each field by its JSON Pointer insideargumentsandstructuredContent.errorcarryingstatus,codeandparam. - An unknown tool name answers JSON-RPC error
-32601, and atools/callwith nonameanswers-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,
codeandreason, andstructuredContent.errorcarriescode,reason,param,doc_url,retry_atandrequest_idas the live server sends them.
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_scoreandrecorded_signal_scorestay in every response with unchanged values. The OpenAPI spec marks themdeprecatedand names the new keys.@0xinsider/sdk0.10.0 typesreview_scoreandrecorded_review_scoreonLargeTrade, and tags every property the spec marks deprecated with@deprecated.@0xinsider/mcp2.5.0 adds themarket_reviewprompt: “Review a market: profitable wallets’ side, large trades, suspicious trades”, with optionalmarketandcategoryarguments.trading_signalsstays registered as its deprecated alias with the same arguments and messages. Themarket_reportprompt now ends in a “Review summary”. Prompts ship in the stdio package only; the remote MCP endpoint serves tools.
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_sha256covers the decompressed bytes a client reads.compressed_sha256covers the stored gzip bytes. The multipartetagis not a checksum.- Historical artifacts can return
manifest: nullbecause they were written before this contract. @0xinsider/sdkcan read the status first and verify the streamed decompressed file withdownloadTraderExport(..., { verifyChecksum: true }). A mismatch throwsExportIntegrityError.
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_sharefield on a trade, which is computed at read time against a volume observation that post-dates the fill and answers a different question.
/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.protocolVersionis negotiated the way the live server negotiates it: a client that asks for2025-11-25,2025-06-18,2025-03-26or2024-11-05gets that revision back, and any other value, or none, gets2025-11-25.Mcp-Session-Idechoes the session the request claimed, ora0b1c2d3e4f5a6b7c8d9eafbecfda0b1when it claimed none. The sandbox stores no session, and neither does the live API.- A
MCP-Protocol-Versionrequest header naming a revision outside those four now answers400with JSON-RPC error-32600andX-Mcp-Error-Code, onPOSTandGET, before the body is read. The sandbox served the request before. - The sandbox preflight now allows the
Mcp-Session-IdandMCP-Protocol-Versionrequest headers and exposesMcp-Session-Id, so a browser client can complete the handshake. ping,tools/listandtools/callstill echo the requestid, and carryMcp-Session-Idwhen the request sent one.
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_readycarriesjob_id,status: "ready",format,next_action: "download",total_trades,processed_trades,file_size, anddata_as_of.export_job_failedcarriesjob_id,status: "failed",format,next_action: "resubmit", andfailure_reason.export_job_expiredcarriesjob_id,status: "expired",format, andnext_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.
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/mcpanswers in the MCP transport’s own shapes. A malformed message (unparseable JSON, not an object, a badid, a missing or non-stringjsonrpcormethod,jsonrpcother than2.0, a notification with anid, a body over 1 MiB) is400with a JSON-RPC error object andX-Mcp-Error-Code(-32700or-32600). An unknown method with anidis200with a-32601error. An id-lessping,notifications/initializedornotifications/cancelledis202with no body.Content-Typeis not checked, and a response’sidechoes the request’s. Before this change, the sandbox refused these with the V1 error envelope (invalid_body,415,413) and answered id-less notifications with200.- Grade fields carry each sample wallet’s own grade (
S,A,A,B,B,C,Dacross the seven rows), somin_gradedrops rows the way it does live:GET /api/v1/positions?min_grade=Areturns 3 rows. Before, every enumerated grade readSandmin_gradedropped nothing. GET /api/v1/market/{condition_id}/holderslists only S, A and B holders whenmin_gradeis 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 nousername, and any other username gets its own address. Before, a posted address carried another sandbox wallet’s username."expand": nullnow gets400 invalid_bodywitherror.paramexpand, as it does live.GET /api/v1/market/{condition_id}/snapshot,/intel,/candles,/holdersand/context.mddescribe thecondition_idin the path (mkt_prefix stripped), inmarket.condition_id,market.id,data.condition_idand each holder’spos_id. Before, they described the documented sample market whatever id you asked for.GET /api/v1/webhooks/eventsgives all eight event types the description,payload_shapeandstatusthe live API serves. Before, seven read “Sandbox example value.” and “sandbox”.
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
nullwhentop_positionsis empty or no returned position has a recorded refresh time. - It does not date
net_flow_usd,buy_volume_usd,sell_volume_usdor the trade counts. Those come from large-trade event times.
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
413witherror.reasonpayload_too_largeanderror.parambody. Before this change, the sandbox read the whole body and answered200or400. nullis refused where the schema does not allow it: a body ofnull, or{"traders": null}, onPOST /api/v1/traders/batchanswers400 invalid_body. An optional field the live API reads as absent, such asnameonPATCH /api/v1/webhooks/{id}, still acceptsnull.- Body fields are held to their
const, length, pattern, bound andformatrules:jsonrpc: "1.0"onPOST /api/v1/mcp, or a webhooknameover 100 characters, answers400 invalid_bodynaming the field. - Every occurrence of a repeated query name is checked, and
walletandwallet[]count together: 26walletvalues onGET /api/v1/positionsanswer400namingwallet, andsections=stats§ions=bogusanswers400. Before, only the first occurrence was checked. - A date-time query value is checked:
from=not-a-dateonGET /api/v1/whale-trades/historyanswers400 invalid_query. - An unknown
expandvalue is ignored, on the query and in the trader batch body, as the live API ignores it. Before, the sandbox answered400. - A
mkt_,wt_orrf_value posted toPOST /api/v1/traders/batchis a per-item error witherror.paramaddress, not a synthetic trader.
- 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, andmkt_<condition_id>for every market. Before, most rows readsbx_01and markets read a bare condition id. POST /api/v1/markets/intel/batchreturns the posted condition id indata.market.condition_idand, prefixed, indata.market.id. Before, every item described the same sample market.- Opt-in sections follow
expand: a trader fromPOST /api/v1/traders/batchorGET /api/v1/trader/{address}carriesstrategy,category_strengths,category_records,category_skill_model,quant_metricsandtrustonly when its expansion is asked for; the market snapshot carriestrustonly withexpand=trust; a replay event carriestradeonly withexpand=trade. GET /api/v1/webhooks/eventslists each of the eight event types once, withhas_more: falseand nonext_cursorortotal. Before, it repeated one event type seven times besidetotal: 7.GET /api/v1/webhooksandGET /api/v1/content/searchalso answer one page.min_gradedrops 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, andX-Effective-Querynames only the parameters the sandbox applied. Before,?strategy=market_makerwas reported as applied beside rows of every strategy.
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 thelive_sports_updatedwebhook pulse carries, so a receiver can read the full game straight from a pulse.game_idis the provider’s GammagameId, 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 withprovider_id,logo,scoreandrecord. For a team league the provider lists the home side first.coverage.competitorsreadsprovider_ids,labelsorunavailable, so you never have to guess whether joining on a name is safe.markets, every linked Polymarket market with itscondition_id,slug,sports_market_type,outcome_yes,outcome_noandoutcome_token_idsin the provider’s own index order.sidesays which ofhome,away,draworotherthe YES leg pays, anddraw_offeredsays whether the game has a draw leg at all.status.state, one ofscheduled,live,paused,ended,postponed,cancelled,suspended,delayedorunknown. 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.freshnessandcoverage, per source.freshness.delayedapplies the board half’s own servable-age bar (15 seconds live, 120 seconds upcoming), which is not the same asfreshness.source_freshness.series_formatfor esports, for exampleBo3.
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()sendsinitialize,ping,tools/listandnotifications/*without an API key, asPOST /api/v1/mcpallows;tools/callstill needs one. Before, everymcp()call without a key failed locally.mcp()also always sendsjsonrpc: "2.0", even when a request passesjsonrpc: undefined, which before produced a400.@0xinsider/sdk: in a runtime withoutAbortSignal.any,downloadTraderExportkeepssignalanddownloadTimeoutMsconnected 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.jsonis merged again if Claude Code saved the file whileinitwas running, instead of overwriting that save.
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:terminalsays whether the job can no longer change.next_actiontells you topoll,download, orresubmit;poll_after_sprovides polling timing.created_at,started_at,ready_at,failed_at, andexpired_atrecord lifecycle times.expires_atis the download deadline, 24 hours after submission.data_as_ofidentifies the time represented by the file, matching itsexport_metadata.attemptandmax_attemptsreport attempt counts.- Once ready,
artifactincludesetag,compressed_size_bytes,content_type, andcontent_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
largespelling beside it, with the same value:top_large_trades,total_large_tradesandtotal_large_trade_volumeon reports;rep_large_trades,large_trade_count,large_trade_distinct_wallets,large_trade_total_usd,large_trade_last_atandlarge_trade_signalonGET /api/v1/markets/explore;large_tradesin platform capabilities. - Webhooks and the event stream accept
large_trades_insertedandLargeTradesInsertedwherever you name an event. A webhook endpoint registered underlarge_trades_insertedreceives thattype; an endpoint registered underwhale_trades_insertedkeeps receiving the original. SSE frames keep the originalWhaleTradesInsertedname. @0xinsider/sdk0.6.0 addslistLargeTrades,getLargeTradeandlistLargeTradeHistory. The whale methods stay and are marked deprecated.@0xinsider/mcp2.2.0 and the remote MCP server addget_large_trades,get_large_tradeandget_large_trades_history; the CLI addslarge-trades list. The whale tools andwhales liststay as deprecated aliases.
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-radarroutes stay live as deprecated aliases with no retirement date. Every response on them now carriesDeprecation: @1790121600, which is 2026-09-23T00:00:00Z, and aLinkheader naming the canonical route withrel="successor-version"and the versioning policy withrel="deprecation". NoSunsetheader 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 answeringobject: "radar_flag", so a client that branches on the envelope is unaffected on the old path. Both list routes answerobject: "list". - The OpenAPI component schema
RadarFlagis renamed toSuspiciousTrade. Every wire field is identical. GET /api/v1/webhooks/eventsaddssuspicious_trade_flagged, the canonical spelling ofinsider_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 thex-0xinsider-event-typeheader and in the payload’stype. A subscription made before today keeps receivinginsider_radar_flag_raised, and the payload fields are identical.GET /api/v1/streamacceptssuspicious_trade_flaggedin theeventfilter as well asinsider_radar_flag_raised. The frame itself still carriestype: insider_radar_flag_raised.GET /api/v1/platformsaddsplatforms.polymarket.suspicious_tradesbesideinsider_radar, holding the same value.- MCP adds the tools
get_suspicious_tradesandget_suspicious_trade.get_insider_radarandget_insider_radar_flagstay as deprecated aliases that call the deprecated routes, sotools/listreturns 37 tools. - The next release of
@0xinsider/sdkaddslistSuspiciousTrades(),getSuspiciousTrade(), and the typesSuspiciousTrade,SuspiciousTradesListParams,SuspiciousTradeFlaggedDataandSuspiciousTradeFlaggedEvent.listInsiderRadar(),getInsiderRadarFlag(),InsiderRadarListParams,InsiderRadarFlagRaisedData,InsiderRadarFlagRaisedEventandRadarFlagall remain as deprecated aliases, so no import breaks, andInsiderRadarFlagRaisedEventkeeps its own"insider_radar_flag_raised"discriminant.
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
changedand 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.
clockstill 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_signalsreturns the route’smeta, includingdirectional_source(degradedmeans the ranking fell back to raw conviction) andrequest_id. Before, the stdio tool droppedmeta; the remote server already carried it. get_leaderboardlists the tenstrategyvalues (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 answered400 bad_request.
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
Deprecationand aLinkheader naming the successor path. - The old field names stay in every response with the same values. Nothing is removed.
- Response schemas are renamed:
SportsEdgeSignalis nowPreGameSide, andSportsEdgeObservationis nowPreGameSideObservation. @0xinsider/sdk0.8.0 addslistPreGameSides,listPreGameSideObservations, andlistPreGameSideObservationsConditional. ThelistSportsEdge*methods stay as deprecated aliases on the deprecated paths.@0xinsider/mcp2.3.0 adds theget_pre_game_sidesandget_pre_game_side_observationstools.get_sports_edge_signalsandget_sports_edge_observationsstay as deprecated aliases.
/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_gamestakessport,league,status,starts_after,starts_before,limitandcursor, and returnsdata,has_more,next_cursor,as_of,coverageandmeta. Readcoverage: it names the sports and leagues served, so an empty page is never ambiguous.get_gametakesevent_slugand returns the game object withmetabeside it.statusis restricted to the route’s nine values, so a typo is refused by the tool schema. An unknownsportorleaguestill returns an empty page, as the route does.- Neither tool returns a price, a sharp-money split or a holder identity.
@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.initwrites each config through a temporary file and a rename, so a crash cannot leave~/.claude.jsonhalf-written, and a symlinked config stays a symlink.- A
~/.claude/settings.jsonthatinitcannot 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_marketson the stdio server.
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.mdandGET /api/v1/market/{condition_id}/context.mdanswer200withcontent-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/downloadanswers the302it documents.Locationpoints 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/mcpanswers405withAllow: POSTandX-Mcp-Error-Code: -32004, the same as the live API.GET /api/v1/streamis still refused with400, 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.
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.canceledandunpaidare still refused past the included quota, and they are not metered.- This changes behavior only. No response shape changed.
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: nullmeans the active trader’s current grade is unavailable.
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, andwatch --dry-run. The setup preview is forced dry, even with an inheritedLIVE=yes. - The shared local JSON ledger reserves each pick and its spending under an inter-process lock before it posts.
DAILY_CAP_USDnow 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=0is rejected,OXINSIDER_API_BASEmust behttps://api.0xinsider.com, and live trading requires an active.envcontrol file.- A watcher started dry stays dry until it restarts.
live oncan resume one that started live.
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, withid,selected_at,consistency(repeatable_read), andsource_watermarksfor 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, andtrades_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:
marketsholds at most 50,000 rows ordered by latest activity, andpnlholds 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, andexport_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, andx-amz-meta-export-provenanceheaders 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.