> ## 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 24 to 28, 2026

> 45 API, SDK, CLI, MCP, OAuth, and feed changes from September 24 to 28, 2026, each with its effect on clients. Newer changes are on the API changelog.

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

**September 28, 2026**

Expanded trader categories keep live source freshness during historical fill imports.

[`GET /api/v1/trader/{address}`](https://docs.0xinsider.com/api-reference/endpoint/get-trader) with `expand=categories` now judges category-source freshness from the newest Polymarket block received.

Before this change, a historical fill arriving after a live fill could replace the current source clock with an older block time. That could make `category_records` and `category_skill_model` report `degraded` while live fills were still arriving.

`source_last_success_at` reports the provider block time used for source freshness. Stale, future-dated, or unavailable source clocks still produce `degraded` records with withheld scores. Other readiness checks continue to apply.

**Backward compatible.** Fields and response formats are unchanged.

**What to change:** Nothing.

**September 28, 2026**

Wallet position feed updates survive interrupted refreshes

The wallet position feed on `/api/ws` now publishes each committed group of row updates while a refresh is running. A later refresh failure retains those updates.

Before this change, a large refresh could lose all pending updates when its write transaction exceeded its deadline. Position events were sent only after the entire refresh committed.

A position event describes the available facts for that row. It does not prove that every position in the wallet has finished refreshing. Missing provider values remain unavailable.

**Backward compatible.** Event fields and authentication are unchanged.

**What to change:** Nothing for clients that apply each position event independently. If you display a wallet-wide total, reread its endpoint rather than treating one position event as a complete wallet snapshot.

**September 28, 2026**

Trending wallet responses limit cache freshness to 120 seconds.

[GET /api/v1/leaderboard/trending](https://docs.0xinsider.com/api-reference/endpoint/list-trending-wallets.md) now limits HTTP cache freshness to 120 seconds from the ranking's original computation. Any `stale-while-revalidate` allowance ends before the retained ranking expires. A response whose age is unknown grants no cache window.

Before this change, a response could advertise 300 seconds of freshness even though a server request started a refresh after 120 seconds. A ranking can still be up to 24 hours old after a quiet period. Successful responses keep `meta.cached: true`; `meta.cache_age_s` reports the original age when known and is omitted when unknown.

The same cache policy applies to `200` and `304` responses. Response fields, ranking values, pagination cursors, and cold-cache errors are unchanged.

**Backward compatible.**

**What to change:** Respect `Cache-Control` and use `meta.cache_age_s` to decide whether an older ranking meets your needs. If you override the server's cache policy with a fixed 300-second window, remove that override.

**September 28, 2026**

Trending wallets keep the previous board when a refresh fails.

[GET /api/v1/leaderboard/trending](https://docs.0xinsider.com/api-reference/endpoint/list-trending-wallets.md) now restores the previous cached board after confirmed refresh failure handling, and waits 120 seconds before retrying. The retained board keeps its original age and expiry.

Before this change, a failed web cache update could remove the retained board, and repeated requests could immediately restart a failed refresh. The response fields, normal refresh timing, and cold-cache `503` response are unchanged. Redis coordination failures can prevent confirmation of restoration and the cooldown; these failures are reported in server logs.

**Backward compatible.**

**What to change:** Nothing. Continue using the response's freshness information and handling `503` while a board is unavailable.

**September 28, 2026**

Sports edge observations refresh market identity before category selection.

[GET /api/v1/sports/pre-game-side-observations](https://docs.0xinsider.com/api-reference/endpoint/get-pre-game-side-observations.md), and its deprecated alias `GET /api/v1/sports-edge-observations`, now refresh a candidate market's category, title, event identity, outcomes, and token IDs before selecting the requested category. A market moved into that category can appear on the next computation.

Before this change, those fields could remain stale for up to 10 minutes while the stored market list was reused. A candidate whose current market or Polymarket identity is missing is now omitted instead of keeping its old identity.

**Backward compatible.** The fields and response format are unchanged.

**What to change:** Nothing.

**September 28, 2026**

Expanded trader category ranks and totals use the same publication.

[GET /api/v1/trader/{address}](https://docs.0xinsider.com/api-reference/endpoint/get-trader.md) with `expand=categories` now returns `category_strengths` whose `rank` and `total_in_category` use the same hourly publication. A category absent from that publication is omitted until a later publication includes it.

Before this change, `total_in_category` could use a newer population than `rank`, and a newly added category could contain a null `rank`. Each trader's current performance values still update separately, so these fields do not describe a frozen historical record.

**Backward compatible.** The fields and response format are unchanged.

**What to change:** Nothing.

**September 28, 2026**

Category-filtered positions pages keep consistent membership during updates.

[GET /api/v1/positions](https://docs.0xinsider.com/api-reference/endpoint/get-positions.md) now keeps one consistent view of category membership while building a category-filtered page.

Before this change, a position whose category changed during the read could be missing from the page.

**Backward compatible.** The response and cursor formats are unchanged.

**What to change:** Nothing.

**September 27, 2026**

Large trades take since, so a poll returns only trades recorded after one you hold

[Large trades](https://docs.0xinsider.com/api-reference/endpoint/get-large-trades.md) (`GET /api/v1/large-trades`, and its deprecated alias `GET /api/v1/whale-trades`) take a `since` query parameter: the `id` of a trade the API returned. The response keeps its shape and newest-first order, holds only the trades recorded after that one, and pages with `cursor`.

Before this change, a client that polled for new trades downloaded a full page on every poll. `since` follows the order trades were recorded in, so a trade recorded late with an earlier `traded_at` still comes back, and a trade still being written comes back on a later poll.

* `since` works with `sort=recent` only.
* `400` with `error.param` set to `since`: an `id` that does not parse or names no trade, `since` with `sort=market_volume_share`, or an `id` more than 10,000 trades behind the newest. Poll once without `since` and continue from its first trade.
* The remote MCP tools `get_large_trades` and `get_whale_trades` take `since`, and `@0xinsider/mcp` 2.12.0 brings it to the stdio server. The Node.js SDK types `since` and adds `listLargeTradesConditional()`, which returns a typed `304`.

**Backward compatible.** A request without `since` answers exactly as before.

**What to change:** Nothing. To poll, send `since` set to the first trade of your last response that had trades, and `If-None-Match` set to its `ETag`: a poll with nothing new is `304 Not Modified` with an empty body.

**September 27, 2026**

A cold sports-edge observations process answers a replica hiccup with 503, not 500

[`GET /api/v1/sports/pre-game-side-observations`](https://docs.0xinsider.com/api-reference/endpoint/get-pre-game-side-observations) and its deprecated alias `GET /api/v1/sports-edge-observations` now answer `503 read_model_warming` (with `Retry-After`) when a process with no cached observation universe hits a replica recovery conflict, the replica's own statement timeout, a busy connection pool, or a replica marked down while refreshing that universe.

Before this change, the same replica-side conditions on a cold process answered `500 internal_error`. A universe refresh failure on a process that already holds a usable universe is unaffected: it keeps serving the last good universe. Any other observation-universe SQL error, or an invalid compact result, still answers `500 internal_error`.

**Backward compatible.** This narrows one existing failure case from `500` to the route's existing `503 read_model_warming` retry contract; no field, parameter, or other error mapping changed.

**What to change:** If you branch on `error.reason` for this route, treat a cold-process replica hiccup as `read_model_warming` (retryable per `Retry-After`) rather than `internal_error`.

**September 27, 2026**

Trending wallets reports ranking age and serves the previous ranking during refresh

[`GET /api/v1/leaderboard/trending`](https://docs.0xinsider.com/api-reference/endpoint/list-trending-wallets) now builds its ranking when it is requested, not on a schedule, and reports how old the ranking is. `meta.cached` is `true` and `meta.cache_age_s` is the ranking's age in seconds.

Before this change the default 7-day ranking was rebuilt every 2 minutes whether or not anyone read it, so it was never more than 5 minutes old, and `meta` reported `cached: false` with no `cache_age_s`. Now:

* A request that finds a ranking at least 2 minutes old gets it at once and starts one rebuild. A ranking read at least every 2 minutes stays about as current as before.
* The first request after a quiet period can get a ranking up to 24 hours old; its `meta.cache_age_s` says so. The next request after the rebuild, usually within 30 seconds, gets the new ranking.
* Just after midnight UTC, a request can get the previous day's ranking, with its own `start_date` and `end_date`.
* `503` with `error.reason` `read_model_warming` now happens only when no ranking was built in the last 24 hours.

**Backward compatible.** `meta.cached` and `meta.cache_age_s` were already part of the response envelope, and the `503` was already documented.

**What to change:** If you need a ranking newer than a few minutes, read `meta.cache_age_s` and request again after about 30 seconds when it is higher than you accept.

**September 27, 2026**

OAuth responses include server processing duration in Server-Timing

[OAuth authorization and discovery responses](https://docs.0xinsider.com/authentication.md) now append `api;dur=<milliseconds>` to `Server-Timing`. This measures server work until the response is ready, including protocol checks and response construction.

Before this change, OAuth routes outside `/api` did not provide this processing duration. The measurement excludes network transit and body transfer; a cached discovery response carries the timing of the original response.

**Backward compatible.** This adds a header measurement and does not change OAuth payloads, authorization, or caching.

**What to change:** Nothing.

**September 26, 2026**

Four-decimal price values preserve small provider division residues

`avg_price` on [`GET /api/v1/positions`](https://docs.0xinsider.com/api-reference/endpoint/get-positions) and other price fields served with four-decimal precision now preserve the four-decimal step when their source value is within `0.0000002` of it. For example, `0.4899999968` displays as `0.49` instead of `0.4899`.

Before this change, provider division residue within that range could lower the last displayed digit. Values outside the tolerance still truncate: `0.003499` becomes `0.0034`.

A snapped display value can be up to `0.0000002` above its source. Use `exact.avg_price` when you need decimal calculations from the source precision.

**Backward compatible.** Field names, types, and response shapes are unchanged. Money truncation is unchanged.

**What to change:** Nothing. Continue using exact decimal fields for calculations and display numbers for presentation.

**September 26, 2026**

Web API mirrors expose origin response timing and a web request ID

Requests through the [web API mirror](https://0xinsider.com/api/v1/health) now append `web_api;dur=<milliseconds>` to `Server-Timing` and return `X-Web-Request-Id` for the web origin execution. Before this change, the mirror could forward backend timing without reporting its own authentication gates, upstream waiting, retries, and response construction.

The existing `api` timing stays present. The web duration includes upstream waiting, so adding the two durations double-counts that work.

The new clock excludes platform routing, cold module initialization, CDN delivery, and later stream or file transfer. On a cache hit, the headers describe the earlier origin fill; static Markdown API routes do not get a request clock. This adds observations, not a latency guarantee or a new timeout.

**Backward compatible.** Response bodies, authorization, and cache policies are unchanged.

**What to change:** Nothing. Read `web_api` separately from `api` when diagnosing requests through `0xinsider.com`; use `X-Web-Request-Id` to correlate that origin execution.

**September 26, 2026**

The bare 0xinsider npm package installs the product CLI 2.x

[`0xinsider@2.0.0`](https://www.npmjs.com/package/0xinsider) installs the product CLI from `@0xinsider/mcp@^2.10.1`, including browser login, account identity, market and trader reads, bounded pagination, and JSON output. Install it with `npm install --global 0xinsider@2`, or run `npx --yes 0xinsider@2 --help`.

Before this change, `0xinsider@1.0.0` selected the older MCP-only runtime through `@0xinsider/mcp@^1.2.2`. Publishing a scoped 2.x version did not upgrade users of the bare package.

**Breaking** for Node.js 18 and 20 users: version 2 requires Node.js 22 or newer. Existing MCP startup, `serve`, and `init` remain available; the scoped package also retains `0xinsider-mcp`. `--version` reports the installed runtime version.

**What to change:** Upgrade Node.js and reinstall `0xinsider@2`, or keep `0xinsider@1` for the older MCP runtime. If you already installed `@0xinsider/mcp` globally, keep that package or uninstall it before switching, because both packages own the `0xinsider` command. See the [CLI guide](https://docs.0xinsider.com/integrations/cli.md).

**September 26, 2026**

MCP schemas document suspicion thresholds, watch severity, and accepted ID formats

The remote MCP endpoint at `https://api.0xinsider.com/api/v1/mcp` and `@0xinsider/mcp` 2.11.4 now state four things in the `inputSchema` an agent reads from `tools/list`, not only in the tool description:

* `min_suspicion` on `get_suspicious_trades` and the deprecated `get_insider_radar` reads "Minimum suspicion score (0-100); the live floor of 60 also applies". A score under 60 is never returned, so `min_suspicion=10` and `min_suspicion=60` answer the same rows.
* `severity` on the same two tools reads `"flag" selects live rows; "watch" returns none because no live watch policy exists`. `severity=watch` is accepted and answers an empty page, not an error.
* `id` on `get_large_trade` and `get_whale_trade` reads "Large trade ID such as wt\_123 or 123", and on `get_suspicious_trade` and `get_insider_radar_flag` "Suspicious trade ID such as rf\_123 or 123". Each used to read "Entity ID returned by the matching list endpoint", so a client that renders only the schema never saw which prefix the tool takes.
* `limit` on `list_games` reads "Max results (1-100, default 20)". It had no description.

Before this change the remote served the first two as "Minimum suspicion score (0-100)" and "Filter by severity level", so a caller could read the 0 lower bound as reachable and `watch` as a filter that selects something.

**Backward compatible.** Only advertised text changed: no tool, argument, default, bound or response moved, and both transports still serve 47 tools. The floor and the empty `watch` page are the behavior `GET /api/v1/suspicious-trades` and `GET /api/v1/insider-radar` have always had, and [the OpenAPI document](https://0xinsider.com/api/v1/openapi.json) already stated the floor for the same REST parameter.

**What to change:** Nothing. If you set `min_suspicion` below 60 expecting lower-scored rows, or treat `severity=watch` as a source of rows, read the floor instead of the bound.

**September 26, 2026**

The sandbox's events/feed/since meta describes each page

[`GET /api/v1/events/feed/since`](https://docs.0xinsider.com/api-reference/endpoint/get-event-replay-since) on the sandbox at `https://0xinsider.com/sandbox` now builds `meta` from the page it returns, as the live API does. `meta.replay.to_cursor` equals `next_cursor` on every page, and `meta.completeness.status` is `caught_up` only on a page with no events.

Before this change, every sandbox page carried the documented example of a caught-up page:

* `meta.replay.from_cursor` and `meta.replay.to_cursor` read `ZWYyXzBfMA`, a cursor the sandbox does not accept, beside `next_cursor: "sbx_7"`.
* `meta.replay.from_sequence`, `meta.replay.to_sequence`, and `meta.retention.retained_events` read `0` on pages that carried events.
* `meta.completeness.status` read `caught_up` on pages that carried events.
* `meta.replay.expand` read `[]` on a request with `expand=trade`.

Now `from_cursor` is the cursor you sent, or `sbx_0` when you sent none, and `from_sequence` is the `sequence` of the event that cursor points at. `to_sequence` is the `sequence` of the page's last event, `retained_events` counts the page's events, and a page with events reads `complete`. The live API is unchanged.

**Backward compatible.**

**What to change:** Nothing. A client that reads `meta.replay.to_cursor` or `meta.completeness.status` to decide when to stop polling now gets the same answer from the sandbox as from the live API.

**September 26, 2026**

Unmatched trader lookups return 404 instead of echoing the input as an address

[`GET /api/v1/trader/{address}`](https://docs.0xinsider.com/api-reference/endpoint/get-trader), [`GET /api/v1/trader/{address}/context`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-context) and [`GET /api/v1/trader/{address}/context.md`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-context-markdown) now answer `404` with `error.code` `not_found` and `error.param` `address` when the path value is not a wallet address and matches no username or trader ID the API knows. The `.md` route answers the same JSON error envelope, not a Markdown document.

Before this change, those routes answered `200` with a trader whose `address` was the value you sent, whose `id` was `trd_` plus that value, and whose `sync_status` was `unknown`. For example, `GET /api/v1/trader/definitely-not-a-real-username-zzz9` returned `"address": "definitely-not-a-real-username-zzz9"`, which is not a wallet. [`GET /api/v1/trader/{address}/categories`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-category-records) already answered `404` for the same value.

A wallet address the API does not track yet is unchanged: it still returns `200` with `sync_status` `unknown` and that wallet as `address`. `@0xinsider/mcp` 2.11.3 says the same in `get_trader`.

**Not backward compatible for one input class.** A lookup by username or trader ID that matches no trader moves from `200` to `404`. Every lookup that resolves, and every wallet-address lookup, returns what it did before.

**What to change:** If you treated `data.sync_status == "unknown"` after a username or trader ID lookup as "not found", handle the `404` `not_found` instead, and stop polling that value; it will not start resolving on its own. Polling a wallet address needs no change.

**September 26, 2026**

The sandbox's events/feed/since sends next\_cursor on its last page

[`GET /api/v1/events/feed/since`](https://docs.0xinsider.com/api-reference/endpoint/get-event-replay-since) on the sandbox at `https://0xinsider.com/sandbox` now sends `next_cursor` on every page, as the live API does. On the last page it names the end of the sandbox's seven rows, and a request with that cursor returns `200` with `data: []`, `has_more: false`, and the same `next_cursor`.

Before this change, the sandbox's last page left `next_cursor` out, although the API reference lists it as required and the live API always sends it. A request with a cursor at the end of the rows returned `400 bad_request` with `error.reason` `cursor_expired`.

Every other paginated list on the sandbox answers as before: its last page has no `next_cursor`, or `next_cursor: null` where the reference marks the field nullable. The live API is unchanged.

**Backward compatible.**

**What to change:** Nothing. A client that stores `next_cursor` and polls the feed can now run that loop against the sandbox.

**September 26, 2026**

Response platform is typed polymarket, and coverage documents one status value

`@0xinsider/sdk` and the OpenAPI document now type the response field `platform` as `polymarket` rather than as any string. It covers twelve response fields, across games, the leaderboard, market search, the market explorer, market flow, sharp money flows, Pick of the Day with its two commitment payloads, and report snapshots. The seven that can be empty are typed `polymarket` or `null`.

Before this change a generated client could not tell from the type that the API serves one venue. Nothing on the wire changes, and the `platform` query parameter still accepts `polymarket` and `all`.

[`GET /api/v1/coverage`](https://docs.0xinsider.com/api-reference/endpoint/get-coverage) and its deprecated alias [`GET /api/v1/platforms`](https://docs.0xinsider.com/api-reference/endpoint/get-platforms) now document one capability status, `supported`, the only value the API has ever returned. Before, the document and the SDK offered `supported`, `partial` and `unsupported`. `@0xinsider/mcp` 2.11.2 says the same in `get_coverage` and `get_platforms`.

**Backward compatible.** No response body changes: the types and the tool text narrow to what the API already sends.

**What to change:** Nothing, unless your code assigns some other string to a `platform` field or to a capability status. TypeScript refuses that now.

**September 26, 2026**

Local and remote MCP tools publish the same descriptions and defaults

The next `@0xinsider/mcp` release, 2.11.0, advertises for every tool exactly what the remote MCP endpoint at `https://api.0xinsider.com/api/v1/mcp` advertises for the same tool. 35 of the 47 tools differed in 116 advertised fields: 19 tool descriptions, 80 argument descriptions, and 17 defaults the package's schema left out.

The defaults an agent can now read from `tools/list` on the package, each one the value the REST route already applied to a request that omitted the argument:

* `get_large_trades` and `get_whale_trades`: `suspicious_only=false`, `sort=recent`.
* `get_large_trades_history` and `get_whale_trades_history`: `min_size=5000`, `suspicious_only=false`, `sort=recent`.
* `get_suspicious_trades` and `get_insider_radar`: `mode=live`.
* `get_positions`: `consistency=live`. `min_size` still advertises no default, because the route's default is 100, or 0 when `wallet` is present.
* `get_sharp_money_flows` and `get_smart_money_flows`: `min_grade=B`, `platform=all`.

44 arguments that the remote describes carried no description at all on the package, among them every filter on `get_large_trades_history` and `get_event_replay_since`. The deprecated aliases -- `get_whale_trades`, `get_whale_trade`, `get_whale_trades_history`, `get_market_intel`, `batch_get_market_intel`, `get_sports_edge_signals` and `get_sports_edge_observations` -- had been sharing the canonical tool's description and now carry the text the remote serves for them, naming their own deprecated path and envelope.

**Backward compatible.** No tool was added or removed, no argument was added or removed, and no response changed; both transports still serve 47 tools. A request that omitted one of those arguments now sends the value the route was already applying, so the same rows come back.

**What to change:** Nothing. If you pinned prompts or tests to the package's older tool text, reread `tools/list`.

**September 26, 2026**

Invalid queries and bodies return structured errors on every API route

Every `/api/v1` route now returns the documented error envelope for an invalid query or body. Previously, these failures returned a `400` with a zero-byte body; the status stays `400`.

This includes:

* An unparseable query such as `limit=abc` on [`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), or [`GET /api/v1/leaderboard`](https://docs.0xinsider.com/api-reference/endpoint/get-leaderboard).
* A JSON body that does not fit the schema, such as the old `{"identifiers": [...]}` body on [`POST /api/v1/traders/batch`](https://docs.0xinsider.com/api-reference/endpoint/batch-get-traders).

Before this change, either failure answered `400` with `content-type: application/json` and an empty body, so a client had no `error.code`, `error.reason`, or `error.param` to read.

It now answers `error.code` `bad_request` with `error.reason` `invalid_query` for the query case or `invalid_body` for the body case, and `error.param` naming the parameter or JSON path.

* [`GET /api/v1/games`](https://docs.0xinsider.com/api-reference/endpoint/list-games) had its own gap: an unparseable `limit` already answered `400` with `error.code` `bad_request`, but with no `error.reason`. It now sends `error.reason` `invalid_query`, matching every other list route. This is the only response where the `error.code` a client already reads was correct before and after; the addition is `error.reason`.
* [`GET /api/v1/positions`](https://docs.0xinsider.com/api-reference/endpoint/get-positions) now sets `error.param` to `wallet` on the `404` it answers when a `wallet` value resolves to no trader. The API reference already documented this field; the server was omitting it.

No documented response shape changed: the reference already promised `error.reason` and `error.param` on these routes, and these are server fixes that make the responses match it.

**Backward compatible.** `error.code` and the `400`/`404` status are unchanged everywhere. A client that read an empty body as a generic failure keeps working; `error.reason` and `error.param` are new information, not a replacement for anything it read before.

**What to change:** Nothing to keep working. To branch on the specific field or cause instead of treating every `400` the same, read `error.reason` (`invalid_query` or `invalid_body`) and `error.param`.

**September 26, 2026**

Batch traders answers an unknown username or trader id with not\_found

[`POST /api/v1/traders/batch`](https://docs.0xinsider.com/api-reference/endpoint/batch-get-traders) now answers an identifier that names no trader with a per-item `not_found` error with `error.param` `traders`, the error the TypeScript client has always documented. The item keeps its `index` and `input`, so you can tell which of the 25 failed, and `meta.failed_items` counts it.

Before this change the item came back as `status: "ok"` with a trader object whose `address` and `id` were built from the string you sent, and `data_quality.status` was `unavailable`. A typo in a username read as a real wallet with no data.

A wallet address is unchanged. An address this API does not track yet still returns `status: "ok"` with `sync_status: "unknown"`, because that address is real and may still be graded, so keep polling it. Only a username, a `trd_` id, or a numeric trader id that resolves to nothing is now `not_found`.

This is the rule [`GET /api/v1/trader/{address}`](https://docs.0xinsider.com/api-reference/endpoint/get-trader) adopted earlier the same day, where the same identifier answers `404 not_found` with `error.param` `address`.

**Breaking** for clients that read every batch item as `status: "ok"` and took `data.address` as a wallet.

**What to change:** Check `status` on each item before reading `data`, and treat `error.code` `not_found` as "no such trader" rather than retrying it.

**September 26, 2026**

A malformed condition\_id on large positions now returns 400, not an empty list

[`GET /api/v1/large-positions`](https://docs.0xinsider.com/api-reference/endpoint/list-large-positions) now validates `condition_id` and returns `400` with `param: "condition_id"` when the value is not a market condition id. A valid value is `0x` followed by 64 hexadecimal characters, sent on its own or with the `mkt_` prefix this API accepts on any market id.

Before this change any string was accepted and the response was `200` with an empty list, so a typo read as "this market has no large positions".

An omitted, empty, or whitespace-only `condition_id` still means every market.

**Breaking** for clients that send `condition_id` in any other shape. Every condition id this API returns already matches the accepted shape, so a request that matched positions before still matches them.

**What to change:** Send `condition_id` as the API returns it, in `market.condition_id` or as the `mkt_` prefixed `market.id`. Handle `400` on the parameter instead of reading an empty list as "no positions".

**September 26, 2026**

Pick of the Day archive entries carry published\_at and resolved\_at

Every entry from [`GET /api/v1/pick-of-the-day/archive`](https://docs.0xinsider.com/api-reference/endpoint/get-pick-of-the-day-archive) now carries `published_at`, the RFC 3339 time the pick became public, and `resolved_at`, the time its outcome was last written to a settled value. `resolved_at` is the same instant [`GET /api/v1/pick-of-the-day/ledger`](https://docs.0xinsider.com/api-reference/endpoint/get-pick-of-the-day-ledger) already publishes, and it moves when a corrected market re-maps an already-settled pick.

Before this change the only date on an entry was `pick_date`, the calendar day the pick belongs to in America/New\_York. That is a day and not a time, so a client that needed an instant had to invent one, and reading the bare date as UTC midnight lands the evening before the pick existed.

* A publication's UTC date can differ from `pick_date`, which follows `America/New_York`. Read the returned timestamps instead of deriving them from the date.
* Both fields are left out when the time is unknown. `resolved_at` is absent for a pending pick and for a pick that settled before the time was recorded, so a missing value means unknown, never unsettled, and `outcome` is what answers that.
* The [RSS feed](https://0xinsider.com/feed.xml) reads `published_at` for the same reason. Each Pick of the Day item is now stamped with the time the day's first pick went public, instead of 00:00 GMT of that day, and carries an `atom:updated` element that moves as later picks go out and outcomes settle. Item `guid` values are unchanged, so nothing is redelivered.

**Backward compatible.** Both fields are additive and optional, and `pick_date` is unchanged.

**What to change:** Read `published_at` instead of parsing `pick_date` as a timestamp. In the feed, compare `atom:updated` rather than `pubDate` to decide whether to re-read an item.

**September 26, 2026**

Market explorer smart\_label names a lean only when graded money leans against the price

`smart_label` on [`GET /api/v1/markets/explore`](https://docs.0xinsider.com/api-reference/endpoint/explore-markets) now follows the market page's price-relative rule. It names an outcome only when the graded money's share of that side diverges from the side's price-implied share by at least 10 points, with at least \$500 on the leaning side and outside the crowded-side guard.

Before, it named whichever side held more graded money, so a 90% share on an outcome priced at 95 cents read as a lean even though the money only matched the price. `smart_label` is `null` when the money sits with the price, when there is no graded money, or when the price is unavailable. `smart_score`, `smart_count` and the dollar fields are unchanged.

**Behavior change, same schema:** more rows carry `smart_label: null`, and a row's label now agrees with the market page for the same market.

**What to change:** If you read `smart_label` as "the side with more graded money", derive that from the dollar fields or `smart_score` instead. Read `smart_label` as the lean against the price.

**September 25, 2026**

Pick of the Day display\_category names competitions outside the curated list

[`GET /api/v1/pick-of-the-day`](https://docs.0xinsider.com/api-reference/endpoint/get-pick-of-the-day) and [`GET /api/v1/pick-of-the-day/archive`](https://docs.0xinsider.com/api-reference/endpoint/get-pick-of-the-day-archive) now return the competition's name in `display_category` when the pick's competition is not on the curated list. The name comes from Polymarket's series title, with a trailing season year removed.

For example, a UEFA Nations League pick now reads `UEFA Nations League` instead of `Soccer`, and ATP and WTA picks read `ATP` and `WTA` instead of `Tennis`. A curated label, such as `LaLiga`, still wins, and the sport name is returned only when Polymarket gives the event no series title.

**Backward compatible.** The field keeps its type and meaning, a free-text label for the pick's competition, and `category` still carries the sport.

**What to change:** If you map `display_category` to a fixed set of sport names, expect competition names too, and use `category` for the sport.

**September 24, 2026**

Settled market exports sum each side's shares from its own cost and entry price

Files from [`POST /api/v1/trader/{address}/export`](https://docs.0xinsider.com/api-reference/endpoint/submit-trader-export) now calculate `total_shares` for a settled two-sided position from each side's own cost and entry price when both cost bases are known and no live share count remains.

Before this change, the calculation divided the combined cost by one side's price. A settled position with several legs on the same outcome now also uses that side's combined values and weighted entry price, rather than the last leg's price. A position with unknown per-side cost keeps the existing fallback.

**Backward compatible.** `total_shares`, `entry_price_yes`, and `entry_price_no` keep their names and types. Only affected computed values change.

**What to change:** Nothing. Download a new export to read updated stored values; a previously downloaded file does not change.

**September 24, 2026**

API prices and money values keep exact published digits before truncation

Price, rate, and money fields on `/api/v1`, the SDKs, and the MCP servers now preserve exact published digits when floating-point scaling introduces a tiny error. A price of `0.57` returns `0.57`, and a money value of `$0.29` returns `$0.29`.

Before this change, those values could return `0.5699` and `$0.28`. The correction applies to fill prices and sizes on [Large trades](https://docs.0xinsider.com/api-reference/endpoint/get-large-trades.md), [candles](https://docs.0xinsider.com/api-reference/endpoint/get-market-candles.md), [book quotes](https://docs.0xinsider.com/api-reference/endpoint/get-market-snapshot.md), and the position and trader money fields.

Fields still truncate values beyond their published precision. This correction does not guarantee that a provider's raw value equals the truncated API value.

**Backward compatible.** No field is added, removed, or retyped.

**What to change:** Nothing. A cached value from before this correction can differ by one cent or one price unit.

**September 24, 2026**

Trader edge\_consistency requires at least 10 days with realized P\&L

`quant_metrics.edge_consistency` on [`GET /api/v1/trader/{address}`](https://docs.0xinsider.com/api-reference/endpoint/get-trader) with `expand=quant_metrics`, and [`POST /api/v1/traders/batch`](https://docs.0xinsider.com/api-reference/endpoint/batch-get-traders), now returns `null` below 10 days with realized P\&L.

The value is the share of the trader's last 30 days with realized P\&L that closed positive, from 0 to 1. The window counts observed P\&L days rather than calendar days, so it can span months. Before this change, one profitable day could return `1.0`.

The field's OpenAPI description and generated SDK comment now explain the same window and minimum. `consistency_percentile` remains `null` when `edge_consistency` is `null`.

**Backward compatible.** Both fields already accepted `null`, which means unavailable and never zero.

**What to change:** Nothing, if you already handle `null`. Keep an unavailable value separate from a measured score.

**September 24, 2026**

Leaderboard score ties use consistent ranking, and older cursors must restart

[`GET /api/v1/leaderboard`](https://docs.0xinsider.com/api-reference/endpoint/get-leaderboard) now orders wallets with the same displayed `score` consistently with the website leaderboard and profile rank.

Before this change, tied wallets were ordered by address alone, and their relative positions could disagree across these pages.

* Wallets inside a former tie can change places. The response fields and the meaning of `score` are unchanged.
* A cursor issued in the previous format returns `400`, with `error.code: "bad_request"` and `error.param: "cursor"`.
* The `lbv1_` prefix is unchanged. Treat `next_cursor` as opaque.

**Breaking** for clients that replay a cursor issued before this change.

**What to change:** Discard a refused cursor and restart at the first page. Do not assume a stored page boundary identifies the same wallet after the ranking changes.

**September 24, 2026**

Trader P\&L drawdown includes losses below break-even

The `drawdown` series on [`GET /api/v1/trader/{address}/pnl`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-pnl) now measures cumulative P\&L against a running peak that starts at zero. A point below that peak carries the negative dollar difference, with a difference smaller than 1 cent represented as `0`.

Before this change, the peak started at negative infinity, and the series reported a drawdown only after the curve had reached positive cumulative P\&L. An opening losing period therefore returned `0`, even when the wallet had substantial losses.

* A cumulative curve of `[-1000, -50000, -20000]` now has drawdowns of `[-1000, -50000, -20000]`, instead of `[0, 0, 0]`.
* Points before the first positive peak can change even when the wallet becomes profitable later.
* Points after a positive peak retain the previous calculation. The response shape, date window, and other P\&L sections are unchanged.

**Breaking** for clients that interpreted an all-zero drawdown series as proof that the wallet had no losses.

**What to change:** Use the returned drawdown values. A maximum drawdown computed from them can now be nonzero for a wallet that previously showed `0`.

**September 24, 2026**

Pick of the Day carries up to 10 picks a day, and one sport can fill more of the day

A day of picks on [`GET /api/v1/pick-of-the-day`](https://docs.0xinsider.com/api-reference/endpoint/get-pick-of-the-day) now carries up to 10 ranked picks instead of 6. `pick_rank` and `pick_count` run from 1 to 10 in `picks`, `scheduled_picks`, the archive, and the proof routes, and the Pick of the Day MCP tool and the TypeScript client describe the same range.

A day no longer stops at 4 picks from one sport, so a weekend with most qualifying markets in soccer can carry more soccer picks. A day still carries at most one pick per game, and a day with fewer qualifying markets still carries fewer picks.

**Backward compatible** for clients that read `picks` as a list. Days published before this change keep their ranks.

**What to change:** If you validate `pick_rank` or `pick_count` against a maximum of 6, or size a fixed array to 6 picks, raise it to 10.

**September 24, 2026**

OpenAPI adds five Pick of the Day fields, sort=large\_trades, and a 500 on every route

The [OpenAPI document](https://0xinsider.com/api/v1/openapi.json), the SDK types generated from it, and `@0xinsider/mcp` 2.10.1 now describe fields and values the API already served:

* [`GET /api/v1/pick-of-the-day`](https://docs.0xinsider.com/api-reference/endpoint/get-pick-of-the-day): `polymarket_url` and `game_ended` on each pick, `last_traded_at` (always sent, `null` when unknown) and `entry_value_usd` on each holder, and `logo_mark_dark` on each team in `sports_context`.
* [`GET /api/v1/markets/explore`](https://docs.0xinsider.com/api-reference/endpoint/explore-markets): `sort=large_trades`, the canonical spelling of `whales`. The stdio `explore_markets` tool in `@0xinsider/mcp` 2.10.1 accepts it; 2.10.0 and earlier refused it.
* Every operation lists its `500` response. [`GET /api/v1/pick-of-the-day/ledger`](https://docs.0xinsider.com/api-reference/endpoint/get-pick-of-the-day-ledger) documents its `500` as an integrity refusal, when one entry fails a consistency check.
* `next_cursor` on [`GET /api/v1/sports/pre-game-side-observations`](https://docs.0xinsider.com/api-reference/endpoint/get-pre-game-side-observations) and [`GET /api/v1/sports-edge-observations`](https://docs.0xinsider.com/api-reference/endpoint/get-sports-edge-observations) is typed `string | null`: both routes have always sent `null` on the last page.

Before this change the document left these out, so a client generated from it dropped the fields and refused `large_trades`.

**Backward compatible.** No response changed. A client generated from the old document that reads `next_cursor` as `string | undefined` should also accept `null`.

**What to change:** Nothing. Regenerate your client from the document, or update `@0xinsider/mcp` to 2.10.1 once it is published, to read the new fields and send `sort=large_trades`.

**September 24, 2026**

Trader P\&L totals use exact daily changes, including gains or losses below 1 cent

[`GET /api/v1/trader/{address}/pnl`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-pnl) now builds `monthly[].pnl`, `year_totals` and its daily statistics from each day's exact P\&L change. Until now each day was rounded to the cent before it was summed, so a month or year total could drift from the change in the cumulative curve over the same period. Read on 2026-09-24 for one wallet, the 2026 total was \$0.05 short of the curve.

Two values a client can see change with it:

* `win_days` and `loss_days` now count a day that moved by less than a cent, which the rounding used to turn into a zero day. That agrees with the daily win rate shown on the wallet's profile.
* `daily_change` is still returned to the cent, but it is now truncated from the exact value instead of rounded from it, the same way every other money field on the route is served.

**Backward compatible.** No field is added, removed or retyped, and every money field still carries at most two decimal places. The MCP tools that read this route change the same way.

**What to change:** Nothing. If you compared `monthly[].pnl` or `year_totals` with a sum of `daily_change` values, expect the totals to differ from that sum by up to a cent per day, because the totals now come from the exact values.

**September 24, 2026**

Candles reject a from or to sent in milliseconds instead of seconds

[`GET /api/v1/market/{condition_id}/candles`](https://docs.0xinsider.com/api-reference/endpoint/get-market-candles) now checks the magnitude of `from` and `to` before using either as a unix-seconds timestamp. A value above `100000000000` (the year 5138 in seconds) now returns `400` with `error.param` naming the offending parameter and a message saying the value must be seconds, not milliseconds.

Before this change, a millisecond value that Rust's date library could still parse as a far-future timestamp was accepted silently. The most common way to trigger it was passing JavaScript's `Date.now()` (13 digits) where the endpoint expects unix seconds (10 digits): a millisecond `from` with no `to` returned an empty `200` instead of the requested range, and a millisecond `to` paired with a seconds `from` passed the endpoint's inverted-range check and was applied as no upper bound at all.

**Backward compatible.** Every `from`/`to` value under `100000000000` (about the year 5138) is unaffected.

**What to change:** Nothing, if you already send `from`/`to` in unix seconds. If your client sent milliseconds and relied on the previous behavior without an error, send seconds instead; the new `400` names which parameter is wrong.

**September 24, 2026**

Report periods that end before 2024-03-01 or start after tomorrow UTC return 400

[`GET /api/v1/reports`](https://docs.0xinsider.com/api-reference/endpoint/get-reports), [`GET /api/v1/reports/daily`](https://docs.0xinsider.com/api-reference/endpoint/get-daily-report-snapshot), [`GET /api/v1/reports/weekly`](https://docs.0xinsider.com/api-reference/endpoint/get-weekly-report-snapshot) and [`GET /api/v1/reports/monthly`](https://docs.0xinsider.com/api-reference/endpoint/get-monthly-report-snapshot) now check the requested period before reading any data. A period must end on or after 2024-03-01, the first day report data covers, and start no later than tomorrow UTC.

Any other period returns `400` with `error.code` `bad_request` and an `error.param` naming the parameter you sent: `date`, `week`, `month` or `period`. For an explicit weekly `from`/`to` range, `error.param` is `to` when the range ends before 2024-03-01 and `from` when it starts after tomorrow UTC.

Before this change these routes accepted any calendar date, so `date=9999-12-31` or `period=0001-01-01` returned `200` with an empty report.

**Backward compatible** for every period that holds data. Every period that reaches 2024-03-01, through today, returns exactly what it did before, including ISO week 2024-09, which starts on 2024-02-26.

**What to change:** Nothing, if you only ask for periods that have report data. If you request future dates or dates before March 2024 and expect an empty `200`, handle `400 bad_request` and read `error.param` instead.

**September 24, 2026**

List routes clamp limit values and report them in X-Effective-Query

[`GET /api/v1/games`](https://docs.0xinsider.com/api-reference/endpoint/list-games), [`GET /api/v1/events/feed/since`](https://docs.0xinsider.com/api-reference/endpoint/get-event-replay-since), [`GET /api/v1/large-trades/{id}/counterparties/executions`](https://docs.0xinsider.com/api-reference/endpoint/get-large-trade-counterparty-executions), and [`GET /api/v1/large-trades/{id}/counterparties/executions/{execution_id}/makers`](https://docs.0xinsider.com/api-reference/endpoint/get-large-trade-counterparty-makers) (and their deprecated `whale-trades` spellings) now clamp a `limit` outside 1 to 100 into that range, the way every other list route already did. Before this change, these four answered `400` with `error.param` `limit` for `limit=0` or `limit=500`, while `GET /api/v1/positions` and the rest returned 100 rows.

* `limit=0` now returns 1 row and `limit=500` returns the route's maximum on every list route. The [pagination](https://docs.0xinsider.com/concepts/pagination.md) page holds each route's default and maximum.
* A `limit` that is not a whole number still answers `400` with `error.reason` `invalid_query`.
* The `X-Effective-Query` response header now reports the `limit` the page used. Before this change it echoed the value you sent, so `limit=500` appeared as `limit=500` beside a page of 100 rows.
* `GET /api/v1/events/feed/since` accepts `expand[]` as an alias for `expand`, as the trader and market snapshot routes do. Before this change `expand[]=trade` was applied but reported in `X-Query-Ignored`, and `X-Query-Validation: strict` refused it.

**Backward compatible.** A request with a `limit` inside the published range is unchanged. A client that relied on the `400` to detect its own out-of-range `limit` now gets a full page instead; read `X-Effective-Query` for the value applied.

**What to change:** Nothing.

**September 24, 2026**

MCP tools take the parameters their REST routes take, and get\_positions returns snapshot

`@0xinsider/mcp` 2.10.0 and the remote MCP server at `https://api.0xinsider.com/api/v1/mcp` now expose four things their REST routes already had:

* `get_positions` returns the page's `snapshot` object (`as_of`, `expires_at`, `row_count`) when you call it with `consistency=snapshot`, the same object [`GET /api/v1/positions`](https://docs.0xinsider.com/api-reference/endpoint/get-positions) returns. Before this change both transports left it out, so a client learned that the five-minute snapshot had ended only from a `cursor_expired` error on the next page.
* `get_large_trades`, `get_large_trades_history`, and their deprecated `get_whale_trades` and `get_whale_trades_history` spellings take `min_market_volume_share` (a fraction from 0 to 1) and `sort` (`recent` or `market_volume_share`), the parameters [`GET /api/v1/large-trades`](https://docs.0xinsider.com/api-reference/endpoint/get-large-trades) and [`GET /api/v1/large-trades/history`](https://docs.0xinsider.com/api-reference/endpoint/get-large-trades-history) take.
* `batch_get_traders` accepts `trust` in `expand`, as [`POST /api/v1/traders/batch`](https://docs.0xinsider.com/api-reference/endpoint/batch-get-traders) does. Before this change the remote server refused it as an unknown value.
* `get_trader_pnl` takes `from`, `to` (calendar dates in `YYYY-MM-DD` form), and `sections` (`entries`, `stats`, `monthly`, `year_totals`, `drawdown`), the parameters [`GET /api/v1/trader/{address}/pnl`](https://docs.0xinsider.com/api-reference/endpoint/get-trader-pnl) takes.

No tool was added or removed; both transports serve the same 47 tools.

**Backward compatible.** Every new parameter is optional, and `snapshot` appears only on a `consistency=snapshot` page.

**What to change:** Nothing. Update to `@0xinsider/mcp` 2.10.0 to use the new parameters over stdio; the remote server serves them as soon as this change is live.

**September 24, 2026**

OAuth client ID metadata document URLs are checked before they are fetched

A `client_id` that is a client ID metadata document URL (the [CIMD](https://docs.0xinsider.com/authentication.md) form: an `https` URL with a path, used by clients that skip `POST /oauth/register`) is now checked against the same host and port rules a webhook URL must satisfy before this backend fetches it: port 443 only, no embedded credentials, and no `localhost`, `.internal` name, or private-range address. A URL that fails is refused with no network attempt.

Every refused fetch, for any reason, now answers the same fixed message. Before this change, the error text varied with the cause (a specific connect or DNS failure, or the fetched URL's HTTP status), which let a caller distinguish a name that resolves to a non-public address from a DNS failure, and probe whether an internal name exists, from the wording alone.

`GET /api/oauth/authorize/preview` and `POST /api/oauth/authorize/decision` now count toward the same per-IP rate limit (`temporarily_unavailable`, 60 requests per minute) as `POST /oauth/register`, `POST /oauth/device/code`, `POST /oauth/token`, and `POST /oauth/revoke`. Before this change, these two routes took no admission at all, unlike every other OAuth endpoint.

**Backward compatible.** A legitimate client ID metadata document, hosted on the default HTTPS port with a publicly routable host, is unaffected. A client integrating against `preview` or `decision` at a rate the shared OAuth limiter already allows for the rest of the flow is unaffected.

**What to change:** Nothing, unless your client ID metadata document is served on a non-standard port or from a private address; move it to a publicly routable host on port 443. If your integration calls `preview` or `decision` at a high rate, it is now subject to the same 60-per-minute-per-IP limit as the rest of the authorization flow.

**September 24, 2026**

The OAuth consent page no longer redirects on its own after an error

The `/oauth/authorize` consent page no longer navigates to a client's `redirect_uri` on its own after an error. Before this change, a request that failed validation after the `client_id` and `redirect_uri` check (an unsupported `response_type`, a missing or malformed PKCE `code_challenge`, or a `scope` outside the client's registered ceiling) rendered the error and then, four seconds later, sent the signed-in visitor's browser to that `redirect_uri` with no click.

No client registered through `POST /oauth/register` or a client ID metadata document is reviewed, so that redirect target was never a vetted destination.

The page still shows a "Return to the application" link to the same URL; it is now clicked, never automatic.

Error responses from `GET /api/oauth/authorize/preview` and `POST /api/oauth/authorize/decision` also stop echoing the request's raw `response_type` or `code_challenge_method` value into `error_description`; both now read a fixed message.

**Backward compatible.** `redirect_to` is still present in the same refusal responses, and its value is unchanged. Nothing in the JSON contract changed shape.

**What to change:** Nothing, for a standard OAuth 2.1 client following its own redirect on receiving `code` or `error` at its `redirect_uri`. A client that depended on the browser navigating there automatically from the 0xinsider-hosted consent page, without a user action, should not have; build the same redirect into your own client-side error handling instead.

**September 24, 2026**

A read-scoped credential reaches trader usernames that start with export

[`GET /api/v1/trader/{address}`](https://docs.0xinsider.com/api-reference/endpoint/get-trader) and the routes under it now answer an OAuth token or integration key that has only the `read` scope when `{address}` is a username that starts with `export`, such as `/api/v1/trader/exporter` or `/api/v1/trader/exporter/pnl`. Only `/api/v1/trader/{address}/export` and the routes under it need the `export` scope.

Before this change, those requests answered `403` with `error.code` `insufficient_scope` and named `export` as the missing scope. The export snapshot and the export job routes still need `export`, as [Authentication](https://docs.0xinsider.com/authentication.md) lists, and the remote MCP tools follow the same rule.

**Backward compatible.** Requests that were refused now succeed; no route needs a scope it did not need before.

**What to change:** Nothing.

**September 24, 2026**

MCP get\_positions omits min\_size by default for complete wallet position lists

The [`get_positions`](https://docs.0xinsider.com/integrations/mcp) MCP tool in `@0xinsider/mcp` 2.9.1 sends `min_size` to [`GET /api/v1/positions`](https://docs.0xinsider.com/api-reference/endpoint/get-positions) only when you set it. A call with only `wallet` now returns every one of the wallet's open positions, including those worth under \$100, the same result as the REST route with `wallet` and no `min_size`.

On the remote server, the tool's input schema no longer advertises a `default` of 100 for `min_size`; its description states the route's rule instead: 100, or 0 when `wallet` is present.

Before this change, the `@0xinsider/mcp` package filled `min_size` with 100 on every call and sent it, so a wallet-scoped call dropped every position under \$100 even though the tool's description said the floor was 0 with `wallet`. The remote server never filled the value itself, but a client that pre-fills schema defaults sent the same 100.

**Backward compatible.** A call that sets `min_size` gets exactly what it asks for, as before. A call without `wallet` and without `min_size` still gets positions worth \$100 or more.

**What to change:** Nothing. Update to `@0xinsider/mcp` 2.9.1 to get the wallet default of 0. If you relied on the package's old floor for a wallet call, send `min_size: 100`.

**September 24, 2026**

Market search reports a closed but unresolved market as closed

[`GET /api/v1/markets/search`](https://docs.0xinsider.com/api-reference/endpoint/search-markets) now returns `status` as `closed` for a market whose trading Polymarket has closed but which has not yet resolved, and `status=active` no longer returns that market. This is the rule [`GET /api/v1/markets/explore`](https://docs.0xinsider.com/api-reference/endpoint/explore-markets) and [`GET /api/v1/market/{condition_id}/snapshot`](https://docs.0xinsider.com/api-reference/endpoint/get-market-snapshot) already applied, so the three routes now agree on every market.

Before this change, search called a market `active` until it resolved. A finished game stayed `active` on search for hours while explore and the snapshot called it `closed`, and `status=active` returned markets you could no longer trade.

On all three routes, `status` is now:

* `closed`: Polymarket has closed trading, or the market has resolved.
* `active`: neither has happened.

**Breaking** for a client that read `status=active` on market search as "not yet resolved". The enum, the parameters, and the response shape are unchanged.

**What to change:** Nothing, if you use `status=active` to find markets you can still trade. If you relied on it to include markets awaiting resolution, request `status=all` or `status=closed` and read each row's `status`.

**September 24, 2026**

The stream marks a sequence numbering restart instead of going quiet

[`GET /api/v1/stream`](https://docs.0xinsider.com/api-reference/endpoint/get-stream) now sends one `event: resync` marker with `completeness.status` set to `reset` when the server's sequence numbering restarts while your connection is open, and then keeps sending frames. The marker's `id` is one below the first `seq` of the new numbering, `from_sequence` is the last `seq` you were sent, and `to_sequence` is the first of the new numbering.

Before this change, a restart under an open connection sent no marker and no further frames, while the keep-alive comments continued, so the connection looked healthy but was silent. It stayed silent until the new numbering passed your last `seq`, which could take days. A reconnect always recovered, because a resume point past the current `seq` answers a `truncated` marker.

The `completeness.status` values are now:

* `truncated`: your resume point is older than the retained window.
* `lagged`: the stream fell behind. A `lagged` marker is no longer followed by a second marker for the same loss.
* `reset`: the sequence numbering restarted under your open connection.

**Backward compatible.** The new status is additive, and the frame shape is unchanged.

**What to change:** Treat a `reset` marker like any other `resync`: refetch current state, keep the connection open, and store the marker's `id` as your cursor. Expect the `id` values after it to be lower than the ones before it. A client that accepts only `truncated` and `lagged` as status values must accept `reset`.

**September 24, 2026**

Trader reads say when open-position P\&L could not be read

[`GET /api/v1/trader/{address}`](https://docs.0xinsider.com/api-reference/endpoint/get-trader) and [`POST /api/v1/traders/batch`](https://docs.0xinsider.com/api-reference/endpoint/batch-get-traders) now report the `positions` group of `data_quality` as `unavailable`, with a `reason` that names the failed read, when the open-position P\&L behind `pnl.unrealized` could not be read for that request. That body carries `meta.cached: false` and is not kept for later callers, so a retry reads again. With `expand=trust`, `trust.unrealized_pnl` carries the same reason in its `completeness.detail`.

Before this change, a failed open-position read served `pnl.unrealized` as absent with the ordinary reason `open-position unrealized P&L is unavailable for this wallet`, which is also what a wallet with no open positions gets, and that body was served with `meta.cached: true` to every caller for up to 60 seconds.

* The reason begins `the open-position read failed on this request`. Match on `status` and `group`, not on the sentence: reasons are prose and may be reworded.
* Every other group is unchanged by the failure: `sync`, `ranking`, `leaderboard_rank` and `volume` keep their own verdicts, so top-level `data_quality.status` reads `partial`.
* `max_age_s` on `GET /api/v1/trader/{address}` still refuses such a body, because its status is not `fresh`.

**Backward compatible.** No field was added or removed. `reason` is an existing free-text field and `unavailable` an existing status.

**What to change:** Nothing. If you read an absent `pnl.unrealized` as "no open positions", check `data_quality.field_groups[]` for `group: "positions"` first, and retry when its `status` is `unavailable` and `meta.cached` is `false`.


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