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

# REST API Changelog

> API changes, new features, breaking changes, and migration guides for Radion.

API-facing changes for Radion integrators. See the [API overview](/api/overview) for the current contract.

<Warning>
  Entries tagged **Breaking** require changes to existing integrations. Review
  the migration notes before upgrading.
</Warning>

<Update label="July 7, 2026" description="Events and markets use named list arrays and split detail routes">
  List endpoints for events and markets now return `events` and `markets` arrays instead of a shared `data` field.
  Detail endpoints now use separate paths for id-based and slug-based lookups.

  ### Breaking

  * `GET /v1/polymarket/events` now returns `{ events, nextCursor }`.
  * `GET /v1/polymarket/markets` now returns `{ markets, nextCursor }`.
  * `GET /v1/polymarket/events/{identifier}` is replaced by `/events/{event_id}` for numeric ids and `/events/slug/{event_slug}` for slugs.
  * `GET /v1/polymarket/markets/{identifier}` is replaced by `/markets/{condition_id}` for condition ids and `/markets/slug/{market_slug}` for slugs. Numeric market ids are no longer accepted by the public REST route.

  ### Migration

  * Replace `res.data` with `res.events` for events lists.
  * Replace `res.data` with `res.markets` for markets lists.
  * Send event slugs to `/v1/polymarket/events/slug/{event_slug}`.
  * Send market slugs to `/v1/polymarket/markets/slug/{market_slug}`.
  * Keep condition ids on `/v1/polymarket/markets/{condition_id}`.
</Update>

<Update label="July 5, 2026" description="Bare single-object responses; consistent list, search, and error shapes">
  Single-object endpoints no longer wrap their result in a `data` field. This is a **breaking change** for any code that reads `res.data` on a single object.

  ### Changed

  * Single objects return bare: `price`, `midpoint`, `spread`, `last-trade-price`, `orderbook`, `events/{id}`, `markets/{id}`, `auth/me`, and `POST /auth/keys`. Read fields at the top level (`res.price`, not `res.data.price`).
  * `data` is now reserved for cursor-paged lists only (`events`, `markets`): `{ data: [...], nextCursor }`.
  * `GET /search` returns the composite object bare: `{ events, themes, profiles, pagination }`.
  * `GET /auth/keys` renames its array from `data` to `keys`: `{ keys, plan, trialEndsAt, monthlyUsage }`.
  * Error bodies use camelCase: `request_id` is now `requestId`.

  ### Migration

  * Single object: replace `res.data` with `res` (or read the field directly).
  * Search: replace `res.data` with `res`.
  * Keys list: replace `res.data` with `res.keys`.
  * Errors: read `requestId` instead of `request_id`.
</Update>

<Update label="July 3, 2026" description="New markets endpoints">
  Markets are now a first-class endpoint group, mirroring events. Data comes from the Polymarket Gamma catalog.

  ### Added

  * `GET /v1/polymarket/markets` — list markets, cursor-paginated.
  * `GET /v1/polymarket/markets/{identifier}` — one market by condition id (`0x…`), numeric id, or slug.
  * Both are also exposed as the `list_markets` and `get_market` MCP tools.
</Update>

