> ## Documentation Index
> Fetch the complete documentation index at: https://docs.0xinsider.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> The Markdown of an API reference page leaves out the OpenAPI schema.
> Every object an endpoint returns or a webhook carries, with each field's type, presence and meaning, is on https://docs.0xinsider.com/api-reference/objects.md.
> The full OpenAPI 3.1 spec names each operation's and each webhook's exact parameters, request body and response schema: https://docs.0xinsider.com/api-reference/openapi.json.

# API changelog: September 23, 2026

> 52 API, SDK, CLI, MCP, OAuth, and feed changes from September 23, 2026, newest first, each with its effect on clients. Newer changes are on the API changelog.

These are the API changelog entries from September 23, 2026, newest first. The newest changes are on the [API changelog](https://docs.0xinsider.com/changelog.md).

**September 23, 2026**

Trader P\&L reads accept date bounds and selected sections

[`GET /api/v1/trader/{address}/pnl`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-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.

**September 23, 2026**

The forecasting axis covers nearly every graded wallet

[`GET /api/v1/trader/{address}`](https://docs.0xinsider.com/api-reference/endpoint/get-trader) 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.

**September 23, 2026**

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`](https://docs.0xinsider.com/api-reference/endpoint/get-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.

**September 23, 2026**

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`](https://docs.0xinsider.com/integrations/mcp), `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](https://docs.0xinsider.com/recipes/copy-trade.md) 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.

**September 23, 2026**

Coverage gets its own route; /platforms becomes a deprecated alias

[`GET /api/v1/coverage`](https://docs.0xinsider.com/api-reference/endpoint/get-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.

**September 23, 2026**

Market flow gets canonical paths; the intel paths become deprecated aliases

[`GET /api/v1/market/{condition_id}/flow`](https://docs.0xinsider.com/api-reference/endpoint/get-market-flow) and [`POST /api/v1/markets/flow/batch`](https://docs.0xinsider.com/api-reference/endpoint/batch-get-market-flow) 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.

**September 23, 2026**

Wallet positions offer a five-minute snapshot for repeatable pagination

[`GET /api/v1/positions`](https://docs.0xinsider.com/api-reference/endpoint/get-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.

**September 23, 2026**

MCP get\_positions adds wallet and consistency, matching the REST route

The [`get_positions`](https://docs.0xinsider.com/integrations/mcp) 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`](https://docs.0xinsider.com/api-reference/endpoint/get-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.

**September 23, 2026**

Large-trade subscriptions add individual payloads and filters

[`GET /api/v1/stream`](https://docs.0xinsider.com/api-reference/endpoint/get-stream) now emits `LargeTradeInsertedV2` with one trade's market, wallet, grade snapshot, size, side, price, and durable `event_id`. [`POST /api/v1/webhooks`](https://docs.0xinsider.com/api-reference/endpoint/create-webhook) 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.

**September 23, 2026**

Trader grade at a past decision time

[`GET /api/v1/trader/{address}/grade-at`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-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`.

**September 23, 2026**

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.

**September 23, 2026**

Game API markets now include provider moneyline prices and availability states

[`GET /api/v1/games`](https://docs.0xinsider.com/api-reference/endpoint/list-games) and [`GET /api/v1/games/{event_slug}`](https://docs.0xinsider.com/api-reference/endpoint/get-game) 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.

**September 23, 2026**

List reads add stored data\_quality

[`GET /api/v1/positions`](https://docs.0xinsider.com/api-reference/endpoint/get-positions), [`GET /api/v1/large-trades`](https://docs.0xinsider.com/api-reference/endpoint/get-large-trades), [`GET /api/v1/large-trades/history`](https://docs.0xinsider.com/api-reference/endpoint/get-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.

**September 23, 2026**

Trader data quality dates rank and open-position P\&L

[`GET /api/v1/trader/{address}`](https://docs.0xinsider.com/api-reference/endpoint/get-trader), [`POST /api/v1/traders/batch`](https://docs.0xinsider.com/api-reference/endpoint/batch-get-traders) and [`GET /api/v1/trader/{address}/context`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-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.

**September 23, 2026**

Trader reads can require a freshness ceiling

[`GET /api/v1/trader/{address}`](https://docs.0xinsider.com/api-reference/endpoint/get-trader) 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.

**September 23, 2026**

The webhook event catalog lists all 14 subscribable types

[`GET /api/v1/webhooks/events`](https://docs.0xinsider.com/api-reference/endpoint/list-webhook-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](https://docs.0xinsider.com/api-reference/endpoint/create-webhook.md) 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).

**September 23, 2026**

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`](https://docs.0xinsider.com/api-reference/endpoint/get-stream).
* [`GET /api/v1/pick-of-the-day`](https://docs.0xinsider.com/api-reference/endpoint/get-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`](https://docs.0xinsider.com/api-reference/endpoint/explore-markets) 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](https://docs.0xinsider.com/api-reference/endpoint/get-market-intel.md) and [ranked flows](https://docs.0xinsider.com/api-reference/endpoint/sharp-money-flows.md), 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.

**September 23, 2026**

Export jobs can be cancelled; the job adds cancel\_requested and cancelled

[`POST /api/v1/trader/{address}/export/cancel`](https://docs.0xinsider.com/api-reference/endpoint/cancel-trader-export) 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`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-export-status) and [`POST .../export`](https://docs.0xinsider.com/api-reference/endpoint/submit-trader-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`.

**September 23, 2026**

Trader reads always return data\_quality, and trust names the real owner of each clock

[`GET /api/v1/trader/{address}`](https://docs.0xinsider.com/api-reference/endpoint/get-trader), [`POST /api/v1/traders/batch`](https://docs.0xinsider.com/api-reference/endpoint/batch-get-traders) and [`GET /api/v1/trader/{address}/context`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-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.

**September 23, 2026**

Large trades add a market-significance filter and sort

[`GET /api/v1/large-trades`](https://docs.0xinsider.com/api-reference/endpoint/get-large-trades), [`GET /api/v1/large-trades/history`](https://docs.0xinsider.com/api-reference/endpoint/get-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`.

**September 23, 2026**

Trader and positions responses add exact decimal atoms

[`GET /api/v1/trader/{address}`](https://docs.0xinsider.com/api-reference/endpoint/get-trader) adds `pnl.exact.realized` and `stats.exact.total_volume`. [`GET /api/v1/positions`](https://docs.0xinsider.com/api-reference/endpoint/get-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.

**September 23, 2026**

The sandbox lists all 44 MCP tools and answers tools/call with a tool result

The [sandbox](https://docs.0xinsider.com/sandbox.md) 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.

**September 23, 2026**

Large trades return review\_score beside signal\_score; MCP adds market\_review

[`GET /api/v1/large-trades`](https://docs.0xinsider.com/api-reference/endpoint/get-large-trades), [`GET /api/v1/large-trades/{id}`](https://docs.0xinsider.com/api-reference/endpoint/get-large-trade), [`GET /api/v1/large-trades/history`](https://docs.0xinsider.com/api-reference/endpoint/get-large-trades-history), their `/api/v1/whale-trades` aliases, and `expand=trade` on [`GET /api/v1/events/feed/since`](https://docs.0xinsider.com/api-reference/endpoint/get-event-replay-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`.

**September 23, 2026**

Export status adds immutable artifact manifests and checksums

[`GET /api/v1/trader/{address}/export/status`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-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`](https://docs.0xinsider.com/api-reference/endpoint/download-trader-export).

* `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.

**September 23, 2026**

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`](https://docs.0xinsider.com/api-reference/endpoint/get-whale-trades), [`GET /api/v1/whale-trades/history`](https://docs.0xinsider.com/api-reference/endpoint/get-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.

**September 23, 2026**

The sandbox answers the MCP handshake the way the live API does

The [sandbox](https://docs.0xinsider.com/sandbox.md) 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.

**September 23, 2026**

Export jobs can notify their owner when ready, failed or expired

[`POST /api/v1/webhooks`](https://docs.0xinsider.com/api-reference/endpoint/create-webhook) can now subscribe to `export_job_ready`, `export_job_failed`, and `export_job_expired`; [`GET /api/v1/webhooks/events`](https://docs.0xinsider.com/api-reference/endpoint/list-webhook-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`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-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.

**September 23, 2026**

Trader profiles publish a capital-normalized forecasting axis

[`GET /api/v1/trader/{address}`](https://docs.0xinsider.com/api-reference/endpoint/get-trader) 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.

**September 23, 2026**

Sandbox responses align with MCP errors, grades, holders, batches, and webhooks

The [sandbox](https://docs.0xinsider.com/sandbox.md) 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`](https://docs.0xinsider.com/api-reference/endpoint/get-market-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`](https://docs.0xinsider.com/api-reference/endpoint/batch-get-traders): 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`.

**September 23, 2026**

Market intel names the age of the oldest position behind top\_positions

[`GET /api/v1/market/{condition_id}/intel`](https://docs.0xinsider.com/api-reference/endpoint/get-market-intel) and [`POST /api/v1/markets/intel/batch`](https://docs.0xinsider.com/api-reference/endpoint/batch-get-market-intel) 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.

**September 23, 2026**

The sandbox aligns 12 more request checks and response shapes with the live API

The [sandbox](https://docs.0xinsider.com/sandbox.md) 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`](https://docs.0xinsider.com/api-reference/endpoint/batch-get-traders) 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`](https://docs.0xinsider.com/api-reference/endpoint/batch-get-market-intel) 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.

**September 23, 2026**

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`](https://docs.0xinsider.com/api-reference/endpoint/list-games) lists what 0xinsider covers and [`GET /api/v1/games/{event_slug}`](https://docs.0xinsider.com/api-reference/endpoint/get-game) 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`](https://docs.0xinsider.com/api-reference/endpoint/get-market-snapshot), [`GET /api/v1/markets/sharp-money-flows`](https://docs.0xinsider.com/api-reference/endpoint/sharp-money-flows) and [`GET /api/v1/market/{condition_id}/holders`](https://docs.0xinsider.com/api-reference/endpoint/get-market-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.

**September 23, 2026**

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.

**September 23, 2026**

Export jobs report lifecycle details; expired downloads return 410

[`POST /api/v1/trader/{address}/export`](https://docs.0xinsider.com/api-reference/endpoint/submit-trader-export) and [`GET /api/v1/trader/{address}/export/status`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-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`](https://docs.0xinsider.com/api-reference/endpoint/download-trader-export) 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.

**September 23, 2026**

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`](https://docs.0xinsider.com/api-reference/endpoint/get-large-trades), [`/api/v1/large-trades/{id}`](https://docs.0xinsider.com/api-reference/endpoint/get-large-trade), [`/api/v1/large-trades/history`](https://docs.0xinsider.com/api-reference/endpoint/get-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.

**September 23, 2026**

Insider Radar is now Suspicious trades, and the old routes stay live as aliases

[`GET /api/v1/suspicious-trades`](https://docs.0xinsider.com/api-reference/endpoint/get-suspicious-trades) and [`GET /api/v1/suspicious-trades/{id}`](https://docs.0xinsider.com/api-reference/endpoint/get-suspicious-trade) 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}`](https://docs.0xinsider.com/api-reference/endpoint/get-insider-radar-flag) 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`](https://docs.0xinsider.com/api-reference/endpoint/list-webhook-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`](https://docs.0xinsider.com/api-reference/endpoint/get-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`](https://docs.0xinsider.com/api-reference/endpoint/get-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.

**September 23, 2026**

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`](https://docs.0xinsider.com/api-reference/endpoint/create-webhook) and each live game sends one pulse when its `scores`, `status`, `period`, `live`, or `ended` state moves. [`GET /api/v1/webhooks/events`](https://docs.0xinsider.com/api-reference/endpoint/list-webhook-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](https://docs.0xinsider.com/guides/webhooks.md#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`.

**September 23, 2026**

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"`.

**September 23, 2026**

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`.

**September 23, 2026**

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.

**September 23, 2026**

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.

**September 23, 2026**

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.

**September 23, 2026**

Market holders answers 200 with an empty roster for a market no graded wallet holds

[`GET /api/v1/market/{condition_id}/holders`](https://docs.0xinsider.com/api-reference/endpoint/get-market-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.

**September 23, 2026**

Pre-game sides get canonical paths, and four canonical row field names

[`GET /api/v1/sports/pre-game-sides`](https://docs.0xinsider.com/api-reference/endpoint/get-pre-game-sides) and [`GET /api/v1/sports/pre-game-side-observations`](https://docs.0xinsider.com/api-reference/endpoint/get-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`.

**September 23, 2026**

MCP adds list\_games and get\_game for the game catalog

The [MCP servers](https://docs.0xinsider.com/integrations/mcp.md) add two tools: `list_games`, which calls [`GET /api/v1/games`](https://docs.0xinsider.com/api-reference/endpoint/list-games), and `get_game`, which calls [`GET /api/v1/games/{event_slug}`](https://docs.0xinsider.com/api-reference/endpoint/get-game). 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.

**September 23, 2026**

V1 query diagnostics decode form values and reject malformed unknown names

[`/api/v1`](https://docs.0xinsider.com/api-reference/introduction) 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.

**September 23, 2026**

@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`.

**September 23, 2026**

The sandbox answers the two Markdown routes, the export download and the MCP GET

The [sandbox server](https://0xinsider.com/sandbox/api/v1) answers four operations it used to refuse with `400 bad_request`. A caller holding only a sandbox key from [`POST /api/v1/agents/register`](https://docs.0xinsider.com/api-reference/endpoint/register-agent) 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`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-context-markdown) and [`GET /api/v1/market/{condition_id}/context.md`](https://docs.0xinsider.com/api-reference/endpoint/get-market-context-markdown) 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`](https://docs.0xinsider.com/api-reference/endpoint/download-trader-export) 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`](https://docs.0xinsider.com/api-reference/endpoint/remote-mcp-stream) 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.

**September 23, 2026**

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.

**September 23, 2026**

Report grade distributions now describe the traders in the report period

[`GET /api/v1/reports`](https://docs.0xinsider.com/api-reference/endpoint/get-reports) and the [daily](https://docs.0xinsider.com/api-reference/endpoint/get-daily-report-snapshot.md), [weekly](https://docs.0xinsider.com/api-reference/endpoint/get-weekly-report-snapshot.md), and [monthly](https://docs.0xinsider.com/api-reference/endpoint/get-monthly-report-snapshot.md) 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.

**September 23, 2026**

potd-trader 0.3.0 adds shared stopping, atomic reservations, and a UTC daily budget

The [POTD auto-trader](https://docs.0xinsider.com/guides/auto-buy-the-pick.md) 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](https://github.com/0xinsider/potd-trader/releases/tag/v0.3.0) with `uv sync --locked`. Run with `--dry-run` before you restart live.

**September 23, 2026**

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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.