<Update label="July 3, 2026" description="Public JWT keys; paid plans lose the per-second rate limit">
  Public JWT keys let apps with no backend call Radion straight from the browser or a mobile client. A public key (`pk_jwt_...`) is safe to ship, and each request also carries a signed end-user token. Separately, per-second rate limiting was removed from all paid plans.

  ### Added — public JWT keys

  * A new key type, `pk_jwt_...`, is safe to embed in a browser or mobile app. On its own it does nothing: every request must also send a signed user JWT in the `Authorization: Bearer` header.
  * Configure the key in the dashboard with a **JWKS URL** or an inline **public key (PEM)**, plus optional `aud` / `iss` checks and a per-user rate limit.
  * Radion verifies the JWT signature, checks `exp` (and `aud` / `iss` when set), and rate-limits per user (`sub`). Monthly usage still counts against your plan.
  * Tokens must use an asymmetric algorithm (`RS*`, `PS*`, `ES*`, or `EdDSA`). Symmetric algorithms (`HS256` and friends) are rejected.
  * Browser WebSocket clients that cannot set headers may pass `api-key` and `token` as query parameters on the `/ws` upgrade. See [Authentication](/api/authentication#public-jwt-keys).

  ### Breaking

  * `requestsPerSecond` in the `GET /auth/me` response is now `null` on paid plans. It stays a number on the free plan. Handle a nullable value.
</Update>

<Update label="July 2, 2026" description="Full-depth orderbook and dedicated price endpoints; lookup removed">
  The orderbook read model expanded from a single midpoint into full depth plus dedicated price endpoints. Search now paginates, events carry richer metadata, and two fields were removed.

  ### Breaking — removed

  * `GET /v1/polymarket/lookup`. Use `/search` for fuzzy text search, or the id-scoped endpoints (`/markets/{condition_id}`, `/markets/slug/{market_slug}`, and the trader endpoints by address) for exact reads.
  * The `expiresAt` field from `GET /auth/me`. API keys do not expire.

  ### Breaking — changed

  * `GET /v1/polymarket/orderbook` now returns full depth instead of only the midpoint. The response holds `bids` and `asks` arrays of `{ price, size }` levels, plus `lastTradePrice` and `tokenId`. It still takes `token_id`. Read the midpoint from the new `/midpoint` endpoint.

  ### Added

  * `GET /v1/polymarket/midpoint` — midpoint price for one outcome token. Takes `token_id`.
  * `GET /v1/polymarket/price` — best `bid` or `ask` price. Takes `token_id` and `side` (`buy` or `sell`).
  * `GET /v1/polymarket/spread` — bid-ask spread. Takes `token_id`.
  * `GET /v1/polymarket/last-trade-price` — last executed trade price. Takes `token_id`.

  ### Changed

  * `GET /v1/polymarket/search` now returns a `pagination` object with `hasMore` and `totalResults`, and each profile includes `createdAt`. Query params are `q`, `limit`, and `page`.
  * `GET /v1/polymarket/events` and `/events/{identifier}` now return enriched metadata: `ticker`, `subtitle`, `resolutionSource`, `image`, `icon`, `featuredImage`, `subcategory`, `seriesSlug`, the `active` / `closed` / `archived` / `new` / `featured` / `restricted` flags, `enableOrderbook`, `negRisk`, `competitive`, `volume24hr` / `volume1wk` / `volume1mo` / `volume1yr`, `commentCount`, `creationDate`, `closedTime`, and the full `markets` array.
</Update>

<Update label="June 30, 2026" description="Trader profile, themes, and edge now populated from Polymarket">
  Trader endpoints now enrich responses with live Polymarket data instead of placeholders.

  ### Added

  * `GET /v1/polymarket/traders/{trader_id}/profile` now returns a real `identity` (pseudonym, name, x username, verified badge, account created at) from the trader's Polymarket public profile.
  * `GET /v1/polymarket/traders/{trader_id}/themes` now returns `themeLeaderboard`, `eliteThemes`, and `avoidThemes` aggregated from market tags; each per-market entry now carries the market `slug`.
  * `GET /v1/polymarket/traders/{trader_id}/edge` now fills `bestEdgeConditions.themes`.

  ### Breaking — removed

  * The `warnings.themeDataUnavailable` field from the trader `edge` and `themes` responses.
</Update>

<Update label="June 28, 2026" description="On-chain read model replaces analysis and backtesting">
  Radion now serves Polymarket data from its own on-chain indexer instead of stored analysis snapshots. The analysis and backtesting products were removed and replaced by direct read-model endpoints for markets, traders, events, search, and the orderbook.

  ### Breaking — removed

  * `GET /v1/polymarket/markets/analysis` and `GET /v1/polymarket/markets/{slug}/analysis`.
  * `GET /v1/polymarket/traders/analysis` and `GET /v1/polymarket/traders/{id}/analysis`.
  * All `/v1/polymarket/backtesting/copytrading/*` endpoints.
  * Response schema (component) names dropped the `Dto` suffix, e.g. `TradeDto` → `Trade`. This affects code generated from the OpenAPI schema.
  * Every single-object response is now wrapped in a `data` field, so all endpoints share one shape. `GET /traders/{trader_id}/pnl`, market detail, market-by-slug, orderbook, search, and event endpoints used to return the object on its own; read the value from `data` now.

  ### Added

  * `GET /v1/polymarket/markets` — list markets by volume; plus `/markets/{condition_id}` and `/markets/slug/{market_slug}`.
  * `GET /v1/polymarket/markets/{condition_id}/candlestick`, `/holders`, and `/trades`.
  * `GET /v1/polymarket/traders/{id}/trades`, `/positions`, and `/pnl`.
  * `GET /v1/polymarket/traders/{id}/profile`, `/performance`, `/risk`, `/edge`, `/distribution`, and `/themes` — rebuilt on the on-chain read model. `identity` fields are `null` and theme grouping ships empty for now; `/themes` still returns best/worst markets by realized PnL, keyed by condition id.
  * `GET /v1/polymarket/traders/global-pnl` — PnL leaderboard.
  * `GET /v1/polymarket/events` and `/events/{identifier}`.
  * `GET /v1/polymarket/search` — venue-agnostic text search across events, topics, and profiles (proxied from Polymarket).
  * `GET /v1/polymarket/lookup` — exact lookup of a market or trader by id (condition id or address). This is the old `/search` behaviour, renamed.
  * `GET /v1/polymarket/orderbook` — live orderbook midpoint for one outcome token. The path is `orderbook` (one word, no hyphen) and takes `token_id`, not `position_id`.

  ### Migration

  * Replace analysis reads with the on-chain endpoints: market metadata via `/markets` and `/events`, trader data via `/traders/{id}/pnl`, `/positions`, and `/trades`.
  * Drop backtesting calls; the copytrading simulator is no longer offered.
  * Regenerate any OpenAPI-derived client so schema names without the `Dto` suffix resolve.
  * Stop reading `traderScore` and `confidence.score` / `rating` from the performance response.
</Update>

<Update label="June 26, 2026" description="Hourly quotas replaced by per-second rate limits">
  Request throttling moved from an hourly quota to a per-second token bucket. Monthly quotas are unchanged. The burst is keyed by your account and shared across all keys.

  ### Breaking

  * Hourly quotas are removed. Per-second rate limits are now Free: 5, Starter: 10, Pro: 50 requests/second. Enterprise stays uncapped.
  * `GET /auth/me`: `hourlyLimit` is renamed to `requestsPerSecond`.

  ### Unchanged

  * Monthly quotas remain Free: 300, Starter: 3,000, Pro: 50,000 requests/month, shared across all keys and reset at the start of each UTC month.
  * Exceeding either limit still returns `429 Too Many Requests` with a `Retry-After` header.
</Update>

<Update label="June 22, 2026" description="Renamed win rate, PnL, and volume fields; entry buckets are structured objects">
  Win rate and PnL shared one field name across endpoints while measuring different things. Trader analysis is lifetime (includes resolved-but-unclaimed positions and unrealized PnL); performance is realized-only over a trailing 365-day window. Fields were renamed so one name never means two computations. Entry-bucket fields became structured objects instead of raw price arrays.

  ### Breaking — field renames

  * `GET /v1/polymarket/traders/{trader_id}/performance`: `winRate` → `trailing365WinRate`, `realizedPnl` → `realized365Pnl` (both: closed positions, trailing 365 days).
  * `GET /v1/polymarket/traders/{trader_id}/analysis` and the trader analyses list: `winRate` → `lifetimeWinRate`, `profitLoss` → `lifetimePnl` (both: lifetime), `medianVolume` → `medianPositionSizeUsd` (value unchanged).
  * Market analysis `topTraders[]`: `winRate` → `lifetimeWinRate`, `profitLoss` → `lifetimePnl` (same lifetime basis).

  ### Breaking — entry buckets

  * `GET /v1/polymarket/traders/{trader_id}/distribution`: `sweetSpotBucket` → `sweetSpotBuckets` and `deadZoneBucket` → `deadZoneBuckets`. Both changed from a nullable price array (`number[] | null`) to a required array of `EntryBucket` objects.
  * `GET /v1/polymarket/backtesting/copytrading/jobs/{jobId}/summary`: `priceRange` (`number[] | null`) → required `bestBuckets` (`EntryBucket[]`).
  * `EntryBucket` objects gained two required fields: `realizedPnl` and `volumeUsd`.

  ### Migration

  * Update field reads to the new names. The two win rates and two PnL figures are not interchangeable — analysis values are lifetime (unclaimed + unrealized), performance values are realized over a trailing 365 days.
  * Read `medianPositionSizeUsd` instead of `medianVolume`; value and units (USD) unchanged.
  * Read bucket fields as `EntryBucket` objects, not price numbers, and drop null handling — these fields are now always present.
</Update>

<Update label="June 18, 2026" description="Trader category affinity renamed to theme affinity">
  Trader profile affinity now uses themes derived from Polymarket market tags instead of Polymarket's legacy category field. The API naming changed from categories to themes.

  ### Breaking

  * `GET /v1/polymarket/traders/{trader_id}/categories` → `GET /v1/polymarket/traders/{trader_id}/themes`.
  * Response types and fields renamed: `categoryLeaderboard` → `themeLeaderboard`, `eliteCategories` → `eliteThemes`, `avoidCategories` → `avoidThemes`.
  * Leaderboard and ROI entries now expose `theme` instead of `category`.
  * Warning field `categoryDataUnavailable` → `themeDataUnavailable`.
  * `bestEdgeConditions.categories` → `bestEdgeConditions.themes`.

  ### Migration

  * Call the `/themes` endpoint for trader theme affinity.
  * Update clients to read `themeLeaderboard`, `eliteThemes`, `avoidThemes`, `theme`, and `themeDataUnavailable`.
  * Update edge consumers to read `bestEdgeConditions.themes`.
</Update>

<Update label="June 15, 2026" description="API keys required, multi-key management, monthly limits, gated premium features">
  API access now requires an API key on every endpoint except `GET /health`. Dashboard key management supports multiple active keys, per-plan key caps, and shared monthly usage counters. Premium features are now enforced by plan.

  ### Breaking

  * Unauthenticated API requests are no longer accepted. Send `X-API-Key` on every request except `GET /health`.
  * Plan names changed from `signal` / `agent` / `custom` to `starter` / `pro` / `enterprise`.
  * `GET /auth/me` now returns `keyLabel` instead of `keyId`, and may return `plan: null` for Free keys.
  * `GET /v1/polymarket/markets/{slug}/analysis` now returns `{ "data": [...] }`. Without `from`, the array contains the latest snapshot as a single item.

  ### Added

  * `GET /auth/keys` — list active keys for the signed-in dashboard user.
  * `POST /auth/keys` — create a key with a `keyLabel`. The raw key is returned once as `rawKey`.
  * `DELETE /auth/keys/{keyLabel}` — revoke an active key by label.
  * `GET /auth/me` and `GET /auth/keys` now expose monthly usage via `monthlyUsage.used`, `monthlyUsage.limit`, and `monthlyUsage.resetsAt`. `GET /auth/me` also includes `hourlyLimit`.
  * `GET /v1/polymarket/markets/{slug}/analysis?from=YYYY-MM-DD` returns historical snapshots computed on or after the date.

  ### Changed

  * Hourly quotas are now Free: 50, Starter: 100, Pro: 1,000 requests, Enterprise: custom.
  * Monthly quotas are now enforced: Free: 300, Starter: 3,000, Pro: 50,000 requests, Enterprise: unlimited.
  * Monthly usage is shared across all keys for the same user. Extra keys do not create extra quota.
  * Active key caps are Free: 1, Starter: 3, Pro: 10, Enterprise: unlimited.
  * `POST /v1/polymarket/backtesting/copytrading/run` now requires Pro or Enterprise.
  * Historical market snapshots through the `from` parameter require Starter or higher.
  * Monthly quota exhaustion returns `429 Too Many Requests` with `detail: "Monthly request limit exceeded"` and a `Retry-After` header pointing to the next monthly reset.

  ### Migration

  * Create a free key from the dashboard and send it with `X-API-Key` on all API calls.
  * Update plan handling to use `starter`, `pro`, `enterprise`, or `null` for Free keys.
  * Read `keyLabel` instead of `keyId` from auth responses.
  * Update market-analysis-by-slug clients to read the first item of `data` when they need only the latest snapshot.
</Update>

<Update label="June 3, 2026" description="Backtesting summary warnings; auth profile endpoint renamed">
  Copytrading backtest jobs now continue when upstream price history is unavailable for individual tokens. Summary responses include warning metadata so you can detect degraded price-history coverage. The authenticated API key metadata endpoint was renamed to `GET /auth/me`.

  ### Breaking

  * `GET /auth/api-key` was removed. Use `GET /auth/me` with the same `X-API-Key` header instead.

  ### Changed

  * `GET /auth/me` returns the same API key metadata response shape that `GET /auth/api-key` returned.
  * `GET /backtesting/copytrading/jobs/{jobId}/summary`: completed jobs may include a `warnings` object with `missingPriceHistoryTokenCount`, `totalTokenCount`, and `priceHistoryCoveragePct`.
  * If price history is missing for some tokens, open-position valuation may be less precise. The simulator continues with its fallback pricing instead of failing the whole job.

  ### Example

  ```json theme={null}
  {
    "data": {
      "status": "done",
      "warnings": {
        "missingPriceHistoryTokenCount": 3,
        "totalTokenCount": 42,
        "priceHistoryCoveragePct": 92.86
      }
    }
  }
  ```
</Update>

<Update label="June 2, 2026" description="API v1 namespace, cursor pagination, traderIds, stricter input validation">
  API endpoints are now available under the `/v1` namespace. List endpoints use opaque cursor-based pagination. The copytrading backtest request field was renamed for multi-venue compatibility. All endpoints now validate input parameters and return structured errors on invalid input.

  ### Breaking

  * `GET /markets/analysis` and `GET /traders/analysis`: the `offset` parameter is removed. Use `cursor` instead (omit on first request).
  * Response shape now includes `nextCursor`: `{ "data": [...], "nextCursor": "..." | null }`.
  * `POST /backtesting/copytrading/run`: request field `traderAddresses` renamed to `traderIds`. The Ethereum-address format constraint was removed — any non-empty string up to 256 characters is accepted, supporting non-Ethereum venues.

  ### Changed

  * Use `/v1` as the canonical prefix for versioned analysis and backtesting requests, e.g. `GET /v1/polymarket/markets/analysis`.
  * Health and authentication endpoints stay at root: `GET /health` and `GET /auth/api-key`.
  * Existing unversioned routes such as `GET /markets/analysis` continue to work for now.
  * All endpoints now validate input. Invalid values return `400 Bad Request` with a structured `application/problem+json` body.
  * `GET /markets/{slug}/analysis`: `slug` must be 1–200 chars.
  * `GET /traders/{id}/analysis`: `id` must be non-empty, max 256 chars.
  * `GET /backtesting/copytrading/jobs/{jobId}/*`: `jobId` must be a valid UUID.
  * `GET /markets/analysis` and `GET /traders/analysis`: default `limit` changed from 50 to 5, max `limit` from 100 to 10. `offset` is replaced by cursor-based pagination.

  ### Deprecation notice

  * Unversioned endpoints are backward-compatible aliases and will be removed on June 16, 2026.

  ### Migration

  * Update API clients to call analysis and backtesting endpoints under `/v1/polymarket`. Generic `/v1/markets`, `/v1/traders`, and `/v1/backtesting` paths are not served.
  * No request-body or response-body changes are required for the `/v1/polymarket` migration.
  * Remove `offset` from all requests to list endpoints.
  * Pass the `nextCursor` value from each response as the `cursor` parameter on the next request. Stop when `nextCursor` is `null`.
  * Update analysis list requests that set `limit` above 10. The default is now 5, and the maximum is now 10.
  * Rename `traderAddresses` to `traderIds` in all `POST /backtesting/copytrading/run` request bodies.

  See [Pagination](/api/pagination) for full details and code examples.
</Update>

<Update label="June 1, 2026" description="Trader metrics fields renamed and deduplicated">
  Trader metrics fields were renamed and deduplicated for consistency across trader-analysis and market top-trader responses.

  ### Changed

  * `walletMedianVolume` on trader-analysis responses was renamed to `medianVolume`. Value and type unchanged.
  * The redundant `positionCount` field was removed from market top-trader entries. It always duplicated `totalPositionCount`.

  ### Migration

  * Read `medianVolume` instead of `walletMedianVolume` wherever you consume trader-analysis responses.
  * Read `totalPositionCount` instead of `positionCount` on market top-trader entries.
</Update>

<Update label="May 30, 2026" description="Market analysis identifier field moved">
  Market analysis responses now expose the canonical venue-scoped market identifier only at `market.id`.

  ### Changed

  * The top-level `marketConditionId` field was removed from market-analysis responses. Use `market.id` instead.
  * Request paths are unchanged. Continue to use market slugs in endpoints such as `GET /markets/{slug}/analysis`.

  ### Migration

  Update clients to read `market.id` instead of `marketConditionId`.

  ```json Before theme={null}
  { "marketConditionId": "0xabcdef..." }
  ```

  ```json After theme={null}
  { "market": { "id": "0xabcdef..." } }
  ```
</Update>

<Update label="May 19, 2026" description="Structured error responses">
  Error responses now return a structured JSON body following [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457).

  ### Changed

  * All error responses now return `type`, `status`, `title`, `detail`, and `request_id` with `Content-Type: application/problem+json`.
  * The `429 Too Many Requests` response still includes a `Retry-After` header.

  ### Migration

  Update error handling to read `detail` and `request_id` instead of a flat `error` string.

  ```json Before theme={null}
  { "error": "Invalid API key" }
  ```

  ```json After theme={null}
  {
    "type": "about:blank",
    "status": 401,
    "title": "Unauthorized",
    "detail": "Invalid API key",
    "request_id": "1748123456-1a"
  }
  ```
</Update>

<Update label="May 18, 2026" description="Rate limiting on all endpoints">
  All API endpoints except `GET /health` now enforce hourly request quotas. Unauthenticated requests are allowed and count against the free-tier quota, keyed by IP address.

  ### Quotas by plan

  * Free (no key): 10 requests/hour, keyed by IP address.
  * Signal: 100 requests/hour, keyed by API key.
  * Agent: 1,000 requests/hour, keyed by API key.
  * Custom: unlimited.

  ### Behavior

  * Your full hourly quota is available immediately as a burst, then replenishes continuously through the hour.
  * Requests exceeding the quota receive `429 Too Many Requests` with a `Retry-After` header.
</Update>

<Update label="May 13, 2026" description="Removed analysis run and status endpoints">
  The endpoints for creating on-demand analysis runs and checking their status were removed. Analysis snapshots are still available via the read endpoints.

  ### Removed

  * `POST /markets/{slug}/analysis`
  * `GET /markets/{slug}/analysis/status`
  * `POST /traders/{id}/analysis`
  * `GET /traders/{id}/analysis/status`

  ### Unchanged

  * `GET /markets/{slug}/analysis` — read the latest market analysis snapshot.
  * `GET /traders/{id}/analysis` — read the latest trader analysis snapshot.

  ### Migration

  Replace run-and-poll flows with direct reads. Analysis is now refreshed automatically.

  ```bash Before theme={null}
  curl -X POST "https://api.radion.app/markets/will-trump-win-2024/analysis" \
    -H "X-API-Key: YOUR_API_KEY"
  # then poll /analysis/status ...
  ```

  ```bash After theme={null}
  curl "https://api.radion.app/markets/will-trump-win-2024/analysis" \
    -H "X-API-Key: YOUR_API_KEY"
  ```
</Update>

<Update label="April 10, 2026" description="Trader analysis transparency fields">
  Analysis responses now expose additional trader-history fields without changing route shapes or request parameters.

  ### Added

  * `winRate` on trader-analysis responses and `topTraders` entries in market-analysis responses. Based on closed positions only.
  * `latestPositionTimestamp` on trader-analysis responses and `topTraders` entries in market-analysis responses.

  Both fields may be `null` on older snapshots created before these fields were tracked.
</Update>

<Update label="April 8, 2026" description="Trader analysis endpoint">
  Radion's API now separates featured-feed browsing, search, and analysis flows.

  ### Added

  * `GET /traders/{id}/analysis` — read trader analysis snapshots.
  * `venue` query filtering for search and market-list flows. `polymarket` is the currently supported venue.
</Update>
