# Backtest Copy PnL — Batch Source: https://docs.polynode.dev/api-reference/backtesting/batch POST /v2/copy-pnl/batch Score multiple wallets in one call. Same math as the single-wallet endpoint. ```http theme={null} POST https://api.polynode.dev/v2/copy-pnl/batch ``` Use this when you need to score many wallets at once — for example, ranking a candidate-leader list. The endpoint returns one result per wallet, processed in parallel so 50 wallets don't take 50× single-wallet time. ## Request body | Field | Type | Required | Description | | ---------------- | ------------------ | -------- | ----------------------------------------------------------------------------------------------------------------- | | `addresses` | `string[]` | yes | Array of wallet addresses (Safe proxy or EOA). 1 to 100 entries per call. | | `from` | `string \| number` | no | Window start. `YYYY-MM-DD` (UTC midnight) or unix seconds. Defaults to last 30 days when omitted. | | `to` | `string \| number` | no | Window end. Same format as `from`. Defaults to current time. | | `include_trades` | `boolean` | no | When `true`, every result includes the per-fill `trades` array. Off by default — payloads can grow large quickly. | The `from`/`to` window applies to **every** wallet in the batch — pass per-wallet windows by making separate calls. `?period=` preset is **not supported** in the batch body. Use explicit `from`/`to`. ## Response ``` { count: int, elapsed_ms: int, results: [ /* one entry per address — same shape as the single-wallet endpoint */ ] } ``` Per-result fields are identical to the [single-wallet response](/api-reference/backtesting/copy-pnl#response-fields). ## Example: 3 wallets over a 10-day window Request: ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ -H "Content-Type: application/json" \ -X POST \ -d '{ "addresses": [ "0x0c73d8abc3cd918bc62d0204e33ee05c3f5a68e7", "0xba4ac793e68eacb93b41566137f25757656a9fa6", "0x4b2995b6c8cc2cf56899ad62ee87c3f70c97716f" ], "from": "2026-04-15", "to": "2026-04-25" }' \ "https://api.polynode.dev/v2/copy-pnl/batch" ``` Response (`200 OK`, abridged): ```json theme={null} { "count": 3, "elapsed_ms": 1948, "results": [ { "wallet": "0x0c73d8abc3cd918bc62d0204e33ee05c3f5a68e7", "actual_pnl_usdc": -7323.06, "backtest_copy_pnl_usdc": -9664.1, "slippage_amount_usdc": 2341.04, "slippage_cost_rate_pct": 31.97, "toxic_for_copying": true, "trade_count": 1637, "pnl_definition": "cashflow", "applied_filters": { "from": 1776211200, "to": 1777075200, "window_days": null }, "partial": false, "sources": { "...": "..." } }, { "wallet": "0xba4ac793e68eacb93b41566137f25757656a9fa6", "actual_pnl_usdc": 0, "backtest_copy_pnl_usdc": 0, "slippage_amount_usdc": 0, "slippage_cost_rate_pct": null, "toxic_for_copying": false, "trade_count": 0, "pnl_definition": "cashflow", "applied_filters": { "from": 1776211200, "to": 1777075200, "window_days": null }, "partial": false, "sources": { "...": "..." } }, { "wallet": "0x4b2995b6c8cc2cf56899ad62ee87c3f70c97716f", "actual_pnl_usdc": -2637.15, "backtest_copy_pnl_usdc": -2690.17, "slippage_amount_usdc": 53.02, "slippage_cost_rate_pct": 2.01, "toxic_for_copying": false, "trade_count": 12, "pnl_definition": "cashflow", "applied_filters": { "from": 1776211200, "to": 1777075200, "window_days": null }, "partial": false, "sources": { "...": "..." } } ] } ``` Note the second wallet — no on-chain trading activity in the window — returns the standard zero-shape with `slippage_cost_rate_pct: null` (denominator unstable per the spec edge case). Wallets that error individually return `{"wallet": "...", "error": "..."}` instead of the full shape, so a single bad wallet won't fail the whole batch. ## Errors `400 Body must include "addresses": [...]`: ```json theme={null} { "error": "Body must include \"addresses\": [...]." } ``` `400 Invalid wallet`: ```json theme={null} { "error": "Invalid wallet: not-a-wallet" } ``` `400 Too many addresses`: ```json theme={null} { "error": "Max 100 addresses per batch." } ``` Auth errors (`401`, `403`) and rate-limit errors (`429`) are identical to the single-wallet endpoint — see [Backtest Copy PnL](/api-reference/backtesting/copy-pnl#errors). ## Limits and behavior * **Max 100 addresses per call**. * **Typical batch latency** — a 100-wallet batch with average \~1.5s per wallet completes in roughly 20s. * **45-second total timeout**. Heavy batches (many high-volume wallets) may exceed — split the address list across multiple calls if so. * **Same rate limit** as the single endpoint: 1 request per 5 seconds per API key. The batch counts as one request regardless of how many wallets are inside. * **Per-wallet errors don't fail the batch.** A wallet that errors during processing returns `{"wallet": "...", "error": "..."}` in its slot. Other wallets still return their full result. * **Same `partial: true` behavior per wallet** as the single endpoint — extreme high-frequency wallets may flag partial inside their result. # BYOB — Add Wallets Source: https://docs.polynode.dev/api-reference/backtesting/byob-add-wallets POST /v2/copy-pnl/wallets Add wallets to your BYOB tracked-pool. Newly-added wallets get scored within seconds (on-add freshening). ```http theme={null} POST https://api.polynode.dev/v2/copy-pnl/wallets ``` Add one or more wallets to your private BYOB tracked-pool. Wallets are immediately added to the global refresh queue AND scored right away by the on-add freshening pass — usually within \~30 seconds for normal wallets, longer for whales. ## Request body | Field | Type | Required | Description | | ----------- | ---------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | | `addresses` | `string[]` | yes | Array of valid `0x…` wallet addresses (Safe proxy or EOA). Addresses are normalized to lowercase. Max 1000 per request. | ## Response fields | Field | Type | Description | | ----------------- | ------ | -------------------------------------------------------------------------------------------- | | `added` | int | Number of NEW wallets added to your pool (excludes those already tracked) | | `already_tracked` | int | Number of wallets in the request body that were already in your pool | | `total` | int | Current size of your pool after this request | | `max` | int | Hard cap on pool size per API key (currently 1000) | | `capped` | bool | `true` when your request would have exceeded `max` and we accepted only the first N that fit | | `refresh_kicked` | int | Number of newly-added wallets queued for immediate background scoring (matches `added`) | | `hint` | string | Only present when `capped: true` — explains how many of your wallets were dropped | ## Example: fresh add Request: ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ -H "Content-Type: application/json" \ -X POST \ -d '{ "addresses": [ "0xdead0000000000000000000000000000000000aa", "0xdead0000000000000000000000000000000000bb" ] }' \ "https://api.polynode.dev/v2/copy-pnl/wallets" ``` Response (`200 OK`): ```json theme={null} { "added": 2, "already_tracked": 0, "total": 110, "max": 1000, "capped": false, "refresh_kicked": 2 } ``` The two new wallets are now in your pool AND queued for immediate scoring. Within \~30s (faster for small wallets, longer for whales) you'll see them populated in `/v2/copy-pnl/leaderboard`. ## Example: re-add (dedupe) Wallets already in your pool are silently skipped — sending the same address twice doesn't count toward your cap. Request (one wallet already tracked, one new): ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ -H "Content-Type: application/json" \ -X POST \ -d '{ "addresses": [ "0xdead0000000000000000000000000000000000aa", "0xdead0000000000000000000000000000000000cc" ] }' \ "https://api.polynode.dev/v2/copy-pnl/wallets" ``` Response (`200 OK`): ```json theme={null} { "added": 1, "already_tracked": 1, "total": 111, "max": 1000, "capped": false, "refresh_kicked": 1 } ``` Only the genuinely new wallet (`0xdead…cc`) was added and refresh-kicked. The duplicate was a no-op. ## Errors `400 Missing or invalid addresses` (covers: missing `addresses` field, non-string in array, malformed hex): ```json theme={null} { "error": "Body must include \"addresses\" — array of valid 0x… wallet addresses." } ``` `400 Too many addresses in one request`: ```json theme={null} { "error": "At most 1000 wallets per request (and per tenant)." } ``` `401 Missing API key`, `403 Free tier`, `429 Rate limited` — same as the on-demand `/v2/copy-pnl/{wallet}` endpoint. See [Backtest Copy PnL → Errors](/api-reference/backtesting/copy-pnl#errors). ## Notes * **Wallets are normalized to lowercase.** Mixing case across requests is fine; the same wallet in different case won't double-track. * **Within a single request, duplicates are de-duped** before the dedup-against-pool check. So passing the same address twice in one body counts as one. * **Pool isolation:** each API key has its own private pool keyed on the SHA256 of your key. Adding wallets to one pool doesn't affect any other customer. * **On-add freshening** runs in the background — the HTTP response returns within milliseconds even though scoring takes 30s+ to complete. Poll `/v2/copy-pnl/leaderboard` to see scores land. * **Capacity behavior**: if your request would have pushed your pool past `max`, we accept the first N that fit and set `capped: true` plus a `hint` explaining what was dropped. We do NOT reject the entire request. # BYOB — Leaderboard Source: https://docs.polynode.dev/api-reference/backtesting/byob-leaderboard GET /v2/copy-pnl/leaderboard Sub-second sorted, filtered, paginated leaderboard over your tracked-wallet pool. Reads precomputed scores from cache. ```http theme={null} GET https://api.polynode.dev/v2/copy-pnl/leaderboard ``` Query the precomputed leaderboard over your private wallet pool. All reads come from cache — sub-second latency regardless of pool size or window. Sort by any output field, filter out toxic wallets or low-volume traders, paginate. ## Query parameters Time window the scores were computed against. One of: `7d`, `14d`, `30d`, `60d`, `90d`, `180d`. Scores are precomputed for **all six periods** so any choice is sub-second. Field to sort by. One of: `backtest_copy_pnl_usdc`, `actual_pnl_usdc`, `slippage_amount_usdc`, `slippage_cost_rate_pct`, `trade_count`. `asc` or `desc`. Wallets with `null` `slippage_cost_rate_pct` (when `abs(actual_pnl) < 1`) always sort last regardless of direction. Max results to return. 1 to 1000. Skip the first N results. Useful for paginating beyond `limit`. Filter out wallets with fewer than N fills in the window. Useful to drop sample-too-small noise. When `true`, drops wallets where `slippage_cost_rate_pct > 15`. The leader-selection power filter. ## Response shape | Field | Type | Description | | ---------------- | ------------------ | -------------------------------------------------------------------------------------------------- | | `period` | string | Echo of the requested period | | `sort_by` | string | Echo of the sort field | | `order` | string | Echo of the sort direction | | `filters` | object | `{min_trade_count, exclude_toxic}` echo | | `total_in_pool` | int | Total wallets in your pool | | `scored` | int | Wallets with a score for this period | | `filtered_in` | int | Wallets that passed the `min_trade_count` + `exclude_toxic` filters | | `pending` | int | Wallets in your pool with no score yet (newly added or queued for next refresh) | | `errored` | int | Wallets whose last refresh attempt failed (typically heavy whales hitting timeout on long windows) | | `offset` | int | Echo | | `limit` | int | Echo | | `last_refresh` | int (unix) \| null | When the last full refresh cycle completed | | `results` | array | Top N matching rows after filters and sort | | `errored_sample` | array | Up to 10 errored wallets with their last-error message — only present when `errored > 0` | | `pending_sample` | array | Up to 10 pending wallet addresses — only present when `pending > 0` | ### Per-result row | Field | Type | Description | | ------------------------ | -------------- | ----------------------------------------------------------------------------------------- | | `local_rank` | int | 1-indexed rank within your pool (after offset) | | `wallet` | string | Lowercased address | | `actual_pnl_usdc` | number | Cashflow PnL over the period | | `backtest_copy_pnl_usdc` | number | Same walk with 2% slippage | | `slippage_amount_usdc` | number | Dollar friction the copier eats | | `slippage_cost_rate_pct` | number \| null | Friction as % of actual PnL (null when \|actual\| \< \$1) | | `toxic_for_copying` | bool | `true` when rate > 15% | | `trade_count` | int | Number of fills in the window | | `partial` | bool | `true` if the underlying walk hit the per-wallet 180s budget | | `computed_at` | int (unix) | When this specific score was last refreshed — surface this in your UI as freshness signal | ## Example: top 3 by actual\_pnl\_usdc desc ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ "https://api.polynode.dev/v2/copy-pnl/leaderboard?period=30d&sort_by=actual_pnl_usdc&order=desc&limit=3" ``` Response (`200 OK`): ```json theme={null} { "period": "30d", "sort_by": "actual_pnl_usdc", "order": "desc", "filters": { "min_trade_count": 0, "exclude_toxic": false }, "total_in_pool": 108, "scored": 104, "filtered_in": 104, "pending": 1, "errored": 3, "offset": 0, "limit": 3, "last_refresh": 1777521943, "results": [ { "local_rank": 1, "wallet": "0x492442eab586f242b53bda933fd5de859c8a3782", "actual_pnl_usdc": 22915787.92, "backtest_copy_pnl_usdc": 22129678.06, "slippage_amount_usdc": 786109.86, "slippage_cost_rate_pct": 3.43, "toxic_for_copying": false, "trade_count": 8045, "partial": false, "computed_at": 1777522146 } ] } ``` ## Example: leader screening — best non-toxic, min volume ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ "https://api.polynode.dev/v2/copy-pnl/leaderboard?period=30d&sort_by=slippage_cost_rate_pct&order=asc&exclude_toxic=true&min_trade_count=1000&limit=3" ``` Response (`200 OK`): ```json theme={null} { "period": "30d", "sort_by": "slippage_cost_rate_pct", "order": "asc", "filters": { "min_trade_count": 1000, "exclude_toxic": true }, "total_in_pool": 108, "scored": 104, "filtered_in": 50, "pending": 2, "errored": 2, "offset": 0, "limit": 3, "results": [ { "local_rank": 1, "wallet": "0x9c16127eccf031df45461ef1e04b52ea286a09cb", "actual_pnl_usdc": 102171.68, "backtest_copy_pnl_usdc": 101643.41, "slippage_amount_usdc": 528.27, "slippage_cost_rate_pct": 0.52, "toxic_for_copying": false, "trade_count": 9472, "partial": false, "computed_at": 1777521515 } ] } ``` This is the canonical "find good leaders to copy" query: lowest slippage rate, but only among wallets that have actually traded enough (`min_trade_count: 1000`) and aren't already flagged toxic. ## Example: pagination (offset) ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ "https://api.polynode.dev/v2/copy-pnl/leaderboard?period=30d&offset=10&limit=3" ``` Response includes results with `local_rank: 11, 12, 13` — i.e. ranks within your pool start at `offset + 1`. The default sort is `backtest_copy_pnl_usdc desc`. ## Errors `400 Invalid period`: ```json theme={null} { "error": "Invalid period. Allowed: 7d, 14d, 30d, 60d, 90d, 180d" } ``` `400 Invalid sort_by`: ```json theme={null} { "error": "Invalid sort_by. Allowed: backtest_copy_pnl_usdc, actual_pnl_usdc, slippage_amount_usdc, slippage_cost_rate_pct, trade_count" } ``` `400 Invalid order`: ```json theme={null} { "error": "Invalid order. Allowed: asc | desc" } ``` Auth/rate-limit errors mirror the rest of the `/v2/copy-pnl/*` family. ## Notes * **Sub-second.** All scores are precomputed and served from cache. Even a 1000-wallet pool with all six periods scored returns in under 100ms. * **`pending` and `errored` aren't included in `results`.** Use `pending_sample` and `errored_sample` to identify which specific wallets need attention. The `errored_sample` includes the upstream error message — typically `timeout_180s` for the heaviest whales on long windows. * **`computed_at` per row.** Use this to render "as of X minutes ago" in your UI. Newly-added wallets (via on-add freshening) typically have a `computed_at` within \~30s of the add. Periodic refresh updates it once per chunk slot. * **`last_refresh` at top level.** When the most recent full refresh cycle completed. Individual wallets may have been refreshed earlier or later within the cycle — use the per-row `computed_at` field for exact freshness. * **Sort ties + nulls.** When two wallets share the exact sort\_by value, secondary order is undefined. Wallets with `null` `slippage_cost_rate_pct` always sort last when sorting on that field. # BYOB — List Wallets Source: https://docs.polynode.dev/api-reference/backtesting/byob-list-wallets GET /v2/copy-pnl/wallets Return every wallet in your BYOB tracked-pool, sorted lexicographically. ```http theme={null} GET https://api.polynode.dev/v2/copy-pnl/wallets ``` Returns every wallet currently in your private BYOB pool. Useful for showing your tracked-set in a UI, exporting, or auditing what's queued for the leaderboard. ## Response fields | Field | Type | Description | | --------- | ---------- | ------------------------------------------------------------------------------ | | `wallets` | `string[]` | Every wallet in your pool, lowercased, sorted lexicographically (stable order) | | `count` | int | Length of the `wallets` array | | `max` | int | Hard cap on pool size per API key (1000) | ## Example Request: ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ "https://api.polynode.dev/v2/copy-pnl/wallets" ``` Response (`200 OK`): ```json theme={null} { "wallets": [ "0x000d257d2dc7616feaef4ae0f14600fdf50a758e", "0x01c78f8873c0c86d6b6b92ff627e3802237ee995", "0x034475def048324ad32d87c5fd90e99f0f7b2538", "0x090a9efe46c0e42e4fd598cd81be011ad72f27e7", "0x0979bad57d7a1403db89cbcd9c52bf43f2138d9b" ], "count": 108, "max": 1000 } ``` ## Notes * **Sorted output.** Wallets are returned in lexicographic order, not in the order you added them. The order is stable — useful for paginating through your own pool client-side without surprises. * **No pagination on this endpoint.** The full list returns in one response. With `max: 1000` and \~42 bytes per wallet address, the largest possible payload is well under 50 KB. * **Pool isolation.** Only wallets added by THIS API key are returned. No way to see other customers' pools. * **Includes wallets that haven't been scored yet.** A wallet you just added via `POST /v2/copy-pnl/wallets` shows here immediately, even if its `computed_at` won't appear in the leaderboard for \~30 seconds while on-add freshening runs. # BYOB — Remove Wallets Source: https://docs.polynode.dev/api-reference/backtesting/byob-remove-wallets DELETE /v2/copy-pnl/wallets Remove wallets from your BYOB tracked-pool. Removal is idempotent — re-removing a wallet that's not in the pool returns 0 with no error. ```http theme={null} DELETE https://api.polynode.dev/v2/copy-pnl/wallets ``` Remove one or more wallets from your private BYOB pool. Wallets not in the pool (or already removed) are silently skipped — `removed` reflects only how many wallets were actually present. ## Request body | Field | Type | Required | Description | | ----------- | ---------- | -------- | ------------------------------------------------------------------------------- | | `addresses` | `string[]` | yes | Array of `0x…` wallet addresses to remove. Wallets not in your pool are no-ops. | ## Response fields | Field | Type | Description | | --------- | ---- | ------------------------------------------------- | | `removed` | int | Number of wallets actually removed from your pool | | `total` | int | Current size of your pool after removal | | `max` | int | Hard cap on pool size per API key (1000) | ## Example: remove an existing wallet Request: ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ -H "Content-Type: application/json" \ -X DELETE \ -d '{ "addresses": [ "0xfeed0000000000000000000000000000000000aa", "0xfeed0000000000000000000000000000000000ff" ] }' \ "https://api.polynode.dev/v2/copy-pnl/wallets" ``` Response (`200 OK`): ```json theme={null} { "removed": 1, "total": 109, "max": 1000 } ``` Only the first wallet was actually in the pool — the second was never tracked, so it's a silent no-op. The response tells you exactly how many were touched. ## Example: idempotent re-remove Calling delete on a wallet already removed is safe — returns `removed: 0` with no error. Request: ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ -H "Content-Type: application/json" \ -X DELETE \ -d '{"addresses": ["0xfeed0000000000000000000000000000000000aa"]}' \ "https://api.polynode.dev/v2/copy-pnl/wallets" ``` Response (`200 OK`): ```json theme={null} { "removed": 0, "total": 109, "max": 1000 } ``` This makes "set my pool to exactly this list" workflows simple — DELETE old, POST new, both safe to re-run. ## Errors `400 Missing or invalid addresses`: ```json theme={null} { "error": "Body must include \"addresses\" — array of valid 0x… wallet addresses." } ``` Same auth/rate-limit errors as the rest of the `/v2/copy-pnl/*` family. ## Notes * **Removal is per-tenant.** Removing a wallet from your pool does NOT remove it from the global refresh queue if other tenants still track it — the wallet's score keeps refreshing for them. Your private query just stops returning that wallet. * **Cached scores survive briefly.** The wallet's last-computed score stays in our cache (24h TTL). If you re-add the same wallet within 24h, the leaderboard will show its existing score immediately while the on-add freshening kicks off a fresh recompute. * **No bulk-clear endpoint.** To wipe your entire pool, GET your wallet list first, then DELETE all of them in chunks of 1000. # BYOB — Snapshot Source: https://docs.polynode.dev/api-reference/backtesting/byob-snapshot GET /v2/copy-pnl/snapshot Every wallet in your tracked pool with backtest scores across every period in a single response. The headline read for stats-card UIs. ```http theme={null} GET https://api.polynode.dev/v2/copy-pnl/snapshot ``` Returns every wallet currently in your BYOB pool together with their backtest copy-PnL scores across all six time windows (or a chosen subset). One request — no per-period round trips, no client-side stitching. This is the canonical read for any UI that renders stats cards, dashboards, or grids over the entire tracked-wallet set. Pair it with `GET /wallets` if you need to render the empty-state pool too. ## Query parameters Comma-separated subset of periods to include. Defaults to all six. Useful when you only render a single time window — e.g. `?periods=30d` returns \~1/6 the payload. ## Response shape | Field | Type | Description | | --------------- | ------------------ | ---------------------------------------------------------------------------------------- | | `total_in_pool` | int | Wallets currently in your pool | | `scored_any` | int | Wallets with a score for at least one of the requested periods | | `errored_any` | int | Wallets whose last refresh attempt failed (heavy whales hitting timeout on long windows) | | `pending_any` | int | Wallets with no score yet (newly added or queued for the next refresh) | | `periods` | string\[] | Echo of the periods returned (matches input or default order) | | `last_refresh` | int (unix) \| null | When the last full refresh cycle completed | | `wallets` | array | One entry per pooled wallet (see below) | ### Per-wallet entry | Field | Type | Description | | --------- | ------ | ---------------------------------------------------------------------------------------------------------------- | | `wallet` | string | Lowercased address | | `periods` | object | Map keyed by period (`7d`, `14d`, …). Each value is the score blob, or `null` if that specific period is pending | | `error` | object | Only present if this wallet's last refresh errored. `{ error: string, ts: int }` | ### Per-period score blob | Field | Type | Description | | ------------------------ | -------------- | --------------------------------------------------------------- | | `actual_pnl_usdc` | number | Cashflow PnL the wallet realized over the period | | `backtest_copy_pnl_usdc` | number | Same trade walk with 2 % slippage on every entry/exit | | `slippage_amount_usdc` | number | Dollar friction the copier eats | | `slippage_cost_rate_pct` | number \| null | Friction as % of `\|actual_pnl\|` (null when `\|actual\| < $1`) | | `toxic_for_copying` | bool | `true` when rate > 15 % | | `trade_count` | int | Number of fills in the window | | `partial` | bool | `true` if the underlying walk hit the per-wallet 180 s budget | | `computed_at` | int (unix) | When this specific score was last refreshed | ## Examples ### Full snapshot (default) ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ "https://api.polynode.dev/v2/copy-pnl/snapshot" ``` Response (`200 OK`, abridged): ```json theme={null} { "total_in_pool": 108, "scored_any": 107, "errored_any": 1, "pending_any": 0, "periods": ["7d", "14d", "30d", "60d", "90d", "180d"], "last_refresh": 1777545636, "wallets": [ { "wallet": "0x000d257d2dc7616feaef4ae0f14600fdf50a758e", "periods": { "7d": { "actual_pnl_usdc": 104635.85, "backtest_copy_pnl_usdc": 101463.76, "slippage_amount_usdc": 3172.09, "slippage_cost_rate_pct": 3.03, "toxic_for_copying": false, "trade_count": 90, "partial": false, "computed_at": 1777544903 }, "14d": { "actual_pnl_usdc": -123675.90, "backtest_copy_pnl_usdc": -134838.05, "slippage_amount_usdc": 11162.15, "slippage_cost_rate_pct": 9.03, "toxic_for_copying": false, "trade_count": 458, "partial": false, "computed_at": 1777544903 }, "30d": { "actual_pnl_usdc": 317864.92, "backtest_copy_pnl_usdc": 266790.44, "slippage_amount_usdc": 51074.48, "slippage_cost_rate_pct": 16.07, "toxic_for_copying": true, "trade_count": 2253, "partial": false, "computed_at": 1777544903 }, "60d": { "actual_pnl_usdc": -66101.80, "backtest_copy_pnl_usdc": -181792.53, "slippage_amount_usdc": 115690.73, "slippage_cost_rate_pct": 175.02, "toxic_for_copying": true, "trade_count": 5916, "partial": false, "computed_at": 1777544903 }, "90d": { "actual_pnl_usdc": 289034.81, "backtest_copy_pnl_usdc": 122089.67, "slippage_amount_usdc": 166945.14, "slippage_cost_rate_pct": 57.76, "toxic_for_copying": true, "trade_count": 8761, "partial": false, "computed_at": 1777544903 }, "180d": { "actual_pnl_usdc": 665955.87, "backtest_copy_pnl_usdc": 311352.42, "slippage_amount_usdc": 354603.45, "slippage_cost_rate_pct": 53.25, "toxic_for_copying": true, "trade_count": 18545, "partial": false, "computed_at": 1777544903 } } } ] } ``` ### Subset of periods ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ "https://api.polynode.dev/v2/copy-pnl/snapshot?periods=7d,30d" ``` Returns the same shape but with only the requested periods inside each `wallets[].periods` object. Use this when your UI only renders a single window — payload drops proportionally. ## Performance | Pool size | Periods | Payload | Latency (warm) | | ------------ | ------- | -------- | -------------- | | 100 wallets | all 6 | \~150 KB | \~70 ms | | 100 wallets | 1 | \~30 KB | under 30 ms | | 1000 wallets | all 6 | \~1.5 MB | \~500 ms | | 1000 wallets | 1 | \~250 KB | \~100 ms | All reads come from the precomputed score cache. ## Notes * **`scored_any` vs `scored` on the leaderboard.** A wallet counts as `scored_any` here if it has a score for *at least one* of the requested periods. The leaderboard's `scored` is per-requested-period. * **Per-period `null`.** A wallet may be scored for `30d` but `null` for `180d` (heavy whales often time out on long windows). Read each period's value independently. * **`error` is wallet-global.** A single `error` object per wallet covers the most recent refresh failure across any period. Use it to surface "X wallets retrying" in your UI. * **No pagination.** This endpoint always returns the full pool. Use the leaderboard endpoint with `limit`/`offset` if you need server-side paging — that's the trade-off vs the one-shot snapshot. * **Race-safe with adds.** Wallets you add via `POST /v2/copy-pnl/wallets` show up here immediately, with `null` periods until the on-add freshening finishes (\~30 s). # Backtest Copy PnL Source: https://docs.polynode.dev/api-reference/backtesting/copy-pnl GET /v2/copy-pnl/{wallet} Score a wallet for copy-trading quality. Returns realized PnL, cashflow PnL, simulated copier PnL with 2% slippage friction, and a toxic-for-copying flag. ```http theme={null} GET https://api.polynode.dev/v2/copy-pnl/{wallet} ``` ## TL;DR — which PnL field do you actually want? This endpoint returns **two different PnL numbers** that can look very different for the same wallet. Pick the right one for your use case: | Field | What it answers | When to use | | ------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `total_realized_pnl_usdc` | "Did this wallet **make money** trading?" | **Default for customer-facing UI.** Closest to what other trading platforms call "PnL". Equivalent to summing per-position WAVG-realized PnL across every position the wallet touched. | | `actual_pnl_usdc` | "How much **net cash** moved through this wallet's USDC balance from trading?" | Diagnostic / slippage-analysis. Used for `slippage_*` fields. Inflated for active wallets that hold inventory. | **Both numbers are correct** — they measure different things. See [the worked example below](#understanding-cashflow-pnl-vs-realized-pnl) for the math. ## Path parameters The Polymarket wallet address (Safe proxy or EOA, normalized to lowercase). ## Query parameters Convenience window preset. One of: `7d`, `14d`, `30d`, `60d`, `90d`, `180d`. Anchored to "now". If you pass `period`, you don't need `from`. Start of the window. Accepts `YYYY-MM-DD` (UTC midnight) or unix seconds. Overrides `period` if both are passed. End of the window. Same format as `from`. Defaults to current time when omitted. When `1` or `true`, response includes a `trades` array with one entry per fill (`ts`, `side`, `price`, `shares`, `actual_usd`, `backtest_usd`). Useful for transparency and debugging. ## Response fields | Field | Type | Description | | | | | | | | ----------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----- | -------- | --------------- | -------- | --------- | | `wallet` | string | Echo of the requested wallet (lowercased) | | | | | | | | **`total_realized_pnl_usdc`** | **number** | **Realized PnL: signed sum of per-position WAVG-realized P/L across every position the matcher touched in the window. Counts only profit/loss on positions the wallet actually closed (or partially closed). Recommended primary "PnL" for customer UI.** | | | | | | | | `actual_pnl_usdc` | number | **Cashflow PnL** over the window: `-buys + sells + settlements`. Counts every USDC inflow/outflow from trading. Includes inventory bought but not yet sold (so often very negative for active buyers). Used as input to slippage formula. | | | | | | | | `backtest_copy_pnl_usdc` | number | Same cashflow walk with 2% slippage applied to buys (capped at \$1) and sells. Settlements unchanged. | | | | | | | | `slippage_amount_usdc` | number | `actual_pnl_usdc − backtest_copy_pnl_usdc`. The dollar friction the copier eats. | | | | | | | | `slippage_cost_rate_pct` | number \| null | `slippage / abs(actual_pnl) × 100`. `null` when `abs(actual_pnl) < 1` (denominator unstable). | | | | | | | | `toxic_for_copying` | boolean | `true` when `slippage_cost_rate_pct > 15`. | | | | | | | | `trade_count` | int | Number of fills walked in the window. | | | | | | | | `positions_closed` | int | Count of positions that contributed non-zero realized PnL in the window. | | | | | | | | `avg_entry_prob_weighted` | number \| null | PnL-weighted average entry probability across closed positions. \`Σ(entry\_price × | realized | ) / Σ | realized | `. Null when `Σ | realized | \< \$1\`. | | `avg_hold_seconds_weighted` | number \| null | PnL-weighted average hold duration. Currently always `null` (WAVG matcher doesn't track per-segment timestamps; planned for future). | | | | | | | | `pnl_definition` | string | Always `"cashflow"` for `actual_pnl_usdc`. Reminds callers `actual_pnl_usdc` differs from PM website PnL (which marks open positions). | | | | | | | | `applied_filters` | object | Resolved `from` (unix), `to` (unix), and `window_days` (only set when default 30d window was used). | | | | | | | | `partial` | boolean | `true` if the underlying walk hit the 30s budget and returned incomplete data. | | | | | | | | `sources` | object | Breakdown: `window_trades`, `window_activity`, per-event-type counts, `cashflow_breakdown` (buys/sells/settlements), `fifo_breakdown` (matcher diagnostics including `total_realized_pnl_usdc`). For transparency. | | | | | | | | `trades` | array | Only present when `include_trades=1`. One entry per fill. | | | | | | | ## Understanding cashflow PnL vs realized PnL The most common source of confusion with this endpoint is "why don't `actual_pnl_usdc` and `total_realized_pnl_usdc` agree?" — they're answering different questions. **Worked example.** A wallet's first 30 days: ``` Day | Action | actual_pnl_usdc | total_realized_pnl_usdc -----+-----------------------+-----------------+------------------------- 1 | BUY 100 YES @ $0.40 | -$40.00 | $0.00 5 | SELL 50 YES @ $0.60 | -$10.00 | +$10.00 10 | BUY 100 YES @ $0.50 | -$60.00 | +$10.00 15 | SELL 80 YES @ $0.55 | -$16.00 | +$17.20 30 | (still hold 70) | -$16.00 | +$17.20 ``` **Final state explained:** * `actual_pnl_usdc = -$16` — the wallet paid out \$16 more USDC than it received over the 30 days. * `total_realized_pnl_usdc = +$17.20` — actual trading profit on the 130 shares the wallet closed. * The wallet still HOLDS 70 shares (cost basis \$32.20). Cashflow counts that as money "spent". Realized ignores it. **Reconciliation:** `cashflow_pnl + cost_basis_of_open_positions ≈ realized_pnl`. In this example: `-$16 + $32.20 = $16.20 ≈ $17.20` (small rounding from WAVG cost basis updates). **Which to use:** * For "did the wallet make money trading?" → **`total_realized_pnl_usdc`**. This is what trading platforms typically call "PnL" and what users intuitively expect. * For "how much net cash flowed through this wallet?" → `actual_pnl_usdc`. Useful for liquidity analysis and the slippage formula, but **not** what most people mean by "PnL". * For copy-trading slippage analysis → use `slippage_*` fields (which are derived from cashflow). The slippage formula needs apples-to-apples cashflow comparison; this is why `actual_pnl_usdc` is defined as cashflow. **One-line summary:** realized = closed-trade profit; cashflow = USDC delta in/out of the wallet. They reconcile when the wallet holds zero open inventory. *** ## Example: default 30-day window Request: ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ "https://api.polynode.dev/v2/copy-pnl/0x0c73d8abc3cd918bc62d0204e33ee05c3f5a68e7" ``` Response (`200 OK`): ```json theme={null} { "wallet": "0x0c73d8abc3cd918bc62d0204e33ee05c3f5a68e7", "actual_pnl_usdc": -5697.9, "backtest_copy_pnl_usdc": -8932.81, "slippage_amount_usdc": 3234.91, "slippage_cost_rate_pct": 56.77, "toxic_for_copying": true, "trade_count": 1726, "avg_entry_prob_weighted": 0.4892, "avg_hold_seconds_weighted": null, "positions_closed": 142, "total_realized_pnl_usdc": -2412.06, "pnl_definition": "cashflow", "applied_filters": { "from": 1774916101, "to": 1777508101, "window_days": 30 }, "partial": false, "sources": { "window_trades": 1726, "window_activity": 51, "activity_breakdown": { "redemption": 51 }, "cashflow_breakdown": { "actual_buy_cost": 85766.5, "actual_sell_rev": 77266.87, "settlement_in": 2801.73, "settlement_out": 0 }, "fifo_breakdown": { "over_sells": 3, "unresolved_activity": 0, "total_abs_pnl_usdc": 4823.18, "total_realized_pnl_usdc": -2412.06 }, "fetch_ms": 1361 } } ``` Reading this response: the wallet's **realized** trading P/L is \*\*-$2,412** (lost ~$2.4K on 142 positions that closed in the window). The **cashflow** says \*\*-$5,697** — the extra $3,285 is the cost basis of positions still open at window-end (inventory the wallet bought but hasn't sold yet). For "did this wallet make money?" the answer is `total_realized_pnl_usdc` — and the answer is no. ## Example: 7-day window via preset Request: ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ "https://api.polynode.dev/v2/copy-pnl/0x9977760c6bd6f824cac834d1a36ee99478d63020?period=7d" ``` Response (`200 OK`): ```json theme={null} { "wallet": "0x9977760c6bd6f824cac834d1a36ee99478d63020", "actual_pnl_usdc": 8082.45, "backtest_copy_pnl_usdc": 7077.07, "slippage_amount_usdc": 1005.38, "slippage_cost_rate_pct": 12.44, "toxic_for_copying": false, "trade_count": 1753, "avg_entry_prob_weighted": 0.5210, "avg_hold_seconds_weighted": null, "positions_closed": 87, "total_realized_pnl_usdc": 6428.91, "pnl_definition": "cashflow", "applied_filters": { "from": 1776903319, "to": 1777508119, "window_days": null }, "partial": false, "sources": { "window_trades": 1753, "window_activity": 144, "activity_breakdown": { "redemption": 144 }, "cashflow_breakdown": { "actual_buy_cost": 339438.1, "actual_sell_rev": 596.28, "settlement_in": 346924.27, "settlement_out": 0 }, "fifo_breakdown": { "over_sells": 0, "unresolved_activity": 0, "total_abs_pnl_usdc": 12857.82, "total_realized_pnl_usdc": 6428.91 }, "fetch_ms": 632 } } ``` ## Example: explicit date range Request — score the window from April 15 to April 25 inclusive: ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ "https://api.polynode.dev/v2/copy-pnl/0x9977760c6bd6f824cac834d1a36ee99478d63020?from=2026-04-15&to=2026-04-25" ``` Response (`200 OK`): ```json theme={null} { "wallet": "0x9977760c6bd6f824cac834d1a36ee99478d63020", "actual_pnl_usdc": -70450.73, "backtest_copy_pnl_usdc": -74413.34, "slippage_amount_usdc": 3962.61, "slippage_cost_rate_pct": 5.62, "toxic_for_copying": false, "trade_count": 3787, "pnl_definition": "cashflow", "applied_filters": { "from": 1776211200, "to": 1777075200, "window_days": null }, "partial": false, "sources": { "...": "..." } } ``` ## Example: per-fill drill-down Request: ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ "https://api.polynode.dev/v2/copy-pnl/0x0c73d8abc3cd918bc62d0204e33ee05c3f5a68e7?period=7d&include_trades=1" ``` Response (`200 OK`, truncated): ```json theme={null} { "wallet": "0x0c73d8abc3cd918bc62d0204e33ee05c3f5a68e7", "actual_pnl_usdc": -5027.89, "backtest_copy_pnl_usdc": -6783.42, "slippage_amount_usdc": 1755.53, "slippage_cost_rate_pct": 34.92, "toxic_for_copying": true, "trade_count": 838, "pnl_definition": "cashflow", "trades": [ { "ts": 1777232322, "side": "BUY", "price": 0.7916, "shares": 252.66, "actual_usd": 200, "backtest_usd": 204 }, { "ts": 1777232322, "side": "SELL", "price": 0.21, "shares": 48, "actual_usd": 10.08, "backtest_usd": 9.88 } ] } ``` ## Errors `400 Invalid period`: ```json theme={null} { "error": "Invalid period. Allowed: 7d, 14d, 30d, 60d, 90d, 180d" } ``` `401 No API key`: ```json theme={null} { "error": "API key required. Pass via ?key= or x-api-key header." } ``` `403 Free tier`: ```json theme={null} { "error": "V2 endpoints require a paid plan. See polynode.dev/pricing for details." } ``` `429 Rate limited`: ```json theme={null} { "error": "copy-pnl rate limited (1 req per 5s per key).", "retryAfterMs": 4231 } ``` The `Retry-After` header (in seconds, rounded up) is also set on 429 responses. The rate limit is shared across all `/v2/copy-pnl/*` calls per API key — calling the endpoint with different wallets or different params still counts toward the same 1 req per 5 seconds budget. ## Notes * **Wallet address is case-insensitive**. Internally lowercased. * **Time precision**: when `from` is `YYYY-MM-DD`, it resolves to that date at 00:00 UTC. Pass unix seconds for sub-day precision. * **Settlement events** (redemption, merge, split, neg\_risk\_conversion) are processed at face value on both `actual_pnl` and `backtest_copy_pnl`, so they cancel out in `slippage_amount` but remain in the absolute PnL numbers. * **High-volume wallets**: the underlying walk is paginated by time bucket and runs in parallel. Wallets with up to \~1.6M fills in the window have been validated. Beyond that, pass a tighter window or expect `partial: true`. # Backtesting Overview Source: https://docs.polynode.dev/api-reference/backtesting/overview Score any wallet for copy-trading quality. Walks every fill in a window, applies realistic slippage, returns a single rate that flags toxic-to-copy wallets. The backtesting endpoint answers one question for any Polymarket wallet: > "If I had copied every trade this wallet made, how much would slippage friction have eaten?" You pass a wallet and a time window. We walk every fill in that window, apply a realistic 2% slippage on each buy and sell (capped at \$1.00 per share for buys), settle redemptions / merges / splits at face value, and return both the wallet's actual cashflow PnL and the simulated copier's PnL. The dollar gap between them — divided by the trader's actual PnL — is the **slippage cost rate**. A wallet with a high slippage rate (>15%) profits primarily from execution speed and tight spreads. A copier following them with normal latency and slippage won't replicate the returns. We flag those as `toxic_for_copying`. ## How the math works For each fill in the window: * **BUY**: copier pays `price × 1.02 × shares`, capped at `1.00 × shares` * **SELL**: copier receives `price × 0.98 × shares` For each settlement event (redemption, merge, split, neg\_risk\_conversion): * Processed at face value, no slippage applied to either side ``` actual_pnl = -sum(buy_usd) + sum(sell_usd) + settlements_in - settlements_out backtest_pnl = -sum(buy_usd × slippage_mult) + sum(sell_usd × 0.98) + settlements_in - settlements_out slippage_amount = actual_pnl - backtest_pnl slippage_cost_rate_pct = slippage_amount / abs(actual_pnl) × 100 toxic_for_copying = slippage_cost_rate_pct > 15 ``` When `abs(actual_pnl) < $1`, the rate denominator is unstable and we return `null` for the rate. ## What "PnL" means here `actual_pnl_usdc` is **cashflow PnL** over the requested window: the real dollars that moved through this wallet's Safe. It does **not** mark open positions to current price the way Polymarket's website does. The response includes a `pnl_definition: "cashflow"` field to make this explicit. This is the right number for copy-trading quality scoring because a copier eventually realizes their open positions too — what matters is the friction-adjusted dollars actually banked, not paper marks that can evaporate. If you want PM-website style realized + unrealized PnL, use the [Trader PnL Series](/api-reference/enriched/trader-pnl) endpoint. ### Per-position metrics: parity with Polymarket The `avg_entry_prob_weighted` and `positions_closed` fields are computed via per-position weighted-average cost basis matching across every fill, split, merge, redemption, and neg-risk conversion in the requested window — the same method used to match Polymarket's own data-api. Validation against `data-api.polymarket.com/positions` `realizedPnl` across 30 positions on diverse wallets (standard CTF + neg-risk markets): | Threshold | Match rate | | --------- | ---------- | | sub-penny | 97 % | | sub-\$1 | **100 %** | | sub-\$10 | **100 %** | If your wallet has positions on Polymarket's positions page, the per-position basis our matcher tracks is the same one Polymarket displays. Any drift larger than \$1 indicates a data-fetching issue (e.g. very old activity outside our event-fetch window), not an algorithm difference. ## Endpoints **Score on demand:** | Method | Endpoint | Description | | ------ | ----------------------- | ----------------------------------- | | `GET` | `/v2/copy-pnl/{wallet}` | Score one wallet over a time window | | `POST` | `/v2/copy-pnl/batch` | Score up to 100 wallets in one call | **BYOB — Bring Your Own Backtest** (precomputed leaderboard): | Method | Endpoint | Description | | -------- | -------------------------- | ------------------------------------------------------ | | `POST` | `/v2/copy-pnl/wallets` | Add wallets to your private tracked-pool | | `DELETE` | `/v2/copy-pnl/wallets` | Remove wallets from your pool | | `GET` | `/v2/copy-pnl/wallets` | List wallets in your pool | | `GET` | `/v2/copy-pnl/leaderboard` | Sorted, filtered, paginated leaderboard over your pool | ## BYOB — when to use it The on-demand endpoints (`GET /v2/copy-pnl/{wallet}` and `POST /v2/copy-pnl/batch`) are **synchronous** — you wait while we walk the wallet's history, which can take up to 30 seconds per wallet. Great for one-off lookups. **BYOB inverts that.** You hand us a list of wallets you care about; we precompute their copy-pnl scores in the background across multiple time windows; you query the resulting leaderboard with sub-second latency. Best for: * Picking leader wallets to surface in your UI from a candidate pool * Daily-refreshed dashboards * Anywhere you need "top N by backtest\_copy\_pnl\_usdc, excluding toxic, with min trade count" answered fast **How it works:** 1. POST your wallet list to `/v2/copy-pnl/wallets` (max 1000 wallets per API key) 2. Newly-added wallets get scored immediately (on-add freshening — usually within \~30s of the add) 3. The full pool also refreshes daily in the background 4. Query `/v2/copy-pnl/leaderboard` to read sorted/filtered results — all from cache, sub-second 5. Each result row includes `computed_at` so you can show freshness in your UI **Tenant isolation:** each API key has its own private pool. Pools are keyed on the SHA256 of your API key — your wallets are never visible to other customers. ## Time-window options * **Default**: last 30 days (when no `from`, `to`, or `period` is provided) * **Preset**: `?period=7d|14d|30d|60d|90d|180d` * **Explicit**: `?from=YYYY-MM-DD` and/or `?to=YYYY-MM-DD` (also accepts unix seconds) * **Precedence**: explicit `from`/`to` beats `period` beats default For BYOB, scores are precomputed for **all six period presets** (7/14/30/60/90/180 days) so the leaderboard query just picks one. ## Time-window options * **Default**: last 30 days (when no `from`, `to`, or `period` is provided) * **Preset**: `?period=7d|14d|30d|60d|90d|180d` * **Explicit**: `?from=YYYY-MM-DD` and/or `?to=YYYY-MM-DD` (also accepts unix seconds) * **Precedence**: explicit `from`/`to` beats `period` beats default ## Limits and behavior **On-demand endpoints (`{wallet}` + `batch`):** * **Paid tier required** — free tier returns 403 * **Rate limit**: 1 request per 5 seconds per API key (backtest is compute-heavy) * **Server budget**: 30 second hard cap on the underlying walk. Extreme high-frequency wallets (1M+ fills in the window) may return `partial: true` — pass a tighter window to fit in budget * **Maximum window**: `180d` (6 months). Past that, even normal wallets exceed budget * **Batch**: max 100 wallets per call **BYOB:** * **Pool size**: 1000 wallets per API key max * **Refresh cadence**: 24 hour periodic cycle, processed in chunks of 50 wallets spread across the day. Plus on-add freshening (new wallets scored immediately, usually within seconds). * **Per-wallet budget on background refresh**: 180 seconds (3 minutes) — longer than the on-demand 30s cap because the customer isn't waiting on the response * **Leaderboard query latency**: sub-second (all reads from cache) * **Freshness signal**: every result row includes `computed_at` (unix ts of when that score was last computed); top-level response includes `last_refresh` (last full-cycle complete) ## FAQ: Why do two traders on the same market get different rates? The math (2% friction per fill) is identical for every wallet. What changes between traders is **how many fills they need** to extract their PnL, and how big that PnL is. The rate is `slippage / |actual_pnl|` — same numerator math, different denominators and trade counts. The cleanest rule of thumb: **the more trades it takes to extract a given dollar of PnL, the less of that PnL survives reproduction.** | Wallet | Strategy | Actual PnL | Trades | Slip \$ | Rate | Copyable? | | ------------- | ---------------------------------------------------------- | ---------- | -----: | ------: | ----------------: | ---------------------------------- | | `0x4924…3782` | Concentrated whale: few large positions held to resolution | \$20.0M | 8K | \$786K | **3.9 %** | Yes — copier captures \~\$19.2M | | `0x37c1…74a6` | Profitable scalper: tight spreads, fast execution | +\$755K | 93K | \$925K | **122 %** (toxic) | No — copier ends up at -\$170K | | `0xee61…fc18` | High-frequency loser: many fills for tiny per-trade margin | -\$132K | 303K | \$929K | **705 %** (toxic) | No — copier loses 7x what they did | All three traded across the same broad set of Polymarket markets in the same 30-day window. The rate does not measure "is this trader good?" — it measures **"how much of their edge survives reproduction by a copier with realistic friction?"** * **Strategic edge** (market reads, conviction, timing of large positions) → low rate, reproducible * **Execution edge** (HFT, queue priority, spread capture) → high rate, not reproducible A wallet can be massively profitable AND uncopyable. The rate tells you which kind they are before you wire money to copy them. # Add Wallets Source: https://docs.polynode.dev/api-reference/byol/add-wallets POST /v2/leaderboard/wallets Add wallets to your tracked leaderboard set. Add one or more Polymarket proxy wallet addresses to your BYOL set. Newly added wallets are tracked immediately in the background and will appear in your leaderboard queries within seconds. ``` POST /v2/leaderboard/wallets ``` ## Authentication Pass your API key via query parameter `?key=`, header `x-api-key`, or `Authorization: Bearer`. ## Request body ```json theme={null} { "wallets": [ "0x94f199fb7789f1aef7fff6b758d6b375100f4c7a", "0xe9ad918c7678cd38b12603a762e638a5d1ee7091" ] } ``` Array of Polymarket proxy wallet addresses. Each must be a valid Ethereum address (`0x` + 40 hex characters). Maximum 500 wallets per request. ## Response ```json theme={null} { "added": 2, "already_tracked": 0, "total": 106, "limit": 5000 } ``` | Field | Type | Description | | ----------------- | ------ | -------------------------------------------------------------------- | | `added` | number | Number of new wallets added | | `already_tracked` | number | Wallets that were already in your set (not counted toward the limit) | | `total` | number | Total wallets now in your set | | `limit` | number | Maximum wallets allowed (5,000) | ## Deduplication If you add wallets that are already in your set, they are silently ignored. This means you can safely re-send your full wallet list without worrying about duplicates. ```json theme={null} // Request: adding 2 wallets that are already tracked { "wallets": [ "0x94f199fb7789f1aef7fff6b758d6b375100f4c7a", "0xe9ad918c7678cd38b12603a762e638a5d1ee7091" ] } // Response: nothing was added, total unchanged { "added": 0, "already_tracked": 2, "total": 106, "limit": 5000 } ``` ## Examples ```bash cURL theme={null} curl -X POST "https://api.polynode.dev/v2/leaderboard/wallets?key=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "wallets": [ "0x94f199fb7789f1aef7fff6b758d6b375100f4c7a", "0xe9ad918c7678cd38b12603a762e638a5d1ee7091" ] }' ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.polynode.dev/v2/leaderboard/wallets?key=YOUR_API_KEY", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ wallets: [ "0x94f199fb7789f1aef7fff6b758d6b375100f4c7a", "0xe9ad918c7678cd38b12603a762e638a5d1ee7091" ] }) } ); const data = await response.json(); console.log(`Added: ${data.added}, Total: ${data.total}`); ``` ```python Python theme={null} import requests response = requests.post( "https://api.polynode.dev/v2/leaderboard/wallets", params={"key": "YOUR_API_KEY"}, json={ "wallets": [ "0x94f199fb7789f1aef7fff6b758d6b375100f4c7a", "0xe9ad918c7678cd38b12603a762e638a5d1ee7091" ] } ) data = response.json() print(f"Added: {data['added']}, Total: {data['total']}") ``` Newly added wallets are fetched immediately in the background. They typically appear in leaderboard queries within a few seconds. # Get Leaderboard Source: https://docs.polynode.dev/api-reference/byol/get-leaderboard GET /v2/leaderboard/custom Query your custom leaderboard ranked by P&L or volume, filtered by time period and category. Returns your custom leaderboard built from the wallets in your BYOL set. By default, returns all categories in a single response. You can also filter to a specific category. ``` GET /v2/leaderboard/custom ``` ## Authentication Pass your API key via query parameter `?key=`, header `x-api-key`, or `Authorization: Bearer`. ## Parameters Time period for P\&L and volume. Matches Polymarket's leaderboard tabs. * `day` — Today * `week` — Last 7 days * `month` — Last 30 days * `all` — All time Market category filter. Use `all` to get every category in one response, or specify one: * `all` — Returns all categories grouped in a `categories` object (default) * `overall` — All markets combined * `politics` `sports` `crypto` `finance` `tech` `weather` `culture` `mentions` Sort metric. `pnl` (Profit/Loss) or `vol` (Volume). Maximum entries per category. Max 500. Pagination offset. ## Response: all categories (default) When `category=all` or omitted, the response groups results by category. Each category is sorted and ranked independently. ```bash theme={null} curl "https://api.polynode.dev/v2/leaderboard/custom?timePeriod=all&key=YOUR_API_KEY" ``` ```json theme={null} { "timePeriod": "all", "category": "all", "orderBy": "pnl", "offset": 0, "limit": 50, "lastRefreshed": "2026-04-01T16:04:19.000Z", "categories": { "overall": [ { "localRank": 1, "rank": "1", "proxyWallet": "0x56687bf447db6ffa42ffe2204a05edaa20f55839", "userName": "Theo4", "xUsername": "", "verifiedBadge": false, "vol": 43013258.52, "pnl": 22053933.75, "profileImage": "" }, { "localRank": 2, "rank": "8", "proxyWallet": "0x8119010a6e589062aa03583bb3f39ca632d9f887", "userName": "PrincessCaro", "xUsername": "", "verifiedBadge": false, "vol": 23520809.95, "pnl": 6083643.10, "profileImage": "" }, { "localRank": 3, "rank": "5004", "proxyWallet": "0xb595d09ce5bbc4d39e3b3d04e80c402d2c8d5922", "userName": "...", "xUsername": "", "verifiedBadge": false, "vol": 2467445.01, "pnl": 22617.64, "profileImage": "" } ], "politics": [ { "localRank": 1, "rank": "1", "proxyWallet": "0x56687bf447db6ffa42ffe2204a05edaa20f55839", "userName": "Theo4", "xUsername": "", "verifiedBadge": false, "vol": 43012710.75, "pnl": 22053952.53, "profileImage": "" } ], "sports": [ { "localRank": 1, "rank": "1696", "proxyWallet": "0xb595d09ce5bbc4d39e3b3d04e80c402d2c8d5922", "userName": "...", "xUsername": "", "verifiedBadge": false, "vol": 2466252.61, "pnl": 22677.91, "profileImage": "" } ], "crypto": [...], "finance": [...], "tech": [], "weather": [], "culture": [...], "mentions": [...] } } ``` ## Response: single category When you specify a category like `category=crypto`, the response is a flat leaderboard with a `total` count. ```bash theme={null} curl "https://api.polynode.dev/v2/leaderboard/custom?timePeriod=week&category=crypto&key=YOUR_API_KEY" ``` ```json theme={null} { "timePeriod": "week", "category": "crypto", "orderBy": "pnl", "total": 6, "offset": 0, "limit": 50, "lastRefreshed": "2026-04-01T16:04:19.000Z", "leaderboard": [ { "localRank": 1, "rank": "551", "proxyWallet": "0x53decedc72531ef57b2d54b5542c509e233c822f", "userName": "jack118", "xUsername": "", "verifiedBadge": false, "vol": 0, "pnl": 11077.58, "profileImage": "" }, { "localRank": 2, "rank": "34808", "proxyWallet": "0x05d5e0403427a6223f5673b3234ac9405c19db37", "userName": "0x05d5e0403427a6223f5673b3234ac9405c19db37", "xUsername": "", "verifiedBadge": false, "vol": 0, "pnl": 23.24, "profileImage": "" } ] } ``` ## Response: sorted by volume ```bash theme={null} curl "https://api.polynode.dev/v2/leaderboard/custom?timePeriod=month&category=overall&orderBy=vol&limit=5&key=YOUR_API_KEY" ``` ```json theme={null} { "timePeriod": "month", "category": "overall", "orderBy": "vol", "total": 18, "offset": 0, "limit": 5, "lastRefreshed": "2026-04-01T16:04:19.000Z", "leaderboard": [ { "localRank": 1, "rank": "1086", "proxyWallet": "0xb595d09ce5bbc4d39e3b3d04e80c402d2c8d5922", "userName": "...", "xUsername": "", "verifiedBadge": false, "vol": 43406.94, "pnl": 22107.49, "profileImage": "" }, { "localRank": 2, "rank": "129764", "proxyWallet": "0xc0220b02ad4cf50f8d612dbb3aa7783973266494", "userName": "...", "xUsername": "", "verifiedBadge": false, "vol": 25.75, "pnl": 11.87, "profileImage": "" } ] } ``` ## Response: today's activity ```bash theme={null} curl "https://api.polynode.dev/v2/leaderboard/custom?timePeriod=day&category=sports&key=YOUR_API_KEY" ``` ```json theme={null} { "timePeriod": "day", "category": "sports", "orderBy": "pnl", "total": 9, "offset": 0, "limit": 50, "lastRefreshed": "2026-04-01T16:04:19.000Z", "leaderboard": [ { "localRank": 1, "rank": "768", "proxyWallet": "0xb595d09ce5bbc4d39e3b3d04e80c402d2c8d5922", "userName": "...", "xUsername": "", "verifiedBadge": false, "vol": 43406.94, "pnl": 516.36, "profileImage": "" }, { "localRank": 2, "rank": "96658", "proxyWallet": "0x1116147499ba1da24081a5f09077cdbc4586997c", "userName": "...", "xUsername": "", "verifiedBadge": false, "vol": 0, "pnl": 0.13, "profileImage": "" } ] } ``` ## Top-level response fields | Field | Type | Description | | --------------- | ------ | ------------------------------------------------------------------ | | `timePeriod` | string | The time period used for this query | | `category` | string | `"all"` or the specific category queried | | `orderBy` | string | Sort metric used (`pnl` or `vol`) | | `total` | number | Total wallets with data for this query (single-category mode only) | | `offset` | number | Pagination offset | | `limit` | number | Maximum entries returned | | `lastRefreshed` | string | ISO timestamp of the last background data refresh | | `categories` | object | Leaderboard entries grouped by category (when `category=all`) | | `leaderboard` | array | Flat leaderboard entries (when a specific category is requested) | ## Leaderboard entry fields | Field | Type | Description | | --------------- | ------- | ------------------------------------------------------------------------------------------- | | `localRank` | number | Rank within your custom leaderboard set | | `rank` | string | Global rank on Polymarket's leaderboard. This is a string matching Polymarket's API format. | | `proxyWallet` | string | Polymarket proxy wallet address | | `userName` | string | Polymarket display name | | `xUsername` | string | Twitter/X username (if set) | | `verifiedBadge` | boolean | Polymarket verified badge | | `vol` | number | Trading volume (USD) for this time period | | `pnl` | number | Profit/Loss (USD) for this time period | | `profileImage` | string | Profile image URL | ## Code examples ### Get everything for the current week ```bash cURL theme={null} curl "https://api.polynode.dev/v2/leaderboard/custom?timePeriod=week&key=YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.polynode.dev/v2/leaderboard/custom?timePeriod=week&key=YOUR_API_KEY" ); const data = await response.json(); // All 9 categories in one response for (const [category, traders] of Object.entries(data.categories)) { if (traders.length > 0) { console.log(`${category}: ${traders.length} traders`); console.log(` Top trader: ${traders[0].userName} ($${traders[0].pnl.toFixed(2)})`); } } ``` ```python Python theme={null} import requests response = requests.get( "https://api.polynode.dev/v2/leaderboard/custom", params={"timePeriod": "week", "key": "YOUR_API_KEY"} ) data = response.json() for category, traders in data["categories"].items(): if traders: top = traders[0] print(f"{category}: {len(traders)} traders, top: {top['userName']} (${top['pnl']:,.2f})") ``` ### Get a single category ```bash cURL theme={null} curl "https://api.polynode.dev/v2/leaderboard/custom?timePeriod=month&category=crypto&orderBy=pnl&key=YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.polynode.dev/v2/leaderboard/custom?timePeriod=month&category=crypto&orderBy=pnl&key=YOUR_API_KEY" ); const data = await response.json(); // Flat leaderboard array console.log(`${data.total} traders in crypto this month`); for (const trader of data.leaderboard || []) { console.log(`#${trader.localRank} ${trader.userName} — $${trader.pnl.toFixed(2)} P&L (global rank: ${trader.rank})`); } ``` ```python Python theme={null} import requests response = requests.get( "https://api.polynode.dev/v2/leaderboard/custom", params={ "timePeriod": "month", "category": "crypto", "orderBy": "pnl", "key": "YOUR_API_KEY" } ) data = response.json() print(f"{data['total']} traders in crypto this month") for trader in data["leaderboard"]: print(f"#{trader['localRank']} {trader['userName']} — ${trader['pnl']:,.2f} (global #{trader['rank']})") ``` ### Sort by volume, paginated ```bash cURL theme={null} # Top 10 by volume, all time curl "https://api.polynode.dev/v2/leaderboard/custom?timePeriod=all&category=overall&orderBy=vol&limit=10&key=YOUR_API_KEY" # Next 10 curl "https://api.polynode.dev/v2/leaderboard/custom?timePeriod=all&category=overall&orderBy=vol&limit=10&offset=10&key=YOUR_API_KEY" ``` ```javascript JavaScript theme={null} // Paginate through all results let offset = 0; const limit = 50; while (true) { const response = await fetch( `https://api.polynode.dev/v2/leaderboard/custom?timePeriod=all&category=overall&orderBy=vol&limit=${limit}&offset=${offset}&key=YOUR_API_KEY` ); const data = await response.json(); for (const trader of data.leaderboard || []) { console.log(`#${trader.localRank} ${trader.userName} — $${trader.vol.toFixed(2)} volume`); } if (!data.leaderboard || data.leaderboard.length < limit) break; offset += limit; // Respect rate limit: 1 request per 5 seconds await new Promise(resolve => setTimeout(resolve, 5000)); } ``` ## Rate limiting This endpoint is rate limited to **1 request per 5 seconds** per API key. If you exceed this, you'll get a 429 response: ```json theme={null} { "error": "Leaderboard is rate limited to 1 request per 5 seconds.", "retryAfterMs": 3784 } ``` | Field | Type | Description | | -------------- | ------ | ------------------------------------ | | `error` | string | Error message | | `retryAfterMs` | number | Milliseconds to wait before retrying | The response also includes a `Retry-After` header (in seconds). Data refreshes every \~30 minutes in the background. Repeat requests within 60 seconds are served from an in-memory cache for maximum speed. # List Wallets Source: https://docs.polynode.dev/api-reference/byol/list-wallets GET /v2/leaderboard/wallets List all wallets currently in your tracked leaderboard set. Returns every wallet address in your BYOL set along with your wallet limit. ``` GET /v2/leaderboard/wallets ``` ## Authentication Pass your API key via query parameter `?key=`, header `x-api-key`, or `Authorization: Bearer`. ## Response ```json theme={null} { "wallets": [ "0x56687bf447db6ffa42ffe2204a05edaa20f55839", "0xb595d09ce5bbc4d39e3b3d04e80c402d2c8d5922", "0x53decedc72531ef57b2d54b5542c509e233c822f", "0xd252bce657d99d4e943708a8d7a2b3222da2775b", "0x05d5e0403427a6223f5673b3234ac9405c19db37" ], "count": 104, "limit": 5000 } ``` | Field | Type | Description | | --------- | --------- | -------------------------------- | | `wallets` | string\[] | All wallet addresses in your set | | `count` | number | Total number of wallets | | `limit` | number | Maximum wallets allowed (5,000) | ## Examples ```bash cURL theme={null} curl "https://api.polynode.dev/v2/leaderboard/wallets?key=YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.polynode.dev/v2/leaderboard/wallets?key=YOUR_API_KEY" ); const data = await response.json(); console.log(`Tracking ${data.count} wallets (limit: ${data.limit})`); ``` ```python Python theme={null} import requests response = requests.get( "https://api.polynode.dev/v2/leaderboard/wallets", params={"key": "YOUR_API_KEY"} ) data = response.json() print(f"Tracking {data['count']} wallets (limit: {data['limit']})") ``` # BYOL Overview Source: https://docs.polynode.dev/api-reference/byol/overview Bring Your Own Leaderboard. Track any set of Polymarket wallets and get a private, ranked leaderboard with P&L across all time periods and categories. BYOL (Bring Your Own Leaderboard) lets you build a custom Polymarket leaderboard from any set of wallets you choose. Add the wallets you care about, and polynode continuously tracks their P\&L, volume, and global rank across every time period and category that Polymarket tracks. ## How it works 1. **Add wallets** you want to track via the API 2. polynode tracks every wallet across all time periods and categories in the background (\~30 min refresh cycle) 3. **Query your leaderboard** by time period, category, or get everything at once 4. Each API key has its own private wallet set. Your wallets and leaderboard are completely isolated from other users. ## What you get back Every wallet in your set is tracked across: **4 time periods** matching Polymarket's leaderboard: * `day` (Today) * `week` (Weekly) * `month` (Monthly) * `all` (All time) **9 categories:** * `overall` `politics` `sports` `crypto` `finance` `tech` `weather` `culture` `mentions` **2 sort options:** * `pnl` (Profit/Loss) * `vol` (Volume) The response fields match Polymarket's leaderboard format exactly: `rank`, `proxyWallet`, `userName`, `xUsername`, `verifiedBadge`, `vol`, `pnl`, `profileImage`. We add `localRank` to show where each wallet ranks within your custom set. ## Limits * **5,000 wallets max** per API key * Free tier keys cannot access BYOL. Any paid tier has full access. * Adding a wallet that's already in your set doesn't count toward the limit ## Endpoints | Method | Endpoint | Description | | -------- | ------------------------- | ------------------------------- | | `POST` | `/v2/leaderboard/wallets` | Add wallets to your tracked set | | `DELETE` | `/v2/leaderboard/wallets` | Remove wallets from your set | | `GET` | `/v2/leaderboard/wallets` | List all wallets in your set | | `GET` | `/v2/leaderboard/custom` | Query your leaderboard | # Remove Wallets Source: https://docs.polynode.dev/api-reference/byol/remove-wallets DELETE /v2/leaderboard/wallets Remove wallets from your tracked leaderboard set. Remove one or more wallets from your BYOL set. They will no longer appear in your leaderboard queries. ``` DELETE /v2/leaderboard/wallets ``` ## Authentication Pass your API key via query parameter `?key=`, header `x-api-key`, or `Authorization: Bearer`. ## Request body ```json theme={null} { "wallets": [ "0x94f199fb7789f1aef7fff6b758d6b375100f4c7a", "0xe9ad918c7678cd38b12603a762e638a5d1ee7091" ] } ``` Array of wallet addresses to remove from your set. ## Response ```json theme={null} { "removed": 2, "total": 104 } ``` | Field | Type | Description | | --------- | ------ | ----------------------------- | | `removed` | number | Number of wallets removed | | `total` | number | Remaining wallets in your set | ## Examples ```bash cURL theme={null} curl -X DELETE "https://api.polynode.dev/v2/leaderboard/wallets?key=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "wallets": [ "0x94f199fb7789f1aef7fff6b758d6b375100f4c7a", "0xe9ad918c7678cd38b12603a762e638a5d1ee7091" ] }' ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.polynode.dev/v2/leaderboard/wallets?key=YOUR_API_KEY", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ wallets: [ "0x94f199fb7789f1aef7fff6b758d6b375100f4c7a", "0xe9ad918c7678cd38b12603a762e638a5d1ee7091" ] }) } ); const data = await response.json(); console.log(`Removed: ${data.removed}, Remaining: ${data.total}`); ``` ```python Python theme={null} import requests response = requests.delete( "https://api.polynode.dev/v2/leaderboard/wallets", params={"key": "YOUR_API_KEY"}, json={ "wallets": [ "0x94f199fb7789f1aef7fff6b758d6b375100f4c7a", "0xe9ad918c7678cd38b12603a762e638a5d1ee7091" ] } ) data = response.json() print(f"Removed: {data['removed']}, Remaining: {data['total']}") ``` Removing a wallet from your set does not affect other API keys that may be tracking the same wallet. # Active 5-Minute Markets Source: https://docs.polynode.dev/api-reference/crypto/active GET /v1/crypto/active Currently active 5-minute up-or-down markets for all 7 coins. Returns the currently active 5-minute up-or-down markets for all 7 supported coins. These markets rotate every 5 minutes. Each response includes token IDs, outcomes, and current odds for immediate trading. ```json theme={null} { "markets": [ { "id": "312746", "slug": "btc-updown-5m-1774674300", "title": "Bitcoin Up or Down - March 28, 1:05AM-1:10AM ET", "active": true, "closed": false, "startDate": "2026-03-27T05:16:04.211285Z", "endDate": "2026-03-28T05:10:00Z", "markets": [ { "question": "Bitcoin Up or Down - March 28, 1:05AM-1:10AM ET", "conditionId": "0x...", "outcomes": ["Up", "Down"], "outcomePrices": [0.465, 0.535], "tokenId": "12345...", "active": true, "closed": false } ] } ], "count": 7, "windowStart": 1774674300 } ``` Cached for 30 seconds. The `windowStart` field is the epoch timestamp of the current 5-minute window. Use this as the `window` parameter for the [Price to Beat](/api-reference/crypto/price) endpoint. # Oracle Candles Source: https://docs.polynode.dev/api-reference/crypto/candles GET /v1/crypto/candles 5-minute OHLC candles from PolyNode's live Chainlink tick archive. Returns 5-minute OHLC candles from PolyNode's live Chainlink tick archive for a given crypto asset. Approximately 30 candles (\~2.5 hours of history) are returned. These are the same oracle prices used to resolve Polymarket's short-form crypto markets. ```json theme={null} { "candles": [ { "time": 1774665300, "open": 65954.03, "high": 65992.01, "low": 65947.14, "close": 65949.69 }, { "time": 1774665600, "open": 65949.69, "high": 65987.20, "low": 65949.69, "close": 65987.20 } ] } ``` Cached for about 5 seconds. The `symbol` parameter accepts bare asset symbols like `BTC` and feed names like `BTC/USD`. Each candle's `time` field is a Unix epoch timestamp marking the start of that 5-minute window. # Crypto Markets Source: https://docs.polynode.dev/api-reference/crypto/markets GET /v1/crypto/markets All crypto prediction markets with liquidity, volume, and open interest. Returns all currently active crypto prediction markets on Polymarket, including monthly, weekly, and daily markets across 7 supported assets (BTC, ETH, SOL, BNB, XRP, DOGE, HYPE). ```json theme={null} { "events": [ { "id": "238474", "slug": "what-price-will-bitcoin-hit-in-march-2026", "title": "What price will Bitcoin hit in March?", "startDate": "2026-03-01T05:20:27.791222Z", "endDate": "2026-04-01T04:00:00Z", "active": true, "volume": 88886711.74, "volume24hr": 5733750.73, "liquidity": 6074250.39, "openInterest": 12827944.10, "seriesSlug": "bitcoin-hit-price-monthly", "markets": [ { "id": "1629442", "question": "$100,000?", "conditionId": "0x...", "outcomes": ["Yes", "No"], "outcomePrices": [0.045, 0.955], "volume": 24084104.74, "liquidity": 2028310.67, "active": true, "closed": false, "groupItemTitle": "↑ 150,000" } ] } ] } ``` Cached for 3 minutes. # Price to Beat Source: https://docs.polynode.dev/api-reference/crypto/price GET /v1/crypto/price Open and close price for a specific crypto market window. Returns the opening and current closing price for a crypto market window. This is the "price to beat" for short-form markets. The `openPrice` is the Chainlink oracle price at market open. The `closePrice` updates in real time until the window completes (`completed: true`). ```json theme={null} { "openPrice": 66285.01, "closePrice": 66238.61, "timestamp": 1774674466843, "completed": false, "incomplete": true, "cached": false } ``` Real-time. No cache. `closePrice` is locked in the instant a window ends — no waiting. The `window` parameter is a Unix epoch timestamp. Get window timestamps from the `/v1/crypto/active` endpoint. # Crypto Series Source: https://docs.polynode.dev/api-reference/crypto/series GET /v1/crypto/series Recurring crypto market series. Returns all recurring crypto market series across different intervals (5m, 15m, 1h, 4h, daily, weekly, monthly). Each series generates new markets on its recurrence schedule. ```json theme={null} { "series": [ { "id": "10422", "slug": "xrp-up-or-down-15m", "title": "XRP Up or Down 15m", "recurrence": "15m", "active": true, "eventCount": null }, { "id": "10065", "slug": "ethereum-neg-risk-weekly", "title": "Ethereum Neg Risk Weekly", "recurrence": "weekly", "active": true, "eventCount": null } ], "count": 7 } ``` Cached for 5 minutes. # Historical Price Ticks Source: https://docs.polynode.dev/api-reference/crypto/ticks GET /v1/crypto/ticks Historical 1-second crypto price ticks for short-form chart backfill. Returns historical 1-second price ticks for the same crypto feeds available on the `price_feed` WebSocket stream. Use this endpoint to backfill a 5-minute, 15-minute, or 1-hour chart when a user opens it part way through the market window, then keep the chart current with live `price_feed` events. Asset symbol. One of `BTC`, `ETH`, `SOL`, `BNB`, `XRP`, `DOGE`, `HYPE`. Start of the range as a Unix timestamp in milliseconds. End of the range as a Unix timestamp in milliseconds. Maximum ticks to return. Max 100000. Optional feed source filter. Valid values are `chainlink_data_streams` and `polymarket_chainlink`. ```bash theme={null} curl -H "x-api-key: YOUR_API_KEY" \ "https://api.polynode.dev/v1/crypto/ticks?symbol=BTC&from=1780458961000&to=1780459082000&limit=5" ``` ```json theme={null} { "symbol": "BTC", "feed": "BTC/USD", "from": 1780458961000, "to": 1780459082000, "limit": 5, "count": 5, "truncated": true, "source": null, "ticks": [ { "id": "1780458962000-0", "type": "price_feed", "feed": "BTC/USD", "timestamp": 1780458962, "timestamp_ms": 1780458962000, "source": "chainlink_data_streams", "data": { "feed": "BTC/USD", "price": 65869.02124677686, "bid": 65865.65936734258, "ask": 65870.4120597498, "timestamp": 1780458962 } } ] } ``` ## Range limits Each request can cover up to 24 hours. If `truncated` is `true`, the response hit the requested `limit`; request a narrower time range or increase `limit` to retrieve more ticks. Historical availability starts when tick collection was enabled. Ranges before available history return an empty `ticks` array. ## Chart backfill For a 15-minute market, request ticks from the market window start to now, render those points, then append live WebSocket `price_feed` events as they arrive. ```javascript theme={null} const apiKey = "YOUR_API_KEY"; const symbol = "BTC"; const now = Date.now(); const windowStart = Math.floor(now / 1000 / 900) * 900 * 1000; const url = new URL("https://api.polynode.dev/v1/crypto/ticks"); url.searchParams.set("symbol", symbol); url.searchParams.set("from", String(windowStart)); url.searchParams.set("to", String(now)); url.searchParams.set("limit", "2000"); const response = await fetch(url, { headers: { "x-api-key": apiKey } }); const history = await response.json(); const points = history.ticks.map((tick) => ({ t: tick.timestamp_ms, price: tick.data.price })); console.log(points.at(0), points.at(-1)); ``` ## Pair with the live stream The response uses the same event shape as the WebSocket `price_feed` stream. The main difference is that historical responses include an `id`, `timestamp_ms`, and optional `source` label for replay and deduplication. ```javascript theme={null} const ws = new WebSocket("wss://ws.polynode.dev/ws?key=YOUR_API_KEY"); ws.addEventListener("open", () => { ws.send(JSON.stringify({ action: "subscribe", type: "chainlink", filters: { feeds: ["BTC/USD"] } })); }); ws.addEventListener("message", (event) => { const msg = JSON.parse(event.data); if (msg.type !== "price_feed") return; const point = { t: msg.data.timestamp * 1000, price: msg.data.price }; console.log(point); }); ``` BTC/USD can include more than one source for the same second. Deduplicate by `timestamp_ms` or choose a single `source` when your chart needs exactly one point per second. # Activity Feed Source: https://docs.polynode.dev/api-reference/enriched/activity GET /v1/activity Live platform-wide trade feed. Returns the 50 most recent trades across all Polymarket markets. Includes full trade metadata, wallet addresses, and transaction hashes. ```json theme={null} { "trades": [ { "wallet": "0x312ac123ff9551126f79e9ec53dab4df87637f59", "side": "BUY", "tokenId": "34377190941598917...", "conditionId": "0x7f76a575cfb2d0b8...", "size": 78.43, "price": 0.607, "timestamp": 1774115115, "title": "Bitcoin Up or Down - March 21, 1:45PM-1:50PM ET", "slug": "btc-updown-5m-1774115100", "eventSlug": "btc-updown-5m-1774115100", "outcome": "Up", "name": "Elastic-Alpenhorn", "txHash": "0xa0fe7f342aef603d..." } ], "count": 50 } ``` Activity data refreshes every 60 seconds. # Equity Curve Source: https://docs.polynode.dev/api-reference/enriched/equity-curve GET /v2/trader/{wallet}/equity Time-ordered equity curve for any Polymarket wallet, with optional dollar-normalized copy-trade analysis. Returns a time-ordered equity curve showing cumulative profit and loss across every position a wallet has ever taken on Polymarket. By default, the curve is built from **realized** P\&L only — the locked-in result from every closed position, every partial sell, every market resolution, every redemption. This makes the curve fully deterministic: the same query returns the same numbers regardless of when you call it. It's also fast, typically returning in under a second. When `normalize=1` is set, the response also includes a **\$1-normalized** equity curve. Each position is scaled so its entry cost equals exactly \$1, then summed. This shows what your returns would look like if you copied every trade with \$1 of risk per position — the standard format for evaluating a wallet's edge across thousands of trades. ## Simple example ```bash theme={null} curl "https://api.polynode.dev/v2/trader/0xb4ab48b451101a779bf8c644318bb17fe652571d/equity?period=all&normalize=1" \ -H "Authorization: Bearer YOUR_API_KEY" ``` Response (truncated for clarity — `curve` arrays contain up to 500 points each): ```json theme={null} { "wallet": "0xb4ab48b451101a779bf8c644318bb17fe652571d", "period": "all", "source": "onchain", "positions_count": 564, "markets_count": 296, "open_count": 30, "applied_filters": { "fromTs": null, "toTs": null, "maxMarkets": null, "includeUnrealized": false }, "partial": false, "raw": { "points": 500, "final_pnl": -857.48, "curve": [ { "t": 1774555427, "pnl": 10.56 }, { "t": 1774555427, "pnl": -14.44 }, { "t": 1774555441, "pnl": -6.62 } ] }, "normalized": { "bet_size": 1, "positions": 564, "points": 500, "final_pnl": -18.4366, "curve": [ { "t": 1774555427, "pnl": 0.1234 }, { "t": 1774555427, "pnl": -0.0466 }, { "t": 1774555441, "pnl": 0.5934 } ] } } ``` ## Query parameters Polymarket wallet address. Time window. Positions whose first activity falls before the start of the window are excluded. One of: `7d`, `30d`, `90d`, `1y`, `all`. Set to `1` to include the \$1-normalized equity curve alongside the raw curve. Start date. Accepts `YYYY-MM-DD` (treated as UTC midnight) or a unix timestamp in seconds. Drops positions whose first activity is before this date. End date. Same format as `from`. Drops positions whose first activity is after this date. Keep only the most recent N markets the wallet has touched. A "market" is a unique outcome group; binary markets count as one. Useful for quickly evaluating a wallet's recent performance without scanning their full history. Set to `1` to mark currently-open positions to current market price and include the unrealized P\&L in the curve. **Off by default.** See [Realized vs unrealized](#realized-vs-unrealized) below before turning this on. ## Realized vs unrealized The default response is **realized-only**. Every closed position contributes its locked-in P\&L. Open positions contribute whatever they've already realized through partial sells (often zero if the wallet hasn't sold any shares of that position yet). This is the right default for backtesting and for evaluating a wallet's edge over time. It's deterministic — the same query returns the same numbers regardless of when you call it — and it returns in under a second even for whales. If you want a **current-portfolio snapshot** that marks open positions to the latest market price, pass `include_unrealized=1`. The curve's final point will reflect what the wallet would have if it closed all open positions right now. Tradeoffs: * **Slower.** Open positions need a current price each, fetched live. * **Non-deterministic.** Two queries five minutes apart return slightly different numbers as prices move. * **Worth it** when you specifically want a "what's my position worth right now" view rather than a backtest signal. ```bash theme={null} # Realized-only (default, fast, deterministic) curl ".../equity?period=all&normalize=1" # Mark-to-market (slower, current-snapshot) curl ".../equity?period=all&normalize=1&include_unrealized=1" ``` ## Filtering examples Last 30 days: ```bash theme={null} curl ".../equity?period=30d&normalize=1" ``` Specific date range — backtest the wallet's behavior in early April 2026: ```bash theme={null} curl ".../equity?from=2026-04-01&to=2026-04-15&normalize=1" ``` Recent 50 markets only — fast scan of the wallet's most recent activity: ```bash theme={null} curl ".../equity?period=all&max_markets=50&normalize=1" ``` Composed filters — fast realized-only curve over the last 100 markets in a specific window: ```bash theme={null} curl ".../equity?from=2026-04-01&max_markets=100" ``` ## Response fields | Field | Type | Description | | ------------------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `wallet` | string | The lowercased wallet address. | | `period` | string | The period filter that was applied (`7d`, `30d`, `90d`, `1y`, or `all`). | | `source` | string | Always `"onchain"`. | | `positions_count` | integer | Total positions included in the curve after all filters. | | `markets_count` | integer | Distinct markets (unique outcome groups). For binary markets, two positions (Yes + No) on the same market count as one market. | | `open_count` | integer | Positions that still hold a non-zero balance. | | `applied_filters` | object | Echoes back exactly which filters were applied — useful for debugging. | | `partial` | boolean | `true` if the request hit the 10-second server-side timeout. The response will still contain whatever was computed; the `error` field will be set. | | `raw.points` | integer | Number of points in the raw curve (downsampled to a max of 500). | | `raw.final_pnl` | number | Final cumulative P\&L across the included positions, in USD. | | `raw.curve[].t` | integer | Unix timestamp (seconds) when the position was first opened. | | `raw.curve[].pnl` | number | Cumulative P\&L at that point, in USD. | | `normalized.bet_size` | number | Always `1`. Each position is scaled to \$1 of risk. | | `normalized.positions` | integer | Total positions in the normalized curve. | | `normalized.points` | integer | Number of points (downsampled to a max of 500). | | `normalized.final_pnl` | number | Final cumulative \$1-normalized P\&L. | | `normalized.curve[].pnl` | number | Cumulative \$1-normalized P\&L at that point. | ## Performance | Query shape | Typical first-hit latency | Cached | | ------------------------------------------------- | ------------------------- | ------ | | Default (any wallet) | 50ms – 2s | \<50ms | | `include_unrealized=1`, large open-position count | 1s – 8s | \<50ms | | Composed filters (`from` + `max_markets`) | \<1s | \<50ms | Responses are cached for 5 minutes per unique parameter combination. The cache key separates every filter, so two callers using different filters won't collide. ## Rate limit This endpoint is rate-limited at **1 request per 10 seconds per API key**. It's a heavy endpoint — caching results client-side is recommended. ## \$1 normalization math For every position in the included set: 1. Take the position's P\&L (realized only by default; realized + unrealized when opted in). 2. Divide by `total_bought` for that position. `total_bought` is the total number of shares ever acquired across every acquisition method. 3. Add the result to the running normalized cumulative. If a wallet put \$500 into a market and made \$50 realized, the normalized contribution is `$50 / $500 = $0.10`. For every dollar risked on that market, they made 10 cents. Sum across all positions and you get the normalized final P\&L. A wallet with 2,000 positions and a normalized final P\&L of `+12.0` means each \$1 risked returned about \$0.006 on average. Across thousands of positions, that indicates a real and consistent edge. ## Notes on coverage * **NegRisk-split positions.** When a wallet acquires tokens via a USDC split into a Yes/No pair (rather than a CLOB trade), there is no on-chain trade event for that token. We use sibling-token timestamps and redemption timestamps as fallbacks where available, but in rare cases a split-derived position with no sibling activity and no redemption record can fall back to "now" on the curve. This affects shape, not totals — `final_pnl` remains correct. * **Curve downsampling.** Both the raw and normalized curves are downsampled to a maximum of 500 points. The first and last points are always preserved. # Event Detail Source: https://docs.polynode.dev/api-reference/enriched/event GET /v1/event/{slug} Full event data with all markets, prices, token IDs, and metadata. Returns comprehensive data for a single Polymarket event, including all sub-markets with current outcome prices, volumes, liquidity, and token IDs for price history lookups. Event slug (e.g., `how-many-fed-rate-cuts-in-2026`). Found in event URLs on Polymarket. ```json theme={null} { "id": "51456", "slug": "how-many-fed-rate-cuts-in-2026", "title": "How many Fed rate cuts in 2026?", "description": "This market will resolve according to the exact amount of cuts...", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/how-many-fed-rate-cuts-in-2025-9qstZkSL1dn0.jpg", "icon": "https://polymarket-upload.s3.us-east-2.amazonaws.com/how-many-fed-rate-cuts-in-2025-9qstZkSL1dn0.jpg", "active": true, "closed": false, "volume": 12679447.65, "volume24hr": 531530.07, "liquidity": 1120355.33, "openInterest": 711723.41, "startDate": "2025-09-29T22:29:04.30848Z", "endDate": "2026-12-31T00:00:00Z", "markets": [ { "question": "Will no Fed rate cuts happen in 2026?", "conditionId": "0xd4e77ba6f29fc093509d24f508631abd445ecf506bbdc9c4c80e60256a318527", "outcomes": ["Yes", "No"], "outcomePrices": ["0.3455", "0.6545"], "volume": 2368774.98, "liquidity": 54958.02, "active": true, "closed": false, "groupItemTitle": "0 (0 bps)", "tokenId": "12403602920039269077597917340921667997547115084613238528792639013246536343316" }, { "question": "Will 1 Fed rate cut happen in 2026?", "conditionId": "0x5e082f0b57f47a29044aa35b4c5658393122e659d5feae521c06b57cdd7f905c", "outcomes": ["Yes", "No"], "outcomePrices": ["0.195", "0.805"], "volume": 780389.52, "liquidity": 65401.81, "active": true, "closed": false, "groupItemTitle": "1 (25 bps)", "tokenId": "113379839734351069617987084078322474966003108854908079701423911002443710490196" }, { "question": "Will 2 Fed rate cuts happen in 2026?", "conditionId": "0xe0d9f508a249e0070db06eb7d1e1fb17eb23c963f6fb722c4c3f81e23240c1cd", "outcomes": ["Yes", "No"], "outcomePrices": ["0.165", "0.835"], "volume": 754043.87, "liquidity": 75307.43, "active": true, "closed": false, "groupItemTitle": "2 (50 bps)", "tokenId": "72535544017897924722695722172278828562733090748474862987195303914909938482758" } ], "series": null, "similarMarkets": 0, "annotations": 0 } ``` # Event Search Source: https://docs.polynode.dev/api-reference/enriched/event-search GET /v1/events/search Search Polymarket events by text query. Returns events with all sub-markets, token IDs, and current prices. Search for events by keyword. Each result includes the full list of sub-markets with `tokenId` (YES token for CLOB price history) and current `price`. This is an event-level search — results are grouped by event, not individual markets. A multi-outcome event like "How many Fed rate cuts in 2026?" returns as one result with all 13 outcomes. Search query (e.g., `recession`, `Fed rate`, `Trump tariff`). Maximum number of events to return. Max 20. ```json theme={null} { "query": "Fed rate", "events": [ { "id": "51456", "slug": "how-many-fed-rate-cuts-in-2026", "title": "How many Fed rate cuts in 2026?", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/how-many-fed-rate-cuts-in-2025-9qstZkSL1dn0.jpg", "active": true, "markets": [ { "question": "Will no Fed rate cuts happen in 2026?", "groupItemTitle": "0 (0 bps)", "conditionId": "0xd4e77ba6f29fc093509d24f508631abd445ecf506bbdc9c4c80e60256a318527", "tokenId": "12403602920039269077597917340921667997547115084613238528792639013246536343316", "price": 0.348, "active": true }, { "question": "Will 1 Fed rate cut happen in 2026?", "groupItemTitle": "1 (25 bps)", "conditionId": "0x5e082f0b57f47a29044aa35b4c5658393122e659d5feae521c06b57cdd7f905c", "tokenId": "113379839734351069617987084078322474966003108854908079701423911002443710490196", "price": 0.19, "active": true }, { "question": "Will 2 Fed rate cuts happen in 2026?", "groupItemTitle": "2 (50 bps)", "conditionId": "0xe0d9f508a249e0070db06eb7d1e1fb17eb23c963f6fb722c4c3f81e23240c1cd", "tokenId": "72535544017897924722695722172278828562733090748474862987195303914909938482758", "price": 0.165, "active": true } ] }, { "id": "101936", "slug": "fed-rate-hike-in-2026", "title": "Fed rate hike in 2026?", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/will-the-fed-raise-interest-rates-in-2025-PQTEYZMvmAGr.jpg", "active": true, "markets": [ { "question": "Fed rate hike in 2026?", "groupItemTitle": "", "conditionId": "0x80b3af88cb991980e8da1ce86b9794a0957f96ec98c29319dd7ba65e9744d82b", "tokenId": "75028752776148090296091099469912621384650554615761384992997579209329182670110", "price": 0.33, "active": true } ] } ], "count": 2 } ``` ### Response Fields | Field | Type | Description | | ----------------------------------- | ------------ | ---------------------------------------------------------- | | `query` | string | The search query | | `events` | array | Matching events | | `events[].id` | string | Event ID | | `events[].slug` | string | URL slug | | `events[].title` | string | Event title | | `events[].image` | string\|null | Event image URL | | `events[].active` | boolean | Whether event is active | | `events[].markets` | array | All sub-markets in this event | | `events[].markets[].question` | string | Full market question | | `events[].markets[].groupItemTitle` | string | Short outcome label (e.g., "0 (0 bps)") | | `events[].markets[].conditionId` | string | Condition ID for CLOB queries | | `events[].markets[].tokenId` | string\|null | YES token ID for price history via `/v1/candles/{tokenId}` | | `events[].markets[].price` | number\|null | Current YES price (0-1) | | `events[].markets[].active` | boolean | Whether market is active | | `count` | number | Number of events returned | ### SDK Usage ```typescript theme={null} import { PolyNode } from 'polynode-sdk'; const pn = new PolyNode({ apiKey: 'pn_live_...' }); const results = await pn.searchEvents('Fed rate', { limit: 5 }); for (const event of results.events) { console.log(event.title, `(${event.markets.length} outcomes)`); for (const m of event.markets) { console.log(` ${m.groupItemTitle || m.question}: ${m.price} — tokenId: ${m.tokenId}`); } } ``` ### Notes * The `tokenId` is the YES token for each market. Use it with `/v1/candles/{tokenId}` to fetch OHLCV price history. * `groupItemTitle` is the short outcome label (e.g., "2 (50 bps)", "June Meeting"). Empty string for single-outcome events. * Rate limited to 1 request per second per API key (shared with other enriched data endpoints). * Results are cached for 3 minutes. # Leaderboard Source: https://docs.polynode.dev/api-reference/enriched/leaderboard GET /v1/leaderboard Top traders ranked by profit or volume. Returns the top 20 Polymarket traders for the specified time period and ranking metric. Time period. One of: `daily`, `weekly`, `monthly`, `all`. Ranking metric. One of: `profit`, `volume`. ```json theme={null} { "traders": [ { "rank": 1, "wallet": "0xc2e7800b5af46e6093872b177b7a5e7f0563be51", "name": "beachboy4", "pnl": 4320443.26, "volume": 12846475.79, "profileImage": "https://..." } ], "period": "monthly", "sort": "profit", "count": 20 } ``` Data refreshes every 2-3 minutes. Rate limit: 1 request per second. # Markets by Category Source: https://docs.polynode.dev/api-reference/enriched/markets GET /v2/markets/{category} Browse markets by category with pagination and event counts. Returns markets for a specific category, sorted by 24h volume. Supports pagination with `limit` and `offset`. Category slug. One of: | Category | Slug | | ----------- | ------------- | | Crypto | `crypto` | | Politics | `politics` | | Sports | `sports` | | Finance | `finance` | | Economy | `economy` | | Tech | `tech` | | Pop Culture | `pop-culture` | | Geopolitics | `geopolitics` | | Weather | `weather` | | Iran | `iran` | | Elections | `elections` | | Mentions | `mentions` | | Esports | `esports` | Number of events to return. Min 1, max 100. Pagination offset. Use with `limit` to page through results. ### Example ```bash theme={null} curl "https://api.polynode.dev/v2/markets/crypto?limit=50&offset=0&key=YOUR_API_KEY" ``` ```json theme={null} { "category": "crypto", "counts": { "all": 260, "fiveM": 7, "fifteenM": 7, "pre-market": 112, "etf": 2, "hourly": 9, "fourhour": 7, "daily": 11, "weekly": 64, "monthly": 23, "yearly": 22, "bitcoin": 35, "ethereum": 21, "solana": 12, "xrp": 11, "dogecoin": 6, "bnb": 6, "microstrategy": 8 }, "events": [ { "id": "273112", "slug": "bitcoin-above-on-april-23", "title": "Bitcoin above ___ on April 23?", "image": "https://...", "volume": 12345678, "volume24hr": 5678901, "liquidity": 1234567, "openInterest": 890123, "startDate": "2025-04-22T00:00:00Z", "endDate": "2025-04-23T23:59:59Z", "active": true, "closed": false, "new": false, "featured": true, "competitive": true, "commentCount": 42, "tags": ["Crypto", "Bitcoin"] } ], "limit": 50, "offset": 0 } ``` ### Response Fields Each event object includes: | Field | Type | Description | | -------------- | --------- | -------------------------------- | | `id` | string | Event ID | | `slug` | string | URL slug | | `title` | string | Event title | | `image` | string | Event image URL | | `volume` | number | Total lifetime volume | | `volume24hr` | number | 24-hour trading volume | | `liquidity` | number | Current liquidity | | `openInterest` | number | Open interest | | `startDate` | string | Event start date (ISO 8601) | | `endDate` | string | Event end date (ISO 8601) | | `active` | boolean | Whether the event is active | | `closed` | boolean | Whether the event is closed | | `new` | boolean | Whether the event is new | | `featured` | boolean | Whether the event is featured | | `competitive` | boolean | Whether the event is competitive | | `commentCount` | number | Number of comments | | `tags` | string\[] | Category tags | ### Pagination Page through all markets in a category: ```python theme={null} import requests API_KEY = "pn_live_..." category = "politics" all_events = [] offset = 0 while True: resp = requests.get( f"https://api.polynode.dev/v2/markets/{category}", params={"key": API_KEY, "limit": 100, "offset": offset}, ).json() events = resp.get("events", []) if not events: break all_events.extend(events) offset += len(events) print(f"Total {category} events: {len(all_events)}") ``` The `counts` object varies by category. Crypto includes subcounts by asset and timeframe. Other categories return a simple `{"total": N}` count. Counts are cached and may not exactly match the paginated event total. # Biggest Movers Source: https://docs.polynode.dev/api-reference/enriched/movers GET /v1/movers Markets with the largest 24-hour price changes. Returns markets that have experienced the biggest price swings in the last 24 hours. Useful for volatility alerts and "markets that moved" dashboards. ```json theme={null} { "markets": [ { "id": "1437746", "slug": "will-elon-musks-net-worth-be-between-670b-and-680b-on-march-31", "question": "Will Elon Musk's net worth be between $670b and $680b on March 31?", "image": "https://...", "outcomePrices": ["0.026", "0.974"], "oneDayPriceChange": -0.945 } ], "count": 20 } ``` `oneDayPriceChange` is the absolute change in the Yes price over 24 hours. Negative means the Yes price dropped. # Trader PnL Series Source: https://docs.polynode.dev/api-reference/enriched/trader-pnl GET /v1/trader/{wallet}/pnl PnL time series for a trader at multiple resolutions. Returns a time series of cumulative PnL values for a trader. Ethereum wallet address. Resolution. One of: `1D` (hourly points), `1W` (\~57 points), `1M` (\~41 points), `ALL` (\~223 points). ```json theme={null} { "wallet": "0xc2e7800b5af46e6093872b177b7a5e7f0563be51", "period": "1W", "series": [ { "timestamp": 1773511200, "pnl": 1391835.20 }, { "timestamp": 1773522000, "pnl": 3438215.00 }, { "timestamp": 1773532800, "pnl": 3441273.50 } ], "count": 57 } ``` # Trader Profile Source: https://docs.polynode.dev/api-reference/enriched/trader-profile GET /v1/trader/{wallet} Full profile for any Polymarket trader. Returns comprehensive stats for a trader: PnL, volume, trade count, largest win, current portfolio value, and account metadata. Data is sourced from Polymarket's own profile page. For V3 wallet-level accounting, including exact all-time trader-paid fees, use [`GET /v3/wallets/{address}?include_accounting_summary=true`](/data/wallets/summary). The V3 accounting summary is sourced from PolyNode's indexed onchain fill data and does not require paging raw fee rows. `totalPnl` is the net portfolio PnL that matches the number shown on a Polymarket profile page. This factors in realized gains, cost basis still deployed in open positions, and unrealized gains/losses. If you want total realized gains from closed positions instead, use the [onchain positions](/api-reference/wallets/onchain-positions) endpoint's `total_realized_pnl` field. The response includes an `eoaWallet` field that resolves the underlying EOA (externally owned account) for Polymarket Safe proxy wallets. This is derived onchain via the Gnosis Safe `getOwners()` call. For older lightweight proxy wallets that don't support this method, `eoaWallet` returns `null`. Polymarket wallet address (e.g., `0xc2e7800b5af46e6093872b177b7a5e7f0563be51`). ```json theme={null} { "wallet": "0xc2e7800b5af46e6093872b177b7a5e7f0563be51", "eoaWallet": "0xb49e5499562a4bc3345c1a1f2db13a5360dfddac", "name": "beachboy4", "pseudonym": "Threadbare-Skunk", "profileSlug": "@beachboy4", "joinDate": "2025-11-30T17:26:07.997000Z", "trades": 149, "marketsTraded": 149, "largestWin": 3487512.609533, "views": 111735, "totalVolume": 199137316.6, "totalPnl": 4142102.07, "realizedPnl": 0, "unrealizedPnl": 0, "positionValue": 4418.03, "profileImage": null } ``` # Trending Source: https://docs.polynode.dev/api-reference/enriched/trending GET /v1/trending Trending markets, hot topics, featured events, and biggest movers. Returns a curated snapshot of what's trending on Polymarket right now. Combines carousel highlights, breaking news markets, hot search topics, featured events, and biggest 24h price movers. ```json theme={null} { "carousel": [ { "id": "69987", "slug": "2026-ncaa-tournament-winner", "title": "2026 NCAA Tournament Winner", "image": "https://...", "volume24hr": 1234567 } ], "breaking": [ { "id": "1193228", "slug": "will-robert-golob-be-the-next-prime-minister-of-slovenia", "question": "Will Robert Golob be the next Prime Minister of Slovenia?", "outcomePrices": ["0.595", "0.405"], "oneDayPriceChange": -0.015 } ], "hotTopics": [ { "title": "Gen", "volume": 24471729.10, "url": "/search?_q=gen" }, { "title": "Bayern", "volume": 4733154.92, "url": "/predictions/bayern" } ], "featured": [], "movers": [] } ``` # Candles (Trade-Indexed OHLCV) Source: https://docs.polynode.dev/api-reference/onchain/candles GET /v2/onchain/candles/{token_id} Server-built OHLCV candles from real onchain trades. Anchor-based pagination with buy/sell volume split, VWAP, and trade counts. Resolutions from 1m to 1d. Build OHLCV candles directly from settled CLOB fills. Each candle includes open, high, low, close, total volume in USD and shares, buy and sell volume split, trade count, and VWAP. Backed by the same trade source as `/v2/onchain/trades`, so candles and trades stay in lockstep. Pagination is **anchor-based** rather than range-based. Each request returns up to 1000 trades worth of candles, anchored at a timestamp, block, or transaction hash. Walk older history by passing the cursor from the previous response. This is the same model used by major exchange APIs (Binance, Kraken, Coinbase) and avoids open-ended range queries timing out on hot markets. The response includes a `window.duration_seconds` field so callers immediately see how dense the market is — a hot market may pack 500 trades into 10 seconds, while a sleepy market may span weeks. ## Which candles endpoint should I use? PolyNode exposes three candle-shaped endpoints. They cover different data sources and are not interchangeable. | Endpoint | Source | Best for | | ------------------------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `GET /v2/onchain/candles/{token_id}` | Real settled CLOB fills on Polygon | Charting any Polymarket outcome token with full history, VWAP, and buy/sell split. **This is the endpoint you want for a price chart.** | | `GET /v1/candles/{token_id}` | Live rolling in-memory buffer | Short, live tail of recent activity. No historical depth, no pagination. Useful as a lightweight live poll but not for charts. | | `GET /v1/crypto/candles` | Chainlink oracle feed | 5-minute OHLC for crypto assets (BTC, ETH, SOL…). These are the same prices that resolve Polymarket's short-form crypto markets — not CLOB trade data. | If you're building a chart for a Polymarket market, use `/v2/onchain/candles`. ## Build a chart in 2 calls The fastest way to go from "I have an API key" to "I have candles on screen." Every response below is a real capture — you can paste the curls and get back the same shape. ### 1. Find a market and grab the token you want to chart `/v2/movers` returns the biggest daily movers with the full outcomes array already enriched — one REST call gives you the `token_id` for both the Yes and No outcome without any extra lookups. ```bash theme={null} curl "https://api.polynode.dev/v2/movers?limit=3" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "markets": [ { "id": "1707841", "slug": "israel-x-hezbollah-ceasefire-by-april-30-2026-989-656", "question": "Israel x Hezbollah ceasefire by April 30, 2026?", "condition_id": "0xc7140ddb5ae5dc94d4553fb05d4600816f33ff024844cebe8326f4c41c4a1a47", "outcomes": [ { "name": "Yes", "token_id": "71076253073516159380702286801576688253388973161726933428722204989810362065275", "price": 0.674 }, { "name": "No", "token_id": "38902668316823899581329108924389881286009857048696806385615295625967267371713", "price": 0.326 } ], "one_day_price_change": 0.3815 } ] } ``` `outcomes[0].token_id` is the Yes side. That's the identifier you pass to the candles endpoint. `/v2/trending` has the exact same shape if you'd rather chart what's popular than what's moving. ### 2. Pull candles for that token ```bash theme={null} TOKEN=71076253073516159380702286801576688253388973161726933428722204989810362065275 curl "https://api.polynode.dev/v2/onchain/candles/$TOKEN?resolution=5m&limit=500" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "token_id": "71076253073516159380702286801576688253388973161726933428722204989810362065275", "resolution": "5m", "window": { "start_ts": 1776274178, "end_ts": 1776278436, "trade_count": 500, "duration_seconds": 4258 }, "count": 16, "candles": [ { "time": 1776273900000, "open": 0.82, "high": 0.8221, "low": 0.82, "close": 0.82, "volume": 1842.3, "trades": 16, "vwap": 0.8205 }, { "time": 1776274200000, "open": 0.831, "high": 0.8349, "low": 0.765, "close": 0.781, "volume": 6194.1, "trades": 49, "vwap": 0.7912 }, { "time": 1776276600000, "open": 0.741, "high": 0.766, "low": 0.62, "close": 0.62, "volume": 9338.7, "trades": 68, "vwap": 0.6823 } ], "pagination": { "older_end_ts": 1776274177, "newer_start_ts": 1776278437 }, "question": "Israel x Hezbollah ceasefire by April 30, 2026?", "outcome": "Yes", "condition_id": "0xc7140ddb5ae5dc94d4553fb05d4600816f33ff024844cebe8326f4c41c4a1a47", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/israel+lebanon+dove+flags.png" } ``` The `window.duration_seconds` tells you the 500 trades covered \~71 minutes of wall time — a moderately active market. The header fields (`question`, `outcome`, `image`, `condition_id`) are included so a chart header can render in the same round trip. That's the full loop: discover → chart, two REST calls, everything you need. ### 3. Walk older history Each response returns `pagination.older_end_ts`. Pass it back as `anchor_ts` on the next call to fetch the window immediately before it. ```bash theme={null} # Older page, using the anchor from the previous response curl "https://api.polynode.dev/v2/onchain/candles/$TOKEN?resolution=5m&limit=500&anchor_ts=1776274177" \ -H "x-api-key: YOUR_KEY" ``` Real response header from this exact call: ```json theme={null} { "window": { "start_ts": 1776267442, "end_ts": 1776274174, "trade_count": 500, "duration_seconds": 6732 }, "count": 23, "pagination": { "older_end_ts": 1776267441, "newer_start_ts": 1776274175 } } ``` Same token, 500 older trades, 23 candles over \~112 minutes. Keep walking by chaining `older_end_ts → anchor_ts` until you have the depth you need. ## Request ``` GET /v2/onchain/candles/{token_id} ``` You can also pass the market identifier as a query parameter instead of a path parameter: ``` GET /v2/onchain/candles?token_id=... ``` ### Path parameters | Parameter | Type | Required | Description | | ---------- | ------ | ------------ | ----------------------------------------------------------------- | | `token_id` | string | One of these | Outcome token ID. Can also be passed as `?token_id=` query param. | ### Query parameters | Parameter | Type | Default | Description | | -------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `token_id` | string | — | Outcome token ID (alternative to path param) | | `condition_id` | string | — | Condition ID. Errors if the condition has multiple outcomes — pass `token_id` to disambiguate. | | `market_slug` | string | — | Market slug. Errors if the slug has multiple outcomes — pass `token_id` to disambiguate. | | `resolution` | string | `1h` | Bucket size: `1m`, `5m`, `15m`, `1h`, `4h`, `1d` | | `limit` | integer | `500` | Trades per page. Clamped to `[100, 1000]`. The candles are built from this trade window. | | `direction` | string | `before` | `before` returns the `limit` trades ending at the anchor (newest-first walk). `after` returns the `limit` trades starting at the anchor (oldest-first walk). | | `anchor_ts` | integer | now | Unix timestamp anchor | | `anchor_block` | integer | — | Polygon block number anchor. Resolved to its block timestamp. | | `anchor_tx` | string | — | Polygon transaction hash anchor. Resolved to its block timestamp. | | `gap_fill` | boolean | `false` | When `true`, empty buckets between active candles are filled with flat carry-forward candles (`O=H=L=C=prev_close`, `volume=0`). | Exactly one of `token_id`, `condition_id`, or `market_slug` is required. If multiple anchor params are passed, precedence is `anchor_tx > anchor_block > anchor_ts`. ## Response ```json theme={null} { "token_id": "85713379202339219190689591569895900631137520291992037720582155738835687752247", "resolution": "1m", "window": { "start_ts": 1776276218, "end_ts": 1776276228, "trade_count": 500, "duration_seconds": 10 }, "count": 1, "candles": [ { "time": 1776276180000, "open": 0.78, "high": 0.93, "low": 0.77, "close": 0.9, "volume": 7941.87, "volume_shares": 9094.21, "volume_buy": 1569.20, "volume_sell": 6372.67, "trades": 500, "vwap": 0.8733 } ], "pagination": { "older_end_ts": 1776276217, "newer_start_ts": 1776276229 }, "question": "Bitcoin Up or Down - April 15, 2:00PM-2:05PM ET", "slug": "btc-updown-5m-1776276000", "outcome": "Up", "condition_id": "0x...", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/BTC+fullsize.png" } ``` ### Candle fields | Field | Type | Description | | --------------- | ------ | -------------------------------------------------------------------------- | | `time` | number | Bucket start time in milliseconds since epoch (TradingView convention) | | `open` | number | First trade price in the bucket | | `high` | number | Highest trade price in the bucket | | `low` | number | Lowest trade price in the bucket | | `close` | number | Last trade price in the bucket | | `volume` | number | Total volume in USD | | `volume_shares` | number | Total volume in outcome shares | | `volume_buy` | number | USD volume where the taker was buying the outcome token (aggressor buys) | | `volume_sell` | number | USD volume where the taker was selling the outcome token (aggressor sells) | | `trades` | number | Count of fills in the bucket | | `vwap` | number | Volume-weighted average price for the bucket | ### Window fields | Field | Type | Description | | ------------------------- | ------ | ---------------------------------------------------------------- | | `window.start_ts` | number | Unix timestamp of the oldest trade in the window | | `window.end_ts` | number | Unix timestamp of the newest trade in the window | | `window.trade_count` | number | Number of trades that built these candles | | `window.duration_seconds` | number | Wall-clock span of the window. Use this to gauge market density. | ### Pagination fields | Field | Type | Description | | --------------------------- | ------ | -------------------------------------------------------------------------- | | `pagination.older_end_ts` | number | Pass as `anchor_ts` with `direction=before` to fetch the next older window | | `pagination.newer_start_ts` | number | Pass as `anchor_ts` with `direction=after` to fetch the next newer window | ### Market enrichment fields `question`, `slug`, `outcome`, `condition_id`, and `image` are pulled from our market index. Returned alongside the candles so a chart can render header metadata in one round trip. ## Examples ### Default — last 500 trades, 1h buckets ```bash cURL theme={null} curl "https://api.polynode.dev/v2/onchain/candles/85713379202339219190689591569895900631137520291992037720582155738835687752247?resolution=1h" \ -H "x-api-key: YOUR_KEY" ``` ```javascript Node.js theme={null} const tokenId = "85713379202339219190689591569895900631137520291992037720582155738835687752247"; const resp = await fetch( `https://api.polynode.dev/v2/onchain/candles/${tokenId}?resolution=1h`, { headers: { "x-api-key": "YOUR_KEY" } } ); const data = await resp.json(); console.log(`${data.window.trade_count} trades over ${data.window.duration_seconds}s`); data.candles.forEach(c => console.log(c.time, c.open, c.high, c.low, c.close, c.volume)); ``` ```python Python theme={null} import requests token_id = "85713379202339219190689591569895900631137520291992037720582155738835687752247" resp = requests.get( f"https://api.polynode.dev/v2/onchain/candles/{token_id}", params={"resolution": "1h"}, headers={"x-api-key": "YOUR_KEY"}, ) data = resp.json() print(f"{data['window']['trade_count']} trades over {data['window']['duration_seconds']}s") for c in data["candles"]: print(c["time"], c["open"], c["high"], c["low"], c["close"], c["volume"]) ``` ### Walking backwards through history Each response includes `pagination.older_end_ts`. Pass that as `anchor_ts` to fetch the previous window. ```bash theme={null} # First page — most recent 500 trades curl "https://api.polynode.dev/v2/onchain/candles/$TOKEN_ID?resolution=15m&limit=500" \ -H "x-api-key: YOUR_KEY" # Next page — 500 trades older than the first curl "https://api.polynode.dev/v2/onchain/candles/$TOKEN_ID?resolution=15m&limit=500&anchor_ts=OLDER_END_TS_FROM_PREVIOUS" \ -H "x-api-key: YOUR_KEY" ``` ### Forward walk from a starting point Use `direction=after` to walk forward from a specific timestamp. ```bash theme={null} # Earliest 500 trades on this market curl "https://api.polynode.dev/v2/onchain/candles/$TOKEN_ID?resolution=1h&direction=after&anchor_ts=0" \ -H "x-api-key: YOUR_KEY" ``` ### Anchor by block number Useful when you want to align candles to a specific Polygon block — for example to compare against another onchain event. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/candles/$TOKEN_ID?resolution=5m&anchor_block=70000000" \ -H "x-api-key: YOUR_KEY" ``` ### Anchor by transaction hash Same idea, but resolved from a transaction hash. Useful for "show me what the chart looked like around this trade." ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/candles/$TOKEN_ID?resolution=1m&anchor_tx=0xfffff7ed9080f46d7975f905e7f9358c436a1b177c6e19e54510c3ba6dcfddc4" \ -H "x-api-key: YOUR_KEY" ``` ### Gap-filled candles By default, buckets with zero trades are simply absent from the response. Set `gap_fill=true` to fill them with flat carry-forward candles for charting libraries that expect a continuous time axis. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/candles/$TOKEN_ID?resolution=1m&gap_fill=true" \ -H "x-api-key: YOUR_KEY" ``` ## Error responses | Status | Response | Condition | | ------ | --------------------------------------------------------------------------------------- | ---------------------------- | | 400 | `{"error": "token_id, condition_id, or market_slug required"}` | No market identifier passed | | 400 | `{"error": "Invalid resolution. Use one of: 1m, 5m, 15m, 1h, 4h, 1d"}` | Bad `resolution` value | | 400 | `{"error": "condition_id resolves to multiple outcomes — pass token_id to select one"}` | Multi-outcome `condition_id` | | 400 | `{"error": "market_slug resolves to multiple outcomes — pass token_id to select one"}` | Multi-outcome `market_slug` | | 401 | `{"error": "API key required. Pass via ?key= or x-api-key header."}` | Missing API key | | 403 | `{"error": "V2 endpoints require a paid plan. See polynode.dev/pricing for details."}` | Free tier key | | 429 | `{"error": "Rate limited. N req/s for your tier.", "retryAfterMs": ...}` | Rate limited | ## Notes * Candles are built from real onchain fills, not midpoint snapshots. Sparse markets will show sparse candles. * `volume_buy` and `volume_sell` are taker-side attributions — i.e. the side that lifted/hit the book. Same convention as every major exchange API. * The trade window is fixed-page, not range-bound. To cover a long history, walk pages via `pagination.older_end_ts`. Use `window.duration_seconds` in each response to gauge market density up front. * Block and transaction anchors are resolved via Polygon RPC and cached for 24 hours — repeated queries against the same anchor are free after the first hit. * Responses are cached server-side for 5 minutes per unique anchor, direction, and limit. # Global Open Interest Source: https://docs.polynode.dev/api-reference/onchain/global-oi GET /v2/onchain/oi Total platform-wide open interest from onchain data. Returns the total open interest across the entire Polymarket platform, sourced from onchain data. ## Request ``` GET /v2/onchain/oi ``` No parameters required. ## Response ```json theme={null} { "source": "onchain", "open_interest_usdc": 488484168.103811 } ``` | Field | Type | Description | | -------------------- | ------ | ------------------------------------ | | `open_interest_usdc` | number | Total platform open interest in USDC | ## Example ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/oi" \ -H "x-api-key: YOUR_KEY" ``` # Market Open Interest Source: https://docs.polynode.dev/api-reference/onchain/market-oi GET /v2/onchain/markets/{condition_id}/oi Onchain open interest for a specific market. Returns the current open interest for a specific market, sourced from onchain data. Enriched with market metadata. ## Request ``` GET /v2/onchain/markets/{condition_id}/oi ``` | Parameter | Type | Location | Description | | -------------- | ------ | -------- | ----------------------------------------------- | | `condition_id` | string | path | Market condition ID (0x-prefixed, 64 hex chars) | ## Response ```json theme={null} { "condition_id": "0x8180fee481707671aa45de84a9c3740ab607f3f2643b456e0380afb02d12cf7d", "source": "onchain", "found": true, "market": "Bitcoin Up or Down - March 28, 10:55AM-11:00AM ET", "slug": "btc-updown-5m-1774709700", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/BTC+fullsize.png", "open_interest_usdc": 393.153937 } ``` | Field | Type | Description | | -------------------- | ------- | ------------------------------------ | | `found` | boolean | Whether the market was found onchain | | `market` | string | Market question | | `slug` | string | Market slug | | `image` | string | Market image URL | | `open_interest_usdc` | number | Current open interest in USDC | ## Example ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/markets/0x8180fee481707671aa45de84a9c3740ab607f3f2643b456e0380afb02d12cf7d/oi" \ -H "x-api-key: YOUR_KEY" ``` # Trade History (Market) Source: https://docs.polynode.dev/api-reference/onchain/market-trades GET /v2/onchain/markets/{token_id}/trades Complete onchain trade history for any market token. Returns every onchain trade fill for a specific market token, enriched with market metadata. **Want the entire market in one call?** Use [Trade History (Market) — Bulk](/api-reference/onchain/market-trades-all) (Growth plan and above). Returns up to 250K trades in a single response, no pagination. ## Quick start — get the most recent 100 trades ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/markets/{token_id}/trades?limit=100" \ -H "x-api-key: YOUR_KEY" ``` Default `limit=100`, max `limit=1000`. That's all most apps need. ## Get every trade in the market (full history) To walk the entire history, add `?cursor=` (empty value) to the first request, then **for each next page, copy `pagination.cursor` from the response back into the URL**. Stop when `pagination.has_more` is `false`. Three complete copy-paste scripts that walk an entire market: ```python Python theme={null} import requests API_KEY = "YOUR_KEY" TOKEN_ID = "21743669032210695168079601505378236205866986767926346409604806906483294819314" def get_all_trades(token_id): cursor = "" # empty string = first page all_trades = [] while True: r = requests.get( f"https://api.polynode.dev/v2/onchain/markets/{token_id}/trades", params={"limit": 1000, "cursor": cursor}, headers={"x-api-key": API_KEY}, ).json() all_trades.extend(r["trades"]) if not r["pagination"]["has_more"]: break cursor = r["pagination"]["cursor"] # feed this into the next request return all_trades trades = get_all_trades(TOKEN_ID) print(f"got {len(trades)} trades") ``` ```javascript JavaScript theme={null} const API_KEY = "YOUR_KEY"; const TOKEN_ID = "21743669032210695168079601505378236205866986767926346409604806906483294819314"; async function getAllTrades(tokenId) { let cursor = ""; // empty string = first page const allTrades = []; while (true) { const url = `https://api.polynode.dev/v2/onchain/markets/${tokenId}/trades` + `?limit=1000&cursor=${encodeURIComponent(cursor)}`; const r = await fetch(url, { headers: { "x-api-key": API_KEY } }).then(r => r.json()); allTrades.push(...r.trades); if (!r.pagination.has_more) break; cursor = r.pagination.cursor; // feed this into the next request } return allTrades; } const trades = await getAllTrades(TOKEN_ID); console.log(`got ${trades.length} trades`); ``` ```bash bash theme={null} KEY="YOUR_KEY" TOKEN_ID="21743669032210695168079601505378236205866986767926346409604806906483294819314" CURSOR="" TOTAL=0 while true; do RESP=$(curl -s "https://api.polynode.dev/v2/onchain/markets/$TOKEN_ID/trades?limit=1000&cursor=$CURSOR" \ -H "x-api-key: $KEY") COUNT=$(echo "$RESP" | python3 -c "import json,sys;print(json.load(sys.stdin)['count'])") HAS_MORE=$(echo "$RESP" | python3 -c "import json,sys;print(json.load(sys.stdin)['pagination']['has_more'])") TOTAL=$((TOTAL + COUNT)) echo "got $COUNT trades, total: $TOTAL" [ "$HAS_MORE" = "False" ] && break CURSOR=$(echo "$RESP" | python3 -c "import json,sys;print(json.load(sys.stdin)['pagination']['cursor'])") done ``` **Real run** (heavy market, walked live with the Python script above): ``` got 1000 trades (total: 1000) got 1000 trades (total: 2000) got 1000 trades (total: 3000) ... got 1000 trades (total: 20000) DONE. 20,000 trades in 24 seconds. ``` Each page takes \~1 second regardless of how deep you go — the loop just keeps running until `has_more` is `false`. ## Request ``` GET /v2/onchain/markets/{token_id}/trades?limit=100 GET /v2/onchain/markets/{token_id}/trades?limit=1000&cursor= ``` | Parameter | Type | Location | Description | | ---------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `token_id` | string | path | CTF token ID (numeric string) | | `limit` | integer | query | Max results per page (default 100, max 1000) | | `cursor` | string | query | Pass empty string `?cursor=` for first page, then echo back `pagination.cursor` from each response. Required to walk past the first page reliably on large markets. | | `offset` | integer | query | Alternative to cursor: skip first N. Fast for the first \~25K results then degrades. Use `cursor` for anything bigger. | ## Response ```json theme={null} { "token_id": "110959653450933276250915064669875552310439627880508793089816880777942697720191", "source": "onchain", "count": 3, "market": "US x Iran ceasefire extended by April 22, 2026?", "slug": "us-x-iran-ceasefire-extended-by-april-22-2026", "outcome": "No", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/us-x-iran-ceasefire-by-Cgmx3GCuOwjs.jpg", "condition_id": "0x1d2787cb8aed975d092b2799ed6f4083e9445f7420cdc09e9d47e7d54356c6cd", "pagination": { "limit": 3, "has_more": true, "cursor": "1777595648:0xeae6ca0aeb75fc866ddb29d3a4b2cd15382895b923fe05b9315445d6346d1c47_0x49ddd1e169186d350488e3d62f58452fe76862c5f81f33987e659759e2c66100" }, "trades": [ { "tx_hash": "0x044dc412fa97d972fae1e77f8a30e769b7bea339cc3a27b5045e5668b513a520", "order_hash": "0x6b3d58dcf19f26a0b2f325bc43dc793765d1abeedfc449a3c5be38b6523613c9", "timestamp": 1777595650, "maker": "0x84571f1bf97a5c710cbe51daff2dd4556cc887fd", "taker": "0xe111180000d2663c0091e4f400237545b87b996b", "maker_asset_id": "0", "taker_asset_id": "110959653450933276250915064669875552310439627880508793089816880777942697720191", "maker_amount": 617.382, "taker_amount": 618, "fee": 0, "direction": "BUY", "side": "maker" } ] } ``` | Field | Type | Description | | ------------------------- | ------- | ------------------------------------------------------------- | | `count` | integer | Number of trades in this response | | `pagination.has_more` | boolean | `true` if more pages available — keep paginating | | `pagination.cursor` | string | Pass this back as `?cursor=` for the next page | | `market` | string | Market question | | `slug` | string | Market slug | | `outcome` | string | Outcome label for this token | | `image` | string | Market image URL | | `condition_id` | string | Market condition ID | | `trades[].tx_hash` | string | Transaction hash | | `trades[].order_hash` | string | Order hash that was filled | | `trades[].timestamp` | number | Unix timestamp | | `trades[].maker` | string | Maker wallet | | `trades[].taker` | string | Taker wallet | | `trades[].maker_asset_id` | string | Asset the maker provided (`"0"` = USDC) | | `trades[].taker_asset_id` | string | Asset the taker provided | | `trades[].maker_amount` | number | Amount maker provided | | `trades[].taker_amount` | number | Amount taker provided | | `trades[].fee` | number | Fee (USDC) | | `trades[].direction` | string | `"BUY"` or `"SELL"` from the buyer's perspective on this fill | | `trades[].side` | string | Always `"maker"` for this endpoint | # Trade History (Market) — Bulk Source: https://docs.polynode.dev/api-reference/onchain/market-trades-all GET /v2/onchain/markets/{token_id}/trades/all Return every trade for a market token in a single response. Growth plan or above. **Growth plan or above only.** This endpoint returns the **entire** trade history for a market token in **one response** — no client-side pagination, no offset bookkeeping. Because a single call can return tens of thousands of fills (and tens of MB of JSON), it's gated to the **Growth plan and above**. Starter and free tier requests receive `403`. * Hard cap: **250,000 trades per call** — beyond that, response includes `"partial": true` with `"partial_reason": "hard_cap_250000"` * Wall-clock budget: **180 seconds** — first cold-cache call on a busy market may take 1-3 minutes * Cache: full results cached **5 minutes**. Repeat calls return in under 1 second * Typical payload: 1-50 MB. Worst case (capped): 100-200 MB JSON Returns every onchain trade fill for one market token in a single response. The server walks the full trade history with parallel time-bucketed queries server-side — you don't need to paginate. Each trade is enriched with market metadata. This is the right endpoint when you want **bulk-export** a market for analysis, indexing, or backtesting. For interactive UIs that just need the most recent N fills, use the standard [Market Trade History](/api-reference/onchain/market-trades) instead. ## Request ``` GET /v2/onchain/markets/{token_id}/trades/all?from=&to= ``` | Parameter | Type | Location | Description | | ---------- | ------- | -------- | --------------------------------------------------------------------------------------------- | | `token_id` | string | path | CTF token ID (numeric string) | | `from` | integer | query | Optional start of window in unix seconds. Default: 2024-01-01 (covers all Polymarket history) | | `to` | integer | query | Optional end of window in unix seconds. Default: now | ## Response ```json theme={null} { "token_id": "21743669032210695168079601505378236205866986767926346409604806906483294819314", "from_ts": 1774588800, "to_ts": 1775817600, "count": 27601, "partial": false, "partial_reason": null, "fetched_in_ms": 5437, "cache": "miss", "trades": [ { "tx_hash": "0xa73ccafda4acf5473d65ce9b6ed9f9e41861ea2ae0b5c0a8bb5cfd3869583fdf", "order_hash": "0x3e5baca0ae04010bfe01ec0a0a3fe8b6e291ca8373bb4803f31620754f83a17d", "timestamp": 1775817557, "maker": "0x4ef7e2e97735a144c35c04bf22bcac184d3221be", "taker": "0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e", "token_id": "21743669032210695168079601505378236205866986767926346409604806906483294819314", "direction": "SELL", "price": 0.545, "shares": 1827.47, "usd": 995.99, "fee": 0 } ] } ``` ## Response fields | Field | Type | Description | | ------------------- | -------------- | ---------------------------------------------------------------------------------------------------------- | | `token_id` | string | The CTF token ID requested | | `from_ts` / `to_ts` | integer | Resolved time window in unix seconds | | `count` | integer | Total trades returned (≤ 250,000) | | `partial` | boolean | `true` if either the 250K hard cap or the 180s wall-clock budget was hit before the full window was walked | | `partial_reason` | string \| null | `"hard_cap_250000"` or `"wall_clock_180s"` when partial; otherwise `null` | | `fetched_in_ms` | integer | Server-side fetch time. Cached calls show the original miss time | | `cache` | string | `"hit"` if served from Redis cache, `"miss"` if freshly walked | | `trades` | array | All trades, sorted by `timestamp` descending (newest first) | ## Trade fields | Field | Type | Description | | ------------ | ------- | ------------------------------------------------ | | `tx_hash` | string | Polygon transaction hash | | `order_hash` | string | Order hash from the exchange | | `timestamp` | integer | Unix seconds when the fill settled | | `maker` | string | Maker address | | `taker` | string | Taker address | | `token_id` | string | The outcome token id (echoes the request param) | | `direction` | string | `"BUY"` or `"SELL"` from the maker's perspective | | `price` | number | USDC per share, 4-decimal precision | | `shares` | number | Outcome tokens traded | | `usd` | number | USDC notional | | `fee` | number | Maker fee paid (USDC) | ## Example: full lifetime of a market ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/markets/21743669032210695168079601505378236205866986767926346409604806906483294819314/trades/all?key=$YOUR_KEY" ``` ## Example: window inside a longer-running market ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/markets/$TOKEN/trades/all?from=1774588800&to=1775817600&key=$YOUR_KEY" ``` ## Errors `403 Tier required`: ```json theme={null} { "error": "/trades/all requires the Growth plan or above. Your tier: starter. See polynode.dev/pricing." } ``` `401 No API key`: ```json theme={null} { "error": "API key required. Pass via ?key= or x-api-key header." } ``` ## Notes * For a multi-token market (binary YES/NO), call this endpoint **twice** — once per `token_id`. A `condition_id` variant that returns both tokens in one call is on the roadmap. * `partial: true` results are **not cached**. Subsequent calls re-attempt the full walk. Tighten the time window with `from`/`to` if you keep hitting the cap. * Cache key is `(token_id, from_ts, to_ts)`. Different windows are cached independently. Default-window calls (no `from`/`to`) share a cache slot. * The walk is parallelized — total bandwidth use is moderate even for huge markets. # Market Volume Source: https://docs.polynode.dev/api-reference/onchain/market-volume GET /v2/onchain/markets/{token_id}/volume Lifetime onchain volume statistics for any market token. Returns lifetime trading volume statistics for a specific market token, sourced directly from onchain settlement data. Enriched with market metadata. ## Request ``` GET /v2/onchain/markets/{token_id}/volume ``` | Parameter | Type | Location | Description | | ---------- | ------ | -------- | ----------------------------- | | `token_id` | string | path | CTF token ID (numeric string) | ## Response ```json theme={null} { "token_id": "21912724974096796009916816278814088615574660931588091764221331842149572809887", "source": "onchain", "found": true, "market": "Bitcoin Up or Down - March 28, 9:55PM-10:00PM ET", "slug": "btc-updown-5m-1774749300", "outcome": "Up", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/BTC+fullsize.png", "condition_id": "0xb91dbc2ba7f0d33c71fca765909e98b2023af855f1d69b82d5fd59732085700d", "total_trades": 2886, "buys": 2609, "sells": 277, "volume_usdc": 31054.394523, "buy_volume_usdc": 29482.48427, "sell_volume_usdc": 1571.910253 } ``` | Field | Type | Description | | ------------------ | ------- | ----------------------------------- | | `found` | boolean | Whether the token was found onchain | | `market` | string | Market question | | `slug` | string | Market slug | | `outcome` | string | Outcome label for this token | | `image` | string | Market image URL | | `condition_id` | string | Market condition ID | | `total_trades` | number | Lifetime number of fills | | `buys` | number | Number of buy fills | | `sells` | number | Number of sell fills | | `volume_usdc` | number | Total volume in USDC | | `buy_volume_usdc` | number | Buy-side volume in USDC | | `sell_volume_usdc` | number | Sell-side volume in USDC | ## Example ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/markets/21912724974096796009916816278814088615574660931588091764221331842149572809887/volume" \ -H "x-api-key: YOUR_KEY" ``` # Positions (All) Source: https://docs.polynode.dev/api-reference/onchain/positions GET /v2/onchain/positions Query every position on Polymarket. Filter by wallet, market, status, or minimum size. Full history with cursor pagination. Search and filter all Polymarket positions across every wallet. Each position is enriched with market metadata and includes realized P\&L, average entry price, and current size. Sourced directly from onchain settlement data. ## Request ``` GET /v2/onchain/positions ``` ### Query parameters | Parameter | Type | Required | Description | | ---------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `wallet` | string | No | Filter by wallet address | | `market_slug` | string | No | Filter by market slug (e.g. `will-zohran-mamdani-win-the-2025-nyc-mayoral-election`) | | `condition_id` | string | No | Filter by condition ID (0x-prefixed, 64 hex chars) | | `token_id` | string | No | Filter by outcome token ID | | `status` | string | No | `open` (size > 0), `closed` (size = 0), or `all` (default) | | `min_size` | number | No | Minimum position size in shares | | `limit` | integer | No | Results per page (1-500, default 100) | | `order` | string | No | Sort direction: `desc` (default, most recent first) or `asc`. When `wallet` is set, results are ordered by the wallet's most recent on-chain activity per position. Otherwise, ordered by position ID. | | `pagination_key` | string | No | Cursor from a previous response to fetch the next page. Not used for wallet queries (see Pagination). | ### Identifying markets * **`market_slug`** — human-readable URL slug from Polymarket. Returns positions for all outcomes. * **`condition_id`** — unique condition identifier. Returns positions for all outcomes. * **`token_id`** — specific outcome token. Returns positions for only that outcome. ## Response ```json theme={null} { "count": 2, "pagination": { "limit": 2, "has_more": true, "pagination_key": "0xa973ae12882a1c24a75da7a3b52cb01500b2f639-99881503238784655670984673100655147508393351942279611133016622634826369070119" }, "positions": [ { "wallet": "0xa973ae12882a1c24a75da7a3b52cb01500b2f639", "token_id": "99949662063403569895321097192043694236016212986254553767097648841549016857591", "size": 0.012785, "avg_price": 0.859999, "realized_pnl": 0.16, "unrealized_pnl": 0.02, "current_price": 0.88, "market_status": "live", "total_bought": 1.16, "market": "Will FC Bayern Munchen win on 2026-04-07?", "market_slug": "ucl-rma1-bay1-2026-04-07-bay1", "outcome": "Yes", "condition_id": "0x5f5c8d8fa28d77b5552562f32393ac199fb6b92ed1c1e2239e29c09f7e4eb3f5", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/champions-league-pic-QIUFsL8vaDdq.png" }, { "wallet": "0xa973ae12882a1c24a75da7a3b52cb01500b2f639", "token_id": "99881503238784655670984673100655147508393351942279611133016622634826369070119", "size": 0, "avg_price": 0.98, "realized_pnl": 0.1, "unrealized_pnl": 0, "current_price": null, "market_status": "closed", "total_bought": 5, "market": "Solana Up or Down - December 30, 10:30PM-10:45PM ET", "market_slug": "sol-updown-15m-1767151800", "outcome": "Down", "condition_id": "0xa7f50f1bd65e8fd1790117b3b162dc03489df7b89cd35f4b1093792691b8a327", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/SOL+fullsize.png" } ] } ``` ### Position fields | Field | Type | Description | | ----------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `wallet` | string | Wallet address holding the position | | `token_id` | string | Outcome token ID | | `size` | number | Current position size in shares. `0` means the position is fully exited. | | `avg_price` | number | Average entry price (0 to 1) | | `realized_pnl` | number | Realized profit/loss in USD. Reflects gains/losses from closed portions of the position and from onchain redemptions of resolved markets. Stays `0` for resolved-but-not-yet-redeemed positions; check `redeemable` to detect those. | | `unrealized_pnl` | number | Unrealized profit/loss in USD on the remaining open shares. For resolved markets, uses the final settlement price (`1.0` for winners, `0.0` for losers). For live markets, uses the current market price. Returns `0` when `size = 0`. Byte-identical to Polymarket's `cashPnl` field for any position covered by both. | | `current_price` | number \| null | Price used to compute `unrealized_pnl`. `1.0` or `0.0` for resolved markets (derived deterministically from on-chain `payoutNumerators`), the live market price for active markets, and `null` when `size = 0` or no price is available. | | `market_status` | string | One of: `"live"` (market still trading), `"resolved-win"` (market resolved, this outcome won), `"resolved-loss"` (market resolved, this outcome lost), or `"closed"` (position fully exited, `size = 0`). A fifth value `"resolved-unknown"` may appear briefly for very old markets while resolution data is catching up. Never `"live"` when `resolved_at` is set. | | `won` | boolean \| undefined | Present only on `resolved-win` / `resolved-loss` rows. `true` when this outcome won, `false` when it lost. Derived from on-chain payouts. | | `winning_outcome_index` | number \| undefined | Present only on resolved rows. Numeric index of the outcome that won (0 or 1 for binary markets). Pair with `outcome_index` to know whether this row is the winning side. | | `outcome_index` | number \| undefined | Numeric index of this row's outcome within the market's outcomes array (0 or 1 for binary markets). Stable across the API regardless of how the UI labels the outcome (`Yes`/`No`, team names, etc.). Use this for cross-row joins instead of parsing `outcome` strings. | | `total_bought` | number | Total amount bought in USD over the lifetime of the position. | | `initial_value` | number \| undefined | Cost basis in USD of the currently held shares (`size × cost_per_share`). Use this if you need the basis number that matches Polymarket `cashPnl` and what users see in the Polymarket UI. | | `redeemable` | boolean \| undefined | `true` when the market has resolved and the user can call redeem on the CTF contract to claim payout (or accept loss). Useful for detecting "resolved-but-not-redeemed" positions: filter `market_status = "resolved-loss"` AND `redeemable = true` for unclaimed losses, or `market_status = "resolved-win"` AND `redeemable = true` for unclaimed wins. | | `opposite_asset` | string \| undefined | Token ID of the OPPOSITE outcome on the same market (the binary counterpart). Useful for fetching the matching position on the other side without re-resolving the condition. | | `market` | string | Market question text | | `market_slug` | string | Market URL slug | | `outcome` | string | Outcome label (e.g. "Yes", "No") | | `condition_id` | string | Market condition ID | | `image` | string \| null | Market image URL. `null` for some delisted or niche markets. | | `event_slug` | string \| null | Parent **event** slug, distinct from the per-row market `slug`. For multi-market events (NBA games with several lines, election markets with several candidates, FIFA World Cup with one market per team), this is the parent the markets share. For single-market events, equals the market slug. `null` when event metadata is not yet known. | | `last_activity` | number \| undefined | Unix timestamp of the wallet's most recent fill on this position. Present only when querying by `wallet`. Positions with no fill history (e.g. acquired purely via split/merge/redemption) omit this field and sort to the end. | | `last_trade_at` | number \| null | Unix seconds. Latest fill on this token across V1 + V2 exchanges. Same data as `last_activity` but always present (`null` instead of omitted) for consistent shape. Use either; `last_trade_at` is preferred for new integrations. | | `closed_at` | number \| null | Unix seconds. Latest moment this wallet redeemed any outcome of the market for collateral. `null` when the wallet has not redeemed (open positions, positions sold to zero pre-resolution, or wins held but not yet redeemed). | | `resolved_at` | number \| null | Unix seconds. Moment the market was resolved on-chain (when payouts became redeemable). `null` for markets that resolved before polynode began tracking, or for markets that have not yet resolved. Recent markets are fully covered. | ### Pagination fields | Field | Type | Description | | --------------------------- | ------- | ---------------------------------------------------------------------- | | `count` | number | Number of positions in this response | | `pagination.limit` | number | Requested page size | | `pagination.has_more` | boolean | `true` if more results exist beyond this page | | `pagination.pagination_key` | string | Pass this as `pagination_key` in the next request to get the next page | ## Examples ### All positions for a wallet ```bash cURL theme={null} curl "https://api.polynode.dev/v2/onchain/positions?wallet=0xa973ae12882a1c24a75da7a3b52cb01500b2f639&limit=100" \ -H "x-api-key: YOUR_KEY" ``` ```javascript Node.js theme={null} const resp = await fetch( "https://api.polynode.dev/v2/onchain/positions?wallet=0xa973ae12882a1c24a75da7a3b52cb01500b2f639&limit=100", { headers: { "x-api-key": "YOUR_KEY" } } ); const data = await resp.json(); console.log(data.positions); ``` ```python Python theme={null} import requests resp = requests.get( "https://api.polynode.dev/v2/onchain/positions", params={"wallet": "0xa973ae12882a1c24a75da7a3b52cb01500b2f639", "limit": 100}, headers={"x-api-key": "YOUR_KEY"}, ) data = resp.json() print(data["positions"]) ``` ### Open positions only Use `status=open` to get only positions with a non-zero size. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/positions?wallet=0xa973ae12882a1c24a75da7a3b52cb01500b2f639&status=open&limit=100" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "count": 2, "pagination": { "limit": 2, "has_more": true, "pagination_key": "0xa973ae12882a1c24a75da7a3b52cb01500b2f639-99504617..." }, "positions": [ { "wallet": "0xa973ae12882a1c24a75da7a3b52cb01500b2f639", "token_id": "99949662063403569895321097192043694236016212986254553767097648841549016857591", "size": 0.012785, "avg_price": 0.859999, "realized_pnl": 0.16, "unrealized_pnl": 0.02, "current_price": 0.88, "market_status": "live", "total_bought": 1.16, "market": "Will FC Bayern Munchen win on 2026-04-07?", "market_slug": "ucl-rma1-bay1-2026-04-07-bay1", "outcome": "Yes", "condition_id": "0x5f5c8d8fa28d77b5552562f32393ac199fb6b92ed1c1e2239e29c09f7e4eb3f5", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/champions-league-pic-QIUFsL8vaDdq.png" } ] } ``` ### Who holds a market Use `market_slug` to see all wallets with positions on a specific market. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/positions?market_slug=will-zohran-mamdani-win-the-2025-nyc-mayoral-election&status=open&limit=50" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "count": 2, "pagination": { "limit": 2, "has_more": true, "pagination_key": "0xfffe254008792df0c325325a75a3c6e7aaed436a-10583236..." }, "positions": [ { "wallet": "0xffff3840fbf40fd2e193c01cc299cdf1262cffaf", "token_id": "33945469250963963541781051637999677727672635213493648594066577298999471399137", "size": 0.009534, "avg_price": 0.947999, "realized_pnl": -0.54, "unrealized_pnl": -5.07, "current_price": 0.416, "market_status": "live", "total_bought": 539.03, "market": "Will Zohran Mamdani win the 2025 NYC mayoral election?", "market_slug": "will-zohran-mamdani-win-the-2025-nyc-mayoral-election", "outcome": "Yes", "condition_id": "0xebddfcf7b4401dade8b4031770a1ab942b01854f3bed453d5df9425cd9f211a9", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/will-zohran-mamdani-win-the-2025-nyc-mayoral-election-EscSJQTT6hWg.jpg" } ] } ``` ### Large positions Use `min_size` to find positions above a threshold. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/positions?min_size=100&limit=20" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "count": 2, "pagination": { "limit": 2, "has_more": true, "pagination_key": "0xffffffe1e093aacd21e4e281e66d543fb0b23455-98495912..." }, "positions": [ { "wallet": "0xffffffe1e093aacd21e4e281e66d543fb0b23455", "token_id": "98813479054803844837498343855179110721772056323529222930302029314504656450267", "size": 1100, "avg_price": 0.003636, "realized_pnl": 0, "unrealized_pnl": -3.96, "current_price": 0, "market_status": "resolved-loss", "total_bought": 1100, "market": "Bitcoin Up or Down - February 5, 9:45AM-10:00AM ET", "market_slug": "btc-updown-15m-1770302700", "outcome": "Up", "condition_id": "0x3ec09263f6fb247e65635d52c2787dc0f46806f153124e138f42ad03411198b4", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/BTC+fullsize.png" } ] } ``` ### Combined filters Filter by wallet, market, and status at once. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/positions?wallet=0xa973ae12882a1c24a75da7a3b52cb01500b2f639&market_slug=ucl-rma1-bay1-2026-04-07-bay1&status=open&limit=10" \ -H "x-api-key: YOUR_KEY" ``` ### Pagination **Wallet queries** return every position for the wallet in a single response (up to 500), sorted by most recent on-chain activity. No pagination needed — request `limit=500` and read all results. `has_more` is always `false` and no cursor is returned. Wallets with more than 500 lifetime positions are capped at 500. **Non-wallet queries** (filtering by `market_slug`, `condition_id`, `token_id`, or `min_size` alone) use cursor-based pagination for iterating through large result sets. ```bash theme={null} # First page curl "https://api.polynode.dev/v2/onchain/positions?min_size=100&limit=100" \ -H "x-api-key: YOUR_KEY" # Next page curl "https://api.polynode.dev/v2/onchain/positions?min_size=100&limit=100&pagination_key=CURSOR_FROM_PREVIOUS" \ -H "x-api-key: YOUR_KEY" ``` ## Error responses | Status | Response | Condition | | ------ | -------------------------------------------------------------------------------------- | --------------------------------- | | 400 | `{"error": "market_slug not found"}` | Invalid or unknown `market_slug` | | 400 | `{"error": "condition_id not found"}` | Invalid or unknown `condition_id` | | 401 | `{"error": "API key required. Pass via ?key= or x-api-key header."}` | Missing API key | | 403 | `{"error": "V2 endpoints require a paid plan. See polynode.dev/pricing for details."}` | Free tier key | | 429 | `{"error": "Rate limited. N req/s for your tier.", "retryAfterMs": ...}` | Rate limited | ## Notes * Position data is sourced from onchain settlement records. Every position that was ever opened on Polymarket is included. * The `realized_pnl` field reflects actual profit/loss from closed portions of the position, including onchain redemptions of resolved markets. For open positions, it reflects any partial closes. * The `unrealized_pnl` field shows the paper profit/loss on the remaining open shares. For resolved markets, this uses the final settlement price ($1 for winners, $0 for losers). For active markets, the current market price is used. * `market_status` lets clients distinguish live-tradable positions from positions on resolved markets that the wallet never redeemed. Resolved-and-never-redeemed positions still have `size > 0` and will show the correct terminal `unrealized_pnl`. * Market metadata (`market`, `market_slug`, `outcome`, `condition_id`, `image`) is enriched from our index. Very old or delisted markets may not have metadata. * When querying by `wallet`, the sort key is the timestamp of the most recent fill for each position, not the position's close date. A position bought months ago and held to resolution (closed only via redemption) will sort by its original buy date. ## Verifying parity with Polymarket Per-position fields on this endpoint match Polymarket's `data-api.polymarket.com/positions` byte-for-byte. Anyone can verify directly with the standalone Node script below — no internal access required. ### Field map | polynode field | Polymarket field | | ---------------- | ---------------- | | `token_id` | `asset` | | `condition_id` | `conditionId` | | `size` | `size` | | `avg_price` | `avgPrice` | | `realized_pnl` | `realizedPnl` | | `unrealized_pnl` | `cashPnl` | | `current_price` | `curPrice` | | `outcome` | `outcome` | ### Self-test script (Node 18+, no dependencies) ```javascript theme={null} // Save as parity_demo.mjs and run with: // POLYNODE_KEY=pn_live_xxx node parity_demo.mjs const KEY = process.env.POLYNODE_KEY; const WALLET = process.argv[2]; const [pm, us] = await Promise.all([ fetch(`https://data-api.polymarket.com/positions?user=${WALLET}&sizeThreshold=0&limit=2000`, { headers: { 'User-Agent': 'Mozilla/5.0' } }).then(r => r.json()), fetch(`https://api.polynode.dev/v2/onchain/positions?wallet=${WALLET}&status=all&limit=2000`, { headers: { 'x-api-key': KEY } }).then(r => r.json()).then(j => Array.isArray(j) ? j : (j.positions ?? [])), ]); const pmByPid = new Map(pm.map(p => [String(p.asset), p])); const usByPid = new Map(us.map(p => [String(p.token_id), p])); const shared = [...pmByPid.keys()].filter(k => usByPid.has(k)); let exact = 0, sub_dollar = 0; for (const pid of shared) { const p = pmByPid.get(pid), u = usByPid.get(pid); const avgDiff = Math.abs(Number(p.avgPrice) - Number(u.avg_price)); const pnlDiff = Math.abs(Number(p.realizedPnl) - Number(u.realized_pnl)); if (avgDiff < 0.005 && pnlDiff < 0.01) exact++; if (pnlDiff < 1.0) sub_dollar++; console.log(` ${pid.slice(0,8)}… PM realPnl=${Number(p.realizedPnl).toFixed(2).padStart(10)} Us realPnl=${Number(u.realized_pnl).toFixed(2).padStart(10)} Δ=${pnlDiff.toFixed(4)}`); } console.log(`\n byte-perfect: ${exact}/${shared.length} sub-$1: ${sub_dollar}/${shared.length}`); ``` Run against any wallet to confirm. Validated 2026-04-30 across diverse wallets at 100 % byte-perfect match on every shared open position. ### What this proves If you trust Polymarket's positions page, you can trust ours — they're computing the same thing from the same on-chain events. Use this script as ongoing regression coverage in your own integration if PnL accuracy is critical to your product. # Trade History (All) Source: https://docs.polynode.dev/api-reference/onchain/trades GET /v2/onchain/trades Query every trade ever executed on Polymarket. Filter by wallet, market, time range, or minimum size. Full history with cursor pagination. Search and filter the complete history of Polymarket trades. Every CLOB fill is enriched with market metadata. Supports filtering by wallet, market (via slug, condition ID, or token ID), time range, and minimum trade size. When no filters are provided, returns the most recent trades from the last 24 hours. Add any filter to query the full history. ## Request ``` GET /v2/onchain/trades ``` ### Query parameters | Parameter | Type | Required | Description | | ---------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `wallet` | string | No | Filter by wallet address (matches both maker and taker sides) | | `market_slug` | string | No | Filter by market slug (e.g. `will-zohran-mamdani-win-the-2025-nyc-mayoral-election`) | | `condition_id` | string | No | Filter by condition ID (0x-prefixed, 64 hex chars) | | `token_id` | string | No | Filter by outcome token ID | | `start_time` | integer | No | Unix timestamp — only return trades after this time | | `end_time` | integer | No | Unix timestamp — only return trades before this time | | `min_total` | number | No | Minimum trade size in USD | | `limit` | integer | No | Results per page (1-500, default 100) | | `order` | string | No | Sort by timestamp: `desc` (newest first, default) or `asc` (oldest first). Using `asc` without any filter returns an error. | | `sort_by` | string | No | Set to `order_hash` to cluster fills by order. All fills from the same limit order appear adjacent, sorted by time within each group. Groups ordered by most recent fill. Default sort is by timestamp. | | `group_by` | string | No | Set to `order_hash` to aggregate fills into one row per order. Each row contains `total_amount_usd`, `total_shares`, `avg_price`, `fill_count`, `first_fill_at`, `last_fill_at`, and `tx_hashes`. See [Group by order](#group-by-order) below. | | `pagination_key` | string | No | Cursor from a previous response to fetch the next page | ### Identifying markets There are three ways to filter trades by market: * **`market_slug`** — the human-readable URL slug from Polymarket (e.g. `will-zohran-mamdani-win-the-2025-nyc-mayoral-election`). Easiest to use. * **`condition_id`** — the unique condition identifier for a market. Returns trades for all outcomes (Yes and No). * **`token_id`** — the specific outcome token. Returns trades for only that outcome. You can combine market filters with `wallet` to find a specific wallet's trades on a specific market. ## Response ```json theme={null} { "count": 2, "pagination": { "limit": 2, "has_more": true, "pagination_key": "0xfffff7ed9080f46d7975f905e7f9358c436a1b177c6e19e54510c3ba6dcfddc4_0x09c8f6c7a148a83dfc968b1d9aece6ddb43a7a0e1bc2b1cb4b38356bb32220f0" }, "trades": [ { "timestamp": 1775668111, "tx_hash": "0xfffff7ed9080f46d7975f905e7f9358c436a1b177c6e19e54510c3ba6dcfddc4", "order_hash": "0xd808640f8d06bd5d1fa6f86c149d2a66753a6c86a4580c616a9e170f57f96b02", "maker": "0xa973ae12882a1c24a75da7a3b52cb01500b2f639", "taker": "0xc5d563a36ae78145c45a50134d48a1215220f80a", "token_id": "3486411672082301675645165272859578340300389072626261580506086464973862637410", "price": 0.92, "amount_usd": 1, "fee_usd": 0.01, "shares": 1.08695, "market": "Will Goztepe SK win on 2026-04-08?", "market_slug": "tur-goz-gal-2026-04-08-goz", "outcome": "No", "condition_id": "0x8c7ec1f6e3694c402bd204a25756d55b34bb294f28677b624e1068450f1dfb1a", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/tur.png" }, { "timestamp": 1775668111, "tx_hash": "0xfffff7ed9080f46d7975f905e7f9358c436a1b177c6e19e54510c3ba6dcfddc4", "order_hash": "0x09c8f6c7a148a83dfc968b1d9aece6ddb43a7a0e1bc2b1cb4b38356bb32220f0", "maker": "0x84ad9c5c547a82ec9a08547b94bd922446e5bfb7", "taker": "0xa973ae12882a1c24a75da7a3b52cb01500b2f639", "token_id": "107137984184508009127443282317786484656951337636215782600474106740257845898368", "price": 0.08, "amount_usd": 0.09, "fee_usd": 0.11, "shares": 1.08695, "market": "Will Goztepe SK win on 2026-04-08?", "market_slug": "tur-goz-gal-2026-04-08-goz", "outcome": "Yes", "condition_id": "0x8c7ec1f6e3694c402bd204a25756d55b34bb294f28677b624e1068450f1dfb1a", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/tur.png" } ] } ``` ### Trade fields | Field | Type | Description | | -------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `timestamp` | number | Unix timestamp of the fill | | `tx_hash` | string | Polygon transaction hash | | `order_hash` | string | CLOB order hash that was filled | | `maker` | string | Maker wallet address | | `taker` | string | Taker wallet address | | `token_id` | string | Outcome token ID | | `price` | number | Fill price (0 to 1) | | `amount_usd` | number | Trade size in USD | | `fee_usd` | number | Fee paid in USD | | `shares` | number | Number of outcome shares traded | | `side` | string | Exchange role: `"maker"` or `"taker"`. With `wallet` filter, this is the queried wallet's role on the fill. Without `wallet`, always `"maker"` (see "Direction & side semantics" below). Always present. | | `direction` | string | Trader intent: `"BUY"` or `"SELL"`. With `wallet` filter, from the queried wallet's perspective (`BUY` when the wallet contributed USDC, `SELL` when it contributed outcome tokens). Without `wallet`, from the maker's perspective. Always present. | | `market` | string | Market question text | | `market_slug` | string | Market URL slug | | `outcome` | string | Outcome label (e.g. "Yes", "No") | | `condition_id` | string | Market condition ID | | `image` | string \| null | Market image URL. `null` for some delisted or niche markets. | ### Direction & side semantics `direction` and `side` answer two different questions and are always populated: * **`direction` (`"BUY"` or `"SELL"`)** — what the subject of the row did, economically. They contributed USDC and received outcome shares (`BUY`) or contributed outcome shares and received USDC (`SELL`). * **`side` (`"maker"` or `"taker"`)** — the subject's role on the fill. The `maker` placed a resting limit order; the `taker` aggressed the spread. The "subject" of the row depends on the filter: | Query | Subject of `direction` / `side` | | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `?wallet=W` (with or without other filters) | The wallet `W`. `side` reflects whether `W` was the maker or taker on each fill. | | Any market-wide filter (no `wallet`): `?condition_id=…`, `?token_id=…`, `?market_slug=…`, or no filter | The **maker** of each fill. On Polymarket's CLOB the maker is always the user who signed the limit order, so `direction` is always a real user's BUY/SELL. `side` is therefore always `"maker"` in this case. | This means a row from a wallet-filtered query and the same row from a market-wide query may report different `direction` values — both are correct, just anchored on different parties (the queried wallet vs. the maker). ### Pagination fields | Field | Type | Description | | --------------------------- | ------- | ---------------------------------------------------------------------- | | `count` | number | Number of trades in this response | | `pagination.limit` | number | Requested page size | | `pagination.has_more` | boolean | `true` if more results exist beyond this page | | `pagination.pagination_key` | string | Pass this as `pagination_key` in the next request to get the next page | ## Examples ### Recent trades Returns the most recent trades from the last 24 hours. ```bash cURL theme={null} curl "https://api.polynode.dev/v2/onchain/trades?limit=2" \ -H "x-api-key: YOUR_KEY" ``` ```javascript Node.js theme={null} const resp = await fetch( "https://api.polynode.dev/v2/onchain/trades?limit=2", { headers: { "x-api-key": "YOUR_KEY" } } ); const data = await resp.json(); console.log(data.trades); ``` ```python Python theme={null} import requests resp = requests.get( "https://api.polynode.dev/v2/onchain/trades", params={"limit": 2}, headers={"x-api-key": "YOUR_KEY"}, ) data = resp.json() print(data["trades"]) ``` ### Filter by wallet Returns all trades where the wallet was either maker or taker. The `side` field indicates which role the wallet played. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/trades?wallet=0xa973ae12882a1c24a75da7a3b52cb01500b2f639&limit=1" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "count": 1, "pagination": { "limit": 1, "has_more": true, "pagination_key": "0xfffff7ed...d808640f..." }, "trades": [ { "timestamp": 1775668111, "tx_hash": "0xfffff7ed9080f46d7975f905e7f9358c436a1b177c6e19e54510c3ba6dcfddc4", "order_hash": "0xd808640f8d06bd5d1fa6f86c149d2a66753a6c86a4580c616a9e170f57f96b02", "maker": "0xa973ae12882a1c24a75da7a3b52cb01500b2f639", "taker": "0xc5d563a36ae78145c45a50134d48a1215220f80a", "token_id": "34864116720823016756451652728595783403003890726262615805060864649738626374410", "price": 0.92, "amount_usd": 1, "fee_usd": 0.01, "shares": 1.08695, "side": "maker", "market": "Will Goztepe SK win on 2026-04-08?", "market_slug": "tur-goz-gal-2026-04-08-goz", "outcome": "No", "condition_id": "0x8c7ec1f6e3694c402bd204a25756d55b34bb294f28677b624e1068450f1dfb1a", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/tur.png" } ] } ``` ### Filter by market Use `market_slug` to get all trades on a specific market across all wallets. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/trades?market_slug=will-zohran-mamdani-win-the-2025-nyc-mayoral-election&limit=1" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "count": 1, "pagination": { "limit": 1, "has_more": true, "pagination_key": "0xffffffeb...00063fc7..." }, "trades": [ { "timestamp": 1759503806, "tx_hash": "0xffffffeb7983b64a9394a0485c897113ad17ffca0fde8f3fa16aa045396d0582", "order_hash": "0x00063fc790381b8d017b868f153aeed48152c8c7012e33cb4793b3f5c8989b3a", "maker": "0xe05d8c348aee0323cf115c18006a35db54ba2685", "taker": "0xc5d563a36ae78145c45a50134d48a1215220f80a", "token_id": "33945469250963963541781051637999677727672635213493648594066577298999471399137", "price": 0.897, "amount_usd": 100, "fee_usd": 0, "shares": 111.482719, "market": "Will Zohran Mamdani win the 2025 NYC mayoral election?", "market_slug": "will-zohran-mamdani-win-the-2025-nyc-mayoral-election", "outcome": "Yes", "condition_id": "0xebddfcf7b4401dade8b4031770a1ab942b01854f3bed453d5df9425cd9f211a9", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/will-zohran-mamdani-win-the-2025-nyc-mayoral-election-EscSJQTT6hWg.jpg" } ] } ``` You can also filter by `condition_id` to get both outcomes of a market: ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/trades?condition_id=0xebddfcf7b4401dade8b4031770a1ab942b01854f3bed453d5df9425cd9f211a9&limit=10" \ -H "x-api-key: YOUR_KEY" ``` ### Large trades Use `min_total` to find trades above a USD threshold. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/trades?min_total=100&limit=10" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "count": 1, "pagination": { "limit": 1, "has_more": true, "pagination_key": "0xffffffeb...a0017d41..." }, "trades": [ { "timestamp": 1759503806, "tx_hash": "0xffffffeb7983b64a9394a0485c897113ad17ffca0fde8f3fa16aa045396d0582", "order_hash": "0xa0017d41adb20f9cb729243b8d8464218d3f2a9e8d24c8e53f36c744c9f250a7", "maker": "0x12d6cccfc7470a3f4bafc53599a4779cbf2cf2a8", "taker": "0xe05d8c348aee0323cf115c18006a35db54ba2685", "token_id": "33945469250963963541781051637999677727672635213493648594066577298999471399137", "price": 0.897, "amount_usd": 100, "fee_usd": 0, "shares": 111.482719, "market": "Will Zohran Mamdani win the 2025 NYC mayoral election?", "market_slug": "will-zohran-mamdani-win-the-2025-nyc-mayoral-election", "outcome": "Yes", "condition_id": "0xebddfcf7b4401dade8b4031770a1ab942b01854f3bed453d5df9425cd9f211a9", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/will-zohran-mamdani-win-the-2025-nyc-mayoral-election-EscSJQTT6hWg.jpg" } ] } ``` ### Time range Use `start_time` and `end_time` (Unix timestamps) to query a specific window. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/trades?start_time=1775600000&end_time=1775700000&limit=10" \ -H "x-api-key: YOUR_KEY" ``` ### Combined filters Combine any filters. For example, a wallet's trades on a specific market: ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/trades?wallet=0xa973ae12882a1c24a75da7a3b52cb01500b2f639&market_slug=tur-goz-gal-2026-04-08-goz&limit=10" \ -H "x-api-key: YOUR_KEY" ``` ### Pagination Pass the `pagination_key` from the previous response to get the next page. ```bash theme={null} # First page curl "https://api.polynode.dev/v2/onchain/trades?limit=100" \ -H "x-api-key: YOUR_KEY" # Next page curl "https://api.polynode.dev/v2/onchain/trades?limit=100&pagination_key=CURSOR_FROM_PREVIOUS" \ -H "x-api-key: YOUR_KEY" ``` ### Sort by order Use `sort_by=order_hash` to cluster fills from the same limit order together. A single limit order can be filled across many transactions over time. This sort groups all those partial fills adjacently, sorted by timestamp within each order. Orders are ranked by their most recent fill. Every fill is still returned individually with all fields. No data is collapsed or aggregated. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/trades?wallet=0xa973ae12882a1c24a75da7a3b52cb01500b2f639&sort_by=order_hash&limit=100" \ -H "x-api-key: YOUR_KEY" ``` ### Group by order Use `group_by=order_hash` to aggregate all fills from the same limit order into a single row. Each row contains summed totals, a VWAP price, fill count, time range, and a list of transaction hashes. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/trades?wallet=0xa973ae12882a1c24a75da7a3b52cb01500b2f639&group_by=order_hash&limit=100" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "count": 76, "source": "onchain-v2-grouped", "trades": [ { "order_hash": "0x7f69046bdbb2a595667e7ffe24ea285f87f95f7d146333dbc8a7e02948bbe52b", "fill_count": 12, "total_amount_usd": 208.76, "total_shares": 353.82, "avg_price": 0.59, "total_fee_usd": 0.0, "first_fill_at": 1778409433, "last_fill_at": 1778414041, "timestamp": 1778414041, "token_id": "102860925866954003158841129327041711410011624382962867117087237151352260776640", "maker": "0x6ac5bb06a9eb05641fd5e82640268b92f3ab4b6e", "side": "maker", "direction": "BUY", "market": "Pistons vs. Cavaliers", "market_slug": "nba-det-cle-2026-05-11", "outcome": "Cavaliers", "condition_id": "0x52f953bd31997875e5f782fbed7f85b8707aa8fefe95b6854cf5b8c69370b3f0", "tx_hashes": ["0xa7fdeb87...", "0xbb9092bc...", "..."] } ] } ``` #### Grouped trade fields | Field | Type | Description | | ------------------ | -------------- | ----------------------------------------------------------------- | | `order_hash` | string | The CLOB order hash (grouping key) | | `fill_count` | number | Number of individual fills aggregated into this row | | `total_amount_usd` | number | Sum of all fill sizes in USD | | `total_shares` | number | Sum of all outcome shares traded | | `avg_price` | number | Volume-weighted average price (`total_amount_usd / total_shares`) | | `total_fee_usd` | number | Sum of all fees in USD | | `first_fill_at` | number | Unix timestamp of the earliest fill | | `last_fill_at` | number | Unix timestamp of the most recent fill | | `timestamp` | number | Same as `last_fill_at` (for compatibility with default sort) | | `tx_hashes` | string\[] | List of unique transaction hashes for all fills | | `token_id` | string | Outcome token ID | | `maker` | string | Maker address (from the first fill) | | `side` | string | Exchange role (from the first fill) | | `direction` | string | Trade direction (from the first fill) | | `market` | string | Market question text | | `market_slug` | string | Market URL slug | | `outcome` | string | Outcome label | | `condition_id` | string | Market condition ID | | `image` | string \| null | Market image URL | ### Oldest first Use `order=asc` to get trades in chronological order. Requires at least one filter. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/trades?wallet=0xa973ae12882a1c24a75da7a3b52cb01500b2f639&order=asc&limit=10" \ -H "x-api-key: YOUR_KEY" ``` ## Error responses | Status | Response | Condition | | ------ | ------------------------------------------------------------------------------------------------------------------- | --------------------------------- | | 400 | `{"error": "order=asc requires at least one filter (wallet, token_id, condition_id, market_slug, or start_time)."}` | `order=asc` without any filter | | 400 | `{"error": "market_slug not found"}` | Invalid or unknown `market_slug` | | 400 | `{"error": "condition_id not found"}` | Invalid or unknown `condition_id` | | 401 | `{"error": "API key required. Pass via ?key= or x-api-key header."}` | Missing API key | | 403 | `{"error": "V2 endpoints require a paid plan. See polynode.dev/pricing for details."}` | Free tier key | | 429 | `{"error": "Rate limited. N req/s for your tier.", "retryAfterMs": ...}` | Rate limited | ## Notes * Without filters, defaults to the last 24 hours for performance. Add `start_time` to query further back. * The `side` field is only included when filtering by `wallet`. Without a wallet filter, there is no perspective to assign a side from. * Market metadata (`market`, `market_slug`, `outcome`, `condition_id`, `image`) is enriched from our index. Very old or delisted markets may not have metadata. * Results are sourced from onchain settlement data. Every fill that settled on Polygon is included. # Wallet Activity Source: https://docs.polynode.dev/api-reference/onchain/wallet-activity GET /v2/onchain/wallets/{address}/activity Onchain activity for a wallet: splits, merges, and multi-outcome conversions. Returns all onchain activity for a wallet including token splits (minting outcome tokens from collateral), merges (burning outcome tokens back to collateral), and neg-risk conversions (multi-outcome market hedging). Each event is enriched with market metadata. This data is not available through Polymarket's API. ## Request ``` GET /v2/onchain/wallets/{address}/activity?limit=100&offset=0 ``` | Parameter | Type | Location | Description | | --------- | ------- | -------- | ------------------------------------------ | | `address` | string | path | Wallet address (0x-prefixed, 40 hex chars) | | `limit` | integer | query | Max results (default 100, max 1000) | | `offset` | integer | query | Skip first N results for pagination | ## Response ```json theme={null} { "wallet": "0x2f5653a3761f65c5a299f9839eadbd4d4d679ffa", "source": "onchain", "count": 1, "offset": 0, "splits": 0, "merges": 1, "conversions": 0, "events": [ { "type": "merge", "id": "0x583037e505fc38851d81fc129d2eef0cb57a81270d7b5cb9f500dca9a3f898e9_0x5fd", "timestamp": 1774715707, "condition": "0xe84334d8b082ddf50a6aa659bb19283ac94edfd52a5357fb7547c0371bb18084", "amount": 5, "market": "Bitcoin Up or Down - March 28, 12:30PM-12:35PM ET", "slug": "btc-updown-5m-1774715400", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/BTC+fullsize.png" } ] } ``` | Field | Type | Description | | ----------------------------- | ------ | -------------------------------------------------- | | `splits` | number | Count of split events in this page | | `merges` | number | Count of merge events in this page | | `conversions` | number | Count of neg-risk conversion events in this page | | `events[].type` | string | `"split"`, `"merge"`, or `"neg_risk_conversion"` | | `events[].timestamp` | number | Unix timestamp | | `events[].condition` | string | Condition ID | | `events[].amount` | number | USDC amount | | `events[].market` | string | Market question | | `events[].slug` | string | Market slug | | `events[].image` | string | Market image URL | | `events[].neg_risk_market_id` | string | Neg-risk market ID (conversions only) | | `events[].index_set` | string | Index set (conversions only) | | `events[].question_count` | number | Questions in the neg-risk event (conversions only) | ## Event Types **Split** — Collateral deposited and split into outcome tokens. This is how positions are entered via the CTF contract (as opposed to buying on the orderbook). **Merge** — Outcome tokens burned back into collateral. The inverse of a split. Often used when exiting a hedged position. **Neg-risk conversion** — Converting between outcome tokens in a multi-outcome market (e.g., "Who will win the election?" with 5+ candidates). Used for hedging and arbitrage. ## Example ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/wallets/0x2f5653a3761f65c5a299f9839eadbd4d4d679ffa/activity?limit=50" \ -H "x-api-key: YOUR_KEY" ``` # Wallet Redemptions Source: https://docs.polynode.dev/api-reference/onchain/wallet-redemptions GET /v2/onchain/wallets/{address}/redemptions All onchain redemptions for a wallet. See who cashed out, when, and how much. Returns every onchain redemption for a wallet. A redemption is when a user claims their payout after a market resolves. This data is not available through Polymarket's API. Each redemption is enriched with market metadata. ## Request ``` GET /v2/onchain/wallets/{address}/redemptions?limit=100&offset=0 ``` | Parameter | Type | Location | Description | | --------- | ------- | -------- | ------------------------------------------ | | `address` | string | path | Wallet address (0x-prefixed, 40 hex chars) | | `limit` | integer | query | Max results (default 100, max 1000) | | `offset` | integer | query | Skip first N results for pagination | ## Response ```json theme={null} { "wallet": "0x2f5653a3761f65c5a299f9839eadbd4d4d679ffa", "source": "onchain", "count": 1, "offset": 0, "total_payout": 5, "redemptions": [ { "id": "0x655555bc5091e07a453667c20afe4af5df2e099311670abf86a718dc48998b53_0x678", "timestamp": 1774749134, "condition": "0xedb2d159f7917cedf2ca9fe5b2fe70ba5db7fee23eff951ce8d7f96983cf6a66", "index_sets": ["1", "2"], "payout": 5, "market": "Bitcoin Up or Down - March 28, 9:45PM-9:50PM ET", "slug": "btc-updown-5m-1774748700", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/BTC+fullsize.png" } ] } ``` | Field | Type | Description | | -------------------------- | ------ | ------------------------------------------------- | | `total_payout` | number | Sum of all payout amounts (USDC) in this page | | `redemptions[].timestamp` | number | Unix timestamp of the redemption | | `redemptions[].condition` | string | Condition ID of the resolved market | | `redemptions[].index_sets` | array | Outcome index sets redeemed | | `redemptions[].payout` | number | USDC payout amount (0 = losing position redeemed) | | `redemptions[].market` | string | Market question | | `redemptions[].slug` | string | Market slug | | `redemptions[].image` | string | Market image URL | ## Example ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/wallets/0x2f5653a3761f65c5a299f9839eadbd4d4d679ffa/redemptions?limit=10" \ -H "x-api-key: YOUR_KEY" ``` # Trade History (Wallet) Source: https://docs.polynode.dev/api-reference/onchain/wallet-trades GET /v2/onchain/wallets/{address}/trades Complete onchain trade fill history for any wallet. Every fill, every counterparty, every fee. Returns every onchain trade fill for a wallet — both maker and taker sides, every fill, every counterparty, every fee. Pulled directly from onchain settlement data, never misses a fill. Each trade is enriched with market metadata (question, slug, outcome, image). **Want the entire wallet's history in one call?** Use [Trade History (Wallet) — Bulk](/api-reference/onchain/wallet-trades-all) (Growth plan and above). Returns up to 250K trades in a single response, no pagination. ## Quick start — get the most recent 100 trades ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/wallets/{address}/trades?limit=100" \ -H "x-api-key: YOUR_KEY" ``` Default `limit=100`, max `limit=1000`. That's all most apps need. ## Get every trade for the wallet (full history) To walk the entire history, add `?cursor=` (empty value) to the first request, then **for each next page, copy `pagination.cursor` from the response back into the URL**. Stop when `pagination.has_more` is `false`. Three complete copy-paste scripts that walk an entire wallet: ```python Python theme={null} import requests API_KEY = "YOUR_KEY" WALLET = "0xc944d399164b4460a4a79184fc6f419027891e0a" def get_all_trades(wallet): cursor = "" # empty string = first page all_trades = [] while True: r = requests.get( f"https://api.polynode.dev/v2/onchain/wallets/{wallet}/trades", params={"limit": 1000, "cursor": cursor}, headers={"x-api-key": API_KEY}, ).json() all_trades.extend(r["trades"]) if not r["pagination"]["has_more"]: break cursor = r["pagination"]["cursor"] # feed this into the next request return all_trades trades = get_all_trades(WALLET) print(f"got {len(trades)} trades") ``` ```javascript JavaScript theme={null} const API_KEY = "YOUR_KEY"; const WALLET = "0xc944d399164b4460a4a79184fc6f419027891e0a"; async function getAllTrades(wallet) { let cursor = ""; // empty string = first page const allTrades = []; while (true) { const url = `https://api.polynode.dev/v2/onchain/wallets/${wallet}/trades` + `?limit=1000&cursor=${encodeURIComponent(cursor)}`; const r = await fetch(url, { headers: { "x-api-key": API_KEY } }).then(r => r.json()); allTrades.push(...r.trades); if (!r.pagination.has_more) break; cursor = r.pagination.cursor; // feed this into the next request } return allTrades; } const trades = await getAllTrades(WALLET); console.log(`got ${trades.length} trades`); ``` ```bash bash theme={null} KEY="YOUR_KEY" WALLET="0xc944d399164b4460a4a79184fc6f419027891e0a" CURSOR="" TOTAL=0 while true; do RESP=$(curl -s "https://api.polynode.dev/v2/onchain/wallets/$WALLET/trades?limit=1000&cursor=$CURSOR" \ -H "x-api-key: $KEY") COUNT=$(echo "$RESP" | python3 -c "import json,sys;print(json.load(sys.stdin)['count'])") HAS_MORE=$(echo "$RESP" | python3 -c "import json,sys;print(json.load(sys.stdin)['pagination']['has_more'])") TOTAL=$((TOTAL + COUNT)) echo "got $COUNT trades, total: $TOTAL" [ "$HAS_MORE" = "False" ] && break CURSOR=$(echo "$RESP" | python3 -c "import json,sys;print(json.load(sys.stdin)['pagination']['cursor'])") done ``` Each page takes \~1 second regardless of how many trades the wallet has. The loop just keeps running until `has_more` is `false`. ## Request ``` GET /v2/onchain/wallets/{address}/trades?limit=100 GET /v2/onchain/wallets/{address}/trades?limit=1000&cursor= ``` | Parameter | Type | Location | Description | | --------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `address` | string | path | Wallet address (0x-prefixed, 40 hex chars) | | `limit` | integer | query | Max results per page (default 100, max 1000) | | `cursor` | string | query | Pass empty string `?cursor=` for first page, then echo back `pagination.cursor` from each response. Required to walk past the first page reliably on heavy wallets. | | `offset` | integer | query | Alternative to cursor: skip first N. Fast for the first \~25K results then degrades. Use `cursor` for anything bigger. | ## Response ```json theme={null} { "wallet": "0xc944d399164b4460a4a79184fc6f419027891e0a", "source": "onchain", "count": 1, "pagination": { "limit": 1000, "has_more": false }, "trades": [ { "tx_hash": "0x3611178d615caeab8f1fb32bc6232a32aaf93fc1e2f5bc5c9914ce2ddb9a13c3", "order_hash": "0x8676e03168a3cbf39bc1c695e9419127605bd070ff26e7cd628b662c363a82b5", "timestamp": 1777503244, "maker": "0xc944d399164b4460a4a79184fc6f419027891e0a", "taker": "0xe111180000d2663c0091e4f400237545b87b996b", "maker_asset_id": "0", "taker_asset_id": "110959653450933276250915064669875552310439627880508793089816880777942697720191", "maker_amount": 4723.199764, "taker_amount": 4737.412, "fee": 0, "side": "maker", "direction": "BUY", "market": "US x Iran ceasefire extended by April 22, 2026?", "slug": "us-x-iran-ceasefire-extended-by-april-22-2026", "outcome": "No", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/us-x-iran-ceasefire-by-Cgmx3GCuOwjs.jpg" } ] } ``` When `pagination.has_more` is `true`, the response also includes a `pagination.cursor` string — pass that as `?cursor=` for the next request. | Field | Type | Description | | ------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `count` | integer | Number of trades in this response | | `pagination.has_more` | boolean | `true` if more pages available — keep paginating | | `pagination.cursor` | string | Pass back as `?cursor=` for the next page (only present when `has_more` is `true`) | | `trades[].tx_hash` | string | Transaction hash | | `trades[].timestamp` | number | Unix timestamp of the fill | | `trades[].order_hash` | string | Order hash that was filled | | `trades[].maker` | string | Maker wallet address | | `trades[].taker` | string | Taker wallet address | | `trades[].maker_asset_id` | string | Asset the maker provided (`"0"` = USDC) | | `trades[].taker_asset_id` | string | Asset the taker provided (CTF token ID) | | `trades[].maker_amount` | number | Amount maker provided (USDC) | | `trades[].taker_amount` | number | Amount taker provided (tokens) | | `trades[].fee` | number | Fee paid on this fill (USDC) | | `trades[].side` | string | `"maker"` or `"taker"` relative to the queried wallet — exchange role on this fill | | `trades[].direction` | string | `"BUY"` or `"SELL"` from the queried wallet's perspective. `BUY` = wallet contributed USDC and received outcome tokens. `SELL` = wallet contributed outcome tokens and received USDC. | | `trades[].market` | string | Market question | | `trades[].slug` | string | Market slug | | `trades[].outcome` | string | Outcome label (e.g. "Yes", "Up", "Trump") | | `trades[].image` | string | Market image URL | ## `side` vs `direction` Both fields are populated on every row, and they answer different questions: * **`side`** = was the wallet the **maker** (resting limit order) or **taker** (crossing the spread) on this fill? * **`direction`** = did the wallet **BUY** outcome shares (gave USDC) or **SELL** them (received USDC)? You'll see all four combinations in real wallets — a maker can be a buyer or seller depending on which side of the orderbook they posted to. Same for takers. # Trade History (Wallet) — Bulk Source: https://docs.polynode.dev/api-reference/onchain/wallet-trades-all GET /v2/onchain/wallets/{address}/trades/all Return every trade for a wallet in a single response. Growth plan or above. **Growth plan or above only.** This endpoint returns the **entire** trade history for a wallet in **one response** — no client-side pagination, no offset bookkeeping. Because a single call can return tens of thousands of fills (and tens of MB of JSON), it's gated to the **Growth plan and above**. Starter and free tier requests receive `403`. * Hard cap: **250,000 trades per call** — beyond that, response includes `"partial": true` with `"partial_reason": "hard_cap_250000"` * Wall-clock budget: **180 seconds** — first cold-cache call on a heavy wallet may take 1-3 minutes * Cache: full results cached **5 minutes**. Repeat calls return in under 1 second * Typical payload: 1-50 MB. Worst case (capped): 100-200 MB JSON Returns every onchain trade fill for a wallet in a single response — both maker and taker sides, every counterparty, every fee, complete history. The server walks the full trade record with parallel time-bucketed queries server-side; you don't need to paginate. This is the right endpoint for **bulk-export** of a wallet for analysis, indexing, or backtesting. For interactive UIs that just need the most recent N trades, use the standard [Wallet Trade History](/api-reference/onchain/wallet-trades) instead. ## Request ``` GET /v2/onchain/wallets/{address}/trades/all?from=&to= ``` | Parameter | Type | Location | Description | | --------- | ------- | -------- | --------------------------------------------------------------------------------------------- | | `address` | string | path | Wallet address (0x-prefixed, 40 hex chars) | | `from` | integer | query | Optional start of window in unix seconds. Default: 2024-01-01 (covers all Polymarket history) | | `to` | integer | query | Optional end of window in unix seconds. Default: now | ## Response ```json theme={null} { "wallet": "0x2f5653a3761f65c5a299f9839eadbd4d4d679ffa", "from_ts": 1704067200, "to_ts": 1777600000, "count": 8421, "partial": false, "partial_reason": null, "fetched_in_ms": 7813, "cache": "miss", "trades": [ { "tx_hash": "0x2fc63f3efc794d13c747d089d3b42bb9b4539b22216ccb2e6b2ea039ca5bb9ca", "order_hash": "0x84f21e21f5c2a1df3264694a33ec9a8a292b83e163cbda4efe63333243aa08ef", "timestamp": 1774749310, "maker": "0x2f5653a3761f65c5a299f9839eadbd4d4d679ffa", "taker": "0x198098d9c6c1dcb843314b9da212c44396c9a1d0", "token_id": "21912724974096796009916816278814088615574660931588091764221331842149572809887", "direction": "BUY", "price": 0.625, "shares": 4.88, "usd": 3.05, "fee": 0 } ] } ``` ## Response fields | Field | Type | Description | | ------------------- | -------------- | ---------------------------------------------------------------------------------------------------------- | | `wallet` | string | The wallet address requested (lowercased) | | `from_ts` / `to_ts` | integer | Resolved time window in unix seconds | | `count` | integer | Total trades returned (≤ 250,000) | | `partial` | boolean | `true` if either the 250K hard cap or the 180s wall-clock budget was hit before the full window was walked | | `partial_reason` | string \| null | `"hard_cap_250000"` or `"wall_clock_180s"` when partial; otherwise `null` | | `fetched_in_ms` | integer | Server-side fetch time. Cached calls show the original miss time | | `cache` | string | `"hit"` if served from Redis cache, `"miss"` if freshly walked | | `trades` | array | All trades, sorted by `timestamp` descending (newest first) | ## Trade fields | Field | Type | Description | | ------------ | ------- | ------------------------------------------------ | | `tx_hash` | string | Polygon transaction hash | | `order_hash` | string | Order hash from the exchange | | `timestamp` | integer | Unix seconds when the fill settled | | `maker` | string | Maker address | | `taker` | string | Taker address | | `token_id` | string | The outcome token id traded in this fill | | `direction` | string | `"BUY"` or `"SELL"` from the maker's perspective | | `price` | number | USDC per share, 4-decimal precision | | `shares` | number | Outcome tokens traded | | `usd` | number | USDC notional | | `fee` | number | Maker fee paid (USDC) | ## Example: full lifetime trade history of a wallet ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/wallets/0x2f5653a3761f65c5a299f9839eadbd4d4d679ffa/trades/all?key=$YOUR_KEY" ``` ## Example: only trades from the last 30 days ```bash theme={null} NOW=$(date +%s); FROM=$((NOW - 2592000)) curl "https://api.polynode.dev/v2/onchain/wallets/0x2f5653a3761f65c5a299f9839eadbd4d4d679ffa/trades/all?from=$FROM&to=$NOW&key=$YOUR_KEY" ``` ## Errors `403 Tier required`: ```json theme={null} { "error": "/trades/all requires the Growth plan or above. Your tier: starter. See polynode.dev/pricing." } ``` `401 No API key`: ```json theme={null} { "error": "API key required. Pass via ?key= or x-api-key header." } ``` ## Notes * `partial: true` results are **not cached**. Subsequent calls re-attempt the full walk. Tighten the time window with `from`/`to` if you keep hitting the cap. * Cache key is `(wallet, from_ts, to_ts)`. Different windows cached independently. * For wallets that consistently exceed the 250K cap, slice into multiple windowed calls and concatenate client-side. Each window cached separately for 5 minutes. * Trades include both maker AND taker sides — every fill the wallet was involved in. No double-counting. # Midpoint Price Source: https://docs.polynode.dev/api-reference/orderbook/midpoint GET /v1/midpoint/{token_id} Returns the midpoint price for a token, calculated as the average of the best bid and best ask. If the book is one-sided (only bids or only asks), the missing side is treated as 0. Response is enriched with PolyNode market metadata. Returns the midpoint price for a token or market, calculated as the average of the best bid and best ask. ## Identifier types The `{token_id}` parameter accepts three types: | Identifier | Format | Returns | | ---------------- | --------------------- | --------------------------------------- | | **Token ID** | All digits, 70+ chars | Single midpoint (Polymarket-compatible) | | **Condition ID** | Starts with `0x` | Midpoints for both outcomes | | **Slug** | Human-readable string | Midpoints for all outcomes in the event | ### By token ID ```bash theme={null} curl "https://api.polynode.dev/v1/midpoint/114694726451307654528948558967898493662917070661203465131156925998487819889437" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "mid": "0.465", "token_id": "114694726451307654528948558967898493662917070661203465131156925998487819889437", "question": "Netanyahu out by end of 2026?", "slug": "netanyahu-out-before-2027-684-719-226" } ``` ### By condition ID ```bash theme={null} curl "https://api.polynode.dev/v1/midpoint/0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "condition_id": "0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96", "question": "Netanyahu out by end of 2026?", "event_title": "Netanyahu out by...?", "outcomes": [ {"outcome": "Yes", "token_id": "11469472645...", "mid": "0.465"}, {"outcome": "No", "token_id": "66255671088...", "mid": "0.535"} ] } ``` ## Calculation ``` midpoint = (best_bid + best_ask) / 2 ``` If the book is one-sided (only bids or only asks), the missing side is treated as 0. This matches Polymarket's behavior. ## Notes * The `mid` value is a decimal string (e.g. `"0.465"`). * Returns a 404 if no book exists for the identifier. # Order Book Source: https://docs.polynode.dev/api-reference/orderbook/order-book GET /v1/orderbook/{token_id} Returns the full order book for a token, including all bid and ask price levels, market properties, and PolyNode metadata enrichment. The response format is compatible with Polymarket's CLOB API. Data is served from PolyNode's live orderbook engine, which maintains real-time state via WebSocket deltas from Polymarket. Returns the full order book for a Polymarket token or market. ## Identifier types The `{token_id}` parameter accepts three identifier types. The response format depends on which type you use: | Identifier | Format | Returns | | ---------------- | --------------------- | --------------------------------------------------- | | **Token ID** | All digits, 70+ chars | Single book for one outcome (Polymarket-compatible) | | **Condition ID** | Starts with `0x` | Both outcomes for one market (Yes + No) | | **Slug** | Human-readable string | All outcomes across all markets in the event | ### By token ID (single outcome) Best when you already have a specific token ID and want the exact Polymarket-compatible format. ```bash theme={null} curl "https://api.polynode.dev/v1/orderbook/114694726451307654528948558967898493662917070661203465131156925998487819889437" \ -H "x-api-key: YOUR_KEY" ``` Response matches Polymarket's CLOB `/book` format exactly, plus a `metadata` field: ```json theme={null} { "market": "0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96", "asset_id": "114694726451307654528948558967898493662917070661203465131156925998487819889437", "timestamp": "1773901991806", "hash": "b811dc1233a0f250a60018bc94f377955c9a513e", "bids": [ {"price": "0.01", "size": "1130220"}, {"price": "0.02", "size": "76910"}, {"price": "0.46", "size": "18"} ], "asks": [ {"price": "0.99", "size": "24292"}, {"price": "0.98", "size": "2048"}, {"price": "0.47", "size": "687"} ], "min_order_size": "5", "tick_size": "0.01", "neg_risk": false, "last_trade_price": "0.540", "metadata": { "question": "Netanyahu out by end of 2026?", "slug": "netanyahu-out-before-2027-684-719-226", "outcomes": ["Yes", "No"], "condition_id": "0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/netanyahu-out-in-2025-Vc7bE4GtiJzM.jpg" } } ``` ### By condition ID (both outcomes) Best when you want both sides of a market at once. Condition IDs start with `0x`. ```bash theme={null} curl "https://api.polynode.dev/v1/orderbook/0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96" \ -H "x-api-key: YOUR_KEY" ``` Response wraps both outcomes: ```json theme={null} { "condition_id": "0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96", "question": "Netanyahu out by end of 2026?", "event_title": "Netanyahu out by...?", "outcomes": [ { "outcome": "Yes", "token_id": "114694726451307654528948558967898493662917070661203465131156925998487819889437", "market": "0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96", "bids": [{"price": "0.01", "size": "1130220"}, "..."], "asks": [{"price": "0.99", "size": "24292"}, "..."], "min_order_size": "5", "tick_size": "0.01", "neg_risk": false, "last_trade_price": "0.540" }, { "outcome": "No", "token_id": "66255671088804707681511323064315150986307471908131081808279119719218775249892", "market": "0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96", "bids": [{"price": "0.01", "size": "821530"}, "..."], "asks": [{"price": "0.99", "size": "13720.65"}, "..."], "min_order_size": "5", "tick_size": "0.01", "neg_risk": false, "last_trade_price": "0.540" } ] } ``` ### By slug (all outcomes across markets) Best for getting a complete view of an event. Slugs are the human-readable event identifiers from Polymarket (e.g. `netanyahu-out-before-2027`). An event may have multiple markets, each with their own outcomes. ```bash theme={null} curl "https://api.polynode.dev/v1/orderbook/netanyahu-out-before-2027" \ -H "x-api-key: YOUR_KEY" ``` Response includes all outcomes across all markets in the event. Same format as the condition ID response, but with `slug` instead of `condition_id` at the top level. ## Fields (single-token response) | Field | Type | Description | | ------------------ | ------- | ------------------------------------------------------------------- | | `market` | string | Market condition ID (hex) | | `asset_id` | string | Token ID | | `timestamp` | string | Unix timestamp in milliseconds | | `hash` | string | 40-character hex hash of the book state | | `bids` | array | Bid price levels, sorted ascending by price | | `asks` | array | Ask price levels, sorted descending by price | | `min_order_size` | string | Minimum order size for this market | | `tick_size` | string | Minimum price increment (e.g. `"0.01"` or `"0.001"`) | | `neg_risk` | boolean | Whether this is a neg-risk market | | `last_trade_price` | string | Last traded price | | `metadata` | object | PolyNode enrichment: question, slug, outcomes, condition\_id, image | ## Notes * `tick_size` and `min_order_size` vary per market. They are sourced from the Gamma API, not hardcoded. * Bids are sorted ascending by price (best bid is the last element). Asks are sorted descending (best ask is the last element). This matches Polymarket's sort order. * If no book exists for the requested identifier, returns a 404 with `{"error": "No orderbook exists for the requested token id"}`. # Bid-Ask Spread Source: https://docs.polynode.dev/api-reference/orderbook/spread GET /v1/spread/{token_id} Returns the bid-ask spread for a token, calculated as the difference between the best ask and best bid price. For one-sided books, the spread equals the best price on the existing side. Response is enriched with PolyNode market metadata. Returns the bid-ask spread for a token or market, calculated as the difference between the best ask and best bid. ## Identifier types The `{token_id}` parameter accepts three types: | Identifier | Format | Returns | | ---------------- | --------------------- | ------------------------------------- | | **Token ID** | All digits, 70+ chars | Single spread (Polymarket-compatible) | | **Condition ID** | Starts with `0x` | Spreads for both outcomes | | **Slug** | Human-readable string | Spreads for all outcomes in the event | ### By token ID ```bash theme={null} curl "https://api.polynode.dev/v1/spread/114694726451307654528948558967898493662917070661203465131156925998487819889437" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "spread": "0.01", "token_id": "114694726451307654528948558967898493662917070661203465131156925998487819889437", "question": "Netanyahu out by end of 2026?", "slug": "netanyahu-out-before-2027-684-719-226" } ``` ### By condition ID ```bash theme={null} curl "https://api.polynode.dev/v1/spread/0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "condition_id": "0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96", "question": "Netanyahu out by end of 2026?", "event_title": "Netanyahu out by...?", "outcomes": [ {"outcome": "Yes", "token_id": "11469472645...", "spread": "0.01"}, {"outcome": "No", "token_id": "66255671088...", "spread": "0.01"} ] } ``` ## Calculation ``` spread = best_ask - best_bid ``` For one-sided books (only bids or only asks), the spread equals the best price on the existing side. ## Notes * The `spread` value is a decimal string (e.g. `"0.01"`). * Returns a 404 if no book exists for the identifier. # API Reference Source: https://docs.polynode.dev/api-reference/overview Start here for PolyNode REST APIs: V3 Polymarket data, wallet analytics, market data, orderbook snapshots, profiles, search, and legacy endpoints. PolyNode exposes authenticated REST APIs for Polymarket market data, wallet analytics, positions, trades, builders, orderbooks, profiles, search, and supporting tools. ## Core APIs | API | Use it for | | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | [V3 Data API](/data/overview) | Polymarket markets, positions, trades, wallet P\&L, builders, combos, rewards, tags, fees, and global stats. | | [Wallet P\&L](/data/wallets/summary) | Wallet summaries, realized P\&L, P\&L events, open positions, and wallet activity. | | [Markets & Search](/data/markets/search) | Market discovery, condition lookups, market-level positions, prices, trades, and candles. | | [Orderbook](/api-reference/orderbook/order-book) | Current orderbook, midpoint, and spread snapshots. | | [Profiles](/api-reference/polymarket-profiles/overview) | Polymarket profile lookup and username setup flows. | | [Onchain V2](/api-reference/onchain/trades) | Legacy onchain V2 trade, position, redemption, volume, and candle endpoints. | ## Base URLs | Surface | Base URL | | ----------------- | --------------------------------- | | REST API | `https://api.polynode.dev` | | WebSocket streams | `wss://polynode.dev/ws` | | Orderbook stream | `wss://polynode.dev/orderbook/ws` | ## Authentication Send your API key with either `x-api-key`, `Authorization: Bearer `, or `?key=`. ## Rate Limits REST and streaming limits vary by plan. See [Rate Limits](/guides/rate-limits) for the current public limits, including the separate bucket for heavy trade-history endpoints. # Complete Username Action Source: https://docs.polynode.dev/api-reference/polymarket-profiles/complete-username POST /v3/polymarket/profiles/username/complete Submit wallet signatures and create or change the user's Polymarket username. ```text theme={null} POST /v3/polymarket/profiles/username/complete ``` Completes a previously created username challenge. This endpoint consumes the challenge atomically, verifies both wallet signatures, logs in to Polymarket, creates the profile if needed, and sets the username. Call this from your backend with the same PolyNode API key that created the challenge. Do not call it directly from the browser. ## Request ```json theme={null} { "challenge_id": "pmprof_652e85a9839fe3e97348a72ff5018ef5", "address": "0xC3579b0C908F43d50C63951A30C6f72fCEA4F6A0", "username": "alice123", "polymarket_signature": "0x...", "consent_signature": "0x..." } ``` | Field | Type | Required | Description | | ---------------------- | ------ | -------- | ----------------------------------------------------------------- | | `challenge_id` | string | Yes | Challenge id returned by the challenge endpoint | | `address` | string | Yes | Same EOA address used in the challenge | | `username` | string | Yes | Same username used in the challenge | | `polymarket_signature` | string | Yes | Signature of `challenge.polymarket.message` using `personal_sign` | | `consent_signature` | string | Yes | Signature of `challenge.consent` using `eth_signTypedData_v4` | ## Example ```bash theme={null} curl -X POST "https://api.polynode.dev/v3/polymarket/profiles/username/complete" \ -H "content-type: application/json" \ -H "x-api-key: pn_live_..." \ -d '{ "challenge_id": "pmprof_652e85a9839fe3e97348a72ff5018ef5", "address": "0xC3579b0C908F43d50C63951A30C6f72fCEA4F6A0", "username": "alice123", "polymarket_signature": "0x...", "consent_signature": "0x..." }' ``` ## Response ```json theme={null} { "status": "success", "action": "set_username", "username": "alice123", "address": "0xc3579b0c908f43d50c63951a30c6f72fcea4f6a0", "deposit_wallet": "0xf6d30d58add6c6814d1b086ec543a16ab6cc9a31", "safe_address": "0xba2ef45d0fe68cee6351420b60fb554bbe91bf02", "gamma_user_id": "8313180", "gamma_profile_id": "8391726", "created_profile": true, "changed_username": true, "profile_url": "https://polymarket.com/profile/0xf6d30d58add6c6814d1b086ec543a16ab6cc9a31" } ``` | Field | Type | Description | | ------------------ | -------------- | ------------------------------------------------------- | | `status` | string | `success` | | `action` | string | `set_username` | | `username` | string | Final requested username | | `address` | string | EOA address, lowercase | | `deposit_wallet` | string | Deterministic Polymarket deposit wallet | | `safe_address` | string | Deterministic Safe address checked during challenge | | `gamma_user_id` | string or null | Polymarket Gamma user id if returned | | `gamma_profile_id` | string or null | Polymarket Gamma profile id if returned | | `created_profile` | boolean | `true` if PolyNode created a new Gamma profile | | `changed_username` | boolean | `true` if PolyNode changed the profile name | | `profile_url` | string | Polymarket profile URL using the deposit wallet address | ## Server checks The endpoint: * loads and consumes the challenge atomically * requires the same PolyNode API key hash that created the challenge * requires exact address, username, action, and challenge id match * verifies the challenge is not expired * recovers the signer from the Polymarket SIWE signature * recovers the signer from the PolyNode consent typed-data signature * requires both recovered addresses to equal the challenge EOA * re-checks username availability unless the current profile already has that username ## Errors | Status | Code | Meaning | | ------ | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | `400` | `challenge_mismatch` | API key, address, username, action, or challenge id did not match | | `400` | `challenge_expired` | Challenge is older than 10 minutes | | `400` | `signature_invalid` | A signature was malformed or recovered to the wrong address | | `404` | `challenge_not_found` | Challenge was missing, already consumed, or expired from Redis | | `409` | `username_taken` | Username became unavailable before completion | | `409` | `safe_already_deployed` | Derived Safe is deployed but Gamma did not return a user for the session | | `403` | `polymarket_compliance_blocked` | Polymarket rejected the action for compliance or eligibility reasons | | `429` | `rate_limited` | Profile-specific rate limit exceeded | | `502` | `polymarket_login_failed`, `polymarket_profile_create_failed`, `polymarket_profile_update_failed`, or `upstream_unavailable` | Upstream action failed | # Create Username Challenge Source: https://docs.polynode.dev/api-reference/polymarket-profiles/create-challenge POST /v3/polymarket/profiles/username/challenge Create the wallet-signing challenge for a Polymarket username create or update. ```text theme={null} POST /v3/polymarket/profiles/username/challenge ``` Creates a one-time challenge for a user-owned EOA. The response contains: * a Polymarket SIWE message to sign with `personal_sign` * a PolyNode consent typed-data payload to sign with `eth_signTypedData_v4` * derived deposit wallet and Safe addresses The challenge expires after 10 minutes. ## Request ```json theme={null} { "address": "0xC3579b0C908F43d50C63951A30C6f72fCEA4F6A0", "username": "alice123", "action": "set_username" } ``` | Field | Type | Required | Description | | ---------- | ------ | -------- | --------------------------------------------------- | | `address` | string | Yes | User EOA address | | `username` | string | Yes | Desired username | | `action` | string | No | Must be `set_username`. Defaults to `set_username`. | ## Example ```bash theme={null} curl -X POST "https://api.polynode.dev/v3/polymarket/profiles/username/challenge" \ -H "content-type: application/json" \ -H "x-api-key: pn_live_..." \ -d '{ "address": "0xC3579b0C908F43d50C63951A30C6f72fCEA4F6A0", "username": "alice123", "action": "set_username" }' ``` ## Response ```json theme={null} { "challenge_id": "pmprof_652e85a9839fe3e97348a72ff5018ef5", "address": "0xC3579b0C908F43d50C63951A30C6f72fCEA4F6A0", "username": "alice123", "action": "set_username", "expires_at": "2026-05-26T13:03:06Z", "polymarket": { "signature_type": "personal_sign", "message": "polymarket.com wants you to sign in with your Ethereum account:\n0xC3579b0C908F43d50C63951A30C6f72fCEA4F6A0\n\nWelcome to Polymarket! Sign to connect.\n\nURI: https://polymarket.com\nVersion: 1\nChain ID: 137\nNonce: 4b7d...\nIssued At: 2026-05-26T12:53:06.000Z\nExpiration Time: 2026-06-02T12:53:06.000Z", "fields": { "domain": "polymarket.com", "address": "0xC3579b0C908F43d50C63951A30C6f72fCEA4F6A0", "statement": "Welcome to Polymarket! Sign to connect.", "uri": "https://polymarket.com", "version": "1", "chainId": 137, "nonce": "4b7d...", "issuedAt": "2026-05-26T12:53:06.000Z", "expirationTime": "2026-06-02T12:53:06.000Z" } }, "consent": { "signature_type": "typed_data_v4", "domain": { "name": "Polynode", "version": "1", "chainId": 137 }, "types": { "EIP712Domain": [ { "name": "name", "type": "string" }, { "name": "version", "type": "string" }, { "name": "chainId", "type": "uint256" } ], "PolymarketProfileAction": [ { "name": "action", "type": "string" }, { "name": "address", "type": "address" }, { "name": "username", "type": "string" }, { "name": "challengeId", "type": "string" }, { "name": "expiresAt", "type": "uint256" } ] }, "primaryType": "PolymarketProfileAction", "message": { "action": "set_username", "address": "0xc3579b0c908f43d50c63951a30c6f72fcea4f6a0", "username": "alice123", "challengeId": "pmprof_652e85a9839fe3e97348a72ff5018ef5", "expiresAt": 1779800586 } }, "derived": { "deposit_wallet": "0xf6d30d58add6c6814d1b086ec543a16ab6cc9a31", "safe_address": "0xba2ef45d0fe68cee6351420b60fb554bbe91bf02", "safe_deployed": false } } ``` ## Server checks The endpoint: * validates address and username format * checks Polymarket username availability * fetches a Gamma nonce * builds the exact Polymarket SIWE message * builds PolyNode consent typed data * derives deposit wallet and Safe addresses * checks whether the derived Safe is deployed on Polygon * stores a one-time challenge under a 10 minute TTL ## Errors | Status | Code | Meaning | | ------ | --------------------------------------- | ---------------------------------------- | | `400` | `invalid_address` or `invalid_username` | Request validation failed | | `400` | `challenge_mismatch` | Unsupported action | | `409` | `username_taken` | Username is not available | | `429` | `rate_limited` | Profile-specific rate limit exceeded | | `502` | `upstream_unavailable` | Polymarket, Gamma, or Polygon RPC failed | # Get Public Profile Source: https://docs.polynode.dev/api-reference/polymarket-profiles/get-profile GET /v3/polymarket/profiles/{address} Read public Polymarket profile state for an EOA address. ```text theme={null} GET /v3/polymarket/profiles/{address} ``` Returns normalized public profile information from Polymarket for an EOA address. ## Path parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `address` | string | Yes | EOA address | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/polymarket/profiles/0xC3579b0C908F43d50C63951A30C6f72fCEA4F6A0" \ -H "x-api-key: pn_live_..." ``` ```json theme={null} { "address": "0xc3579b0c908f43d50c63951a30c6f72fcea4f6a0", "proxy_wallet": "0xf6d30d58add6c6814d1b086ec543a16ab6cc9a31", "username": "alice123", "pseudonym": "Glistening-Risk", "display_username_public": true, "profile_image": null, "x_username": null, "verified_badge": false, "source": "polymarket" } ``` ## Response fields | Field | Type | Description | | ------------------------- | --------------- | ------------------------------------------- | | `address` | string | EOA address requested, lowercase | | `proxy_wallet` | string or null | Polymarket wallet address returned by Gamma | | `username` | string or null | Public profile username | | `pseudonym` | string or null | Polymarket-generated pseudonym | | `display_username_public` | boolean or null | Whether the username is public | | `profile_image` | string or null | Profile image URL if present | | `x_username` | string or null | Linked X username if present | | `verified_badge` | boolean or null | Polymarket verified badge state | | `source` | string | `polymarket` | ## Errors | Status | Code | Meaning | | ------ | ---------------------- | ------------------------------------------ | | `400` | `invalid_address` | Address is not a valid EVM address | | `404` | `profile_not_found` | Polymarket did not return a public profile | | `429` | `rate_limited` | Profile-specific rate limit exceeded | | `502` | `upstream_unavailable` | Polymarket/Gamma profile lookup failed | # Polymarket Profiles Source: https://docs.polynode.dev/api-reference/polymarket-profiles/overview Create or change a Polymarket username from a user-owned EOA wallet. The Polymarket Profiles API is a secure username provisioning flow for apps that manage their own users and wallets. Use it when your user has an EOA wallet and you want to let them set up a Polymarket username without sending private keys or long-lived Polymarket credentials to PolyNode. ## Base URL ```text theme={null} https://api.polynode.dev ``` ## Authentication Every request requires a paid PolyNode API key. ```bash theme={null} curl "https://api.polynode.dev/v3/polymarket/profiles/username-available?username=alice123" \ -H "x-api-key: pn_live_..." ``` Your backend should call these endpoints. Do not expose your API key to browser clients. ## Endpoints | Method | Endpoint | Description | | ------ | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------- | | `GET` | [`/v3/polymarket/profiles/username-available`](/api-reference/polymarket-profiles/username-available) | Check whether a username is available | | `POST` | [`/v3/polymarket/profiles/username/challenge`](/api-reference/polymarket-profiles/create-challenge) | Create the two wallet-signing payloads | | `POST` | [`/v3/polymarket/profiles/username/complete`](/api-reference/polymarket-profiles/complete-username) | Submit signatures and create or change the username | | `GET` | [`/v3/polymarket/profiles/{address}`](/api-reference/polymarket-profiles/get-profile) | Read public profile state | ## Flow 1. Check the username. 2. Create a challenge for `{address, username, action}`. 3. User signs the Polymarket SIWE message with `personal_sign`. 4. User signs the PolyNode consent typed data with `eth_signTypedData_v4`. 5. Complete the action from your backend. See the [Polymarket Profile Setup guide](/guides/polymarket-profile-setup) for a complete frontend and backend integration. ## Error shape ```json theme={null} { "error": "challenge_mismatch", "message": "challenge fields do not match completion request" } ``` Common error codes: | Code | Meaning | | ------------------------------- | -------------------------------------------------------------------- | | `invalid_address` | Invalid EVM address | | `invalid_username` | Username failed local validation | | `username_taken` | Username is unavailable | | `challenge_not_found` | Challenge expired, already consumed, or missing | | `challenge_expired` | Challenge is older than 10 minutes | | `challenge_mismatch` | Challenge fields or API key do not match | | `signature_invalid` | Wallet signature did not recover to the requested EOA | | `polymarket_compliance_blocked` | Polymarket rejected the action for compliance or eligibility reasons | | `upstream_unavailable` | Polymarket, Gamma, or Polygon RPC failed | | `rate_limited` | Profile-specific rate limit exceeded | # Check Username Availability Source: https://docs.polynode.dev/api-reference/polymarket-profiles/username-available GET /v3/polymarket/profiles/username-available Check whether Polymarket will accept a username. ```text theme={null} GET /v3/polymarket/profiles/username-available ``` Use this endpoint before creating a challenge. It calls Polymarket's username availability check and returns the upstream result. ## Query parameters | Parameter | Type | Required | Description | | ---------- | ------ | -------- | ------------------------------------------------------------------------------------------------------ | | `username` | string | Yes | Desired username. Local validation is `^[A-Za-z0-9_]{3,32}$`. Polymarket is still the final authority. | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/polymarket/profiles/username-available?username=alice123" \ -H "x-api-key: pn_live_..." ``` ```json theme={null} { "username": "alice123", "available": true, "source": "polymarket" } ``` Unavailable usernames return `200` with `available: false`: ```json theme={null} { "username": "alice_123", "available": false, "source": "polymarket" } ``` ## Errors | Status | Code | Meaning | | ------ | ---------------------- | ---------------------------------------- | | `400` | `invalid_username` | Missing username or invalid local format | | `401` | varies | Missing or invalid PolyNode API key | | `402` | varies | Paid plan required | | `429` | `rate_limited` | Profile-specific rate limit exceeded | | `502` | `upstream_unavailable` | Polymarket availability check failed | # Market Card Source: https://docs.polynode.dev/api-reference/pricing/market-statistics GET /v1/stats/{token_id} One-shot consolidated summary for a token — condition, outcomes, neg-risk flag, end date, last trade time, current orderbook liquidity, 24h OHLCV, and last price. Saves you from making 3–4 separate calls when you just need a market overview tile. # Online Search API Source: https://docs.polynode.dev/api-reference/search-online GET /v2/search/online Search the public web and return normalized organic results for agent context, market research, and enrichment workflows. **Beta.** This endpoint is designed for low-volume context enrichment. Results are best-effort, cached, and may be partial when upstream search engines throttle or fail. ## Endpoint ```http theme={null} GET /v2/search/online ``` Search the public web and return normalized organic results. Responses are cached to keep the endpoint lightweight. ```bash theme={null} curl "https://api.polynode.dev/v2/search/online?q=Cavaliers%20Knicks%20injury%20news&max_results=5" \ -H "x-api-key: YOUR_KEY" ``` ## Query parameters | Parameter | Description | | ------------------- | ---------------------------------------------------------------------------------------------------------- | | `q` | Search query. Required. | | `max_results` | Number of normalized organic results. Defaults to `10`, capped at `20`. | | `engines` | Comma-separated engine adapters. Defaults to `bing,duckduckgo`. `brave` is available as an opt-in adapter. | | `language` | Search language. Defaults to `en-US`. | | `time_range` | Optional freshness filter when supported: `day`, `week`, `month`, `year`. | | `safe_search` | Safe-search mode: `0`, `1`, or `2`. Defaults to `0`. | | `cache_ttl_seconds` | Cache TTL for this query. Defaults to `600`, capped at `3600`. | Rate limit: `3` requests per `10` seconds per API key, in addition to your normal tier limit. ## Response ```json theme={null} { "query": "Cavaliers Knicks injury news", "source": "search_online", "adapter": "searxng", "status": "ok", "cached": false, "engines_requested": ["bing", "duckduckgo"], "unresponsive_engines": [], "result_count": 2, "results": [ { "title": "Cleveland Cavaliers Injury Status - ESPN", "url": "https://www.espn.com/nba/team/injuries/_/name/cle/cleveland-cavaliers", "domain": "espn.com", "snippet": "Visit ESPN for the current injury situation...", "engines": ["duckduckgo"], "score": 1, "category": "general", "published_at": null } ], "answers": [], "suggestions": [], "fetched_at": "2026-05-25T11:10:00.000Z", "elapsed_ms": 1200 } ``` `status` may be `partial` when one engine fails but other engines still return results. # Sports Markets Source: https://docs.polynode.dev/api-reference/sports/sports-markets PolyNode sports endpoints expose Polymarket-native sports market data: leagues, teams, games, markets, token IDs, current CLOB prices, and historical price series. These endpoints replace the old sportsbook/OddsJam experiment. They do not return sportsbook odds. They return Polymarket market prices and metadata, normalized around sports games. ## Endpoints | Endpoint | Description | | --------------------------------------- | -------------------------------------------------------------------------------------------- | | `GET /v2/sports/leagues` | Sports and esports league metadata from Polymarket Gamma | | `GET /v2/sports/summary` | League-level active/live/date summary | | `GET /v2/sports/live` | Backward-compatible live overview derived from `/sports/summary` | | `GET /v2/sports/leagues/{league}/teams` | Teams for a league, with logos and records when available | | `GET /v2/sports/leagues/{league}/games` | Games/events for a league, including markets, token IDs, and Gamma fallback metadata | | `GET /v2/sports/games/{slug}` | Full game/event detail with normalized markets | | `GET /v2/sports/games/{slug}/state` | Canonical game state: metadata, score fields, markets, current prices, and subscribe payload | | `GET /v2/sports/games/{slug}/context` | Agent-friendly context bundle from generated game queries and optional external sources | | `GET /v2/sports/games/{slug}/prices` | Current bid, ask, midpoint, and spread for game tokens | | `GET /v2/sports/games/{slug}/history` | Batch CLOB price history for game tokens | | `GET /v2/sports/market-types` | Polymarket sports market type codes with PolyNode labels/categories | | `GET /v2/sports/search?q={query}` | Search sports events and teams | ## Game state ## League games Use league games for schedule discovery, such as listing all active FIFA World Cup match markets: ```bash theme={null} curl "https://api.polynode.dev/v2/sports/leagues/fifwc/games?status=active&sort=startDate&direction=asc&limit=50" \ -H "x-api-key: YOUR_KEY" ``` The response is filtered and sorted by PolyNode after resolving the league code to Polymarket's sports `series_id`. If Polymarket Gamma's primary event listing is unavailable, the response can still succeed through alternate Gamma sources and will include `fallback: true`. If Gamma is fully unavailable and PolyNode has a recent good response, it returns that cached response with `stale: true`. Use this when you want one game-centric object instead of manually joining game metadata, CLOB prices, markets, and websocket subscription details. ```bash theme={null} curl "https://api.polynode.dev/v2/sports/games/nba-cle-nyk-2026-05-31/state?price_limit_tokens=20" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "slug": "nba-cle-nyk-2026-05-31", "title": "Cavaliers vs. Knicks", "status": "scheduled", "source": { "metadata": "polymarket_gamma", "prices": "polymarket_clob", "history": null, "score": "polymarket_gamma" }, "score": { "source": "gamma:event", "score": null, "period": "NS", "live": null, "freshness": "unknown" }, "markets": [ { "question": "Cavaliers vs. Knicks", "sportsMarketType": "moneyline", "clobTokenIds": ["..."], "prices": [ { "outcome": "Cavaliers", "best_bid": "0.31", "best_ask": "0.47", "midpoint": "0.39", "spread": "0.16" } ] } ], "subscribe": { "ws": "wss://ws.polynode.dev/ws", "action": "subscribe", "type": "orderbook", "token_ids": ["..."] } } ``` Add `include_history=true` to include CLOB price history in the same response: ```bash theme={null} curl "https://api.polynode.dev/v2/sports/games/nba-cle-nyk-2026-05-31/state?include_history=true&history_limit_tokens=2&interval=1d" \ -H "x-api-key: YOUR_KEY" ``` ## Game context Use this for agent workflows that need game-specific search context next to the normalized sports object. PolyNode generates a small set of matchup, injury, lineup, news, or market-specific queries from the Polymarket game record, then runs the requested sources. ```bash theme={null} curl "https://api.polynode.dev/v2/sports/games/nba-cle-nyk-2026-05-31/context?sources=x,online&query_set=injuries&max_queries=2&max_per_query=5&include_state=true&price_limit_tokens=20" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "slug": "nba-cle-nyk-2026-05-31", "title": "Cavaliers vs. Knicks", "query_set": "injuries", "generated_queries": [ { "id": "matchup-injuries", "query": "Cavaliers Knicks injury", "reason": "Availability and injury context for both teams." } ], "sources_requested": ["x", "online"], "sources": { "x": { "status": "ok", "result_count": 5, "queries": [ { "query": "Cavaliers Knicks injury", "tweets": [] } ] }, "online": { "status": "ok", "source": "search_online", "adapter": "searxng", "queries": [] } }, "state": { "slug": "nba-cle-nyk-2026-05-31", "markets": [] } } ``` Parameters: | Parameter | Description | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `sources` | Comma-separated sources. Defaults to `x`. Supported: `x`, `online`. `web`, `search`, `ddg`, and `ddg_instant` alias to `online`. | | `query_set` | Query strategy. Supported: `default`, `injuries`, `lineups`, `news`, `social`, `markets`. | | `max_queries` | Number of generated queries to run. Defaults to `3`, capped at `5`. | | `max_per_query` | Per-source result count per query. Defaults to `5`, capped at `10`. | | `include_state` | Include the `/state` response in the same payload. Defaults to `false`. | | `price_limit_tokens` | Token cap used when `include_state=true`. Defaults to `20`, capped at `200`. | The `x` source uses the same X Search beta quota and 1 request/second key-level rate limit as `/v2/x/search`. Each generated X query consumes one X Search quota unit. The `online` source uses `/v2/search/online` and returns normalized public web results. ## Current prices ```bash theme={null} curl "https://api.polynode.dev/v2/sports/games/nba-nyk-cle-2026-05-25/prices?limit_tokens=10" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "slug": "nba-nyk-cle-2026-05-25", "title": "Knicks vs. Cavaliers", "source": "polymarket_clob", "count": 10, "total_tokens": 106, "truncated": true, "tokens": [ { "token_id": "36872217940161491972120830074682109141065398170878601426051147635064624582652", "outcome": "Knicks", "question": "Knicks vs. Cavaliers", "market_type": "moneyline", "best_bid": "0.56", "best_ask": "0.57", "midpoint": "0.565", "spread": "0.01" } ], "subscribe": { "ws": "wss://ws.polynode.dev/ws", "action": "subscribe", "type": "orderbook", "token_ids": ["..."] } } ``` ## Price history ```bash theme={null} curl "https://api.polynode.dev/v2/sports/games/nba-nyk-cle-2026-05-25/history?limit_tokens=2" \ -H "x-api-key: YOUR_KEY" ``` `limit_tokens` defaults to `20` and is capped at `20` because Polymarket's batch history endpoint caps requests at 20 market asset IDs. If you do not pass `interval`, `start_ts`, or `end_ts`, PolyNode requests the full available CLOB history with `interval=max`. ```json theme={null} { "slug": "nba-nyk-cle-2026-05-25", "source": "polymarket_clob", "count": 2, "total_tokens": 106, "truncated": true, "tokens": [ { "token_id": "36872217940161491972120830074682109141065398170878601426051147635064624582652", "outcome": "Knicks", "history": [ { "t": 1779615604, "p": 0.555 } ] } ] } ``` ## Notes * `prices` fans out to Polymarket CLOB batch price, midpoint, and spread endpoints. * `history` uses Polymarket CLOB batch price history. Bare `/history` calls default to `interval=max`; pass `interval=1d`, `interval=1w`, or an explicit `start_ts`/`end_ts` window when you want a smaller range. * Scores are included only where Polymarket Gamma has score fields on the event. Use score fields as best-effort metadata, not guaranteed live scoreboard state. * For live orderbook updates, use the returned `subscribe.token_ids` with PolyNode orderbook WebSocket. # Generate API Key Source: https://docs.polynode.dev/api-reference/system/generate-api-key POST /v1/keys Generates a new API key. Rate limited to 1 per IP per day. The key is returned only once and cannot be retrieved again. # Health Check Source: https://docs.polynode.dev/api-reference/system/health-check GET /healthz Liveness probe. Returns 200 OK if the server is running. # Readiness Check Source: https://docs.polynode.dev/api-reference/system/readiness-check GET /readyz Returns 200 if node is connected and Redis is reachable, 503 otherwise. # System Status Source: https://docs.polynode.dev/api-reference/system/system-status GET /v1/status Returns system status including node connectivity, uptime, stream lengths, and state engine summary. # Top Traders (Market) Source: https://docs.polynode.dev/api-reference/wallets/market-positions GET /v1/markets/{id}/positions Every wallet that holds (or has held) a position in a single market, grouped by outcome and sorted by P&L. The 'who's making money on this market' feed. Returns every wallet with a position in a single market, broken out by outcome (e.g. Up/Down or Yes/No) and ranked by P\&L. The natural feed for "top traders on this market" leaderboards, top-X-by-PnL widgets, and per-market trader audits. Path accepts either the **market slug** (e.g. `btc-updown-5m-1777179000`) or the **condition\_id** (e.g. `0xa7ae8a41...`). It does **not** accept an outcome token id — pass the market identifier, not the side. Field shape is camelCase (`avgPrice`, `realizedPnl`, etc.) — distinct from polynode's V2 onchain endpoints which use snake\_case. Different schemas, different use cases. Default cap is 50 rows per outcome; configurable up to 500. ## Request ``` GET /v1/markets/{slug-or-condition-id}/positions ``` | Parameter | Type | Location | Description | | --------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | string | path | Market slug or condition\_id (`0x…`). **Not** an outcome token id. | | `limit` | integer | query | Max rows per outcome. Default `50`, max `500`. | | `offset` | integer | query | Skip first N rows. | | `sortBy` | string | query | `TOTAL_PNL` (default), `REALIZED_PNL`, `CURRENT_VALUE`, `SIZE`, `INITIAL_VALUE`. | | `sortDirection` | string | query | `DESC` (default) or `ASC`. | | `status` | string | query | `OPEN`, `CLOSED`, or `ALL` (default). | | `min_size` | number | query | Drop positions with `size < min_size` from each outcome. Use `min_size=0.0001` to filter to current holders only (excludes historical participants who closed out to zero). | | `includeTrades` | boolean | query | When `true`, adds `firstTradeAt` / `lastTradeAt` (unix seconds) per row. Heavy — separate 20 req/min rate limit per key. | | `user` | string | query | Filter to a single wallet, or up to 20 wallets comma-separated, scoped to this market. | `TOTAL_PNL` and `REALIZED_PNL` are sorted by Polymarket's data-api directly. `CURRENT_VALUE`, `SIZE`, and `INITIAL_VALUE` are sorted in-process after the fetch (and after `min_size` if set), so they pair cleanly with the holder-filtering use case. ## Response ```json theme={null} { "condition_id": "0xa7ae8a4119fe00f231e693ed717339dd1e13da4617c79f3b1522ab1aee3965b6", "market_title": "Bitcoin Up or Down - April 26, 12:50AM-12:55AM ET", "slug": "btc-updown-5m-1777179000", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/BTC+fullsize.png", "outcome_names": ["Up", "Down"], "outcomes": [ { "token": "90684694382683135738693577247532961739938461809085935450142382916430459093778", "positions": [ { "proxyWallet": "0x9a1d392572d0e6bfefbf9302101b9e44c8ee86d6", "name": "kq9000", "profileImage": "", "verified": false, "asset": "90684694382683135738693577247532961739938461809085935450142382916430459093778", "conditionId": "0xa7ae8a4119fe00f231e693ed717339dd1e13da4617c79f3b1522ab1aee3965b6", "outcome": "Up", "outcomeIndex": 0, "size": 0, "avgPrice": 0.4592, "currPrice": 1, "currentValue": 0, "totalBought": 1633.1987, "realizedPnl": 812.5754, "cashPnl": 0, "totalPnl": 812.5754, "firstTradeAt": 1777179030, "lastTradeAt": 1777179152 } ] }, { "token": "96824…", "positions": [ "…" ] } ] } ``` ### Top-level fields | Field | Type | Description | | ------------------------ | --------- | -------------------------------------------------------------- | | `condition_id` | string | Market condition id | | `market_title` | string | Human-readable market question | | `slug` | string | Market slug | | `image` | string | Market image URL | | `outcome_names` | string\[] | Ordered outcome labels (`["Up","Down"]`, `["Yes","No"]`, etc.) | | `outcomes[]` | array | One entry per outcome — each holds a `positions` array | | `outcomes[].token` | string | Outcome token id | | `outcomes[].positions[]` | array | Traders with positions in this outcome, sorted by `sortBy` | ### Per-trader fields (`outcomes[].positions[]`) | Field | Type | Description | | -------------- | ------- | ------------------------------------------------------------------- | | `proxyWallet` | string | Trader address (Gnosis Safe proxy) | | `name` | string | Display name (often empty or auto-generated) | | `profileImage` | string | Profile image URL (often empty) | | `verified` | boolean | Verified flag | | `asset` | string | Outcome token id (same as `outcomes[].token`) | | `conditionId` | string | Market condition id | | `outcome` | string | Outcome label (`"Up"`, `"No"`, etc.) | | `outcomeIndex` | number | 0-based position in `outcome_names` | | `size` | number | Current token balance. `0` = fully exited or never held. | | `avgPrice` | number | Volume-weighted average entry price | | `currPrice` | number | Current market price (or terminal `1.0`/`0.0` for resolved markets) | | `currentValue` | number | Mark-to-market value of remaining shares (`size × currPrice`) | | `totalBought` | number | Lifetime tokens acquired | | `realizedPnl` | number | Realized P\&L from sells + redemptions, in USDC | | `cashPnl` | number | P\&L from cash flows (separate accounting axis) | | `totalPnl` | number | Net portfolio P\&L for this position | | `firstTradeAt` | number | Unix seconds — only with `?includeTrades=true` | | `lastTradeAt` | number | Unix seconds — only with `?includeTrades=true` | ## Examples ### Top 10 traders on a market by total P\&L ```bash theme={null} curl "https://api.polynode.dev/v1/markets/btc-updown-5m-1777179000/positions?limit=10&sortBy=TOTAL_PNL&sortDirection=DESC" \ -H "x-api-key: YOUR_KEY" ``` ### By condition\_id, with first/last trade timestamps ```bash theme={null} curl "https://api.polynode.dev/v1/markets/0xa7ae8a4119fe00f231e693ed717339dd1e13da4617c79f3b1522ab1aee3965b6/positions?includeTrades=true&limit=20" \ -H "x-api-key: YOUR_KEY" ``` ### Drill into specific wallets within a market ```bash theme={null} curl "https://api.polynode.dev/v1/markets/btc-updown-5m-1777179000/positions?user=0x9a1d392572d0e6bfefbf9302101b9e44c8ee86d6,0x44bd2993a69d8b569859ed8c0bf0b946f733f71a" \ -H "x-api-key: YOUR_KEY" ``` ### Closed positions only ```bash theme={null} curl "https://api.polynode.dev/v1/markets/btc-updown-5m-1777179000/positions?status=CLOSED&sortBy=REALIZED_PNL" \ -H "x-api-key: YOUR_KEY" ``` ## Notes * **Default cap of 50 rows** per outcome unless `limit` is set higher (max 500). On very large markets with deep tail traders, paginate with `offset`. * **Sort ties on equal P\&L are non-deterministic** — two wallets at the same `totalPnl` may swap positions between calls. Use `proxyWallet` as a stable secondary key on the client side if you need consistent ordering. * **`size = 0` is normal** on closed positions, redeemed positions, or fully-exited positions. Use `realizedPnl` and `totalBought` to detect history. * **`firstTradeAt` / `lastTradeAt` require `?includeTrades=true`** and trip a separate heavy-endpoint rate limit (20 req/min per key). Don't request it on every refresh — fetch once and cache. ## When to use this vs. other position endpoints | If you want | Use | | ------------------------------------------------ | -------------------------------------------------------------------------------------- | | Top traders ranked on a single market | **This endpoint** | | One wallet's positions across all markets | [`GET /v2/wallets/{addr}/positions/onchain`](/api-reference/wallets/onchain-positions) | | All trades on a market (chronological fill feed) | [`GET /v2/onchain/markets/{tokenId}/trades`](/api-reference/onchain/market-trades) | # Positions & P&L (Wallet) Source: https://docs.polynode.dev/api-reference/wallets/onchain-positions GET /v2/wallets/{address}/positions/onchain Every position a wallet has held — open and closed — with realized + unrealized P&L, per-position activity timestamps, market resolution timestamps, and parent event slugs. Optional time-window filtering. Returns every position a wallet has ever held, including closed positions that no longer appear in the standard positions endpoint. Each position includes precomputed `realized_pnl`, `avg_price`, `total_bought`, plus per-position activity, redemption, and resolution timestamps that let you build leaderboards, trim to a 30-day or 90-day window, or filter to a parent event. The standard `/v1/wallets/{addr}/positions` endpoint only returns open positions. Once a position is fully exited or a market resolves, it disappears. This endpoint fills that gap. ## Request ``` GET /v2/wallets/{address}/positions/onchain ``` | Parameter | Type | Description | | ---------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `address` | path | Wallet address (0x-prefixed, 40 hex chars) | | `since` | query (optional) | Unix seconds. Drop positions whose `last_trade_at` is before this timestamp. | | `until` | query (optional) | Unix seconds. Drop positions whose `last_trade_at` is after this timestamp. | | `tag_slug` | query (optional) | Keep only positions whose market carries this Polymarket event tag (e.g. `nba`, `politics`, `crypto`, `2026-fifa-world-cup-winner-595`). Matches against the per-market `tag_slugs` array. See [Filtering by tag](#filtering-by-tag). | `since`, `until`, and `tag_slug` are all **opt-in**. When none are supplied, the response is identical to a request with no parameters at all — same shape, same aggregates over the full position set. When any is supplied, positions are filtered and aggregates are recomputed (see [Filtering](#filtering-by-activity-window)). ## Response ```json theme={null} { "wallet": "0xbddf61af533ff524d27154e589d2d7a81510c684", "source": "onchain", "count": 879, "open_count": 340, "closed_count": 288, "total_realized_pnl": 17183579.48, "total_unrealized_pnl": -14346997.32, "total_pnl": 2836582.16, "positions_with_pnl": 309, "positions": [ { "token_id": "100424505911492726143069668369640898890012859227903329249368753635028896002613", "size": 0, "avg_price": 0.63, "realized_pnl": 147588.53019, "unrealized_pnl": 0, "current_price": null, "market_status": "closed", "total_bought": 398887.919433, "market": "Will Fulham FC win on 2026-03-21?", "slug": "epl-ful-bur-2026-03-21-ful", "event_slug": "epl-ful-bur-2026-03-21", "outcome": "Yes", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/Repetitive-markets/premier+league.jpg", "condition_id": "0xae68634cb01137acbe26e65790f7268a523ee1519601f2576bd7b067c7c9f9da", "last_trade_at": 1742568134, "closed_at": 1742573012, "resolved_at": 1742572840 } ] } ``` | Field | Type | Description | | ---------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `wallet` | string | Queried wallet address (lowercased) | | `source` | string | Always `"onchain"` | | `count` | number | Total positions returned | | `open_count` | number | Positions with `size > 0` | | `closed_count` | number | Positions with `size = 0` and nonzero `realized_pnl` | | `total_realized_pnl` | number | Sum of `realized_pnl` across all returned positions | | `total_unrealized_pnl` | number | Sum of `unrealized_pnl` across all returned positions. Reflects open-size paper P\&L at current (or terminal) market prices. | | `total_pnl` | number | `total_realized_pnl + total_unrealized_pnl`. Matches the net portfolio PnL shown on a Polymarket profile page. | | `positions_with_pnl` | number | Positions where `realized_pnl != 0` | | `filtered` | boolean | Present **only** when `since` or `until` was supplied. Always `true` in that case. Absent in default responses. | | `applied_filters` | object | Present **only** when filtering. Echoes `{since, until}` (each is the supplied unix seconds or `null`). Absent in default responses. | | `positions[].token_id` | string | CTF token ID (called `asset` on the [closed-positions](/api-reference/wallets/closed-positions) endpoint) | | `positions[].size` | number | Current token balance (0 = fully exited) | | `positions[].avg_price` | number | Volume-weighted average entry price | | `positions[].realized_pnl` | number | Realized profit/loss in USDC | | `positions[].unrealized_pnl` | number | Paper P\&L on remaining open shares. For resolved markets, uses the terminal settlement price (1.0 winner / 0.0 loser). For live markets, uses the current market price. `0` when `size = 0`. | | `positions[].current_price` | number \| null | Price used for `unrealized_pnl`. `1.0` or `0.0` on resolved markets, live market price on active markets, `null` when `size = 0` or no price is available. | | `positions[].market_status` | string | `"live"` / `"resolved-win"` / `"resolved-loss"` / `"resolved-unknown"` / `"closed"`. See [positions feed](/api-reference/onchain/positions#position-fields) for full enum. | | `positions[].total_bought` | number | Total tokens acquired for this position | | `positions[].market` | string | Market question | | `positions[].slug` | string | **Market** slug — identifies the specific market within an event (e.g. `nba-bos-cle-2026-03-08-spread`). | | `positions[].event_slug` | string \| null | **Event** slug — identifies the parent event a market belongs to (e.g. `nba-bos-cle-2026-03-08`). For single-market events, equals `slug`. For multi-market events (NBA games with several lines, election markets with several candidates, FIFA World Cup with one market per team, etc.) this is the parent the markets share. `null` when the event metadata is not yet known. | | `positions[].outcome` | string | Outcome label (e.g. "Yes", "No", "Up") | | `positions[].image` | string | Market image URL | | `positions[].condition_id` | string | Market condition ID | | `positions[].last_trade_at` | number \| null | Unix seconds. Latest fill (maker or taker) on this token across V1 + V2 exchanges. `null` when polynode has no fill history for the token (e.g. positions acquired purely via splits/merges or NegRisk conversion with no later trade). | | `positions[].closed_at` | number \| null | Unix seconds. Latest moment this wallet redeemed any outcome of this market's `condition_id` for collateral. `null` when the wallet has not redeemed (positions sold to zero before resolution, or open positions, or wins held but not yet redeemed). | | `positions[].resolved_at` | number \| null | Unix seconds. Moment the market was resolved on-chain (when payouts became redeemable). `null` for markets that resolved before polynode began tracking, or for markets that have not yet resolved. Recent markets are fully covered. | | `positions[].tag_slugs` | string\[] \| null | Polymarket event-level tags applied to this market. Examples: `["NBA","Basketball","Sports","All"]` for an NBA game market, `["Crypto","Up or Down","Recurring","All"]` for a 5-min crypto market. Order is most-specific → most-general. Tags live on the parent event (markets sharing an `event_slug` share the same `tag_slugs`). `null` only on the rare event that doesn't carry any tag or hasn't been ingested yet. | ## Distinguishing the three timestamp fields These three timestamp fields look similar but answer different questions. Choosing the right one matters when filtering: | Field | Whose action | When | | --------------- | ----------------------------------------------------------------- | --------------------------------------------------------------- | | `last_trade_at` | The **wallet**'s most recent fill on this exact token | Each fill event (buy or sell) updates this | | `closed_at` | The **wallet**'s most recent collateral redemption for the market | Set when the wallet calls `redeemPositions` on the CTF contract | | `resolved_at` | The **market itself** (not the wallet) becoming redeemable | Set once per market, when the oracle reports payouts on-chain | A position can have `resolved_at` populated and `closed_at` null — the market resolved but the wallet has not yet redeemed. A position can have `closed_at` set and `resolved_at` null — the wallet redeemed during a window polynode does not have full historical resolution coverage for, but the redemption itself was observed. For windowing a leaderboard to "the last 30 days of trading activity," `last_trade_at` is the right field — it captures every position the wallet was active in. For "positions resolved in the last 90 days," `resolved_at` is the right one. For "positions the wallet has actually closed out," `closed_at` is the right one. ## Filtering by activity window `since` and `until` are optional unix-seconds query parameters that filter positions by `last_trade_at`. Designed to support 30d / 90d / 365d leaderboard windows without fetching multi-year history just to trim it client-side. ### Behavior * Both bounds are **inclusive**. * Positions with `last_trade_at = null` are dropped under any active filter — there's no timestamp to compare against. * All seven aggregates (`count`, `open_count`, `closed_count`, `total_realized_pnl`, `total_unrealized_pnl`, `total_pnl`, `positions_with_pnl`) **recompute over the filtered set**. * The response gains two top-level keys to make the shift self-documenting: `filtered: true` and `applied_filters: { since, until }`. These keys are absent from default responses. * Bad inputs return `400` with a descriptive error. ### 30-day window ```bash theme={null} SINCE=$(( $(date -u +%s) - 86400 * 30 )) curl "https://api.polynode.dev/v2/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/positions/onchain?since=$SINCE" \ -H "x-api-key: YOUR_KEY" ``` ### 90-day window ```bash theme={null} SINCE=$(( $(date -u +%s) - 86400 * 90 )) curl "https://api.polynode.dev/v2/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/positions/onchain?since=$SINCE" \ -H "x-api-key: YOUR_KEY" ``` ### Bounded range (e.g. Q1 2026) ```bash theme={null} curl "https://api.polynode.dev/v2/wallets/0x.../positions/onchain?since=1735689600&until=1743465600" \ -H "x-api-key: YOUR_KEY" ``` **The default response is unaffected by these new params.** When you call this endpoint with no `since`/`until`/`tag_slug`, the response shape and every aggregate is identical to what you got before. The `filtered` and `applied_filters` keys are only added when you opt in. ## Filtering by tag `tag_slug` is an optional query parameter that keeps only positions whose market carries the matching Polymarket event tag. Tags come from Polymarket's event taxonomy — typical values include `nba`, `basketball`, `sports`, `politics`, `crypto`, `fed`, `world`, `2025-predictions`, and event-specific slugs like `2026-fifa-world-cup-winner-595`. Each market inherits the tag set of its parent event, so all 32 markets under "Will X win the World Cup?" share the same tag list. The full set is visible in the per-row `tag_slugs` array on every response. ### Behavior * Match is case-sensitive and exact (`?tag_slug=nba` matches markets whose `tag_slugs` array contains `"nba"`). * Positions with `tag_slugs = null` are dropped under an active filter — same logic as `last_trade_at` for `since`/`until` (cannot match what we don't know). * Aggregates recompute over the filtered set. * `applied_filters.tag_slug` echoes the value back so the response is self-documenting. * Combine with `since` / `until` to get e.g. "all NBA positions in the last 30 days" — all three filters AND together. ### Examples #### All NBA positions for a wallet ```bash theme={null} curl "https://api.polynode.dev/v2/wallets/0x.../positions/onchain?tag_slug=nba" \ -H "x-api-key: YOUR_KEY" ``` #### All positions tagged to a specific event ```bash theme={null} curl "https://api.polynode.dev/v2/wallets/0x.../positions/onchain?tag_slug=2026-fifa-world-cup-winner-595" \ -H "x-api-key: YOUR_KEY" ``` This collapses across every children market under the FIFA World Cup winner event. #### Crypto positions in the last 90 days ```bash theme={null} SINCE=$(( $(date -u +%s) - 86400 * 90 )) curl "https://api.polynode.dev/v2/wallets/0x.../positions/onchain?tag_slug=crypto&since=$SINCE" \ -H "x-api-key: YOUR_KEY" ``` ### Discovering available tags Use the dedicated [`/v2/onchain/tags`](/api-reference/onchain/tags) endpoint for the full tag namespace (5,779 tags currently). Returns a flat slug array by default, or `?details=true` for per-tag market counts and timestamps: ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/tags?min_markets=100&details=true" \ -H "x-api-key: YOUR_KEY" ``` The values returned by `/v2/onchain/tags` are the exact strings to pass to `?tag_slug=` here. For a wallet-specific view, you can also fetch the wallet's positions without a filter and read the union of `tag_slugs` across the response — that's the tag namespace the wallet has actually traded in. ## How `realized_pnl` Works Each position's `realized_pnl` reflects the profit or loss after the position is closed (sold or redeemed). For a winning binary position bought at $0.40 that resolves at $1.00: * `avg_price` = 0.40 * `total_bought` = 10,000 (tokens acquired) * Cost basis = 10,000 × $0.40 = $4,000 USDC * Payout at $1.00 = 10,000 × $1.00 = \$10,000 USDC * `realized_pnl` = $10,000 - $4,000 = **\$6,000** The formula: `realized_pnl = total_bought × (1 - avg_price)` for positions that resolve at \$1. Per-position values (`realized_pnl`, `avg_price`, `total_bought`) come from the same onchain settlement data that Polymarket uses. Individual position P\&L matches what Polymarket shows on its portfolio page. ## Which total to use The response returns three portfolio-level totals. Each answers a different question: | Total | Answers | Matches | | ---------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------ | | `total_realized_pnl` | How much profit has this wallet actually taken off the table from closed + resolved positions? | Not shown on Polymarket profile | | `total_unrealized_pnl` | What is the paper P\&L on the wallet's currently-held shares at current (or terminal) prices? | Not shown directly | | `total_pnl` | What is the wallet's net portfolio P\&L right now? | The "Profit" number on a Polymarket profile page | `total_pnl` is what most dashboards want — it's the single number users recognize from Polymarket. `total_realized_pnl` is useful for attribution (which markets made money vs which positions are still out). `total_unrealized_pnl` is useful for risk views (how much is still at stake). `total_pnl = total_realized_pnl + total_unrealized_pnl`. Reach for `total_pnl` when you want a number that matches a Polymarket profile page. Reach for `total_realized_pnl` when you want only closed-out gains. When you supply `since` or `until`, all three totals recompute over the filtered set — the same logic, just narrower input. ## Performance Responses are cached for 5 minutes per wallet. First request for a new wallet takes 200ms-3s depending on position count (up to 20,000 positions supported). Cached responses return in under 50ms. Filter parameters are applied to the cached set; passing `since`/`until` does not invalidate the underlying cache. | Positions | First request | Cached | | ----------- | ------------- | ------- | | \< 500 | \~1s | \< 50ms | | 500-1,000 | \~1.5s | \< 50ms | | 1,000-5,000 | \~3s | \< 50ms | ## Examples ### Default response (no filter) ```bash theme={null} curl "https://api.polynode.dev/v2/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/positions/onchain" \ -H "x-api-key: YOUR_KEY" ``` Returns every position the wallet has held, all-time, with the full set of fields documented above. Default response shape — no `filtered` or `applied_filters` keys. ### Filter to the last 30 days ```bash theme={null} SINCE=$(( $(date -u +%s) - 86400 * 30 )) curl "https://api.polynode.dev/v2/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/positions/onchain?since=$SINCE" \ -H "x-api-key: YOUR_KEY" ``` ### Filter to a parent event `event_slug` is included on every row, so once you have a wallet's positions you can group them by parent event without an extra round-trip: ```bash theme={null} curl "https://api.polynode.dev/v2/wallets/0x.../positions/onchain" \ -H "x-api-key: YOUR_KEY" \ | jq '.positions | group_by(.event_slug) | map({event_slug: .[0].event_slug, market_count: length, pnl: (map(.realized_pnl + .unrealized_pnl) | add)})' ``` This collapses a wallet's positions across the children of an event (e.g. all 32 World Cup winner markets, or the multiple lines of a single NBA game) into one row per event. # Resolve Wallet Source: https://docs.polynode.dev/api-reference/wallets/resolve GET /v1/resolve/{query} Instantly resolve any Polymarket wallet address, EOA, or username. Look up the full identity for any Polymarket user: preferred trading wallet, EOA (externally owned account), username, and wallet type. Accepts any of the three identifiers as input and returns all fields. Supports both Polymarket's original Safe proxy wallets and the newer deposit wallets. The response format is unchanged: `safe` is kept for backward compatibility, but it means the preferred Polymarket trading wallet. If the controlling EOA has a deployed deposit wallet, `safe` returns that deposit wallet and `type` is `"deposit_wallet"` — even when you query the EOA, username, or older Safe wallet. If no deposit wallet is deployed, `safe` falls back to the existing Safe/proxy wallet. One of: * **Wallet address** — Polymarket trading wallet: Safe proxy, deposit wallet, or proxy wallet (`0x...`, 42 chars) * **EOA address** — externally owned account (`0x...`, 42 chars) * **Username** — Polymarket display name (case-insensitive) ## By wallet address (Safe, no deposit wallet) Look up a Polymarket Safe proxy wallet to get the controlling EOA and username. If that EOA also has a deployed deposit wallet, the response returns the deposit wallet in `safe` instead. ```bash theme={null} curl "https://api.polynode.dev/v1/resolve/0xfa85349327a63aa563029737f0492a79dca8f95d" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "safe": "0xfa85349327a63aa563029737f0492a79dca8f95d", "eoa": "0xc2783891b1d2287345e30f75e0f1ecd189a967d0", "username": "Letsgetit6969", "type": "safe" } ``` ## By wallet address (deposit wallet) Look up a Polymarket deposit wallet to get the controlling EOA. Deposit wallets are a newer wallet type introduced by Polymarket for new accounts. ```bash theme={null} curl "https://api.polynode.dev/v1/resolve/0x8b60bf0f650bf7a0d93f10d72375b37de18f8c40" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "safe": "0x8b60bf0f650bf7a0d93f10d72375b37de18f8c40", "eoa": "0xa60601a4d903af91855c52bfb3814f6ba342f201", "username": null, "type": "deposit_wallet" } ``` ## By EOA address Look up an EOA to find its preferred Polymarket trading wallet and username. Deposit wallets are prioritized when deployed. ```bash theme={null} curl "https://api.polynode.dev/v1/resolve/0xc2783891b1d2287345e30f75e0f1ecd189a967d0" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "safe": "0x8b60bf0f650bf7a0d93f10d72375b37de18f8c40", "eoa": "0xa60601a4d903af91855c52bfb3814f6ba342f201", "username": null, "type": "deposit_wallet" } ``` ## By wallet address (magic-link proxy) Resolve a Polymarket magic-link wallet — the wallet type used by accounts created with email or social login. The endpoint returns the controlling EOA, recovered automatically. ```bash theme={null} curl "https://api.polynode.dev/v1/resolve/0x16cbe223607a6513ae76d1e3751c78e4eabc2704" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "safe": "0x16cbe223607a6513ae76d1e3751c78e4eabc2704", "eoa": "0xbe5ba588ab7173b34efc0706b881794951014293", "username": "MRF", "type": "proxy" } ``` ## By username Look up a Polymarket username to find the preferred trading wallet. Case-insensitive. ```bash theme={null} curl "https://api.polynode.dev/v1/resolve/Fredi9999" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "safe": "0x1f2dd6d473f3e824cd2f8a89d9c69fb96f6ad0cf", "eoa": null, "username": "Fredi9999", "type": "safe" } ``` ## Response fields Preferred Polymarket trading wallet address. The field name is kept for backward compatibility. If the controlling EOA has a deployed deposit wallet, this is the deposit wallet. Otherwise it is the existing Safe/proxy wallet. Externally owned account that controls the trading wallet. This is the signer address. May be `null` for some magic-link wallets where the EOA cannot be determined. Polymarket display name. `null` if the user has no profile set. Wallet type for the address returned in `safe`. One of: * `"safe"` — Gnosis Safe proxy wallet (most existing Polymarket accounts) * `"deposit_wallet"` — deposit wallet (newer Polymarket accounts) * `"proxy"` — magic-link wallet (accounts created via email or social login) ## Error responses **404 — Not found** ```json theme={null} {"error": "Address not found."} ``` ```json theme={null} {"error": "Username not found."} ``` ## Coverage The resolver supports every Polymarket wallet type on Polygon, including Safe proxy wallets and the newer deposit wallets. Cached lookups are sub-millisecond. Uncached lookups resolve on-chain in real time, then prefer a deployed deposit wallet when one exists for the resolved EOA. ## Use cases * **Database enrichment** — bulk-resolve trading wallets to EOAs for analytics pipelines * **Profile lookups** — show usernames alongside wallet addresses in your UI * **Cross-referencing** — match on-chain activity (EOA) to the preferred Polymarket trading wallet * **Whale tracking** — identify the EOA behind a trading wallet to track activity across protocols * **Wallet type detection** — use the `type` field to determine how a user's account is set up # X Search API Source: https://docs.polynode.dev/api-reference/x-search GET /v2/x/search Beta endpoint for live X (Twitter) search and account-timeline data. Drop social context next to your prediction market data without building your own X integration. **Beta.** Response shape and quota numbers are stable. We may add optional fields, but no breaking changes. Email `josh@polynode.dev` if you're building something on top of this and need direct support. ## What this is Two read-only endpoints for live X data: * **Search** any text across X. Full operator support (`from:`, `since:`, `min_faves:`, hashtags, exact phrases). * **Account timeline** for any public X handle — most-recent tweets, replies, retweets, and quoted content. Most teams already using polynode for prediction-market data also want to know what's being said on X about a market, an event, or a specific account. Setting up your own X feed is rate-limit hell and a maintenance treadmill — we just do it for you, and you query it like any other polynode endpoint. ## Who this is for * Prediction-market dashboards layering social sentiment over on-chain data. * AI agents that need to factor live X posts into their reasoning about an event. * Research and analytics products that want clean JSON without building their own X integration. ## Base URL ``` https://api.polynode.dev/v2/x ``` ## Authentication Pass your polynode API key on every request — either header works: ```http theme={null} x-api-key: pn_live_... Authorization: Bearer pn_live_... ``` * Free-tier keys are rejected with `402 Payment Required`. Paid plans only. * Hard rate limit: **1 request per second per key**. Two requests on the same key in the same second → the second one gets `429`. * Each successful response (HTTP 200) increments your monthly quota counter. Rejected requests do not. ## Tier quotas | Tier | Searches per month | | ---------- | ------------------ | | starter | 500 | | growth | 1,000 | | enterprise | 5,000 | Quotas reset midnight UTC on the 1st of each month. Track your remaining usage in real time via response headers: ``` X-Quota-Used: 47 X-Quota-Limit: 500 ``` ## Endpoints ### `GET /v2/x/search` Search recent X posts by query. **Query params:** | Param | Type | Default | Notes | | ----- | ------ | -------- | --------------------------------------------------------------- | | `q` | string | required | The search string. URL-encode operators and special characters. | | `max` | int | `20` | Number of tweets to return. Min `1`, max `50`. | **Operator examples:** ``` q=polymarket q=from:Polymarket q=election odds since:2026-04-20 q=#prediction min_faves:100 q="exact phrase" lang:en q=trump until:2026-05-01 ``` **Example request:** ```bash theme={null} curl -H "x-api-key: $PN_KEY" \ "https://api.polynode.dev/v2/x/search?q=polymarket&max=5" ``` **Example response:** ```json theme={null} { "query": "polymarket", "tweets": [ { "id": "2048234907965526306", "text": "Polymarket is showing 65% on Trump for 2024", "created_at": "Sun Apr 26 03:21:58 +0000 2026", "public_metrics": { "retweet_count": 244, "reply_count": 38, "like_count": 1450, "quote_count": 22, "bookmark_count": 87, "impression_count": "184302" }, "author": { "id": "987654321", "name": "Polymarket", "username": "Polymarket" }, "conversation_id": "2048234907965526306", "urls": [ { "url": "https://t.co/...", "expanded_url": "https://polymarket.com/event/...", "display_url": "polymarket.com/event/...", "title": "Will Trump win 2024?", "description": "..." } ], "media": [ { "type": "photo", "url": "https://pbs.twimg.com/media/...", "expanded_url": "..." } ] } ], "result_count": 5, "fetched_at": "2026-04-26T03:35:00Z" } ``` ### `GET /v2/x/user/{handle}/tweets` Get the latest tweets from a specific X account's timeline. **Path params:** | Param | Type | Notes | | -------- | ------ | ----------------------------------------------------------- | | `handle` | string | The X handle without `@`. Letters, digits, underscore only. | **Query params:** | Param | Type | Default | Notes | | ----- | ---- | ------- | ------------------------------------ | | `max` | int | `30` | Number of tweets. Min `1`, max `50`. | **Example request:** ```bash theme={null} curl -H "x-api-key: $PN_KEY" \ "https://api.polynode.dev/v2/x/user/Polymarket/tweets?max=10" ``` The response shape is identical to `/v2/x/search`, except the top-level field is `handle` instead of `query`. ## Errors | HTTP | Meaning | | --------- | ---------------------------------------------------------------------------------------------------------- | | 400 / 422 | Invalid params: empty `q`, `max` out of range, or handle contains invalid characters. | | 401 | Missing or invalid API key. | | 402 | Free-tier key; this endpoint requires a paid plan. | | 404 | Handle does not exist (timeline endpoint only). | | 429 | Either the 1 req/sec rate cap, or your monthly quota is exhausted. Check `X-Quota-Used` / `X-Quota-Limit`. | | 502 | Transient upstream issue. Retry after a few seconds. We're alerted automatically. | ## Tweet shape reference Every tweet object can include these fields. Optional fields are only present when relevant. | Field | Type | Always present? | Notes | | ----------------- | ------ | --------------- | ------------------------------------------------------------------------------------------------------------- | | `id` | string | yes | Tweet's numeric ID as a string. | | `text` | string | yes | The tweet body. `t.co` shortlinks are pre-expanded to their final destination URLs. | | `created_at` | string | yes | X's native timestamp format (e.g. `Sun Apr 26 03:21:58 +0000 2026`). | | `public_metrics` | object | yes | Counters: `retweet_count`, `reply_count`, `like_count`, `quote_count`, `bookmark_count`, `impression_count`. | | `author` | object | yes | `{ id, name, username }`. | | `conversation_id` | string | yes | The root tweet of the thread this tweet belongs to. | | `urls` | array | optional | Embedded link metadata: `{ url, expanded_url, display_url, title, description }`. | | `media` | array | optional | Photos, videos, GIFs: `{ type, url, expanded_url }` (videos include `video_url` for the highest-bitrate MP4). | | `card` | object | optional | Link preview cards: `{ name, url, title, description, domain, thumbnail_url }`. | | `quoted_tweet` | object | optional | Full tweet object of any quoted post (recursive). | | `article` | string | optional | Long-form X post content rendered as Markdown. | ## Quirks worth knowing * **`created_at` is X's native string format**, not ISO 8601. Parse with `new Date(tweet.created_at)` in JS or `dateutil.parser.parse` in Python. * **`impression_count` is a string**, not an int — X returns it as a string and we pass it through verbatim. Cast on your side if you need a number. * **`text` is post-expansion.** All `t.co` shortlinks are already replaced with their target URLs. The `urls` array also gives you the full expanded metadata (title, description) per link, which is the easiest way to surface link previews in your UI. * **No pagination cursor** in the beta. The `max` cap is 50 per call. To go beyond that, run multiple queries narrowed with `since:` / `until:` operators. * **Search prefers recency.** Live results are ranked newest-first. Combine with `min_faves:N` or `min_retweets:N` if you want to floor on engagement. ## FAQ **Why is search slower than timeline?** Search and timeline take different paths. Timeline is typically \~1-2 seconds. Search is typically 5-7 seconds because the underlying X data path is heavier. This is normal and stable. **Can I get my own quota lifted?** Yes — email `josh@polynode.dev` if you're hitting the cap and want a custom limit. **Will the response shape change?** We may add optional fields (e.g. new metrics X exposes). We will not rename or remove existing fields without versioning the endpoint. Code defensively against optional fields, never against missing ones. # Authentication Source: https://docs.polynode.dev/authentication API key generation, authentication methods, and security. All API endpoints (except `/healthz`, `/readyz`, and `POST /v1/keys`) require an API key. ## Passing the key Two methods are supported: ```bash theme={null} curl -H "x-api-key: pn_live_YOUR_KEY" https://api.polynode.dev/v1/markets ``` ```bash theme={null} curl "https://api.polynode.dev/v1/markets?key=pn_live_YOUR_KEY" ``` For WebSocket connections, use the query parameter: ``` wss://ws.polynode.dev/ws?key=pn_live_YOUR_KEY ``` ## Key format API keys use the prefix `pn_live_` followed by a random string. Legacy keys with `qm_live_` prefix are also accepted. ## Generating a key ```bash theme={null} curl -s -X POST https://api.polynode.dev/v1/keys \ -H "Content-Type: application/json" \ -d '{"name": "my-app"}' ``` | Field | Type | Description | | ------ | ----------------- | ----------------------------------------- | | `name` | string (optional) | Label for the key. Defaults to "unnamed". | Key generation is rate limited to **1 per IP per day**. The raw API key is returned only once. It cannot be retrieved after creation — store it securely. ## Security * API keys are **SHA-256 hashed** before storage. The raw key is never persisted. * All traffic should use HTTPS in production. * Rotate keys by generating a new one and decommissioning the old one. ## Error responses | Status | Error | Fix | | ------ | --------------------------- | ------------------------------------------------------- | | 401 | Missing or invalid API key | Include your key as `x-api-key` header or `?key=` param | | 403 | Invalid or inactive API key | Verify your key is correct, or generate a new one | | 429 | Rate limit exceeded | Reduce request frequency. Free tier: 60 req/min | # Changelog Source: https://docs.polynode.dev/changelog All notable changes to the PolyNode API and SDKs. ## 2026-06-22 - V3 wallet accounting summary Added an optional all-time accounting summary to the V3 wallet summary endpoint. What's new: * `GET /v3/wallets/{address}?include_accounting_summary=true` now returns `accounting_summary`. * `accounting_summary.fees_paid.amount` is the exact all-time trader-paid fee total from indexed onchain `OrderFilled` rows. * `accounting_summary.fees_paid.raw` exposes the exact 6-decimal raw amount. * Maker rebates and rewards are represented with explicit status fields. `not_loaded` means the source projection has not been loaded; clients should not treat those values as zero. Existing default responses are unchanged. The accounting object is included only when requested. *** ## 2026-06-15 - PM2 AutoRedeemer redemptions in WebSocket streams PM2 AutoRedeemer redemptions are now surfaced in both the combo stream and the standard redemption stream. What's new: * AutoRedeemer `Redemption`, `BinaryRedemption`, and `NegRiskRedemption` logs emit customer-facing `combo_lifecycle` events with the wallet, exact raw payout, normalized payout, module name, and receipt log index. Binary and negative-risk redemptions also include the condition ID directly. * Confirmed `combo_status_update` messages can include `lifecycle_events[]` for receipt-only AutoRedeemer lifecycle activity. * The same AutoRedeemer payout is also bridged into the standard `redemption` event class, attributed to the user wallet instead of the AutoRedeemer contract. * New `type: "redemptions"` WebSocket preset subscribes directly to `redemption` events. Existing `settlements` or `wallets` subscriptions with `event_types: ["redemption"]` continue to work. No subscription change is required for existing redemption consumers. *** ## 2026-06-12 - Faster V3 builder trade time filters Improved reliability and latency for time-filtered builder trade lookups. What's improved: * `GET /v3/builders/{code}/trades?after=...` now returns consistently for high-volume builders instead of timing out on large builder histories. * `after` / `before` filters now work efficiently with broad `tag_slug` or `category` filters when a time bound is provided. * Time windows outside a builder's activity range return a normal empty response instead of an internal timeout. * Invalid millisecond timestamps now return a structured `400` with a hint to use Unix seconds. No response fields or pagination semantics changed. *** ## 2026-06-11 - Polymarket combos: V3 API and WebSocket event references Added public documentation for Polymarket combo / combinatorial position support. What's new: * New combo API group in the API Reference. * `GET /v3/combos/markets` for observed combo markets and legs. * `GET /v3/combos/activity` for combo lifecycle activity by market, condition, position, or wallet. * `GET /v3/wallets/{address}/combos/positions` for combo-only wallet positions. * `GET /v3/wallets/{address}/combos/trades` for combo-only wallet trades. * `GET /v3/wallets/{address}/combos/activity` for combo-only wallet lifecycle activity. * `GET /v3/wallets/{address}/combos/summary` for combo-only wallet P\&L summary. * `include_combos=true` on wallet summary, wallet P\&L, wallet positions, and wallet trades for additive standard + combo responses. * New WebSocket event reference pages for `combo_execution`, `combo_status_update`, `combo_lifecycle`, and `combo_approval`. Notes: * Existing wallet endpoints keep their default response behavior. Combo data is included only when explicitly requested or when using dedicated combo endpoints. * If a wallet has no combo exposure, `include_combos=true` returns a normal successful response with a zero combo contribution. *** ## 2026-06-09 - Python SDK v0.10.6 WebSocket resilience Released `polynode==0.10.6` for Python WebSocket consumers. What's new: * Event-stream subscriptions preserve frames that arrive before the subscribe acknowledgement, so handlers attached immediately after `await ...send()` still receive initial snapshot/live events. * Default event WebSocket subscribe timeout is now 30s and configurable with `WsOptions(subscribe_timeout=...)`. * Orderbook `subscribe()` now resolves only after the server acknowledges the subscription, and rejects promptly on server errors, disconnects, or timeout. * Orderbook `unsubscribe(token_ids)` can remove a subset of tokens; `unsubscribe()` with no arguments remains the full unsubscribe behavior. * Added protocol regression tests for snapshot-before-ack, pending-subscribe errors, timeout handling, and partial unsubscribe behavior. Upgrade: ```bash theme={null} pip install --upgrade polynode==0.10.6 ``` *** ## 2026-06-09 - TypeScript SDK v0.10.17 WebSocket resilience Released `polynode-sdk@0.10.17` for Node/TypeScript WebSocket consumers. What's new: * Event-stream subscriptions preserve frames that arrive before the subscribe acknowledgement, so handlers attached immediately after `await subscribe(...).send()` still receive initial snapshot/live events. * Default WebSocket subscribe timeout is now 30s and configurable with `subscribeTimeoutMs`. * Orderbook `subscribe()` now resolves only after the server acknowledges the subscription. * Orderbook reconnects replay the active token list without replacing existing handlers. * Orderbook `unsubscribe(tokenIds)` can remove a subset of tokens; `unsubscribe()` with no arguments remains the full unsubscribe behavior. * Added SDK protocol tests and a live orderbook soak test for multi-token subscribe, partial unsubscribe/resubscribe, snapshots, live batches, protocol errors, unexpected unsubscribe frames, and disconnects. Upgrade: ```bash theme={null} npm install polynode-sdk@0.10.17 ws ``` *** ## 2026-05-27 - V3 wallet P\&L alignment, fees, rebates, rewards, and PolyUSD flows Updated V3 wallet accounting surfaces. What's new: * Default/all-time `GET /v3/wallets/{address}/pnl/events` now returns a single all-time bucket so it agrees with `GET /v3/wallets/{address}/pnl`. * Explicit time-windowed P\&L events (`period`, `after`, or `before`) return realized P\&L buckets and ignore timestamp-0 artifacts. * Added `GET /v3/wallets/{address}/fees-paid` for fee-bearing wallet fills. * Added `GET /v3/wallets/{address}/polyusd-flows` for wallet PolyUSD deposits and withdrawals. * Added `GET /v3/wallets/{address}/rebates` for maker rebates. * Added public reward-market configuration at `GET /v3/rewards/markets` and `GET /v3/rewards/markets/{condition_id}`. Notes: * Fees paid and maker rebates are intentionally separate surfaces. LP reward market configuration is public; per-wallet earned LP rewards require authenticated Polymarket data and are not exposed as an arbitrary-wallet lookup in the public docs. * `GET /v3/wallets/{address}/activity` remains the conditional-token activity feed; PolyUSD deposits and withdrawals now live in the separate wallet PolyUSD flows endpoint. *** ## 2026-05-26 — Webhooks beta: first observed position opens Added `position_status_change` support for watched-wallet CTF position opens and exits. What's new: * `position_status_change` now detects first observed `none` -> `open` transitions for watched wallets from CTF ERC-1155 transfer deltas. * Existing wallet positions are seeded when a wallet is first watched, so new webhooks emit future transitions without backfilling historical opens. * Each watched wallet now gets its own seed cursor, so adding a new wallet does not replay stale source rows from before that wallet's baseline. * Payloads now include `previous_amount`, `new_amount`, `delta_amount`, `transfer_amount`, `source`, `source_event_id`, block/transaction fields, and market enrichment. * Filters now include `wallet_addresses`, `token_ids`, `condition_ids`, `event_slugs`, `tags`, `status_from`, and `status_to`. Beta note: * `wallet_addresses` is required for this beta. Unfiltered/global position-status webhooks are intentionally not enabled yet. Docs updated: * See [`position_status_change`](/webhooks/events/position-status-change) for the exact payload and copyable first-open registration example. *** ## 2026-05-26 — TypeScript SDK v0.10.13: V3 parity, resolver, and profile helpers Released a TypeScript SDK parity update for the current REST API surface. What's new: * `pn.v3` namespace for the V3 historical data API while preserving existing top-level SDK behavior. * Typed V3 methods for wallet summaries, wallet P\&L, P\&L events, wallet trades, wallet positions, global trades, global positions, market search, market price, market trades, market positions, leaderboard, tags, builders, builder trades, fees, resolutions, conditions, and token info. * Builder trade filters in the SDK: `tokenId`, `conditionId`, `marketSlug`, `eventSlug`, `side`, `category`, `tagSlug`, `minAmount`, `after`, `before`, `limit`, and `offset`. * `pn.resolve(query)` for wallet / EOA / username resolution, returning `{ safe, eoa, username, type }`. * Fully typed `pn.walletOnchainPositions()` response including `condition_id`, `unrealized_pnl`, `current_price`, `market_status`, `market`, `slug`, `event_slug`, `outcome`, `won`, `winning_outcome_index`, `last_trade_at`, `closed_at`, `resolved_at`, and top-level `total_unrealized_pnl`. * V3 Polymarket profile helpers: `polymarketUsernameAvailable`, `createPolymarketUsernameChallenge`, `completePolymarketUsername`, and `polymarketProfile`. Docs updated: * Added [TypeScript — V3 Data & Profiles](/sdks/ts/v3-data) with copyable SDK examples. * Updated [TypeScript — REST API](/sdks/ts/rest-api), [TypeScript — Types](/sdks/ts/types), and [Polymarket Profile Setup](/guides/polymarket-profile-setup) with SDK-specific examples and the exact signing flow. Notes: * Existing `pn.market()`, `pn.wallet()`, `pn.leaderboard()`, and other top-level methods keep their historical paths and response shapes. * Event-by-ID lookup and a fully filterable events listing remain backend/API work, not just SDK wrapper work. Current SDK support reflects the endpoints that exist publicly today. *** ## 2026-06-04 — `/resolve` prefers deployed deposit wallets `GET /v1/resolve/{query}` keeps the same response fields: `safe`, `eoa`, `username`, and `type`. The `safe` field now returns the preferred Polymarket trading wallet. If the resolved EOA has a deployed deposit wallet, `safe` is that deposit wallet and `type` is `"deposit_wallet"`. If no deposit wallet is deployed, `safe` falls back to the existing Safe/proxy wallet. This applies to address, EOA, and cached username lookups. *** ## 2026-05-22 — V3 grouped trade total alias Added `total_amount_usd` to v3 `group_by=order_hash` trade responses. It is an additive alias of `total_usd` / `amount_usd`, so clients that already use the v2 grouped trade field can read the same total field on v3. Affected endpoints: * `GET /v3/wallets/{address}/trades?group_by=order_hash` * `GET /v3/trades?group_by=order_hash` Existing fields are unchanged. *** ## 2026-05-22 — Builder metadata and filtered builder trades Builder analytics now include public builder profile metadata on all builder endpoints: * `GET /v3/builders` * `GET /v3/builders/{code}` * `GET /v3/builders/{code}/trades` New additive fields: * `builder_code` * `builder_name` * `builder_logo` * `builder_verified` `/v3/builders/{code}/trades` also now supports builder-specific filters for `token_id`, `condition_id`, `market_slug`, `event_slug`, `side=buy|sell`, `min_amount`, `after`, `before`, `limit`, and `offset`. `category` and `tag_slug` filters are available with guardrails. Very broad category/tag requests return a structured `400` asking you to add `market_slug`, `condition_id`, `token_id`, or time bounds. ```bash theme={null} curl "https://api.polynode.dev/v3/builders/0x4898df15.../trades?market_slug=dota2-tundra-xtreme-2026-05-22-game1&tag_slug=sports&limit=10" ``` Existing fields and existing pagination behavior are unchanged. *** ## 2026-05-18 — PnL methodology note For users comparing our PnL numbers to what the Polymarket frontend displays: the two are computed differently and will diverge for wallets that hold a lot of settled-but-not-redeemed positions. **Polymarket's `pnl`** (leaderboard, profile chart) is a **mark-to-market equity curve delta**: `equity(end_of_window) − equity(start_of_window)`. Resolved markets are booked into the curve at settlement, regardless of whether the wallet has called `redeemPositions`. **Our `net_realized_pnl`** counts realized closes only — CLOB sells (the wallet sold their shares back) plus V2 adapter events (redemptions, merges, NRC conversions). Unrealized gains on settled-but-unredeemed positions stay in `unrealized_pnl`, not `net_realized_pnl`. **Empirically verified** against PM's `/closed-positions[].realizedPnl` on a representative wallet: per-position realized matches PM within 1%. The "lifetime pnl" headline diverges by the value of settled-not-redeemed positions in the wallet. `total_pnl` (= `net_realized_pnl + unrealized_pnl`) is the closer comparison to PM's curve delta — it includes both closed and still-open positions priced at current market. *** ## 2026-05-18 — New endpoint: wallet P\&L time series Added `GET /v3/wallets/{address}/pnl/events` — returns realized P\&L bucketed by `hour`, `day`, `week`, or `month` so you can plot a wallet's P\&L over time without pulling every trade. Each bucket includes `realized_pnl`, a running `cumulative_pnl`, gross profit/loss, win/loss counts, and event count. Supports `?period=1d|7d|30d|1y` shortcuts or explicit `after` / `before` Unix-second timestamps. Most full-history queries return sub-second. Other quality-of-life: * `?after` / `?before` outside the wallet's active window now correctly return zero buckets instead of an error. * All `/v3/*` paths now accept an optional trailing slash (`/v3/positions/` is equivalent to `/v3/positions`). * Unknown `/v3/*` paths return a structured 404 with a `hint` pointing to the docs, rather than HTML. * Invalid `sort` and `order` values now return `400` with the list of valid options instead of silently falling back to a default — typos surface immediately. ### Doc / API parity pass (later same day) Round 2 of the parity work — pure alignment between the API and the public reference, no breaking changes: * **`/v3/markets/condition/{condition_id}/positions`** — response field names now match `/v3/markets/{token_id}/positions`. Was returning abbreviated keys (`amt`, `avgp`, `bought`, `rpnl`, `tid`); now returns the long, documented names (`amount`, `avg_price`, `total_bought`, `realized_pnl`, `token_id`). Old keys are gone — update any client code using them. * **`/v3/resolutions`** — `payout_numerators` is now a JSON array of integers (e.g. `[0, 1]`), matching `/v3/conditions/{cid}`. Was returning quoted strings. `payout_denominator` is also an integer now. * **`/v3/builders`, `/v3/tags`, `/v3/tokens/{token_id}`** — response envelopes now include `offset` and `limit` like every other list endpoint. * **Param validation tightened** — invalid `?order=`, `?side=`, `?status=`, `?group_by=`, `?sort_by=` on trades / fees / resolutions / positions endpoints now return `400` with the valid-values list, instead of silently falling back to a default. Valid values still pass through unchanged. * **Token price scale** — `/v3/markets/{token_id}/price.price` and `/v3/tokens/{token_id}.data[].price` are JSON numbers in USD (range `0`–`1`), not 6-decimal strings. Reference page text updated accordingly. *** ## 2026-05-16 — V3 API goes live + performance fixes All `/v3/*` endpoints are now generally available (no longer "Coming Soon"). Several queries that timed out or ran slowly have been rewritten to use index-friendly query plans: * `/v3/wallets/{addr}/positions` — default sort no longer times out, returns in \<100ms * `/v3/markets/slug/{slug}/trades` — fixed timeout, now \<100ms * `/v3/tags/{slug}/leaderboard` — small/medium tags are now sub-second; tags larger than 4,000 markets return a structured "too large" hint pointing to the global leaderboard * `/v3/tags/{slug}` — drops from 4.8s to \~1s * `/v3/wallets/{addr}/trades` — drops from 2.3s to \~200ms Tag slugs are now matched case-insensitively and accept URL-style hyphens: `/v3/tags/us-election/leaderboard` resolves to the stored tag `US Election`. Several doc examples that previously showed a `columns/rows` envelope have been corrected to the `data: [{...}]` shape that the API actually returns. *** ## 2026-05-14 — SDK fix: deposit wallet deploy + approve Fixed the deposit wallet (V2 / POLY\_1271) onboarding flow across all three SDKs. The relayer's `/submit` endpoint now requires builder authentication for WALLET-type submissions. All SDKs have been updated to include the required auth headers. If you previously hit `401 invalid authorization` or `API key and signer mismatch` errors when deploying a deposit wallet or setting approvals, update to the latest SDK version: ```bash theme={null} npm install polynode-sdk@0.10.7 # TypeScript cargo update -p polynode # Rust (0.13.4) pip install polynode==0.10.4 # Python ``` `ensureReady()` handles the full flow automatically — deploy, approve, create credentials. If your wallet is already deployed, it skips to approvals. If approvals are already set, it skips to credentials. *** ## 2026-05-10 — Sort and group trades by order hash `/v2/onchain/trades` now supports two new query parameters for working with limit order fills: **`sort_by=order_hash`** — reorders the response so all fills from the same limit order are adjacent, sorted by time within each order. Every fill is still returned individually. No data is collapsed. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/trades?wallet=0x...&sort_by=order_hash&limit=100" \ -H "x-api-key: YOUR_KEY" ``` **`group_by=order_hash`** — aggregates all fills from the same limit order into a single row with `total_amount_usd`, `total_shares`, `avg_price` (VWAP), `fill_count`, time range (`first_fill_at` / `last_fill_at`), and a `tx_hashes` array. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/trades?wallet=0x...&group_by=order_hash&limit=100" \ -H "x-api-key: YOUR_KEY" ``` A single limit order can be filled across hundreds of transactions over hours or days. These parameters let you view that activity at either granularity. *** ## 2026-05-05 — Magic-link wallet EOA recovery in `/resolve` `/v1/resolve` now returns the controlling EOA for Polymarket magic-link wallets — accounts created with email or social login. Previously these returned `eoa: null` because Polymarket's profile API does not expose the signer for this wallet type. ```json theme={null} { "safe": "0x16cbe223607a6513ae76d1e3751c78e4eabc2704", "eoa": "0xbe5ba588ab7173b34efc0706b881794951014293", "username": "MRF", "type": "proxy" } ``` **No code changes required.** The response shape is unchanged. The `eoa` field is now populated for the vast majority of `type: "proxy"` wallets. A small number may still return `eoa: null` where the EOA cannot be determined. Repeat queries are sub-100ms. *** ## 2026-05-02 — Deposit wallet support Polymarket is rolling out deposit wallets as a new account type for new users. This release adds full support across the API, all three SDKs, and the copy trading engine. **Existing integrations are fully backward compatible** — no code changes required unless you want to take advantage of the new features. Read the full [Deposit Wallets guide](/guides/deposit-wallets) for details, code examples, and FAQ. ### What's new **`/resolve` endpoint** — now returns a `type` field (`"safe"`, `"deposit_wallet"`, or `"proxy"`) in all responses. Resolves deposit wallets in both directions: wallet address to EOA and EOA to wallet address. Existing `safe`, `eoa`, and `username` fields unchanged. ```json theme={null} { "safe": "0x8b60bf0f650bf7a0d93f10d72375b37de18f8c40", "eoa": "0xa60601a4d903af91855c52bfb3814f6ba342f201", "username": null, "type": "deposit_wallet" } ``` **SDK — address derivation and detection** — `deriveDepositWalletAddress(eoa)` computes the deterministic deposit wallet address for any EOA. `detectWalletType()` and `ensureReady()` automatically detect whether an EOA has a Safe, proxy, or deposit wallet. Safe is checked first, so existing users keep their existing wallet type. **SDK — order signing** — when `signatureType` is `POLY_1271` (3), the SDK wraps the EIP-712 signature using ERC-7739 TypedDataSign format automatically. `order()` / `signV2Order()` / `create_signed_order_v2()` detect the signature type from stored credentials and sign correctly. No code changes needed on your side. **SDK — onboarding** — `ensureReady()` handles the full flow for deposit wallet users: derives the wallet, deploys via `WALLET-CREATE` if needed, and sets V2 approvals via a signed `WALLET` batch. Same one-call pattern as Safe wallets. **Copy trading** — the copy trading engine generates the correct typed data for deposit wallet followers (`sig_type: 3`). Your signing code should use `signV2Order()` from the SDK. ### Install ```bash theme={null} cargo add polynode@0.13.3 # Rust npm install polynode-sdk@0.10.6 # TypeScript pip install polynode==0.10.3 # Python ``` ### Do I need to update? * **If you only read data** (positions, trades, resolve): no changes needed. The `type` field is additive. * **If you place orders via the SDK**: update to the versions above. Your code stays the same. * **If you build order signatures yourself**: you need to implement ERC-7739 wrapping for `signatureType: 3`. See the [guide](/guides/deposit-wallets#how-poly_1271-signing-works). *** ## 2026-05-01 — Cursor pagination on `/v2/onchain/*/trades` The two trade-history endpoints now support **cursor pagination** in addition to offset: * `GET /v2/onchain/markets/{token_id}/trades?cursor=:` * `GET /v2/onchain/wallets/{address}/trades?cursor=:` **Why it matters:** offset pagination slows down the deeper you page — page 500 means the database walks 50,000 rows of wasted work to find your starting point. Cursor pagination loads page 1000 just as fast as page 1 — typically \~1 second per page, regardless of how deep you go. **How to use:** 1. First page: pass `?cursor=` (empty value) instead of `?offset=0`. 2. Response includes a `pagination` block with `cursor` and `has_more`. 3. Subsequent pages: pass that `cursor` back as `?cursor=`. 4. Stop when `has_more` is `false`. **Available on every paid tier** — no Growth restriction. **Compatibility:** purely additive. The existing `?offset=` mode is unchanged and remains fully supported. Existing integrations don't need to change anything — switch to cursor when you next touch the integration. See the updated docs for full examples: [Trade History (Market)](/api-reference/onchain/market-trades) · [Trade History (Wallet)](/api-reference/onchain/wallet-trades). *** ## 2026-05-01 — Bulk trade-export endpoints (Growth plan and above) Two new endpoints return the **entire** trade history for a market token or wallet in a **single response** — no client-side pagination required: * `GET /v2/onchain/markets/{token_id}/trades/all` * `GET /v2/onchain/wallets/{address}/trades/all` The endpoint walks the full history for you. Optional `from` and `to` query parameters narrow the window. **Limits:** * Hard cap **250,000 trades** per call. Beyond that, response includes `"partial": true` with `"partial_reason": "hard_cap_250000"`. * Wall-clock budget **180 seconds**. First cold-cache calls on busy markets/heavy wallets may take **1-3 minutes**. Subsequent calls hit Redis cache and return in **under 1 second**. * Typical payload **1-50 MB**. Worst case (capped): **100-200 MB JSON**. * Cache TTL **5 minutes** per `(scope, from, to)` combination. Partial results are **not cached**. **Tier requirement:** **Growth plan (\$200/mo) or above.** Starter and free tier requests receive `403`. The bulk endpoints are gated to paid plans because of their large query and payload sizes. **Compatibility:** purely additive. The standard paginated `/trades` endpoints are unchanged and remain available on all paid tiers. Updated the request timeout to 180 seconds so cold-cache calls complete cleanly. *** ## 2026-05-01 — Backtest Copy PnL: new `total_realized_pnl_usdc` field Added `total_realized_pnl_usdc` to `/v2/copy-pnl/{wallet}` responses. This is the **signed sum of WAVG-realized PnL across every position the matcher touched in the window** — the closest single number to "did this wallet make money trading?" and the recommended primary "PnL" field for customer-facing UI. Existing `actual_pnl_usdc` is unchanged but is now documented as a **cashflow** metric (USDC delta in/out of the wallet over the window), which is intentionally different from realized PnL — see the [endpoint docs](/api-reference/backtesting/copy-pnl#understanding-cashflow-pnl-vs-realized-pnl) for the worked example. Also fixed a long-standing bug in the cashflow accounting where neg-risk-conversion events were over-counting `settlement_in` (treating share counts as USDC). NRC-active wallets see corrected numbers across all four periods (`14d`, `30d`, `60d`, `180d`) on the next BYOB cycle refresh. *** ## 2026-04-30 — Backtest Copy PnL: per-position metrics now match Polymarket byte-for-byte The new fields added earlier today (`avg_entry_prob_weighted`, `positions_closed` on `/v2/copy-pnl/*`) now produce values that match Polymarket's own data-api position math. Validated against `data-api.polymarket.com/positions` `realizedPnl` across 30 positions on diverse wallets (standard CTF + neg-risk markets): * **97 %** sub-penny match * **100 %** sub-\$1 match * **100 %** sub-\$10 match No API changes — same field names, same response shape. Existing integrations automatically benefit. Cached values progressively refresh as background scoring cycles complete. *** ## 2026-04-30 — BYOB Snapshot: every wallet × every period in one read New endpoint `GET /v2/copy-pnl/snapshot` returns every wallet in your tracked-wallet pool with backtest copy-PnL scores across all six time windows (`7d`, `14d`, `30d`, `60d`, `90d`, `180d`) in a single response. Built for stats-card UIs and dashboards that need the whole pool at once instead of issuing one request per period. * **One request** — no per-period round trips, no client-side stitching. * **Sub-second cached** — 100 wallets × 6 periods returns in \~70 ms / \~150 KB. 1000 wallets × 6 periods in \~500 ms / \~1.5 MB. * **Optional period subset** via `?periods=7d,30d` to drop payload when you only need one window. * **Per-wallet error reporting** — heavy whales that errored on the most recent refresh surface their failure reason inline, so your UI can render "X wallets retrying" instead of hiding gaps. ```bash theme={null} curl -H "x-api-key: $YOUR_KEY" \ "https://api.polynode.dev/v2/copy-pnl/snapshot" ``` See [BYOB — Snapshot](/api-reference/backtesting/byob-snapshot) for the full response shape and examples. *** ## 2026-04-30 — `/v2/onchain/markets/{token}/volume` faster Backend optimization for the market volume endpoint. Response shape and values are unchanged — same `buys`, `sells`, `total_trades`, `buy_volume_usdc`, `sell_volume_usdc`, `volume_usdc`, `found` fields. Light and empty markets see roughly halved latency. No integration changes required. *** ## 2026-04-30 — Wallet activity & redemptions: deep pagination unlocked Heavy wallets returning more than 1000 records on `/v2/onchain/wallets/{wallet}/redemptions` and `/v2/onchain/wallets/{wallet}/activity` now return the complete dataset: * **`offset` past 1000 now works.** Previously, pagination beyond the first 1000 records returned empty results regardless of how many records existed. You can now walk a heavy redeemer's full history with `offset=1000`, `offset=2000`, etc. * **No more silent truncation.** Wallets with >1000 redemptions, splits, merges, or neg-risk conversions previously had results capped at 1000. The full record set is now returned. No SDK update required. No integration changes. Same URLs, same query parameters, same response shapes — your existing code automatically benefits. ```bash theme={null} # Walk a heavy redeemer's full history: curl "https://api.polynode.dev/v2/onchain/wallets/{wallet}/redemptions?limit=1000&offset=0" \ -H "x-api-key: $YOUR_KEY" curl "https://api.polynode.dev/v2/onchain/wallets/{wallet}/redemptions?limit=1000&offset=1000" \ -H "x-api-key: $YOUR_KEY" ``` *** ## 2026-04-30 — TypeScript SDK 0.9.7 Pluggable storage for Bun/Deno/edge runtime compatibility: * **`storage` config option** — pass `'memory'` for an in-memory backend (no native dependencies), a file path for SQLite (default), or your own `TradingStorage` implementation. * **Auto-fallback** — if `better-sqlite3` isn't available (Bun, Deno, edge runtimes), the SDK automatically falls back to in-memory storage instead of crashing. * **`BunSqliteBackend`** — persistent SQLite storage using Bun's built-in `bun:sqlite`. Zero native dependencies. Same schema, same persistence as the Node backend. * **Auto-detection** — if you don't pass a `storage` option, the SDK tries `better-sqlite3` first (Node), then `bun:sqlite` (Bun), then falls back to in-memory. Just works in both runtimes. * **Exported interface** — `TradingStorage`, `InMemoryStorage`, and `BunSqliteBackend` are all exported so you can use them directly or build custom adapters. ```typescript theme={null} // Bun — auto-detected, no config needed: const trader = new PolyNodeTrader({ /* storage auto-detects bun:sqlite */ }); // Bun — explicit: import { PolyNodeTrader, BunSqliteBackend } from 'polynode-sdk'; const trader = new PolyNodeTrader({ storage: new BunSqliteBackend('./trading.db'), }); ``` Upgrade: ```bash theme={null} npm install polynode-sdk@0.9.7 ``` *** ## 2026-04-30 — TypeScript SDK 0.9.5 Bug fix release for the trading module: * **Fixed `signer.getAddress is not a function` crash** — `ensureReady()` and `linkWallet()` could throw this error with viem WalletClient signers when Polymarket packages expect an ethers-style `getAddress()` method. The SDK now ensures `getAddress()` is always available on the normalized signer, regardless of which `@polymarket/clob-client` version you have installed. * **Fixed signer detection logic** — a duplicate guard condition was allowing incomplete signer objects through, causing crashes instead of clean error messages. * **Hardened relay signer adapter** — address extraction now handles additional wallet shapes during Safe deployment and approval flows. Upgrade: ```bash theme={null} npm install polynode-sdk@0.9.5 ``` *** ## 2026-04-30 — BYOB (Bring Your Own Backtest) — precomputed leaderboards The on-demand backtest endpoints answer "score this wallet" synchronously. **BYOB inverts that** — you hand us a private wallet pool, we precompute scores in the background across all six period presets, you query the resulting leaderboard with sub-second latency. Four new endpoints under `/v2/copy-pnl/`: ``` POST /v2/copy-pnl/wallets body: {addresses[]} add to pool (max 1000) DELETE /v2/copy-pnl/wallets body: {addresses[]} remove from pool GET /v2/copy-pnl/wallets list pool GET /v2/copy-pnl/leaderboard ?period=&sort_by=&order=&limit=&offset=&min_trade_count=&exclude_toxic= ``` Newly-added wallets are scored within \~30s of being added. The full pool refreshes daily in the background. Each result row includes `computed_at` so you can render freshness in your UI. Per-tenant isolation: each API key has its own private pool keyed on the SHA256 of the key. Your tracked wallets are never visible to other customers. References: [Backtesting Overview](/api-reference/backtesting/overview) · [Add Wallets](/api-reference/backtesting/byob-add-wallets) · [Remove Wallets](/api-reference/backtesting/byob-remove-wallets) · [List Wallets](/api-reference/backtesting/byob-list-wallets) · [Leaderboard](/api-reference/backtesting/byob-leaderboard) *** ## 2026-04-30 — On-chain wallet/market trades: deep pagination unlocked `/v2/onchain/wallets/{w}/trades` and `/v2/onchain/markets/{token}/trades` now properly paginate across the full trade history of any wallet or market, no matter how active. Previously these endpoints silently truncated heavy traders to \~2000 fills lifetime — deep `?offset=` queries returned empty. Customer-visible behavior changes: * Heavy wallets (e.g. 1M+ fills) now return correct, complete trade history * Deep `?offset=` queries return real data instead of empty arrays * No SDK or query-shape changes required — same `?limit=&offset=` parameters * Lower latency on most queries *** ## 2026-04-30 — Backtest Copy PnL: batch endpoint Score up to 100 wallets in one call: ``` POST /v2/copy-pnl/batch { "addresses": ["0x...", ...], "from": "...", "to": "..." } ``` Same math + response shape as the single-wallet endpoint. Per-wallet errors don't fail the batch — the bad slot returns `{"wallet": "...", "error": "..."}` and the rest still come back. Reference: [Batch endpoint](/api-reference/backtesting/batch) *** ## 2026-04-30 — Backtest Copy PnL endpoint New paid endpoint for scoring any wallet's copy-trade quality: ``` GET /v2/copy-pnl/{wallet}?period=30d ``` Walks every fill in the requested window, applies a realistic 2% slippage on buys and sells (capped at \$1.00 per share for buys), settles redemptions / merges / splits at face value, and returns: * `actual_pnl_usdc` — the wallet's cashflow PnL over the window * `backtest_copy_pnl_usdc` — what a copier would have earned with friction * `slippage_amount_usdc` — the dollar gap * `slippage_cost_rate_pct` — friction as a percentage of actual PnL * `toxic_for_copying` — `true` when the rate exceeds 15% (wallet's profit relies on execution speed; copier won't replicate) * `trade_count`, `applied_filters`, `sources`, optional `trades[]` drill-down **Time window options**: `?period=7d|14d|30d|60d|90d|180d` (default 30d), or `?from=&to=` with `YYYY-MM-DD` or unix seconds. **Note on PnL definition**: this returns **cashflow** PnL (real dollars moved), not Polymarket's website PnL (which marks open positions at current price). The response includes a `pnl_definition: "cashflow"` field to make this explicit. For PM-website style PnL use [Trader PnL Series](/api-reference/enriched/trader-pnl). **Limits**: paid tier required, 1 request per 5s per key, 30s server-side budget. Validated on wallets up to \~1.6M fills in the window. Full reference: [Backtest Copy PnL](/api-reference/backtesting/copy-pnl) · [Backtesting Overview](/api-reference/backtesting/overview) *** ## 2026-04-27 — Positions: 100% Polymarket parity, deterministic resolved-market mark, six new fields, real cursor pagination `/v2/onchain/positions` now matches Polymarket's `data-api` to the cent on every shared position. Verified across six wallets, 627 total shared positions, 100.00% sub-penny match on `unrealized_pnl`. What's new on each position row: * **`initial_value`** — cost basis in USD of currently held shares. Use this when you need the basis number that matches Polymarket `cashPnl`. * **`redeemable`** — `true` when the market has resolved and the user can call redeem to claim payout / accept loss. Filter on this with `market_status` to detect resolved-but-not-yet-redeemed positions. * **`outcome_index`** — numeric index of this row's outcome (0 or 1 for binary markets). Stable across the API regardless of UI label. * **`won`** / **`winning_outcome_index`** — present on `resolved-win` / `resolved-loss` rows. Explicit booleans / ints so consumers don't have to parse outcome label strings. * **`opposite_asset`** — token ID of the binary counterpart outcome on the same market. What's fixed on the math: * **`current_price` is deterministic on resolved markets.** Reads settlement values directly from the CTF contract on Polygon. Returns exactly `1.0` for the winning outcome and `0.0` for the loser, the moment the market settles on chain. * **`market_status` never lies.** Was previously stuck on `"live"` for fresh resolutions when our metadata was behind. Now uses the chain timestamp as the authoritative resolution signal: `"live"`, `"resolved-win"`, `"resolved-loss"`, `"resolved-unknown"`, or `"closed"`. * **`unrealized_pnl` is byte-identical to Polymarket `cashPnl`** for any position covered by both sources. `/v2/onchain/trades` cursor pagination now reports truthful `has_more`. Previously `has_more: false` was returned even when more rows existed beyond the requested `limit`. Walk pages by passing `pagination.pagination_key` back as `?pagination_key=…`. ```bash theme={null} # returns has_more: true with pagination_key now curl "https://api.polynode.dev/v2/onchain/trades?wallet=0x…&limit=2" -H "x-api-key: …" # walk to the next page curl "https://api.polynode.dev/v2/onchain/trades?wallet=0x…&limit=2&pagination_key=2" -H "x-api-key: …" ``` Time filters on `/v2/onchain/trades` use **`start_time`** and **`end_time`** (unix seconds), not `from` / `to`. Documented and verified. *** ## 2026-04-27 — Trades: unified schema, `direction` & `side` always present `/v2/onchain/trades`, `/v2/onchain/wallets/{w}/trades`, and `/v2/onchain/markets/{tok}/trades` now return the same `direction` and `side` fields on every row, regardless of which filter combination is passed. Previously these fields appeared only when a `?wallet=` anchor was supplied — market-wide queries (`?condition_id=…`, `?token_id=…`, no filter) omitted both, and consumers had to render two different schemas. ```bash theme={null} # customer's market-wide query — now returns direction+side on every row curl "https://api.polynode.dev/v2/onchain/trades?condition_id=0xb778…&limit=3" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "side": "maker", "direction": "BUY", "maker": "0xdf0d2c…", "taker": "0xb323fd…" } ``` **Anchor rule:** * **With `?wallet=`** — `direction` and `side` are wallet-relative. `direction` is `BUY` when the wallet contributed USDC and received outcome shares, `SELL` when it contributed outcome shares and received USDC. `side` is `maker` when the wallet placed the resting order, `taker` when it crossed the spread. * **Without `?wallet=`** — `direction` is anchored on the **maker** of each fill (the user who signed the limit order; on Polymarket's CLOB this is always a user EOA). `side` is therefore always `"maker"` in this case. This means a single fill viewed from a wallet-filtered query and from a market-wide query may report different `direction` values — both are correct, just anchored on different parties. See the ["Direction & side semantics"](/api-reference/onchain/trades#direction--side-semantics) section on the trades reference page. Strictly additive — calls that previously returned `direction`/`side` continue to return the exact same values. *** ## 2026-04-26 — New endpoint: `GET /v2/onchain/tags` for tag discovery Lists every tag slug in the polynode taxonomy — currently 5,779 tags. Lightweight by default (just slug strings, 73 KB / 43 ms cold for the full list) or `?details=true` for per-tag enrichment (markets, events, first/last seen). ```bash theme={null} # all tags curl "https://api.polynode.dev/v2/onchain/tags" -H "x-api-key: YOUR_KEY" # top 700 mainstream tags with stats curl "https://api.polynode.dev/v2/onchain/tags?min_markets=100&details=true" \ -H "x-api-key: YOUR_KEY" # discover NBA-related tags curl "https://api.polynode.dev/v2/onchain/tags?prefix=nba&details=true" \ -H "x-api-key: YOUR_KEY" ``` Values returned here are exactly what to pass to `?tag_slug=` on the [wallet positions](/api-reference/wallets/onchain-positions) and [unified positions](/api-reference/onchain/positions) endpoints. Use it to populate filter dropdowns or autocomplete inputs. The underlying materialized view refreshes hourly so newly-added Polymarket tags appear within an hour. *** ## 2026-04-26 — Wallet Positions: `tag_slugs` field + `?tag_slug=` filter Every row of [`GET /v2/wallets/{address}/positions/onchain`](/api-reference/wallets/onchain-positions) now includes a `tag_slugs` array carrying the Polymarket event-level tags that apply to that market — typical values like `nba`, `basketball`, `sports`, `politics`, `crypto`, `fed`, `2025-predictions`, plus event-specific slugs like `2026-fifa-world-cup-winner-595`. Order is most-specific to most-general. A new optional `?tag_slug=` query parameter filters the response to only positions whose market carries that tag. Composes with `since` / `until`, so you can ask for e.g. "all NBA positions traded in the last 30 days" in one request: ```bash theme={null} SINCE=$(( $(date -u +%s) - 86400 * 30 )) curl "https://api.polynode.dev/v2/wallets/0x.../positions/onchain?tag_slug=nba&since=$SINCE" \ -H "x-api-key: YOUR_KEY" ``` When the filter is supplied, aggregates recompute over the filtered set and the response gains `filtered: true` plus `applied_filters: { since, until, tag_slug }` so the behavior is self-documenting. Strictly additive — when no filter param is supplied, the response shape is byte-identical to before. Coverage is effectively universal: tags are populated on every event Polymarket indexes (sampled empirically at 100% in our backfill audit), so any non-test market should return a populated `tag_slugs` array. *** ## 2026-04-26 — Unified Trades: additive `direction` (BUY/SELL) field on `/v2/onchain/trades` [`GET /v2/onchain/trades`](/api-reference/onchain/trades) now also returns `direction` (`"BUY"` / `"SELL"`) on every row when filtered by `wallet` — same semantics as the wallet-trades endpoint, applied to the unified-trades flow. Independent of the existing `side` field. When no wallet anchor is supplied, `direction` is omitted (no perspective to derive against). Strictly additive: every other field is byte-identical to prior responses, and queries without `wallet` are unchanged. Verified live: 1,214 trades cross-checked against Polymarket data-api `side` — zero drift. *** ## 2026-04-26 — Wallet Trades: additive `direction` (BUY/SELL) field Every row of [`GET /v2/onchain/wallets/{address}/trades`](/api-reference/onchain/wallet-trades) now carries a `direction` field with values `"BUY"` or `"SELL"`, derived from whether the queried wallet contributed USDC or outcome tokens on the fill. This is independent of the existing `side` field, which remains the exchange role (`"maker"` / `"taker"`) and is unchanged. The two answer different questions: * `side` — did the wallet provide liquidity or take it? * `direction` — did the wallet enter (BUY) or exit (SELL) outcome shares? All four combinations (`maker`+`BUY`, `maker`+`SELL`, `taker`+`BUY`, `taker`+`SELL`) appear in real responses. Strictly additive — every other field is byte-identical to prior responses. The information `direction` carries was always derivable from `maker_asset_id` / `taker_asset_id` (exactly one is `"0"` on every fill, that's the buyer). This change just makes the derivation explicit so analytics integrations don't have to compute it client-side. *** ## 2026-04-26 — Wallet Positions: per-position timestamps, parent event slug, opt-in window filter Four new fields on every row of [`GET /v2/wallets/{address}/positions/onchain`](/api-reference/wallets/onchain-positions) (and the unified [`GET /v2/onchain/positions`](/api-reference/onchain/positions) feed). Two new optional query parameters for server-side window filtering. Every change is **strictly additive** — calls with no new parameters return responses byte-identical to before. **New per-position fields:** * **`last_trade_at`** — unix seconds. Latest fill on this token across V1 + V2 exchanges. The right field for "active in the last 30 / 90 days" leaderboard windows. * **`closed_at`** — unix seconds. Latest moment the wallet redeemed any outcome of the market for collateral. Distinct from `resolved_at` — `resolved_at` is when the market itself resolved on-chain; `closed_at` is when this specific wallet actually redeemed. * **`resolved_at`** — unix seconds. Moment the market became redeemable (oracle reported payouts on-chain). Recent markets are fully covered; markets that resolved before polynode began tracking return `null`. * **`event_slug`** — the parent **event** slug, distinct from the per-row `slug` (which is the per-market slug). For multi-market events (NBA games with several lines, election markets with multiple candidates, FIFA World Cup with one market per team), `event_slug` is the parent the markets share. For single-market events, `event_slug` equals `slug`. Lets you group positions by event without an extra metadata round-trip. **New optional query parameters** — opt-in only: * **`?since=`** — keep positions whose `last_trade_at >= since`. * **`?until=`** — keep positions whose `last_trade_at <= until`. When either is supplied, all aggregates (`count`, `open_count`, `closed_count`, `total_realized_pnl`, `total_unrealized_pnl`, `total_pnl`, `positions_with_pnl`) recompute over the filtered set, and the response gains two top-level keys to make the shift self-documenting: `filtered: true` and `applied_filters: { since, until }`. These keys are absent from default responses, so existing integrations see zero behavior change. ```bash theme={null} # 30-day window — leaderboard-style request SINCE=$(( $(date -u +%s) - 86400 * 30 )) curl "https://api.polynode.dev/v2/wallets/0x.../positions/onchain?since=$SINCE" \ -H "x-api-key: YOUR_KEY" ``` See the [Positions & P\&L (Wallet)](/api-reference/wallets/onchain-positions) reference for the full field catalog and filtering rules. *** ## 2026-04-26 — X Search API beta New paid endpoints for live X (Twitter) search and account-timeline data. Aimed at teams adding social context next to their prediction-market data — sentiment around a market, replies on a leader's post, what's being said about an event. * **`GET /v2/x/search?q=…&max=…`** — search by query, with full operator support (`from:`, `since:`, `min_faves:`, hashtags, `lang:`). * **`GET /v2/x/user/{handle}/tweets?max=…`** — most-recent tweets from any public X account. Available on starter, growth, and enterprise tiers with monthly quotas of 500 / 1,000 / 5,000. Hard rate cap of 1 request per second per key. Track usage live via `X-Quota-Used` / `X-Quota-Limit` response headers. See the [X Search API guide](/api-reference/x-search) for the full schema and examples. *** ## 2026-04-25 — Pricing section retired: candles deduplicated, "Market Card" introduced The "Pricing" sidebar section has been removed. It contained two endpoints: * **`GET /v1/stats/{token_id}`** — kept, renamed to **Market Card**, moved further down the sidebar (just above "System"). This one is genuinely useful: a single call returns the condition\_id, outcomes, neg-risk flag, end date, current orderbook liquidity, 24h OHLCV summary, and last price. Saves you from chaining 3–4 calls when you just need a market overview tile. * **`GET /v1/candles/{token_id}`** — removed from documentation. It was an in-memory rolling buffer with no historical depth, no pagination, no VWAP, and no buy/sell split. Use **`GET /v2/onchain/candles/{token_id}`** instead — same OHLCV plus VWAP, volume in shares, trade count, full history, and pagination. The endpoint itself still responds on the API for backward compatibility. *** ## 2026-04-25 — Wallet endpoints reorganized: V1 wallet endpoints removed from documentation The "Wallets" and "Onchain" sidebar sections have been merged into a single section called **Wallets / Positions / Onchain**. All wallet, position, and trade endpoints now live in one place — the V2 onchain endpoints. **What changed in the docs:** The seven legacy V1 wallet/market endpoints have been removed from the documentation sidebar. They were inferior duplicates of the V2 onchain equivalents — three of them returned empty or error responses, and the rest lacked PnL, average-price, current-price, and market-status fields that the V2 versions provide directly from chain data. | Removed from docs | Use instead | | ------------------------------------------ | ---------------------------------------------------- | | `GET /v1/wallets/{addr}/positions` | `GET /v2/onchain/positions?wallet=…` | | `GET /v1/wallets/{addr}/closed-positions` | `GET /v2/onchain/positions?wallet=…&status=closed` | | `GET /v1/wallets/positions` (multi-wallet) | Loop `GET /v2/onchain/positions?wallet=…` per wallet | | `GET /v1/wallets/{addr}/trades` | `GET /v2/onchain/wallets/{address}/trades` | | `GET /v1/markets/{id}/positions` | `GET /v2/onchain/positions?token_id=…` | | `GET /v1/markets/{id}/trades` | `GET /v2/onchain/markets/{token_id}/trades` | | `GET /v1/resolve/{query}` | `GET /v2/resolve/{query}` | **Important — backward compatibility preserved:** The V1 endpoints **still respond on the API**. We have not turned them off. If you have integrations hitting the legacy paths, they continue to work exactly as before — we just stopped surfacing them in the documentation because the V2 onchain replacements are strictly better. You should migrate, but you don't have to migrate today. **Why V2 onchain is better:** V1 wallet endpoints returned mostly social-flavored fields (bio, profile picture, pseudonym). V2 onchain endpoints return rich trading-relevant fields: `avg_price`, `realized_pnl`, `unrealized_pnl`, `current_price`, `total_bought`, `market_status`, `order_hash`, `fee`, `maker_amount`, `taker_amount`, `condition_id`, and more. Same data your equity curve, leaderboard, and PnL endpoints already use. *** ## 2026-04-25 — Equity Curve endpoint: faster default + four new query parameters The `/v2/trader/{wallet}/equity` endpoint is faster, more flexible, and more accurate. **Default is now realized-only.** The curve is built from completed P\&L by default — the locked-in result of every closed position, partial sell, and resolution. Same query, same numbers, every time. Whales return in 1–2 seconds (was 8+). If you want mark-to-market on currently-open positions (the old behavior), pass `include_unrealized=1`. That path is still supported, just opt-in now because it adds latency and makes responses non-deterministic as live prices move. **Four new optional query parameters:** | Param | Description | | ---------------------- | ----------------------------------------------------------- | | `from=YYYY-MM-DD` | Start date (also accepts unix seconds). | | `to=YYYY-MM-DD` | End date (same format). | | `max_markets=N` | Keep only the most recent N markets the wallet has touched. | | `include_unrealized=1` | Mark-to-market open positions (was the old default). | All four compose. Example — fast realized-only curve over the wallet's last 50 markets in early April: ```bash theme={null} curl "https://api.polynode.dev/v2/trader/{wallet}/equity?from=2026-04-01&to=2026-04-15&max_markets=50&normalize=1" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **New response fields:** `markets_count` (distinct outcome groups in the curve), `applied_filters` (echoes what was applied, useful for debugging), and `partial` (true if the request hit the 10-second server-side cap). **Coverage fix.** Wallets with more than 500 lifetime trades previously had positions whose first-trade timestamp wasn't found, causing those positions to be incorrectly stamped at "now" on the curve. The endpoint now walks all relevant trade history and falls back to sibling-token and redemption timestamps for positions acquired through USDC splits. Curves are dramatically more accurate for active traders. Full reference: [/api-reference/enriched/equity-curve](/api-reference/enriched/equity-curve). *** ## 2026-04-24 — Fee Escrow V2 live on Polygon (pUSD collateral, V2 CLOB) We've deployed a V2 variant of the Fee Escrow contract so platforms building on the V2 CLOB can charge fees in pUSD (V2's native collateral) instead of USDC.e. Both V1 and V2 are live and fully supported — there is no migration. If you're already running V1, nothing changes. **FeeEscrow V2 (new):** | Field | Value | | -------------- | -------------------------------------------------------------- | | Address | `0x3A43D88ef8Aae4dF5a50B3abf67122CAAeEF7c9F` | | Collateral | pUSD `0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB` (6 decimals) | | EIP-712 domain | `name="PolyNodeFeeEscrowV2"`, `version="2"`, chainId 137 | **FeeEscrow V1 (unchanged):** | Field | Value | | -------------- | ------------------------------------------------------ | | Address | `0xa11D28433B79D0A88F3119b16A090075752258EA` | | Collateral | USDC.e `0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174` | | EIP-712 domain | `name="PolyNodeFeeEscrow"`, `version="1"`, chainId 137 | The contracts have byte-identical calldata, identical function signatures, and identical semantics. The only functional differences are the collateral token and the domain name / version. The `FeeAuth` typed-data struct is unchanged. **Opting into V2 today:** The cosigner accepts an `escrow_contract` field in `fee_auth`. Absent or set to the V1 address = V1 (default). Set to the V2 address = V2. Sign the `FeeAuth` against the V2 domain when using V2. ```jsonc theme={null} "fee_auth": { "escrow_contract": "0x3A43D88ef8Aae4dF5a50B3abf67122CAAeEF7c9F", "escrow_order_id": "0x...", "payer": "0xSafe...", "signer": "0xEOA...", "fee_amount": "27500", "deadline": 1777000000, "nonce": 0, "signature": "0x...", // signed against the V2 domain "affiliate": "0xPartnerWallet...", "affiliate_share_bps": 10000 } ``` **SDKs:** V2 fee escrow is wired up in all three SDKs. Set `exchangeVersion` to V2 in your `TraderConfig` and the SDK automatically signs `FeeAuth` against the V2 domain, reads nonces from the V2 contract, approves pUSD against the V2 FeeEscrow during `ensureReady()`, and tells the cosigner to route to the V2 operator. Install: ```bash theme={null} npm install polynode-sdk@0.9.2 pip install "polynode==0.9.2" cargo add polynode@0.12.2 ``` V1 customers: no changes, V1 stays the default. Omit `exchangeVersion` (or set it to V1) and everything keeps working exactly as before. **Validation:** 8/8 end-to-end fork tests pass against the deployed V2 contract, exercising pullFee + distribute (30/70 treasury/affiliate split) + refund + 72-hour claimRefund + every revert path (expired deadline, wrong nonce, tampered signature). The test impersonates a real on-chain pUSD whale to fund the payer. Contract bytecode is a strict 2-change diff from V1 (collateral address + domain strings). Full guide: [/guides/fee-escrow](/guides/fee-escrow). *** ## 2026-04-21 (late) — Unrealized P\&L and market status on positions The `/v2/onchain/positions` and `/v2/wallets/{address}/positions/onchain` endpoints now return unrealized P\&L, the current market price, and a status flag per position — so you don't have to fetch prices separately or special-case resolved markets in client code. **New per-position fields:** * `unrealized_pnl` — paper P\&L on the held size at the current price. For resolved markets, uses the settlement price (1.0 for winners, 0.0 for losers). For live markets, uses the current market price. Returns 0 when `size = 0`. * `current_price` — the price used to compute `unrealized_pnl`. `null` when `size = 0` or no price is available. * `market_status` — one of `live`, `resolved-win`, `resolved-loss`, or `closed`. Lets you tell apart a live-tradable holding from a resolved-but-never-redeemed position without looking up the market separately. **New portfolio totals** on `/v2/wallets/{address}/positions/onchain`: * `total_unrealized_pnl` * `total_pnl` — `total_realized_pnl + total_unrealized_pnl`. This is the number that matches the "Profit" value shown on a Polymarket profile page. Drop-in — every existing field keeps its name and type. *** ## 2026-04-21 (late) — Gasless Safe wrap/unwrap via the Polymarket relayer Patch release. `wrapToPolyUsd` / `unwrapFromPolyUsd` (and their Python/Rust equivalents) now route through the Polymarket relayer automatically when called on a Safe or Proxy wallet — the flow `ensureReady` already uses. Previously these helpers sent the transaction directly from the EOA, which failed for Safe wallets because the funds live in the Safe, not on the EOA, and the EOA has no MATIC for gas. **Install:** ```bash theme={null} npm install polynode-sdk@0.9.1 # TypeScript pip install "polynode>=0.9.1" # Python cargo add polynode@0.12.1 # Rust ``` **Verified end-to-end with real Safe-via-relayer transactions:** * TS: wrap `0xde6c02c3...`, unwrap `0x006cd3dd...` * Python: wrap `0x7de53811...`, unwrap `0xccf9cf32...` * Rust: wrap `0xd80af0f4...`, unwrap `0xd21d6ad6...` All six txs mined on Polygon with `status: 0x1`, funded by third-party relayer EOAs — your EOA only signs the Safe `execTransaction` EIP-712 payload. **Requirements for Safe wrap/unwrap:** pass `builderCredentials` in `TraderConfig`. The SDK uses your Polymarket builder HMAC key to authenticate the relayer submit. Mint one at [polymarket.com/settings?tab=builder](https://polymarket.com/settings?tab=builder). **Tip — avoid stale-cache order rejection after wrap:** after `wrapToPolyUsd()`, the V2 CLOB's cached balance view is stale for a few seconds. If you place an order immediately, the CLOB can return a cryptic `"error parsing fee rate bps"` instead of the actual "balance not yet visible". Call `await trader.refreshBalanceAllowance()` before the order, or wait \~3 seconds. This is why we don't auto-refresh in wrap — not every wrap is followed by an immediate order. **Also in this patch:** * **Rust:** fixed `encode_unwrap` selector (was `0x39f47693`, correct value is `0x8cc7104f`). All Rust V2 unwraps prior to 0.12.1 hit the wrong contract function and reverted on-chain. * **Rust:** `get_polyusd_balance()` / `get_usdce_balance()` now read the funder address (Safe) instead of the EOA. Previously they always returned 0 for Safe-wallet users. * **Python:** `relayer.py` module added with a minimal Python port of `@polymarket/builder-relayer-client`. Implements EIP-712 Safe tx hashing, EIP-191 personal\_sign via `eth_account`, builder HMAC headers, and `/submit` + `/transaction` polling. *** ## 2026-04-21 — SDK V2 order flow hardening (all three SDKs) End-to-end V2 order placement verified live on `clob-v2.polymarket.com` across all three SDKs. Matched fills tagged with your builder code now appear in the V2 CLOB's `/builder/trades` attribution feed. **Install:** ```bash theme={null} npm install polynode-sdk@0.9.0 # TypeScript pip install "polynode>=0.9.0" # Python cargo add polynode@0.12.0 # Rust ``` **What changed:** * **V2 approvals now include the V1 `NegRiskAdapter`** in the spender list. The V2 CLOB's balance-allowance check monitors this address alongside the two V2 exchanges — orders without this approval were rejected with `"not enough balance / allowance"`. * **New methods: `refreshBalanceAllowance()` / `getBalanceAllowance()`.** The V2 CLOB maintains a cached balance-allowance view per API key; `ensureReady()` now auto-refreshes it after setting V2 approvals so the first order after onboarding goes through. * **Amount math hardened against float-precision edge cases.** Raw-units now derived via string-based decimal conversion instead of `Math.trunc(x * 1e6)` (TS) / `(x * 1_000_000.0) as u64` (Rust), which could truncate to `N-1` for prices that round-trip badly through float. * **V2 order body now matches `@polymarket/clob-client-v2` exactly** — adds `taker`, `postOnly`, `deferExec` fields to the POST payload. * **`checkBalance()` is V2-aware** — reads pUSD on V2, USDC.e on V1. Previously always read USDC.e. * **Market neg-risk detection fix (TS).** The old check treated every market as neg-risk (truthiness of the response object rather than its `neg_risk` field), which signed orders against the wrong exchange on standard markets. **Migration:** drop-in. `trader.order(...)` signature is unchanged; if you're on V2 and your orders were going through before, they still will. If you had a custom `ensureReady`-equivalent flow, you may now drop the manual `/balance-allowance/update` call — `ensureReady()` handles it on fresh V2 approvals. The full V2 wire format, required approvals, fee math, and common failure modes are documented in each SDK at `src/trading/V2_ORDER_FLOW.md` (TS, Rust) / `polynode/trading/V2_ORDER_FLOW.md` (Python). **End-to-end verification (from each public registry, fresh sandbox, real V2 orders against `clob-v2.polymarket.com`):** | SDK | Installed from | V2 order placed | Cancelled | | -------------------- | ------------------------------- | -------------------- | --------- | | `polynode-sdk@0.9.0` | `npm install polynode-sdk` | `0xb088a702...` LIVE | ✓ | | `polynode 0.9.0` | `pip install polynode[trading]` | `0x4906757c...` LIVE | ✓ | | `polynode 0.12.0` | `cargo add polynode@0.12` | `0x6d3623c6...` LIVE | ✓ | Each sandbox pulled from the public registry, imported the SDK, placed a real V2 GTC BUY with builder attribution, confirmed `status: "live"` from the V2 CLOB, then cancelled. No local source overrides, no pre-compiled artifacts — same install path a new user follows. **Docs fix also shipped today:** the V2 section of `/sdks/trading` now inlines `ensureReady` / `ensure_ready` so a V2-first reader who pastes only that block hits a copy-paste runnable flow. Previously the snippet silently assumed the reader had already gone through the Quick Start section. *** ## 2026-04-20 — CLOB v2 API launched New paid REST namespace `/clobv2/*` exposing every Polymarket CLOB v2 fill, position, builder, and neg-risk event. **11 endpoints, all enriched with market metadata (question, slug, outcome, image, condition\_id) inline:** * `GET /clobv2/trades` — global fills, filterable by wallet/builder/market/time/size * `GET /clobv2/positions` — every open/closed v2 position * `GET /clobv2/candles/{token_id}` — OHLCV at 1m/5m/15m/1h/4h/1d with VWAP + buy/sell split * `GET /clobv2/wallets/{address}/trades` — per-wallet fill history with `side` * `GET /clobv2/markets/{token_id}/{trades,volume,orderbook}` — per-outcome aggregates * `GET /clobv2/builders` — v2 builder leaderboard (v2-exclusive) * `GET /clobv2/neg-risk/events{,/{parent_id}/children}` — multi-outcome event drilldown * `GET /clobv2/volume/hourly` — platform-wide hourly volume buckets Every list response includes `count`, `pagination.{limit,offset,has_more}`, and rate-limit headers. Paid tier required; free-tier keys receive `402`. See the [overview](/api-reference/clobv2/overview) for auth + numeric-precision conventions. **`/v2/onchain/*` enrichment restored** Trades / wallet-trades / market-trades / market-volume / candles on the legacy `/v2/onchain/*` endpoints now carry `market`, `market_slug`, `outcome`, `image`, and `condition_id` inline again. These fields had been quietly lost during a backend refactor. *** ## 2026-04-19 — Expanded orderbook wire format + Rust SDK 0.11.0 The orderbook WebSocket now delivers PM's full per-level payload so you can maintain a tick-accurate local book without REST polling. Verified at **100% best-bid / 100% best-ask** across 50 concurrent markets. **What's new in the `price_change` message** Every entry in the `assets` array now includes four extra fields: * `size` — the absolute size at that level after the change. `"0"` means the level was removed. * `side` — `"BUY"` (affects bids) or `"SELL"` (affects asks) * `best_bid`, `best_ask` — best bid/ask on the asset after the change Old clients parsing only `{asset_id, price}` are unaffected — the new fields are additive. Multiple distinct levels on the same asset may now appear in one batch; repeat hits on the same `(asset_id, price, side)` within the 250ms coalesce window still merge to last-write-wins. See the [orderbook message reference](/orderbook/messages) for the full field list. **Rust SDK 0.11.0** * `PriceChangeAsset` now carries `size`, `side`, `best_bid`, `best_ask` * New `LocalOrderbook::apply_price_change(&PriceChange)` — apply incremental updates directly to your local book * `OrderbookEngine` automatically applies `price_change` events to its shared state so every `EngineView` stays tick-accurate * `OrderbookUpdate::LastTradePrice(BookTrade)` — executed-trade events now deserialize cleanly (previously dropped) **Install:** ```bash theme={null} cargo add polynode@0.11.0 ``` **Breaking:** none. Enum variants added are additive under existing serde routing. Explicit exhaustive matches on `OrderbookUpdate` will need to handle the new `LastTradePrice` variant — add a `_ => {}` arm or match it explicitly. *** ## 2026-04-19 — V2-ready SDKs released (Rust 0.9.0, TypeScript 0.8.0, Python 0.8.0) All three polynode trading SDKs are now wire-compatible with the Polymarket V2 exchange ahead of the **April 28, 2026 cutover**. Upgrade before then. V1 orders submitted after cutover will be rejected with `order_version_mismatch`. **What changed (all three SDKs):** * V2 CLOB POST body now includes `"expiration": "0"` to match `@polymarket/clob-client-v2`'s wire format * `OrderParams` now exposes a `builder` field (bytes32). Pass your builder code on each order to attribute trades on-chain. Defaults to zero (no attribution). Mint your V2 builder code at [polymarket.com/settings?tab=builder](https://polymarket.com/settings?tab=builder) * All V2 order placement paths live-verified against `clob-v2.polymarket.com` **Install:** ```bash theme={null} cargo add polynode@0.9.0 # Rust npm install polynode-sdk@^0.8.0 # TypeScript pip install "polynode>=0.8.0" # Python ``` **Breaking (Rust only):** `OrderParams` added a required struct field. If you construct `OrderParams { ... }` explicitly without `..Default::default()`, add `builder: None` to your literal. No code change required if you use struct update syntax. See the [V2 Migration Guide](/guides/v2-migration) for the full cutover checklist, the single-line switch, and a troubleshooting reference for common V2 errors. *** ## 2026-04-18 — Rust SDK 0.8.1: batch orderbook API ### New: batch + all-tracked queries Every per-token orderbook method on `OrderbookEngine` and `EngineView` now has a batch and an all-tracked variant. One read lock, one round-trip, results returned as a `HashMap` keyed by token ID. ```rust theme={null} let mids = engine.midpoints(&ids).await; // requested tokens let mids = engine.midpoints_all().await; // every tracked token // Same shape for: spreads / best_bids / best_asks / books let books = engine.books_all().await; // full L2 for everything ``` Tokens not in local state are silently skipped, so callers can pass mixed lists without pre-filtering. ### New: detect inactive markets Each tracked token now records when its local copy was last touched. Use it to find markets that have stopped moving. ```rust theme={null} use std::time::Duration; if let Some(ts) = engine.last_change("token_a").await { println!("last update {:?} ago", ts.elapsed()); } let stale = engine.inactive_since(Duration::from_secs(60)).await; ``` ### New: direct state access `engine.state()` returns the underlying `Arc>` so callers can hold the lock and walk the full state themselves — useful for snapshotting all tokens at one consistent moment or building custom views. ```rust theme={null} let state = engine.state(); let guard = state.read().await; let mids = guard.midpoints_all(); let books = guard.books_all(); ``` ### Install ```bash theme={null} cargo add polynode@0.8.1 ``` All existing per-token methods (`engine.midpoint(id)`, `engine.book(id)`, etc.) are unchanged. This release is purely additive. *** ## 2026-04-17 — Unrealized P\&L + redemption fix ### New: `unrealized_pnl` field on positions Every position returned by `/v2/onchain/positions` now includes an `unrealized_pnl` field showing the paper profit or loss on remaining open shares. * **Resolved markets**: uses the final settlement price ($1 for winners, $0 for losers) * **Active markets**: uses the current market price, refreshed every 15 minutes ### Fixed: realized P\&L for redeemed positions Positions that were closed via onchain payout redemption (rather than selling on the orderbook) now correctly reflect their realized P\&L. Previously, these showed `realized_pnl: 0` even when the position had a gain or loss. *** ## 2026-04-15 — Trade-based candles endpoint ### New: `GET /v2/onchain/candles/{token_id}` Server-built OHLCV candles from real onchain fills. Each candle includes open, high, low, close, total volume in USD and shares, separate buy and sell volume, trade count, and VWAP. Pagination is anchor-based: each request returns up to 1000 trades worth of candles, anchored at a timestamp, block number, or transaction hash. Walk older history by passing the cursor from the previous response. Same model used by major exchange APIs. ```bash theme={null} curl "https://api.polynode.dev/v2/onchain/candles/$TOKEN_ID?resolution=1h" \ -H "x-api-key: YOUR_KEY" ``` Resolutions: `1m`, `5m`, `15m`, `1h`, `4h`, `1d`. Optional `gap_fill=true` for charting libraries that expect a continuous time axis. Full reference at [/api-reference/onchain/candles](/api-reference/onchain/candles). *** ## 2026-04-14 — Positions sort fix + `last_activity` field ### Fixed: `/v2/onchain/positions` wallet queries now sort by recency When querying positions for a specific `wallet`, results are now ordered by the most recent on-chain activity for each position. Previously the `order=desc` parameter did not reflect recency, causing newer positions to appear below older ones. The most recently traded positions now appear at the top. Positions with no fill history (rare — acquired purely via split, merge, or redemption) are placed at the end. ### New: `last_activity` response field Wallet queries now include a `last_activity` field on each position, containing the unix timestamp of the most recent fill for that position. Use it to display "last traded" times in your UI. ```json theme={null} { "wallet": "0x4aefd329896464da0ffb16c4ebcd083a4360c181", "token_id": "99969794444523158222638792918531145371262906249733117976401662588024805483979", "size": 0, "status": "closed", "market": "Suns vs. Lakers", "market_slug": "nba-phx-lal-2026-04-10", "outcome": "Lakers", "last_activity": 1776232830 } ``` ### Pagination behavior Wallet queries return every position for the wallet in a single response (up to 500), ordered by recency. `has_more` is always `false` and no cursor is returned — request `limit=500` once and read all results. Wallets with more than 500 lifetime positions are capped. Non-wallet queries (filtering by `market_slug`, `condition_id`, `token_id`, or `min_size` alone) continue to use cursor-based pagination. *** ## 2026-04-12 — SDK v0.7.0: Fee Escrow + Order History ### New: Per-Order Fee Collection The trading SDK now supports optional per-order fee collection with on-chain escrow. Platforms can charge fees on trades with a single config option. ```bash theme={null} npm install polynode-sdk@0.7.0 ``` ```typescript theme={null} const trader = new PolyNodeTrader({ polynodeKey: 'pn_live_...', feeConfig: { feeBps: 50 }, // 0.5% fee }); const result = await trader.order({ tokenId: '...', side: 'BUY', price: 0.55, size: 100, }); console.log(result.feeEscrowTxHash); // on-chain TX hash console.log(result.feeAmount); // "0.0275" USDC ``` * **Automatic fee handling** -- fees are pulled into escrow before the order, distributed on fill, refunded on cancel * **Cancel auto-refund** -- cancelling an order automatically returns the fee to the user's wallet, inline in the same request * **Affiliate revenue sharing** -- split fees with partners on a per-order basis via `affiliate` and `affiliateShareBps` * **72-hour safety net** -- if the operator doesn't settle, users can self-refund on-chain * **Zero overhead when disabled** -- `feeBps: 0` (default) skips the escrow entirely with no behavior change * **7th approval** -- added to `ensureReady()` batch, gasless for Safe wallets ### Improved: Order History Local order history now persists fee escrow data (`feeAmountRaw`, `escrowOrderId`, `feeEscrowTxHash`). Existing databases auto-migrate on first open. No action required. ### SDK Releases * **TypeScript** `polynode-sdk@0.7.1` on [npm](https://www.npmjs.com/package/polynode-sdk) — adds `FeeConfig` export * **Rust** `polynode@0.7.1` on [crates.io](https://crates.io/crates/polynode) — fixes compile error in 0.7.0 * **Python** `polynode@0.7.2` on [PyPI](https://pypi.org/project/polynode/) — fixes `__version__` reporting See the [Fee Escrow Guide](/guides/fee-escrow) for the full architecture, security model, and code examples. *** ## 2026-04-11 — polynode-charts v0.1.5 ### New: `createShortFormOverlay` One-liner to add price-to-beat overlays to any live chart. Renders interval buttons (5m/15m/1h), auto-discovers Polymarket short-form markets, draws a dashed price line, and shows live odds with a countdown timer. ```typescript theme={null} import { createChart, createShortFormOverlay } from 'polynode-charts' const chart = createChart('#btc') const series = chart.addLineSeries({ color: '#f7931a' }) series.setLive(true) chart.timeScale().goLive() createShortFormOverlay(chart, series, { coin: 'btc' }) ``` ### Improvements * **Multi-outcome event discovery** now prioritizes genuine multi-outcome markets (`neg_risk: true`) over resolved binary matches * **`MarketInfo.outcome`** field identifies which side of a binary market each token represents ("Yes" or "No") * **Price-to-beat badge** renders correctly on the right axis for all price ranges * **Orderbook tooltip** no longer stays stuck after data refresh * **Tighter chart padding** for a denser, more professional look *** ## 2026-04-11 — polynode-charts v0.1.0 New npm package for interactive charting in the browser. Zero dependencies, Canvas 2D rendering at 60fps, purpose-built for prediction market data and live crypto price streams. ### Install ```bash theme={null} npm install polynode-charts ``` ### What's included * **Candlestick, line, area, and volume** series types with smooth animation * **Live streaming mode** with lerp animation, pulsing dots, and auto-scroll * **Orderbook visualization** with depth chart and spread display * **Short-form market overlays** — price-to-beat lines with live odds rotation for 5m/15m/1h crypto markets * **Multi-outcome support** for prediction markets (up to 20+ outcomes with auto-assigned colors) * **Built-in data providers** — `PolynodeProvider` (REST), `PolynodeOBProvider` (WebSocket orderbook), `ShortFormProvider` (market discovery + rotation) * **Interactive** pan, zoom, scroll, pinch-to-zoom on mobile * **Crosshair** with snap-to-candle and OHLC tooltip ### Quick start ```typescript theme={null} import { createChart, PolynodeProvider } from 'polynode-charts' const provider = new PolynodeProvider({ apiKey: 'pn_...' }) const chart = createChart('#chart', { rightPriceScale: { mode: 'probability' }, }) const series = chart.addCandleSeries({ upColor: '#22c55e', downColor: '#ef4444', }) const data = await provider.candles(tokenId, '1h') series.setData(data) ``` ### Documentation Full docs at [Charts SDK Overview](/charts/overview), including API reference, data providers, series types, examples, and configuration. `polynode-charts` is the **browser visualization** companion to `polynode-sdk` (the Node.js data SDK). They are separate npm packages. Building a frontend? Install both. *** ## 2026-04-09 — Confirmed Fills on Status Updates `status_update` events now include a `confirmed_fills` array with exact execution data from on-chain `OrderFilled` receipt logs. This is the same data source Polymarket's own APIs read. ### What changed * `status_update` events now carry a `confirmed_fills` field — an array of per-fill objects with exact price, size, fee, order hash, maker, taker, and token ID * Each fill is decoded directly from `OrderFilled` receipt logs on the CTF Exchange and NegRisk CTF Exchange contracts * Prices match Polymarket's activity data exactly (verified across 1,300+ fills with zero price discrepancies) ### Why this matters polynode detects settlements 3-5 seconds before on-chain confirmation by decoding transaction calldata from the mempool. For single-maker fills, calldata prices are exact. For multi-maker fills (\~5% of trades), the calldata only has aggregate amounts, so per-maker prices are estimated within 0.01-0.04. With `confirmed_fills`, you now get both: the fast pre-confirmation signal AND the exact on-chain execution data in a single subscription. Use whichever fits your use case. ### Use cases * **Copy trading**: Use the pending `settlement` event (speed matters, price difference is negligible) * **Analytics and bookkeeping**: Use `confirmed_fills` from the `status_update` (exact prices, fees, and sizes) * **Full lifecycle**: Track both to compare pre-confirmation estimates against final execution ### Size difference vs Polymarket `confirmed_fills` reports gross token amounts from the OrderFilled event. Polymarket's activity API reports sizes net of fees. The `fee` field on each fill lets you compute the net amount if needed. ### Subscribe No subscription changes needed. If you already subscribe to `settlements`, you're already receiving `status_update` events with `confirmed_fills`. ```json theme={null} {"action": "subscribe", "type": "settlements"} ``` ### Documentation * [Status Update Event Reference](/websocket/events/status-update) — updated with `confirmed_fills` schema and examples * [Trade Tracking Guide](/guides/trade-tracking) — when to use pending settlements vs confirmed fills *** ## 2026-04-08 — Redemption Event Streaming New `redemption` event type in the WebSocket stream. Every time a user redeems their outcome tokens after a market resolves, you get a real-time event with the redeemer address, payout amount, condition ID, and full market metadata enrichment. ### WebSocket Stream * New event type: `redemption` — available in the firehose and via `event_types` filter * Decoded from `PayoutRedemption` logs on the Conditional Tokens contract * Includes payout amount in USDC, redeemer wallet, outcome slots redeemed * Enriched with market title, slug, image, tokens map, neg\_risk flag * Filter with `min_size` to see only winning redemptions, or track specific wallets ### Subscribe ```json theme={null} {"action": "subscribe", "type": "settlements", "filters": {"event_types": ["redemption"]}} ``` ### Documentation * [Redemption Event Reference](/websocket/events/redemption) — full field reference, examples, and use cases *** ## 2026-04-07 — Polymarket V2 Exchange Support polynode now supports the Polymarket V2 exchange system alongside V1. All V2 contracts have been identified, decoded, and verified against live mainnet data. ### Settlement Stream * V2 settlements are detected and streamed automatically — no subscription changes needed * Same event types: `settlement`, `status_update`, `trade`, `deposit` * PolyUSD wrapping/unwrapping events now included in deposit stream * V2 detection is automatic alongside V1 (both supported simultaneously) ### Trading SDK (Rust v0.6.0, TypeScript v0.6.0, Python v0.7.0) * New `ExchangeVersion::V2` / `exchangeVersion: "v2"` config option * V2 order signing with updated EIP-712 domain (version "2") * `wrapToPolyUsd()` and `unwrapFromPolyUsd()` helper methods * `getPolyUsdBalance()` and `getUsdceBalance()` balance checking * V2 approval management (PolyUSD to V2 exchange contracts) * One config change to switch from V1 to V2 — everything else stays the same ### Documentation * [V2 Migration Guide](/guides/v2-migration) — what changed, what stayed the same, SDK examples * [PolyUSD Guide](/guides/polyusd) — how to wrap/unwrap PolyUSD with code examples * [V2 Technical Details](/guides/v2-details) — contract addresses, order struct, event signatures (subscribers only) * Deposit and settlement event pages updated with V2 notes ### What We Verified * V2 order placement tested live on the V2 CLOB * EIP-712 order hash matches the live V2 exchange contract on Polygon mainnet * PolyUSD wrapping detected through our event pipeline in real time * All existing market data, token IDs, and enrichment identical between V1 and V2 *** ## 2026-04-05 — Short-Form Docs: Full Field Reference + Orderbook Guide Updated the [Short-Form Markets](/sdks/short-form) documentation with the complete `ShortFormMarket` field reference. All 14 fields are now documented, including `conditionId`, `clobTokenIds`, `windowStart`, `windowEnd`, `outcomes`, and `outcomePrices` which were previously undocumented. Added a new **"Connect the Orderbook to Crypto Markets"** section showing how to pass `clobTokenIds` from a short-form rotation event directly to `OrderbookEngine` for real-time depth data on crypto prediction markets. Includes TypeScript and Rust examples. No SDK changes. All fields were already available in the SDK — this is a docs-only update. *** ## 2026-04-04 — Crypto Price Fix (All SDKs) Fixed stale price-to-beat values in short-form crypto markets. Polymarket changed the variant parameter on their crypto-price endpoint for 5-minute and hourly markets. The SDK now sends the correct values, matching what Polymarket's own frontend uses. **Affected intervals:** 5-minute and hourly. 15-minute was unaffected. ```bash theme={null} npm install polynode-sdk@0.5.9 # TypeScript cargo add polynode@0.5.8 # Rust pip install polynode==0.6.2 # Python ``` *** ## 2026-04-04 — TypeScript SDK v0.5.8 Fixed a bug where `price_feed` events from Chainlink subscriptions were silently dropped in the TypeScript SDK. The `.on('price_feed', ...)` handler now fires correctly. ```typescript theme={null} const sub = await pn.ws .subscribe('chainlink') .feeds(['BTC/USD']) .send(); sub.on('price_feed', (event) => { console.log(`${event.feed}: $${event.price}`); }); ``` Update with `npm install polynode-sdk@0.5.8`. *** ## 2026-04-02 — Position Management (All SDKs) Split, merge, and convert positions directly from the SDK. No need to interact with smart contracts manually. **TypeScript** — gasless execution via the Polymarket relayer: ```typescript theme={null} await trader.split({ conditionId: '0x...', amount: 100 }); await trader.merge({ conditionId: '0x...', amount: 100 }); await trader.convert({ marketId: '0x...', outcomeIndices: [0, 1], amount: 100 }); ``` **Rust & Python** — transaction builders that return ready-to-submit calldata: ```python theme={null} tx = build_split_txn("0x...", 100.0, neg_risk=True) tx = build_convert_txn("0x...", [0, 1], 100.0) ``` * [Position Management guide](/guides/position-management) — full walkthrough with examples * Convert is unique to neg-risk multi-outcome markets (e.g. "Who will win the World Cup?") * TypeScript SDK handles neg-risk vs standard market routing automatically *** ## 2026-04-02 — Positions Converted Event (WebSocket API) New `positions_converted` event type for tracking position conversions on neg-risk multi-outcome markets. When a trader converts NO positions into USDC + YES positions on complementary outcomes (via the NegRiskAdapter `convertPositions` function), this event fires with the full decoded details. ```json theme={null} { "data": { "event_type": "positions_converted", "event_title": "Hungary Parliamentary Election Winner", "stakeholder": "0x30cecdf29f069563ea21b8ae94492e41e53a6b2b", "converted_outcomes": ["Fidesz-KDNP", "TISZA"], "amount": 98, "market_id": "0x355e7310dd6e18ef5fa456de7ce1331bd8c7540c...", "index_set": "0x...0021", "neg_risk": true } } ``` * `converted_outcomes` decodes the bitmask into human-readable outcome names * `event_title` shows the parent multi-outcome event * Included by default in `wallets` and `markets` subscription types * Add explicitly via `event_types: ["positions_converted"]` for other subscription types * Fires \~13 times per minute across all active neg-risk markets *** ## 2026-04-01 — Builder Credentials (All SDKs) ### TypeScript SDK v0.5.7 / Rust SDK v0.5.7 / Python SDK v0.6.1 Platforms can now pass their own Polymarket builder credentials for order attribution. All volume gets credited to your builder profile on the [Builder Leaderboard](https://builders.polymarket.com). ```typescript theme={null} const trader = new PolyNodeTrader({ polynodeKey: 'pn_live_...', builderCredentials: { key: process.env.POLY_BUILDER_API_KEY, secret: process.env.POLY_BUILDER_SECRET, passphrase: process.env.POLY_BUILDER_PASSPHRASE, }, }); ``` * Get your builder credentials at [polymarket.com/settings?tab=builder](https://polymarket.com/settings?tab=builder) * polynode never stores your builder credentials. They're used per-request for HMAC signing only. * When omitted, polynode's default builder attribution is used (orders still go through). Install: ```bash theme={null} npm install polynode-sdk@0.5.7 # TypeScript cargo add polynode@0.5.7 # Rust pip install polynode==0.6.1 # Python ``` *** ## 2026-03-31 — TypeScript SDK v0.5.6 (Cache Crash Recovery) ### TypeScript SDK v0.5.6 Fixes a crash that could occur when stopping the cache mid-backfill, and adds proper crash recovery for interrupted backfills. **Graceful shutdown**: `cache.stop()` now waits for any in-flight backfill operation to complete before closing the database. Previously, stopping during an active backfill could crash with a `Cannot read properties of null` error on the SQLite handle. **Crash recovery**: on startup, any backfills that were interrupted by a previous crash or kill are automatically detected and retried. The cache logs exactly what's happening: ``` # Restart after crash — resumes incomplete backfills only [PolyNodeCache] Reset 1 interrupted backfill(s) from previous session. [PolyNodeCache] Backfilling 2 entities (1 page of 500 each) — ETA: ~1s # Clean restart — all data persisted, no network calls needed [PolyNodeCache] All 4 entities already backfilled, skipping. ``` **Accurate logging**: the startup log now only reports entities that actually need backfilling, instead of the total watchlist count. ```bash theme={null} npm install polynode-sdk@0.5.6 ``` *** ## 2026-03-31 — Equity Curve Endpoint ### New: Trader Equity Curve Full equity curve for any Polymarket wallet. Returns a time-ordered series of cumulative profit and loss across every position the wallet has ever taken. **Endpoint:** `GET /v2/trader/{wallet}/equity?period=all&normalize=1` **Features:** * Raw P\&L curve with realized gains/losses and mark-to-market on open positions * \$1-normalized curve for copy-trade analysis: each position scaled to \$1 of risk, showing expected returns per dollar deployed * Period filtering: `7d`, `30d`, `90d`, `1y`, `all` * Accuracy within 2-3% of Polymarket's own P\&L calculations (variance from real-time price differences on open positions only) **\$1 normalization** answers the question: "If I copy every trade this wallet makes but only risk \$1 per position, what are my returns?" Useful for evaluating trader skill independent of position sizing. ```bash theme={null} curl "https://api.polynode.dev/v2/trader/0x.../equity?period=all&normalize=1" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "positions_count": 278, "open_count": 83, "raw": { "final_pnl": -395.90, "curve": [{"t": 1774555427, "pnl": 10.56}, ...] }, "normalized": { "bet_size": 1, "final_pnl": -3.19, "curve": [{"t": 1774555427, "pnl": 0.07}, ...] } } ``` See the [full documentation](/api-reference/enriched/equity-curve) for methodology details and response field reference. ### Fix: Market Price Fetching Fixed an issue where current market prices were not being fetched correctly for open positions across multiple endpoints. Price accuracy is now significantly improved for any endpoint that reports unrealized P\&L. *** ## 2026-03-29 — Rust SDK v0.5.6 (Order Placement Fix) ### Rust SDK v0.5.6 Critical fixes for order placement. The trading module now passes full end-to-end testing: wallet onboarding, order placement, cancellation, and order history. **Order signing fixes:** * Fixed EIP-712 struct hashing (domain type filtering) * Fixed signature `v` normalization for CLOB compatibility * Fixed amount rounding to match tick-size-based precision rules * Fixed salt generation range for CLOB compatibility **Authentication fixes:** * Fixed HMAC base64 decoding for URL-safe encoded secrets * Fixed wallet address checksumming (EIP-55) in all auth headers and order payloads **Privy integration fixes:** * Fixed address formatting in Privy wallet API calls * Fixed signature normalization in Privy response extraction ```toml theme={null} [dependencies] polynode = { version = "0.5.6", features = ["trading"] } ``` *** ## 2026-03-29 — Python SDK v0.6.0 ### Python SDK v0.6.0 Trading module fixes and Privy server-side wallet support. **Trading fixes:** * Fixed CLOB credential creation (endpoint, auth headers, EIP-712 message structure) * Fixed order signing (domain name, signature format, payload serialization) * Fixed HMAC authentication for URL-safe base64 secrets * Fixed `fetch_tick_size` and `fetch_neg_risk` response parsing * Full end-to-end verified: wallet generation, onboarding, order placement, cancellation, gasless flow **New: Privy signer** * `PrivySigner` for server-side trading with Privy-managed wallets * No private key needed, signing via Privy's wallet API * Works with all trading methods (`ensure_ready`, `order`, `cancel_all`, etc.) **Other fixes:** * `markets_by_category()` now correctly filters via query parameter * Fixed subscription filters code example in docs ```bash theme={null} pip install polynode==0.6.0 # With trading support: pip install "polynode[trading]==0.6.0" ``` *** ## 2026-03-29 — Rust SDK v0.5.0 (Feature Parity) ### Rust SDK v0.5.0 Major release bringing the Rust SDK to feature parity with the TypeScript SDK. **New REST endpoints (12 methods):** * Orderbook REST: `orderbook_rest()`, `midpoint()`, `spread()` * Enriched data: `leaderboard()`, `trending()`, `activity()`, `movers()` * Trader analytics: `trader_profile()`, `trader_pnl()` * Events: `event()`, `search_events()`, `markets_by_category()` **Trading module** (`--features trading`): * `PolyNodeTrader` for full order lifecycle on Polymarket * Wallet generation, onboarding (auto-detect Safe/Proxy), order placement, cancellation * Native EIP-712 signing via `TradingSigner` trait + `PrivateKeySigner` * CREATE2 address derivation for Safe and Proxy wallets * CLOB authentication with L2 HMAC headers * Builder attribution via co-signer proxy * Local SQLite storage for credentials and order history * On-chain approval and balance checks **Privy integration** (`--features privy`): * `PrivySigner` for server-side trading with Privy-managed wallets * Pure HTTP-based, no native SDK dependency **Redemption watcher:** * Monitor wallets for redeemable positions after oracle resolution * Fires alerts with winner detection and payout estimation ```toml theme={null} [dependencies] polynode = "0.5" # With trading: # polynode = { version = "0.5", features = ["trading"] } ``` Full documentation: [Rust SDK](/sdks/rust) *** ## 2026-03-29 — Trading Module v0.5.5 (TypeScript SDK) ### TypeScript SDK v0.5.5 * **EOA auto-approvals:** `ensureReady()` now automatically sends approval transactions for EOA wallets with proper nonce management. Requires ~~0.05 MATIC in the wallet (~~\$0.01). * **Fix:** `createPrivySigner`, `createPrivyClient`, and other Privy helpers are now correctly exported from the package root. In v0.5.2 these were only reachable through non-public module paths. * **Privy server-auth integration:** `createPrivySigner()` and `createPrivyClient()` for server-side trading with Privy-managed wallets. * **Unlimited gasless operations:** Paid tiers get unlimited gasless on-chain operations. Free tier gets basic gasless onboarding. * **ethers v5/v6 compatibility:** Safe transaction signing works with both ethers v5 and v6. ```bash theme={null} npm install polynode-sdk@0.5.5 viem better-sqlite3 @polymarket/clob-client @polymarket/builder-relayer-client @polymarket/builder-signing-sdk ``` Full documentation: [Trading](/sdks/trading) *** ## 2026-03-29 — Trading Module (TypeScript SDK v0.5.0) ### TypeScript SDK — Order Placement on Polymarket Place and manage orders on Polymarket through the polynode SDK. One function call handles wallet setup, credential creation, approvals, and order placement. ```bash theme={null} npm install polynode-sdk@0.5.0 viem better-sqlite3 @polymarket/clob-client @polymarket/builder-relayer-client @polymarket/builder-signing-sdk ``` **One-call onboarding:** ```typescript theme={null} import { PolyNodeTrader } from 'polynode-sdk'; const trader = new PolyNodeTrader({ polynodeKey: 'pn_live_...' }); // Auto-detects your wallet type. Deploys Safe, sets approvals, creates credentials. const status = await trader.ensureReady('0xYOUR_PRIVATE_KEY'); const result = await trader.order({ tokenId: '...', side: 'BUY', price: 0.55, size: 100, }); await trader.cancelOrder(result.orderId); ``` **Key features:** * **Auto-detection:** Pass your private key. The SDK checks if you have a Safe, Proxy, or EOA on-chain and uses the right one. No wallet type guessing. * **Wallet generation:** `PolyNodeTrader.generateWallet()` for users starting from scratch. * **Gasless onboarding:** Safe deployment + 6 token approvals in one call, \~10 seconds, zero gas. * **Local credential custody:** CLOB credentials stored in local SQLite. Export, import, and back up freely. * **Builder attribution:** Orders route through polynode's relay for affiliate tracking. If the relay is down, orders fall back to direct CLOB submission. * **All three wallet types:** EOA (type 0), Proxy (type 1, legacy Magic Link), Safe (type 2, browser wallets). * **Dome drop-in:** Same `@polymarket/clob-client` signing under the hood. Import existing Dome credentials with `linkCredentials()`. Full documentation: [Trading](/sdks/trading) | [Dome Migration](/dome-migration#order-placement) *** ## 2026-03-28 — Onchain Data Endpoints + Market Metadata Enrichment ### Enrichment: All Onchain Endpoints Now Include Market Metadata Every onchain endpoint now returns enriched data with human-readable market context. Responses include `market` (question text), `slug`, `outcome` label, `image`, and `condition_id` alongside the raw onchain data. Powered by a full backfill of all 706K+ Polymarket markets into a local metadata store that refreshes every 5 minutes. ### New: Onchain Data Section Seven new endpoints for blockchain settlement data. These provide complete, accurate data that never times out or drops records. All onchain endpoints are under `/v2/onchain/` and documented in the new [Onchain](/api-reference/onchain/wallet-trades) section of the API reference. #### Wallet endpoints * **`GET /v2/onchain/wallets/{addr}/trades`** — Complete trade fill history for any wallet. Every fill, every counterparty, every fee. * **`GET /v2/onchain/wallets/{addr}/redemptions`** — All redemptions with payout amounts. See who cashed out, when, and how much. Not available anywhere else. * **`GET /v2/onchain/wallets/{addr}/activity`** — Splits, merges, and multi-outcome conversions. Shows how wallets interact with the CTF contract beyond simple trading. #### Market endpoints * **`GET /v2/onchain/markets/{tokenId}/trades`** — Complete trade tape for any market token. * **`GET /v2/onchain/markets/{tokenId}/volume`** — Lifetime volume stats: total trades, buys, sells, USDC volume with buy/sell breakdown. * **`GET /v2/onchain/markets/{conditionId}/oi`** — Per-market open interest in USDC. * **`GET /v2/onchain/oi`** — Global platform open interest (currently \~\$469M). All endpoints support `limit` and `offset` pagination where applicable. Responses are cached for 1-5 minutes depending on the endpoint. *** ### Wallet P\&L: Complete Position History + Accurate Realized P\&L #### What changed The existing `GET /v1/wallets/{addr}/positions` endpoint only returns **open** positions. Once a market resolves or a position is fully sold, it disappears from the response. The existing `GET /v1/wallets/{addr}/trades` endpoint silently drops trades for high-volume wallets (we verified one wallet showing 403 trades through the API vs 4,405 actual onchain trades). This made it impossible to compute accurate P\&L through our API. That's fixed now. Two new v2 endpoints return complete position history with accurate P\&L sourced from onchain settlement data. Per-position `realized_pnl` values come from the same onchain settlement data Polymarket uses and match individual position P\&L to the penny. The `total_realized_pnl` aggregate measures total realized gains, which is different from Polymarket's profile PnL (see [docs](/api-reference/wallets/onchain-positions#total_realized_pnl-vs-polymarket-profile-pnl) for a full breakdown). #### `GET /v2/wallets/{address}/positions/onchain` **One call. No pagination. All positions (open + closed).** Unlike the v1 positions endpoint which requires the SDK to paginate through trades page by page, this endpoint returns the complete picture in a single request with up to 20,000 positions. ```bash theme={null} curl "https://api.polynode.dev/v2/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/positions/onchain" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "wallet": "0xbddf61af533ff524d27154e589d2d7a81510c684", "source": "onchain", "count": 873, "open_count": 334, "closed_count": 288, "total_realized_pnl": 17183579.48, "positions_with_pnl": 309, "positions": [ { "token_id": "3415885798119615...", "size": 0, "avg_price": 0.316481, "realized_pnl": 447182.95, "total_bought": 654236.31 } ] } ``` | Field | Description | | -------------------------- | ---------------------------------------------- | | `count` | Total positions (open + closed) | | `open_count` | Positions still held (`size > 0`) | | `closed_count` | Fully exited positions with nonzero P\&L | | `positions_with_pnl` | Number of positions with nonzero realized P\&L | | `total_realized_pnl` | Sum of all `realized_pnl` values | | `positions[].realized_pnl` | Profit or loss for this position in USDC | | `positions[].avg_price` | Volume-weighted average entry price | | `positions[].total_bought` | Total tokens acquired | Responses are cached for 5 minutes. First request takes 200ms-3s depending on position count. Cached responses return in under 50ms. #### `GET /v2/wallets/{address}/closed-positions` Closed positions with full metadata (title, outcome, slug). Paginated. ```bash theme={null} curl "https://api.polynode.dev/v2/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/closed-positions?limit=3&sortBy=REALIZEDPNL&sortDirection=DESC" \ -H "x-api-key: YOUR_KEY" ``` Parameters: `limit` (max 50), `offset`, `sortBy` (REALIZEDPNL, AVGPRICE, PRICE, TITLE, TIMESTAMP), `sortDirection` (ASC/DESC). #### SDK: Automatic onchain P\&L backfill The TypeScript (`polynode-sdk@0.4.10`) and Rust (`polynode@0.4.3`) SDKs now fetch onchain position data automatically during backfill. No code changes needed if you're already using `cache.start()`. The backfill makes one additional API call per wallet (the onchain positions endpoint above) and stores the results in your local SQLite database. ```bash theme={null} npm install polynode-sdk@0.4.10 ``` **Before:** The SDK backfilled trades page by page (up to 6 requests per wallet) and computed P\&L from incomplete trade history. Wallets with dropped trades got wrong numbers. **After:** One call per wallet returns all positions with precomputed P\&L. The SDK stores this in SQLite and uses it as the source of truth for all P\&L queries. ```typescript theme={null} const cache = new PolyNodeCache(client); await cache.start(); // P&L from local cache — no API calls const pnl = cache.computeRealizedPnl("0xbddf61af533ff524d27154e589d2d7a81510c684"); ``` ```json theme={null} { "wallet": "0xbddf61af533ff524d27154e589d2d7a81510c684", "total_realized_pnl": 17183579.48, "total_unrealized_pnl": 3030.53, "total_pnl": 17186610.01, "confidence": "full", "trades_analyzed": 403, "tokens": [ { "token_id": "3415885798119615...", "market_title": "Nuggets vs. Warriors", "outcome": "Warriors", "realized_pnl": 447182.95, "avg_cost": 0.3170, "remaining_size": 0 } ] } ``` `trades_analyzed` reflects cached trades from the REST API (403 in this case). The `total_realized_pnl` and per-token `realized_pnl` values come from onchain settlement data and are accurate regardless of cached trade count. `walletDashboard()` now includes `realized_pnl`, `pnl_confidence`, and `token_pnl` fields: ```typescript theme={null} const dash = cache.walletDashboard("0xbddf61af533ff524d27154e589d2d7a81510c684"); // dash.realized_pnl → 17183579.48 // dash.pnl_confidence → "full" // dash.token_pnl → per-token P&L breakdown // dash.total_pnl → realized + unrealized combined ``` *** ### Crypto Price Streaming (WebSocket) Real-time cryptocurrency prices are now available on the same WebSocket connection you already use for settlements and oracle events. Seven feeds, each updating roughly once per second: **BTC/USD**, **ETH/USD**, **SOL/USD**, **BNB/USD**, **XRP/USD**, **DOGE/USD**, **HYPE/USD** Subscribe with `{"action": "subscribe", "type": "chainlink"}`. Optionally filter to specific feeds: ```json theme={null} { "action": "subscribe", "type": "chainlink", "filters": { "feeds": ["BTC/USD", "ETH/USD"] } } ``` Each price update includes bid, ask, and mid price: ```json theme={null} { "type": "price_feed", "feed": "BTC/USD", "timestamp": 1774675338, "data": { "feed": "BTC/USD", "price": 66240.86, "bid": 66234.85, "ask": 66243.30, "timestamp": 1774675338 } } ``` BTC/USD includes actual bid/ask spread. Other feeds currently report bid and ask equal to the mid price. See the [full documentation](/crypto/overview) for connection examples, feed details, and event format reference. *** ### Crypto REST Endpoints Five new REST endpoints for crypto prediction market data: * `GET /v1/crypto/markets` — All crypto prediction markets with volume, liquidity, and open interest (3 min cache) * `GET /v1/crypto/candles?symbol=BTC` — \~30 five-minute OHLC candles (30s cache) * `GET /v1/crypto/price?symbol=BTC&window={epoch}` — Open/close price for a specific market window (10s cache) * `GET /v1/crypto/active` — Currently active 5-minute up-or-down markets across all 7 coins with live odds (30s cache) * `GET /v1/crypto/series` — Recurring crypto market series across timeframes (5 min cache) ```bash theme={null} curl "https://api.polynode.dev/v1/crypto/candles?symbol=ETH" \ -H "x-api-key: YOUR_KEY" ``` Supported symbols: `BTC`, `ETH`, `SOL`, `BNB`, `XRP`, `DOGE`, `HYPE`. See the [REST API reference](/crypto/rest-api) for full parameter details and response samples. *** ## 2026-03-26 ### API — Wallet Resolver New endpoint that instantly resolves any Polymarket wallet identity. Pass a proxy wallet address, EOA address, or username and get back all three. ```bash theme={null} curl "https://api.polynode.dev/v1/resolve/DTCahill" \ -H "x-api-key: YOUR_KEY" ``` ```json theme={null} { "safe": "0x0ecdd241ec1bc84a40b8142bebe65787aee97514", "eoa": "0xddd57cce99ca962fe23aa2da95b139f86a241459", "username": "DTCahill" } ``` Covers \~3 million Polymarket wallets and is updated daily. Lookups are sub-millisecond. Accepts any of the three identifiers as input: * `GET /v1/resolve/{safe_address}` — proxy wallet to EOA + username * `GET /v1/resolve/{eoa_address}` — EOA to proxy wallet + username * `GET /v1/resolve/{username}` — username to both addresses (case-insensitive) See the [full documentation](/api-reference/wallets/resolve) for details. *** ### API — `order_hash` on Settlements and Trades Every settlement trade and confirmed trade event now includes an `order_hash` field — the EIP-712 hash that uniquely identifies a limit order on Polymarket's order book. **Why this matters:** A single limit order can be partially filled across multiple transactions. The `order_hash` stays the same across all of them, letting you track order lifecycle, group partial fills, and build order-level analytics. * **Settlement events:** `order_hash` appears on each trade inside the `trades[]` array. Available on pending (pre-chain) settlements, computed from transaction calldata before block confirmation. * **Trade events:** `order_hash` appears as a top-level field, extracted directly from the on-chain `OrderFilled` event log. ```json theme={null} { "type": "trade", "data": { "order_hash": "0x916fa5c2728c93e19ea5d7c254b07234815ad01e59597573c09d852fe53ee564", "maker": "0x4b8cf80092c60b9b23d22133eb63eb4508fe4d31", "price": 0.71, "size": 3.0 } } ``` No SDK update required — the field is automatically included in the WebSocket stream. ### API — `order_hashes` on Status Updates The `status_update` event now includes an `order_hashes` field containing every EIP-712 order hash from the original pending settlement. This lets you correlate confirmed settlements back to specific limit orders without needing to store state from the initial pending event. ```json theme={null} { "type": "status_update", "data": { "tx_hash": "0xbb26c8cd...", "order_hashes": [ "0xafc3488df3505c4dbba8797b8b6c645630c8ee9decf811a0acc4d63fdecfdd36", "0x9b5a06de7196bde87b4d8baa6cb80681151338d263fac0e643a2d53c4497e27f" ], "maker_wallets": ["0xd408...", "0x9443..."], "latency_ms": 3959 } } ``` Purely additive. Existing clients are unaffected. *** ### TypeScript SDK — RedemptionWatcher Memory Management The `RedemptionWatcher` now automatically evicts resolved conditions and zero-size positions from its local watch set. This keeps memory bounded to only active, non-zero positions regardless of how long the watcher runs or how many wallets it tracks. Previously, resolved markets and fully-sold positions accumulated in memory indefinitely. For long-running services tracking thousands of wallets, this could grow to hundreds of megabytes over weeks. **What changed:** * Resolved conditions are removed from the index immediately after alerts fire * Positions that drop to zero size (full sell) are removed automatically * `refreshInterval` default changed from `0` (disabled) to `300_000` (5 minutes). This periodic REST refresh acts as a safety net, re-populating any positions missed during brief WebSocket disconnections. **No breaking changes.** Alert behavior is identical. If you were explicitly setting `refreshInterval: 0`, that still works. ```bash theme={null} npm install polynode-sdk@latest ``` See [RedemptionWatcher — Memory Management](/sdks/redemption-watcher#memory-management) for details. *** ### Trader Profile — EOA Wallet Resolution The `/v1/trader/{wallet}` endpoint now returns an `eoaWallet` field that resolves the underlying EOA (externally owned account) for Polymarket proxy wallets. This is derived onchain from the Gnosis Safe contract. ```json theme={null} { "wallet": "0xc2e7800b5af46e6093872b177b7a5e7f0563be51", "eoaWallet": "0xb49e5499562a4bc3345c1a1f2db13a5360dfddac", "name": "beachboy4", ... } ``` For older lightweight proxy wallets, `eoaWallet` returns `null`. ### Tier-Aware Rate Limits on Data-Heavy Endpoints A handful of data-heavy endpoints now have their own per-key rate limits that scale with your plan: | Tier | Limit | | ---------- | ----------- | | Free | 1 req/sec | | Starter | 30 req/sec | | Growth | 50 req/sec | | Enterprise | 100 req/sec | Affected endpoints: `/v1/trader/{wallet}`, `/v1/trader/{wallet}/pnl`, `/v1/leaderboard`, `/v1/trending`, `/v1/activity`, `/v1/event/{slug}`, `/v1/movers`, `/v2/markets/{category}`. All other REST endpoints use your plan's standard rate limit. See [Rate Limits](/guides/rate-limits) for full details. *** ## 2026-03-25 ### TypeScript SDK — RedemptionWatcher New high-level class that monitors wallets and fires alerts the instant any position becomes redeemable on-chain. ```bash theme={null} npm install polynode-sdk@latest ``` ```typescript theme={null} import { RedemptionWatcher } from 'polynode-sdk'; const watcher = new RedemptionWatcher({ apiKey: 'pn_live_...' }); watcher.on('alert', (alert) => { if (alert.isWinner) { console.log(`${alert.wallet} can redeem "${alert.marketTitle}" — ~$${alert.estimatedPayoutUsd}`); } }); await watcher.start(['0xabc...', '0xdef...']); ``` **What it does:** Fetches wallet positions via REST, indexes by `condition_id`, subscribes to the oracle stream for `condition_resolution` events, and cross-references in real-time. Optional live position tracking via the wallets stream keeps sizes accurate as users trade. **Key features:** * Runtime `addWallets()` / `removeWallets()` with automatic re-subscription * Callback (`.on('alert', ...)`) and async iterator (`for await`) consumption * Winner detection with payout estimates * Full market metadata on every alert (title, slug, image, outcomes) See the [full documentation](/sdks/redemption-watcher) for lifecycle, configuration, and a production example. *** ### Oracle Stream — `condition_resolution` Event The oracle WebSocket stream now includes `condition_resolution` events. This is the on-chain signal that positions are redeemable on the Conditional Tokens contract. Previously, the stream included `resolution` events (outcome decided on the UMA adapter) but not the separate on-chain step where `reportPayouts()` is called on the CTF contract. For neg-risk markets (the majority on Polymarket), there is a \~2-3 minute gap between these two events. `condition_resolution` closes that gap. **Subscribe and filter:** ```json theme={null} { "action": "subscribe", "type": "oracle", "event_types": ["condition_resolution"] } ``` **Payload includes:** `resolved_price`, `payouts`, `condition_id`, `question_id`, and full market metadata (title, outcomes, token IDs, image). **Use case:** Trigger redemption workflows the instant positions become redeemable, instead of polling or waiting for the Polymarket UI to update. **SDK support:** Available in Rust SDK `polynode 0.4` via `OracleEventType::ConditionResolution`. TypeScript SDK update coming soon. *** ### TypeScript SDK v0.4.8 — Composable Leaderboard Builder The local cache now supports composable leaderboards with multi-metric ranking, market filtering, slug pattern matching, wallet scoping, and time windows. **New:** Call `cache.leaderboard()` with no arguments to get a `LeaderboardBuilder`: ```typescript theme={null} const rows = cache.leaderboard() .metrics(['total_pnl', 'volume', 'win_rate']) .slugs(['*election*']) .since(weekAgo) .limit(10) .build(); // Each row: { wallet, label, rank, metrics: { total_pnl, volume, win_rate } } ``` **7 new metrics** (11 total): `roi`, `realized_pnl`, `volume`, `avg_trade_size`, `largest_win`, `largest_loss`, `market_count`. **Builder methods:** * `.metric()` / `.metrics()` — single or multi-metric per row * `.sortBy()` / `.sort('ASC' | 'DESC')` — control ranking * `.markets([conditionIds])` — filter by market * `.slugs([patterns])` — glob match on market slugs (`*election*`, `btc-*`) * `.category({ name, slugs })` — reusable named slug groups * `.wallets([addrs])` — rank a wallet subset instead of full watchlist * `.since(ts)` / `.until(ts)` — time window for trade metrics * `.limit(n)` — top N Backward compatible. Existing `cache.leaderboard('total_pnl')` still returns the same `LeaderboardEntry[]` format. ```bash theme={null} npm install polynode-sdk@0.4.8 ``` Full documentation: [Local Cache — Leaderboard Builder](/sdks/local-cache#leaderboard-builder) *** ### WebSocket — Application-Level Keepalive Cloud platform reverse proxies (Railway, Render, Heroku, AWS ALB, fly.io) can intercept WebSocket Ping/Pong control frames, preventing the server from detecting that your client is still alive. This caused connections to drop after 1-2 minutes with an empty error and no close frame. **What changed:** * The server now accepts any incoming message (not just Pong frames) as proof the client is alive * New `{"action": "ping"}` message returns `{"type": "pong", "ts": ...}` as an application-level keepalive * Stale connection timeout extended from 90 seconds to 5 minutes * Stale disconnects now include a close frame with a reason, instead of a silent drop **If you're running on a cloud platform**, add a periodic ping to your connection: ```python Python theme={null} async def keepalive(): while True: await asyncio.sleep(30) await ws.send(json.dumps({"action": "ping"})) ping_task = asyncio.create_task(keepalive()) ``` ```javascript Node.js theme={null} setInterval(() => ws.send(JSON.stringify({ action: "ping" })), 30000); ``` If you're running on bare metal or a VM with no reverse proxy in front of your client, this doesn't affect you. Standard WS Ping/Pong still works as before. See [WebSocket Overview](/websocket/overview#application-level-keepalive) for full connection examples. *** ### WebSocket — Reconnect Gap-Fill with `since` Subscribe messages now accept an optional `since` filter (UNIX milliseconds). When set, the initial snapshot returns all events after that timestamp instead of just the most recent N, letting you fill gaps after a disconnect or cold start without missing data. ```json theme={null} { "action": "subscribe", "type": "settlements", "filters": { "since": 1774412600000 } } ``` The response is the same `snapshot` message you already handle. Existing clients are unaffected — `since` is fully optional. **Lookback windows by tier:** | Tier | Max lookback | | ---------- | ------------ | | Free | 30 seconds | | Starter | 2 minutes | | Growth | 5 minutes | | Enterprise | 5 minutes | If `since` is older than your tier's window, it's automatically clamped. Within the window, there is no event count limit — you get everything that matches your filters. *** ## 2026-03-24 ### API — Onchain Redemption State on Wallet Positions The `/v1/wallets/{addr}/positions` and `/v1/wallets/positions` endpoints now include onchain redemption data for resolved markets. Three new fields are returned on positions where `redeemable` is `true`: * **`redeemed`** — `true` if the wallet has burned their tokens onchain (claimed USDC payout), `false` if tokens are still held. * **`unredeemed_balance`** — Number of tokens still held onchain. Tells you exactly how much hasn't been claimed. * **`unredeemed_usd`** — USD value of unclaimed tokens (\$1 per token on winning positions). This data is not available from Polymarket's API. polynode checks the Conditional Token Framework contract onchain in a single batched call, adding \~130ms only when redeemable positions are present. Active (unresolved) positions are unaffected. ```bash theme={null} curl "https://api.polynode.dev/v1/wallets/0x62d2.../positions" \ -H "x-api-key: pn_live_..." ``` ```json theme={null} { "title": "Lana Del Rey divorce in 2025?", "redeemable": true, "redeemed": false, "unredeemed_balance": 418.73, "unredeemed_usd": 418.73, "size": 418.73 } ``` *** ## 2026-03-23 ### API — Event Search & Token IDs Two changes to event endpoints: * **New:** `GET /v1/events/search?q=...&limit=N` — search events by text query. Returns events with all sub-markets, each including `tokenId` (YES token for CLOB price history via `/v1/candles`) and current `price`. Multi-outcome events like "How many Fed rate cuts in 2026?" return as a single result with all outcomes. * **Updated:** `GET /v1/event/{slug}` — now includes `tokenId` on every market in the response. Previously, token IDs were not available on this endpoint. ```bash theme={null} curl "https://api.polynode.dev/v1/events/search?q=recession&limit=5" \ -H "x-api-key: pn_live_..." ``` Rate limit: 1 request per second per API key (shared with other enriched data endpoints). ### TypeScript SDK v0.4.6 * **New:** `searchEvents(query, { limit })` — typed method for event search, returns `EventSearchResponse` * **New types:** `EventSearchResponse`, `EventSearchResult`, `EventSearchMarket` * **Updated:** `EventMarket` now includes optional `tokenId` field ```bash theme={null} npm install polynode-sdk@0.4.6 ``` ```typescript theme={null} const results = await pn.searchEvents('Fed rate', { limit: 5 }); for (const event of results.events) { console.log(event.title, `(${event.markets.length} outcomes)`); for (const m of event.markets) { // Use tokenId to fetch price history const candles = await pn.candles(m.tokenId, { resolution: '1h' }); } } ``` *** ## 2026-03-21 ### TypeScript SDK v0.4.4 — Enriched Data Methods Eight new typed methods on the `PolyNode` client for all enriched data endpoints: * `leaderboard()` — top 20 traders by profit or volume, with period filtering * `trending()` — carousel, breaking markets, hot topics, featured events, and biggest movers * `activity()` — platform-wide trade feed (50 most recent) * `movers()` — markets with largest 24h price swings * `traderProfile(wallet)` — full trader stats: PnL, volume, trades, largest win * `traderPnl(wallet, { period })` — cumulative PnL time series * `event(slug)` — full event detail with all sub-markets * `marketsByCategory(category)` — browse markets by category All methods are fully typed with dedicated response interfaces. Install: ```bash theme={null} npm install polynode-sdk@0.4.4 ``` *** ### TypeScript SDK v0.4.3 — Cache UI Primitives Four new features for building dashboards on top of the local cache. * **View methods** — `watchlistSummary()`, `walletDashboard()`, `leaderboard()`, `marketOverview()`. Pre-shaped data for common dashboard patterns. No SQL, no aggregation. * **Reactive subscriptions** — `onChange()` and `onWalletChange()` fire callbacks when new trades or settlements land from the live WebSocket stream. Returns an `unsub()` function for cleanup. * **Export helpers** — `exportCSV()`, `exportJSON()`, `exportRows()` dump filtered data for charting libraries, spreadsheets, or custom analysis. * **Query builder** — Chainable fluent API: `.wallet()`, `.side()`, `.since()`, `.market()`, `.minPnl()`, `.limit()`, `.run()`. Complex filters without writing SQL. ```bash theme={null} npm install polynode-sdk@0.4.3 ``` Full documentation: [Local Cache](/sdks/local-cache) *** ## 2026-03-21 ### API — Enriched Data Endpoints Eight new REST endpoints for trader analytics, market discovery, and platform trends. * `GET /v1/leaderboard` — Top 20 traders ranked by profit or volume (daily, weekly, monthly, all-time) * `GET /v1/trader/{wallet}` — Full trader profile: PnL, volume, trade count, largest win, portfolio value * `GET /v1/trader/{wallet}/pnl` — PnL time series at 4 resolutions (1D, 1W, 1M, ALL) * `GET /v1/trending` — Carousel highlights, breaking markets, hot topics, featured events, biggest movers * `GET /v1/activity` — Platform-wide live trade feed (last 50 trades with tx hashes) * `GET /v1/event/{slug}` — Full event data with all sub-markets, outcome prices, condition IDs * `GET /v1/movers` — Markets with the largest 24h price changes * `GET /v2/markets/{category}` — Category market listings with counts (crypto, politics, sports, etc.) Rate limit: 1 request per second per API key. Responses cached 1-3 minutes. ### TypeScript SDK v0.4.1 * **Fix:** ESM imports now work correctly. v0.4.0 crashed on `cache.start()` when using `import` syntax. * **Fix:** Eliminated `MODULE_TYPELESS_PACKAGE_JSON` Node.js warning. * **New:** `getActiveTestWallet()` / `getActiveTestWallets()` — returns known-active wallet addresses for testing and examples. ```bash theme={null} npm install polynode-sdk@0.4.1 ``` ### API — Wallet positions fix * Fixed `firstTradeAt` / `lastTradeAt` returning `null` for certain wallets on `GET /v1/markets/{id}/positions?includeTrades=true`. * Added fallback query path for wallets where the primary trade lookup returns empty. ### API — Per-endpoint rate limiting * `includeTrades=true` on market positions now has its own 20 req/min rate limit per key. * Separate bucket from standard rate limit — doesn't consume your normal quota. *** ## 2026-03-21 ### TypeScript SDK v0.4.0 * **New:** Local Cache — SQLite-backed local storage for instant offline queries. * Backfill wallet history in seconds (1 request per wallet, up to 500 trades). * Live WebSocket stream keeps the cache up to date automatically. * Query trades, positions, and settlements locally with zero API calls. * Watchlist file with hot-reload and runtime add/remove API. * Full documentation at [Local Cache](/sdks/local-cache). ### Rust SDK v0.4.0 * **New:** Local Cache ported from TypeScript. Same architecture, same SQL schema, same query methods. * Builder pattern: `PolyNodeCache::builder(client).db_path(...).build()?` * `rusqlite` (bundled) + `notify` as optional dependencies behind `cache` feature flag. *** ## 2026-03-20 ### Web Frontend * Speed comparison test redesigned with 3-second warm-up period, win % scoreboard, and visual win ratio bar. * Updated "Run this test yourself" code snippet to match new logic. *** ## 2026-03-19 ### TypeScript SDK v0.3.0 * **New:** `OrderbookEngine` — higher-level orderbook client with shared state and filtered views per component. * **New:** `LocalOrderbook` — local orderbook state management. * **New:** `ShortFormStream` — condensed event stream for bandwidth-constrained environments. ### API * Orderbook REST endpoints: `/v1/orderbook/{token}`, `/v1/midpoint/{token}`, `/v1/spread/{token}`. * Wallet trades `limit` parameter now allows up to 1,000 (was 500). *** ## 2026-03-15 ### API v1 — Initial Public Release * **WebSocket streaming:** settlements, trades, prices, blocks, wallets, markets, large trades, oracle, chainlink * **REST API:** markets, search, candles, stats, settlements, wallets * **Orderbook streaming** via `ob.polynode.dev` * **RPC endpoint** via `rpc.polynode.dev` — JSON-RPC with TX #1 delivery * **CLI** (`pn`) — stream events, query markets, manage API keys from the terminal # API Reference Source: https://docs.polynode.dev/charts/api-reference Complete API documentation for Chart, Series, TimeScale, PriceScale, and Orderbook # API Reference ## createChart ```typescript theme={null} import { createChart } from 'polynode-charts' const chart = createChart(container, options?) ``` | Parameter | Type | Description | | ----------- | ----------------------- | --------------------------- | | `container` | `HTMLElement \| string` | DOM element or CSS selector | | `options` | `ChartOptions` | Optional configuration | ### ChartOptions ```typescript theme={null} interface ChartOptions { width?: number // fixed width (px) height?: number // fixed height (px) autoSize?: boolean // auto-resize to container (default: true) layout?: LayoutOptions grid?: GridOptions crosshair?: CrosshairOptions timeScale?: TimeScaleOptions rightPriceScale?: PriceScaleOptions } ``` ### LayoutOptions ```typescript theme={null} interface LayoutOptions { background?: string // default: '#080d16' textColor?: string // default: '#556' fontFamily?: string // default: '"SF Mono", "Fira Code", monospace' fontSize?: number // default: 10 } ``` ### GridOptions ```typescript theme={null} interface GridOptions { vertLines?: { color?: string; visible?: boolean } horzLines?: { color?: string; visible?: boolean } } ``` ### PriceScaleOptions ```typescript theme={null} interface PriceScaleOptions { mode?: 'normal' | 'probability' // 'probability' clamps axis to 0-100% autoScale?: boolean scaleMargins?: { top?: number; bottom?: number } } ``` *** ## Chart ### Series Methods ```typescript theme={null} chart.addCandleSeries(opts?: CandleSeriesOptions): CandleSeries chart.addLineSeries(opts?: LineSeriesOptions): LineSeries chart.addAreaSeries(opts?: AreaSeriesOptions): AreaSeries chart.addVolumeSeries(opts?: VolumeSeriesOptions): VolumeSeries chart.removeSeries(series): void ``` ### Scale Access ```typescript theme={null} chart.timeScale(): TimeScale ``` ### Events ```typescript theme={null} // Subscribe to crosshair movement — returns unsubscribe function const unsub = chart.subscribeCrosshairMove((params) => { console.log(params.time, params.point) }) unsub() // stop listening // Subscribe to chart clicks const unsub2 = chart.subscribeClick((params) => { console.log('Clicked at time:', params.time) }) ``` **CrosshairMoveParams:** ```typescript theme={null} interface CrosshairMoveParams { time: number | null point: { x: number; y: number } | null seriesData: Map } ``` **ClickParams:** ```typescript theme={null} interface ClickParams { time: number | null point: { x: number; y: number } } ``` ### Utility ```typescript theme={null} chart.getContainer(): HTMLElement // returns the chart's parent DOM element ``` ### Lifecycle ```typescript theme={null} chart.resize(width, height): void // manual resize chart.remove(): void // cleanup everything ``` *** ## Series All series share these common methods: ```typescript theme={null} series.setData(data[]): void // replace all data series.update(item): void // append or update last point series.setMaxLength(n): void // cap buffer size (drops oldest) ``` ### CandleSeries ```typescript theme={null} const series = chart.addCandleSeries({ upColor: '#22c55e', downColor: '#ef4444', wickUpColor: '#22c55e', // optional, defaults to upColor wickDownColor: '#ef4444', // optional, defaults to downColor borderVisible: true, }) ``` **Data shape:** ```typescript theme={null} interface OhlcData { time: number // epoch milliseconds open: number high: number low: number close: number volume?: number } ``` ### LineSeries ```typescript theme={null} const series = chart.addLineSeries({ color: '#f7931a', lineWidth: 2, smooth: true, // bezier curve interpolation showDot: true, // pulsing dot at last point dotColor: '#f7931a', }) ``` **Data shape:** ```typescript theme={null} interface LineData { time: number // epoch milliseconds value: number } ``` **Live mode:** ```typescript theme={null} series.setLive(true) // phantom point + smooth lerp series.isLive() // check live state ``` **Price line overlay:** ```typescript theme={null} series.setPriceLine(72822.50, { color: '#3b82f6', dash: [6, 4], label: 'PRICE TO BEAT', labelColor: '#fff', }) series.clearPriceLine() series.getPriceLine() // returns PriceLineConfig | null ``` The price line renders as a dashed horizontal line across the chart with a label on the left and a price badge on the right axis. The Y-axis auto-expands to keep the price line visible. ### AreaSeries Extends `LineSeries` with a gradient fill beneath the line. ```typescript theme={null} const series = chart.addAreaSeries({ color: '#6ea8fe', // line color topColor: '#6ea8fe33', // gradient top bottomColor: '#6ea8fe00', // gradient bottom (transparent) lineWidth: 2, smooth: true, }) ``` Uses the same `LineData` shape as LineSeries. ### VolumeSeries Automatically creates a separate pane at 22% of chart height. ```typescript theme={null} const series = chart.addVolumeSeries({ upColor: 'rgba(34, 197, 94, 0.4)', downColor: 'rgba(239, 68, 68, 0.4)', opacity: 0.4, }) ``` **Data shape:** ```typescript theme={null} interface VolumeData { time: number value: number color?: string // override per-bar color } ``` *** ## TimeScale ```typescript theme={null} const ts = chart.timeScale() ``` | Method | Description | | ----------------------------- | ------------------------------------------------------------------------ | | `goLive()` | Enable live mode. Auto-scrolls to newest data. Snaps back after 4s idle. | | `isLive()` | Returns `true` if in live mode | | `setVisibleRange(start, end)` | Set visible time range (epoch ms) | | `animateToRange(start, end)` | Animated zoom to range | | `zoomAt(factor, mouseXFrac)` | Zoom around a point (scroll wheel) | | `getVisibleStart()` | Left edge time (epoch ms) | | `getVisibleEnd()` | Right edge time (epoch ms) | | `getVisibleWindow()` | Duration of visible range (ms) | **Live mode behavior:** When live, the chart auto-scrolls to show the latest data. If the user pans or zooms away, the chart remembers and snaps back to live after 4 seconds of idle. This gives users freedom to explore history while keeping the default view current. *** ## Orderbook ```typescript theme={null} import { createOrderbook } from 'polynode-charts' const ob = createOrderbook('#orderbook', { colorBid: '#22c55e', colorAsk: '#ef4444', depthFillOpacity: 0.08, labelCount: 8, }) ob.update({ bids: [{ price: 0.54, size: 1200 }, { price: 0.53, size: 800 }], asks: [{ price: 0.55, size: 900 }, { price: 0.56, size: 1500 }], }) ``` ### OrderbookOptions ```typescript theme={null} interface OrderbookOptions { colorBid?: string // default: '#22c55e' colorAsk?: string // default: '#ef4444' depthFillOpacity?: number // default: 0.08 labelCount?: number // price labels on axis background?: string textColor?: string } ``` ### BookData ```typescript theme={null} interface BookData { bids: PriceLevel[] asks: PriceLevel[] } interface PriceLevel { price: number size: number } ``` | Method | Description | | ----------------- | ------------------------------- | | `ob.update(data)` | Replace book data and re-render | | `ob.destroy()` | Remove DOM and stop rendering | *** ## createShortFormOverlay One-liner to add Polymarket short-form price-to-beat overlays to any live chart. Adds interval buttons, auto-discovers markets, draws a dashed price-to-beat line, and shows live odds with a countdown timer. ```typescript theme={null} import { createChart, createShortFormOverlay } from 'polynode-charts' const chart = createChart('#btc', { layout: { background: '#0c1220' } }) const series = chart.addLineSeries({ color: '#f7931a', showDot: true }) series.setLive(true) chart.timeScale().goLive() // One line: buttons + discovery + price line + odds + countdown const overlay = createShortFormOverlay(chart, series, { coin: 'btc' }) ``` ### ShortFormOverlayOptions ```typescript theme={null} interface ShortFormOverlayOptions { coin: ShortFormCoin // 'btc' | 'eth' | 'sol' | 'xrp' | 'doge' | 'hype' intervals?: ShortFormInterval[] // default: ['5m', '15m', '1h'] defaultInterval?: ShortFormInterval // if set, auto-starts on creation onRotation?: (event: RotationEvent) => void // callback on each poll/rotation } ``` ### ShortFormOverlay (return type) | Method | Description | | ----------------------------- | ---------------------------------------------- | | `overlay.setInterval('15m')` | Switch to a different interval | | `overlay.stop()` | Stop polling, remove price line, clear display | | `overlay.destroy()` | Full cleanup: stop + remove DOM | | `overlay.getActiveInterval()` | Returns current interval or `null` | ### What it renders * **Interval buttons** (5m, 15m, 1h) — click to toggle on/off * **Price-to-beat line** — horizontal dashed blue line on the chart * **Odds display** — "24% up · 77% down · 1m35s" with live countdown * **Auto-rotation** — discovers the next market window when the current one expires *** ## PolynodeProvider — MarketInfo The `outcome` field tells you which side of a binary market this token represents. ```typescript theme={null} interface MarketInfo { token_id: string question: string // the market question (e.g. "US x Iran ceasefire by April 7?") slug: string image: string last_price: number volume_24h: number outcomes: string[] // e.g. ['Yes', 'No'] outcome: string // which outcome THIS token is ('Yes' or 'No') condition_id?: string neg_risk?: boolean // true for multi-outcome events (elections, championships) } ``` The `neg_risk` field indicates a multi-outcome event on Polymarket. When `provider.event()` searches for multi-outcome markets, it prioritizes `neg_risk: true` markets to find genuine events (elections, World Cup, NBA Finals) rather than binary matches. # Data Providers Source: https://docs.polynode.dev/charts/data-providers Built-in providers for polynode REST API, orderbook streaming, and short-form market discovery # Data Providers The Charts SDK includes three data providers that connect to polynode endpoints. All are optional. The chart itself works with any data source. ## PolynodeProvider Full-featured REST provider for market data, candles, search, events, and multi-outcome charts. ```typescript theme={null} import { PolynodeProvider } from 'polynode-charts' const provider = new PolynodeProvider({ apiKey: 'pn_live_...', baseUrl: 'https://api.polynode.dev', // default }) ``` ### Top Markets ```typescript theme={null} const markets = await provider.markets(10) // Returns MarketInfo[] sorted by 24h volume ``` **Response:** ```json theme={null} [ { "token_id": "82855088893985825781350...", "question": "US x Iran ceasefire by April 7?", "slug": "us-x-iran-ceasefire-by-april-7", "image": "https://polymarket-upload.s3...", "last_price": 0.999, "volume_24h": 19542289.32, "outcomes": ["Yes", "No"], "condition_id": "0x4c5701bc..." } ] ``` ### Search Markets ```typescript theme={null} const results = await provider.search('bitcoin', 5) // Returns MarketInfo[] matching query, sorted by volume ``` ### Fetch Candles ```typescript theme={null} const candles = await provider.candles(tokenId, '1h') // Returns OhlcData[] — ready for series.setData() const series = chart.addCandleSeries() series.setData(candles) ``` The first call fetches trades and caches them locally. After that, switching resolution is instant — the SDK rebuilds candles client-side from the cached trades. **Supported resolutions:** `'1m'`, `'5m'`, `'15m'`, `'1h'`, `'4h'`, `'1d'` **Options:** ```typescript theme={null} // Fetch more history (default: 30 days) const candles = await provider.candles(tokenId, '1h', { days: 90 }) ``` **Response shape (`OhlcData[]`):** ```json theme={null} [ { "time": 1775826000000, "open": 0.54, "high": 0.55, "low": 0.52, "close": 0.54, "volume": 1200 } ] ``` ### Price History (Line/Area Charts) For simple `{time, value}` points (faster than candles — one HTTP call, no pagination): ```typescript theme={null} const history = await provider.priceHistory(tokenId, '7d') // Returns LineData[] const series = chart.addAreaSeries({ color: '#22c55e' }) series.setData(history) ``` **Supported ranges:** `'1h'`, `'6h'`, `'1d'`, `'7d'`, `'30d'`, `'all'` `candles()` fetches trades and builds OHLCV locally. Best for candlestick charts where you need resolution switching. `priceHistory()` hits the CLOB prices-history endpoint directly. Best for line/area charts where you just need price points over time. ### Get Event (Multi-Outcome) ```typescript theme={null} const event = await provider.event('presidential election') ``` Returns `EventInfo | null`. The method searches your loaded markets, groups by shared question pattern, and returns all outcomes with prices and colors. **Response:** ```json theme={null} { "question": "the 2028 US Presidential Election", "slug": "presidential-election-winner-2028", "condition_id": "0x...", "image": "https://...", "volume_24h": 2847291, "outcomes": [ { "name": "Gavin Newsom", "token_id": "98250...", "price": 0.159, "color": "#4378FF" }, { "name": "Kamala Harris", "token_id": "70663...", "price": 0.032, "color": "#22c55e" }, { "name": "Ron DeSantis", "token_id": "10448...", "price": 0.021, "color": "#FDC503" } ] } ``` Colors are assigned automatically from `OUTCOME_COLORS` (a 20-color palette). ### Multi-Outcome Price History (Batch) Fetch price history for all outcomes in parallel: ```typescript theme={null} const event = await provider.event('presidential election') const priceMap = await provider.outcomePrices(event.outcomes, '7d', { maxOutcomes: 5 }) for (const outcome of event.outcomes) { const data = priceMap.get(outcome.token_id) if (!data) continue const series = chart.addLineSeries({ color: outcome.color }) series.setData(data) } ``` `outcomePrices()` is much faster than fetching candles per outcome — one HTTP call each, no trade pagination. For candlestick data per outcome, use `outcomeCandles()` instead: ```typescript theme={null} const candleMap = await provider.outcomeCandles(event.outcomes, '4h', { days: 30, maxOutcomes: 5 }) ``` ### Orderbook Snapshot ```typescript theme={null} const book = await provider.book(tokenId) // Returns { bids: PriceLevel[], asks: PriceLevel[] } ``` ### Poll for Live Updates ```typescript theme={null} const stop = provider.pollPrices(tokenId, (point) => { series.update(point) // { time, value } }, { intervalMs: 5000 }) // Stop polling stop() ``` For multi-outcome polling: ```typescript theme={null} const stop = provider.pollOutcomePrices(event.outcomes, (tokenId, point) => { seriesMap.get(tokenId)?.update(point) }, { intervalMs: 10000, maxOutcomes: 5 }) ``` ### Market Resolution All methods accept flexible market identifiers: * **token\_id** — direct lookup (fastest) * **slug** — matched against loaded markets (e.g. `'bitcoin-above-64k'`) * **condition\_id** — hex string starting with `0x` ### Clear Cache ```typescript theme={null} provider.clearCache() // clears trade cache for all markets ``` ### MarketInfo ```typescript theme={null} interface MarketInfo { token_id: string question: string slug: string image: string last_price: number // 0-1 volume_24h: number // USD outcomes: string[] // ["Yes", "No"] or ["Up", "Down"] condition_id?: string } ``` *** ## PolynodeOBProvider WebSocket-based orderbook streaming with automatic zlib decompression (uses the native `DecompressionStream` API, no dependencies). ```typescript theme={null} import { PolynodeOBProvider } from 'polynode-charts' const ob = new PolynodeOBProvider({ wsUrl: 'wss://ob.polynode.dev/ws', // default }) ob.subscribe(tokenId, (book) => { orderbook.update(book) // pass to Orderbook renderer }) ``` ### Methods | Method | Description | | ------------------------------ | --------------------------------------------------- | | `subscribe(tokenId, callback)` | Subscribe to live book updates | | `unsubscribe(tokenId)` | Stop receiving updates for a token | | `connect()` | Manually connect (auto-connects on first subscribe) | | `disconnect()` | Close WebSocket and stop reconnecting | The provider handles reconnection automatically with exponential backoff (1s to 30s). ### Message Types The provider processes three message types from the orderbook WebSocket: * `snapshot_batch` — initial full book for all subscribed tokens * `book_snapshot` — full book replacement for a single token * `book_update` — incremental update (add/remove/change levels) All are normalized to `BookData` before calling your callback. *** ## ShortFormProvider Discovers short-form crypto markets (5-minute, 15-minute, and hourly up/down markets) and provides live odds rotation. HTTP-only, no WebSocket required. ```typescript theme={null} import { ShortFormProvider } from 'polynode-charts' const sf = new ShortFormProvider() ``` ### One-Shot Discovery ```typescript theme={null} const market = await sf.discover('btc', '15m') if (market) { console.log(market.priceToBeat) // 72822.50 console.log(market.upOdds) // 0.62 console.log(market.downOdds) // 0.38 console.log(market.windowEnd) // unix timestamp } ``` ### Auto-Rotation with Live Odds `startRotation()` discovers the current window, emits immediately, then polls for updated odds every second and re-discovers at each window boundary. ```typescript theme={null} const stop = sf.startRotation('btc', '15m', (event) => { // Called on discovery + every odds update series.setPriceLine(event.market.priceToBeat!, { color: '#3b82f6', label: 'PRICE TO BEAT', }) console.log(`${(event.market.upOdds * 100).toFixed(0)}% up`) console.log(`${event.timeRemaining}s remaining`) }, { pollMs: 1000 }) // Stop rotation and polling stop() ``` ### Supported Coins | Key | Coin | | -------- | -------- | | `'btc'` | Bitcoin | | `'eth'` | Ethereum | | `'sol'` | Solana | | `'xrp'` | XRP | | `'doge'` | Dogecoin | | `'hype'` | Hype | | `'bnb'` | BNB | ### Supported Intervals | Key | Window | | ------- | ---------- | | `'5m'` | 5 minutes | | `'15m'` | 15 minutes | | `'1h'` | 1 hour | ### ShortFormMarket ```typescript theme={null} interface ShortFormMarket { coin: ShortFormCoin slug: string title: string conditionId: string windowStart: number // unix seconds windowEnd: number // unix seconds outcomes: string[] outcomePrices: number[] clobTokenIds: string[] upOdds: number // 0-1 downOdds: number // 0-1 liquidity: number volume24h: number priceToBeat: number | null } ``` ### RotationEvent ```typescript theme={null} interface RotationEvent { interval: ShortFormInterval market: ShortFormMarket windowStart: number // unix seconds windowEnd: number // unix seconds timeRemaining: number // seconds until window closes } ``` ### Options ```typescript theme={null} const sf = new ShortFormProvider({ apiBaseUrl: 'https://api.polynode.dev', // default rotationBuffer: 3, // seconds after window end before re-discovering (default: 3) }) ``` # Examples Source: https://docs.polynode.dev/charts/examples Copy-paste examples for common chart setups # Examples ## Prediction Market — Yes/No Outcome ```typescript theme={null} import { createChart, PolynodeProvider } from 'polynode-charts' const provider = new PolynodeProvider({ apiKey: 'pn_...' }) const chart = createChart('#chart', { rightPriceScale: { mode: 'probability' }, }) const series = chart.addAreaSeries({ color: '#22c55e', topColor: '#22c55e22', bottomColor: '#22c55e00', smooth: true, }) const history = await provider.priceHistory(tokenId, '7d') series.setData(history) ``` The `probability` price scale mode clamps the Y-axis to 0-100% with appropriate tick marks. ## Multi-Outcome Event (e.g., Election) ```typescript theme={null} import { createChart, PolynodeProvider, OUTCOME_COLORS } from 'polynode-charts' const provider = new PolynodeProvider({ apiKey: 'pn_...' }) const event = await provider.event('presidential election') const chart = createChart('#chart', { rightPriceScale: { mode: 'probability' }, }) // Fetch all outcome price histories in one call const priceMap = await provider.outcomePrices(event.outcomes, '7d', { maxOutcomes: 8 }) for (const outcome of event.outcomes) { const data = priceMap.get(outcome.token_id) if (!data || data.length === 0) continue const series = chart.addLineSeries({ color: outcome.color, lineWidth: 2, smooth: true, }) series.setData(data) } ``` `OUTCOME_COLORS` provides a 20-color palette optimized for dark backgrounds. Colors are auto-assigned by `provider.event()`. ## Candlestick + Volume ```typescript theme={null} import { createChart, PolynodeProvider } from 'polynode-charts' const provider = new PolynodeProvider({ apiKey: 'pn_...' }) const chart = createChart('#chart') const candles = chart.addCandleSeries({ upColor: '#22c55e', downColor: '#ef4444', }) const volume = chart.addVolumeSeries({ upColor: 'rgba(34, 197, 94, 0.3)', downColor: 'rgba(239, 68, 68, 0.3)', }) const data = await provider.candles(tokenId, '1h') candles.setData(data) // Volume is included in OhlcData when built from trades volume.setData(data.map(d => ({ time: d.time, value: d.volume || 0, color: d.close >= d.open ? '#22c55e66' : '#ef444466', }))) ``` ### Instant Resolution Switching After the first `candles()` call, trades are cached locally. Switching resolution rebuilds candles client-side with zero network calls: ```typescript theme={null} // First call fetches trades (~2s) await provider.candles(tokenId, '1h') // These are instant — trades are already cached await provider.candles(tokenId, '5m') await provider.candles(tokenId, '1d') ``` ## Live Crypto with Short-Form Overlay ```typescript theme={null} import { createChart, ShortFormProvider } from 'polynode-charts' const chart = createChart('#chart', { layout: { background: '#0c1220' }, }) const series = chart.addLineSeries({ color: '#f7931a', lineWidth: 2, smooth: true, showDot: true, dotColor: '#f7931a', }) series.setLive(true) series.setMaxLength(600) chart.timeScale().goLive() // WebSocket price feed const ws = new WebSocket('wss://ws.polynode.dev/ws?key=pn_...') ws.onopen = () => ws.send(JSON.stringify({ action: 'subscribe', type: 'chainlink' })) ws.onmessage = (e) => { const msg = JSON.parse(e.data) if (msg.type === 'price_feed' && msg.feed === 'BTC/USD') { series.update({ time: Date.now(), value: msg.data.price }) } } // Short-form price-to-beat overlay const sf = new ShortFormProvider() let stopRotation = null function setInterval(interval) { if (stopRotation) stopRotation() stopRotation = sf.startRotation('btc', interval, (event) => { const m = event.market if (m.priceToBeat !== null) { series.setPriceLine(m.priceToBeat, { color: '#3b82f6', label: 'PRICE TO BEAT', }) } updateUI({ upPct: (m.upOdds * 100).toFixed(0), downPct: (m.downOdds * 100).toFixed(0), timeRemaining: event.timeRemaining, }) }, { pollMs: 1000 }) } // Start with 15m interval setInterval('15m') ``` ## Interactive Crosshair ```typescript theme={null} const chart = createChart('#chart') const series = chart.addCandleSeries() series.setData(data) chart.subscribeCrosshairMove((params) => { if (!params.time) { tooltip.style.display = 'none' return } tooltip.style.display = 'block' tooltip.style.left = params.point.x + 'px' tooltip.style.top = params.point.y + 'px' tooltip.textContent = `Time: ${new Date(params.time).toLocaleString()}` }) ``` ## Orderbook with Depth Chart ```typescript theme={null} import { createOrderbook, PolynodeOBProvider } from 'polynode-charts' const ob = createOrderbook('#orderbook', { colorBid: '#22c55e', colorAsk: '#ef4444', depthFillOpacity: 0.08, labelCount: 8, }) const provider = new PolynodeOBProvider() provider.subscribe(tokenId, (book) => { ob.update(book) // Calculate spread const bestBid = book.bids[0]?.price || 0 const bestAsk = book.asks[0]?.price || 0 const spread = bestAsk - bestBid document.getElementById('spread').textContent = `Spread: ${(spread * 100).toFixed(1)}c` }) ``` ## Poll Multiple Outcomes ```typescript theme={null} import { createChart, PolynodeProvider } from 'polynode-charts' const provider = new PolynodeProvider({ apiKey: 'pn_...' }) const event = await provider.event('presidential election') const chart = createChart('#chart', { rightPriceScale: { mode: 'probability' }, }) // Load initial history const priceMap = await provider.outcomePrices(event.outcomes, '7d') const seriesMap = new Map() for (const outcome of event.outcomes) { const data = priceMap.get(outcome.token_id) if (!data) continue const series = chart.addLineSeries({ color: outcome.color, smooth: true }) series.setData(data) seriesMap.set(outcome.token_id, series) } // Poll for live updates const stop = provider.pollOutcomePrices(event.outcomes, (tokenId, point) => { seriesMap.get(tokenId)?.update(point) }, { intervalMs: 10000 }) ``` ## Responsive Chart The chart auto-sizes to its container by default. Just give the container a CSS height: ```html theme={null}
``` ```typescript theme={null} const chart = createChart('#chart') // Chart resizes automatically on window/container resize ``` For mobile, the chart handles touch events natively: single-finger drag to pan, pinch to zoom. No configuration needed. # Getting Started Source: https://docs.polynode.dev/charts/getting-started Create your first chart in under 5 minutes # Getting Started ## Installation ```bash theme={null} npm install polynode-charts ``` The package has **zero runtime dependencies**. It ships as ESM and CJS with full TypeScript declarations. ## Create a Chart Every chart needs a container element with a defined height. ```html theme={null}
``` ```typescript theme={null} import { createChart } from 'polynode-charts' const chart = createChart('#chart') ``` You can also pass an `HTMLElement` directly: ```typescript theme={null} const el = document.getElementById('chart') const chart = createChart(el) ``` ## Add a Series ```typescript theme={null} // Candlestick chart const candles = chart.addCandleSeries({ upColor: '#22c55e', downColor: '#ef4444', }) candles.setData([ { time: 1710000000000, open: 0.52, high: 0.55, low: 0.50, close: 0.54 }, { time: 1710003600000, open: 0.54, high: 0.58, low: 0.53, close: 0.57 }, ]) ``` ```typescript theme={null} // Line chart const line = chart.addLineSeries({ color: '#f7931a', lineWidth: 2, smooth: true, }) line.setData([ { time: 1710000000000, value: 72500 }, { time: 1710000001000, value: 72510 }, ]) ``` ## Chart Options ```typescript theme={null} const chart = createChart('#chart', { layout: { background: '#0a0e17', textColor: '#556', fontFamily: '"SF Mono", monospace', fontSize: 10, }, grid: { horzLines: { color: 'rgba(100, 120, 150, 0.06)' }, vertLines: { visible: false }, }, rightPriceScale: { mode: 'probability', // clamps axis to 0-100% }, }) ``` ## Live Streaming For real-time data, use `update()` instead of `setData()`: ```typescript theme={null} const series = chart.addLineSeries({ color: '#14f195', showDot: true, dotColor: '#14f195', }) // Enable live mode — smooth phantom point between ticks series.setLive(true) // Auto-scroll to latest data chart.timeScale().goLive() // Push updates as they arrive ws.onmessage = (e) => { const { price } = JSON.parse(e.data) series.update({ time: Date.now(), value: price }) } ``` In live mode, the chart adds a phantom leading point that lerps smoothly to the latest value, giving fluid motion even with infrequent ticks. A pulsing dot marks the current price. ## Max Data Length For streaming use cases, cap the data buffer to prevent memory growth: ```typescript theme={null} series.setMaxLength(600) // keep last 600 points (~10 min at 1/sec) ``` Older points are dropped from the front as new ones arrive. ## Volume Pane Adding a volume series automatically creates a second pane at 22% height: ```typescript theme={null} const volume = chart.addVolumeSeries({ upColor: 'rgba(34, 197, 94, 0.4)', downColor: 'rgba(239, 68, 68, 0.4)', }) volume.setData([ { time: 1710000000000, value: 15000, color: '#22c55e' }, { time: 1710003600000, value: 23000, color: '#ef4444' }, ]) ``` ## Cleanup ```typescript theme={null} chart.remove() // stops animation, detaches listeners, removes DOM ``` ## Next Steps * [API Reference](/charts/api-reference) — full Chart, Series, and Scale APIs * [Data Providers](/charts/data-providers) — connect to polynode REST, WebSocket, and short-form endpoints * [Live Streaming](/charts/live-streaming) — real-time crypto prices with price-to-beat overlays # Live Streaming Source: https://docs.polynode.dev/charts/live-streaming Real-time crypto price charts with price-to-beat overlays and live odds # Live Streaming This guide covers building a real-time crypto price chart with WebSocket streaming, live mode, and short-form market overlays. ## Basic Live Chart Connect a WebSocket feed to a line series with live mode enabled: ```typescript theme={null} import { createChart } from 'polynode-charts' const chart = createChart('#chart', { layout: { background: '#0c1220', textColor: '#556' }, grid: { horzLines: { color: 'rgba(100, 120, 150, 0.06)' }, vertLines: { visible: false }, }, }) const series = chart.addLineSeries({ color: '#f7931a', lineWidth: 2, smooth: true, showDot: true, dotColor: '#f7931a', }) // Live mode: smooth phantom point + auto-scroll series.setLive(true) series.setMaxLength(600) // ~10 min at 1/sec chart.timeScale().goLive() // Connect to price feed const ws = new WebSocket('wss://ws.polynode.dev/ws?key=pn_...') ws.onopen = () => { ws.send(JSON.stringify({ action: 'subscribe', type: 'chainlink' })) } ws.onmessage = (e) => { const msg = JSON.parse(e.data) if (msg.type === 'price_feed' && msg.feed === 'BTC/USD') { series.update({ time: Date.now(), value: msg.data.price }) } } ``` ## How Live Mode Works When `setLive(true)` is called: 1. **Phantom point** — a virtual leading data point is appended at `Date.now()` that smoothly lerps toward the latest real value. This creates fluid animation even if ticks arrive only once per second. 2. **Pulsing dot** — a glowing dot marks the current price at the phantom point, pulsing with a subtle sine-wave animation. 3. **Auto-scroll** — `goLive()` on the time scale keeps the visible range pinned to the latest data. If the user pans away, the chart returns to live after 4 seconds of inactivity. 4. **Continuous redraw** — the animation frame loop redraws the data layer every frame (for lerp + pulse), but background and overlay layers only redraw when dirty. ## Price-to-Beat Overlay Overlay a horizontal dashed line showing the opening price for a short-form market window: ```typescript theme={null} import { ShortFormProvider } from 'polynode-charts' const sf = new ShortFormProvider() const stop = sf.startRotation('btc', '15m', (event) => { const m = event.market // Draw dashed price line if (m.priceToBeat !== null) { series.setPriceLine(m.priceToBeat, { color: '#3b82f6', label: 'PRICE TO BEAT', }) } // Display odds const upPct = (m.upOdds * 100).toFixed(0) const downPct = (m.downOdds * 100).toFixed(0) document.getElementById('odds').textContent = `${upPct}% up · ${downPct}% down · ${event.timeRemaining}s` }, { pollMs: 1000 }) ``` The price line automatically stays visible. The Y-axis expands to include the price-to-beat value even if it's outside the current data range. ### What Happens at Window Boundaries When a short-form window expires (e.g., the 15-minute window closes): 1. `startRotation` waits a buffer period (default 3 seconds) 2. Discovers the next window's market via the gamma proxy 3. Fetches the new price-to-beat from the crypto-price endpoint 4. Calls your `onRotation` callback with the new market 5. Resumes polling odds every `pollMs` for the new window No manual intervention needed. The rotation handles the full lifecycle. ## Multiple Coins Run independent charts for multiple coins, each with their own series and short-form rotation: ```typescript theme={null} const coins = [ { coin: 'btc', feed: 'BTC/USD', color: '#f7931a' }, { coin: 'eth', feed: 'ETH/USD', color: '#627eea' }, { coin: 'sol', feed: 'SOL/USD', color: '#14f195' }, ] for (const { coin, feed, color } of coins) { const chart = createChart(`#chart-${coin}`, { /* ... */ }) const series = chart.addLineSeries({ color, smooth: true, showDot: true, dotColor: color }) series.setLive(true) series.setMaxLength(600) chart.timeScale().goLive() // Price updates from WebSocket ws.onmessage = (e) => { const msg = JSON.parse(e.data) if (msg.type === 'price_feed' && msg.feed === feed) { series.update({ time: Date.now(), value: msg.data.price }) } } // Independent short-form rotation per coin const sf = new ShortFormProvider() sf.startRotation(coin, '15m', (event) => { if (event.market.priceToBeat !== null) { series.setPriceLine(event.market.priceToBeat, { color: '#3b82f6', label: 'PRICE TO BEAT', }) } }) } ``` ## Orderbook + Chart Side-by-Side Combine a candle chart with a live orderbook: ```typescript theme={null} import { createChart, createOrderbook, PolynodeProvider, PolynodeOBProvider } from 'polynode-charts' const provider = new PolynodeProvider({ apiKey: 'pn_...' }) // Chart const chart = createChart('#chart') const series = chart.addCandleSeries() const candles = await provider.getCandles(tokenId, '1h') series.setData(candles) // Orderbook const ob = createOrderbook('#orderbook', { colorBid: '#22c55e', colorAsk: '#ef4444', }) const obProvider = new PolynodeOBProvider() obProvider.subscribe(tokenId, (book) => ob.update(book)) ``` ## Reconnection Both the WebSocket feed and the OB provider handle reconnection automatically with exponential backoff (1s up to 30s). No special handling is needed in your code. For the `ShortFormProvider`, discovery retries 3 times with a 2-second delay between attempts before giving up on a window. The rotation timer then retries at the next buffer interval. # Charts SDK Source: https://docs.polynode.dev/charts/overview High-performance Canvas 2D charting library built for prediction markets and live crypto prices # polynode-charts A zero-dependency **browser** charting library purpose-built for prediction market data and real-time crypto price streams. Renders on Canvas 2D with a 60fps animation loop, triple-layer dirty-flag rendering, and native touch/pinch support. This is the **visualization** companion to `polynode-sdk`. The data SDK (`polynode-sdk`) handles REST API calls, WebSocket streaming, trading, and caching in Node.js. This package (`polynode-charts`) renders that data as interactive charts in the browser. They are separate npm packages. ```bash theme={null} # Data layer (Node.js / server) npm install polynode-sdk # Visualization layer (browser) npm install polynode-charts ``` `polynode-charts` also includes its own built-in data providers (REST, WebSocket, short-form discovery) so you can use it standalone without `polynode-sdk` if you only need charts. ## Features * **Candlestick, line, area, and volume** series types * **Live streaming mode** with smooth lerp animation between ticks * **Orderbook visualization** with depth chart and spread display * **Short-form market overlays** with price-to-beat lines and live odds * **Multi-outcome support** for prediction markets (up to 20+ outcomes) * **Auto-scaling** price axis with probability mode (0-100%) * **Interactive** pan, zoom, scroll, pinch-to-zoom on mobile * **Crosshair** with snap-to-candle and OHLC tooltip ## Install ```bash theme={null} npm install polynode-charts ``` ## Quick Start ```typescript theme={null} import { createChart } from 'polynode-charts' const chart = createChart('#my-chart', { layout: { background: '#0a0e17', textColor: '#556' }, }) const series = chart.addCandleSeries({ upColor: '#22c55e', downColor: '#ef4444', }) series.setData([ { time: 1710000000000, open: 0.52, high: 0.55, low: 0.50, close: 0.54 }, { time: 1710003600000, open: 0.54, high: 0.58, low: 0.53, close: 0.57 }, // ... ]) ``` ## Architecture The chart uses a triple-layer canvas stack per pane: | Layer | Z-Index | Content | | ---------- | ------- | ---------------------------------------- | | Background | 1 | Grid lines, axis labels | | Data | 2 | Series (candles, lines, areas, volumes) | | Overlay | 3 | Crosshair, tooltips, interaction capture | Each layer only redraws when its dirty flag is set, keeping GPU load minimal even at 60fps. The animation loop runs continuously for live mode (smooth phantom points, pulsing dots) but skips unchanged layers. ## Series Types | Type | Method | Data Shape | Use Case | | ------ | ------------------- | ------------ | ------------------------------------------- | | Candle | `addCandleSeries()` | `OhlcData` | Market price history | | Line | `addLineSeries()` | `LineData` | Live crypto prices, single-outcome tracking | | Area | `addAreaSeries()` | `LineData` | Probability trends with gradient fill | | Volume | `addVolumeSeries()` | `VolumeData` | Trading volume bars (auto-paned at 22%) | ## Data Providers The SDK includes three built-in data providers for polynode endpoints: * **PolynodeProvider** — REST + polling for market candles, search, events, multi-outcome charts * **PolynodeOBProvider** — WebSocket orderbook streaming with zlib decompression * **ShortFormProvider** — Short-form crypto market discovery with live odds rotation All providers are optional. The chart itself is provider-agnostic and works with any data source. ## Live Demo See the full interactive demo at [charts.polynode.dev](https://charts.polynode.dev). # CLI Source: https://docs.polynode.dev/cli/overview Bloomberg terminal for prediction markets. Real-time TUI dashboards and JSON output for agents. The PolyNode CLI (`pn`) gives you instant access to every PolyNode feature from the terminal. Watch live settlements scroll by, view full-depth orderbooks with bid/ask visualization, track short-form crypto markets with countdown timers, and monitor Chainlink price feeds. Every command also supports `--json` for programmatic use by agents and scripts. ## Install ```bash theme={null} # From source (requires Rust) cargo install --git https://github.com/joinQuantish/polynode-cli ``` The binary is called `pn`. ## Authentication The CLI resolves your API key in this order: 1. `--key` flag: `pn markets --key pn_live_...` 2. `POLYNODE_API_KEY` environment variable 3. `~/.config/polynode/key` file (auto-saved when you run `pn key create`) ```bash theme={null} # Generate a free API key (auto-saves to ~/.config/polynode/key) pn key create ``` ## Commands pn --help | Command | Type | Description | | ----------------------------- | -------- | ------------------------------ | | `pn key create [name]` | REST | Generate a new API key | | `pn status` | REST | System status and metrics | | `pn markets [--count N]` | REST | Top markets by 24h volume | | `pn search ` | REST | Full-text market search | | `pn market ` | REST | Single market detail | | `pn stream [type]` | Live TUI | Scrolling real-time event feed | | `pn orderbook ` | Live TUI | Full-screen bid/ask depth view | | `pn short-form <5m\|15m\|1h>` | Live TUI | Multi-coin crypto dashboard | | `pn chainlink` | Live TUI | Live Chainlink price feeds | Every command supports `--json` for machine-readable output. *** ## Static Commands ### Markets View the top markets by 24h trading volume. ```bash theme={null} pn markets --count 10 ``` pn markets ### Search Full-text search across all market questions. ```bash theme={null} pn search "bitcoin" ``` pn search bitcoin ### Status System health, connection count, and state metrics. ```bash theme={null} pn status ``` pn status ### Market Detail Look up a single market by slug or token ID. ```bash theme={null} pn market will-bitcoin-reach-150k-in-march-2026 ``` *** ## Live TUI Commands Live commands open a full-screen terminal UI that updates in real-time. Press `q` or `Esc` to exit. ### Stream Watch every settlement, trade, block, or oracle event as it happens. ```bash theme={null} # All settlements pn stream settlements # Large trades only (> $100) pn stream settlements --min-size 100 # Filter by wallet pn stream settlements --wallets 0xabc... # Other event types pn stream trades pn stream blocks pn stream oracle pn stream global ``` pn stream settlements Events are color-coded: yellow for pending, green for confirmed. The header shows event count and throughput rate. ### Orderbook Full-depth live orderbook with proportional bid/ask bars. Green bars show bid depth growing left, red bars show ask depth growing right. ```bash theme={null} pn orderbook btc-updown-5m-1774077000 pn orderbook will-bitcoin-reach-150k-in-march-2026 ``` pn orderbook Use arrow keys to scroll deeper into the book. The footer shows midpoint, spread, total updates, and update rate. ### Short-Form Dashboard Multi-coin dashboard for Polymarket's short-form crypto markets. Shows all 7 coins (BTC, ETH, SOL, XRP, DOGE, HYPE, BNB) with beat price, direction, odds, liquidity, volume, and a countdown timer. Auto-rotates to the next window when the current one expires. ```bash theme={null} # 5-minute windows pn short-form 5m # 15-minute, specific coins pn short-form 15m --coins btc,eth,sol # Hourly pn short-form 1h ``` pn short-form 5m ### Chainlink Price Feeds Live Chainlink data stream prices with bid/ask. ```bash theme={null} pn chainlink ``` pn chainlink *** ## JSON Mode (for Agents) Every command supports `--json` for programmatic use. Static commands output pretty-printed JSON. Streaming commands output newline-delimited JSON (NDJSON), one object per line, with no TUI. ```bash theme={null} # Static: pretty JSON pn markets --count 5 --json # Streaming: NDJSON (one event per line, no TUI) pn stream settlements --json # Pipe to jq pn stream settlements --json | jq '.taker_size' # Orderbook snapshots as JSON pn orderbook btc-updown-5m-1774077000 --json ``` This makes `pn` composable with standard Unix tools and usable as a data source for automated trading agents. *** ## Key Bindings (TUI) | Key | Action | | ------------- | --------------------------- | | `q` / `Esc` | Quit | | `Ctrl-C` | Quit | | `Up` / `Down` | Scroll orderbook depth | | `Home` | Reset scroll to top of book | # Event Format Source: https://docs.polynode.dev/crypto/event-format Price feed event schema and field reference. ## Event payload ```json theme={null} { "type": "price_feed", "feed": "ETH/USD", "timestamp": 1774672089, "data": { "feed": "ETH/USD", "price": 1989.49, "bid": 1989.49, "ask": 1989.49, "timestamp": 1774672089 } } ``` ## Fields Always `"price_feed"`. Feed name (e.g. `"BTC/USD"`, `"ETH/USD"`). Top-level convenience field, same value as `data.feed`. Observation timestamp in Unix seconds. Top-level convenience field, same value as `data.timestamp`. Feed name (e.g. `"BTC/USD"`). Mid price in USD. This is the canonical price for this asset at this timestamp. Bid price in USD. Ask price in USD. Observation timestamp in Unix seconds. ## Notes * **Update rate**: approximately 1 update per second per feed. * **No snapshot**: unlike settlement subscriptions, price feed subscriptions do not include an initial snapshot. The first event arrives with the next price observation (\~1 second after subscribing). * **Deduplication**: if you subscribe to all feeds, BTC/USD may arrive at a slightly higher rate (\~2/second) due to multiple upstream sources. Your application should deduplicate by timestamp if needed. # Available Feeds Source: https://docs.polynode.dev/crypto/feeds All crypto price feeds with live examples. ## Feed reference All feeds update at approximately 1 tick per second. Prices are quoted in USD. ### BTC/USD Bitcoin. The most liquid and widely traded crypto asset. ```json theme={null} { "type": "price_feed", "feed": "BTC/USD", "timestamp": 1774672089, "data": { "feed": "BTC/USD", "price": 66236.61, "bid": 66229.77, "ask": 66237.94, "timestamp": 1774672089 } } ``` BTC/USD has bid/ask spread data. Other feeds currently report bid and ask equal to the mid price. ### ETH/USD Ethereum. ```json theme={null} { "type": "price_feed", "feed": "ETH/USD", "timestamp": 1774672588, "data": { "feed": "ETH/USD", "price": 1989.68, "bid": 1989.68, "ask": 1989.68, "timestamp": 1774672588 } } ``` ### SOL/USD Solana. ```json theme={null} { "type": "price_feed", "feed": "SOL/USD", "timestamp": 1774672588, "data": { "feed": "SOL/USD", "price": 82.57, "bid": 82.57, "ask": 82.57, "timestamp": 1774672588 } } ``` ### BNB/USD BNB (Binance). ```json theme={null} { "type": "price_feed", "feed": "BNB/USD", "timestamp": 1774672588, "data": { "feed": "BNB/USD", "price": 610.54, "bid": 610.54, "ask": 610.54, "timestamp": 1774672588 } } ``` ### XRP/USD XRP. ```json theme={null} { "type": "price_feed", "feed": "XRP/USD", "timestamp": 1774672588, "data": { "feed": "XRP/USD", "price": 1.326, "bid": 1.326, "ask": 1.326, "timestamp": 1774672588 } } ``` ### DOGE/USD Dogecoin. ```json theme={null} { "type": "price_feed", "feed": "DOGE/USD", "timestamp": 1774672588, "data": { "feed": "DOGE/USD", "price": 0.08991, "bid": 0.08991, "ask": 0.08991, "timestamp": 1774672588 } } ``` ### HYPE/USD Hyperliquid. ```json theme={null} { "type": "price_feed", "feed": "HYPE/USD", "timestamp": 1774672588, "data": { "feed": "HYPE/USD", "price": 38.60, "bid": 38.60, "ask": 38.60, "timestamp": 1774672588 } } ``` ## Filtering Subscribe to specific feeds only: ```json theme={null} { "action": "subscribe", "type": "chainlink", "filters": { "feeds": ["BTC/USD", "ETH/USD", "SOL/USD"] } } ``` Feed names are case-insensitive. `"btc/usd"` and `"BTC/USD"` both work. ## Subscribing to all feeds Omit the `feeds` filter: ```json theme={null} {"action": "subscribe", "type": "chainlink"} ``` You'll receive \~7 events per second (one per feed). At approximately 200 bytes per event, that's under 1.5 KB/s of bandwidth. # Crypto Prices Source: https://docs.polynode.dev/crypto/overview Real-time crypto price streaming over WebSocket. 7 assets, ~1 update per second per feed. Stream real-time crypto prices over WebSocket. 7 feeds, \~1 update per second each. ## Connection ``` wss://ws.polynode.dev/ws?key=YOUR_API_KEY ``` This is the same WebSocket endpoint used for settlements and orderbook. If you already have a connection open, you don't need a new one. ## Available feeds | Feed | Pair | Update rate | | ---------- | ----------------------- | ----------- | | `BTC/USD` | Bitcoin / US Dollar | \~1/second | | `ETH/USD` | Ethereum / US Dollar | \~1/second | | `SOL/USD` | Solana / US Dollar | \~1/second | | `BNB/USD` | BNB / US Dollar | \~1/second | | `XRP/USD` | XRP / US Dollar | \~1/second | | `DOGE/USD` | Dogecoin / US Dollar | \~1/second | | `HYPE/USD` | Hyperliquid / US Dollar | \~1/second | ## Quick start Connect and subscribe to all 7 feeds: ```javascript JavaScript theme={null} const WebSocket = require("ws"); const ws = new WebSocket( "wss://ws.polynode.dev/ws?key=YOUR_API_KEY" ); ws.on("open", () => { ws.send(JSON.stringify({ action: "subscribe", type: "chainlink" })); }); ws.on("message", (data) => { const msg = JSON.parse(data); if (msg.type === "price_feed") { console.log(`${msg.data.feed}: $${msg.data.price}`); } }); ``` ```python Python theme={null} import asyncio import json import websockets async def main(): uri = "wss://ws.polynode.dev/ws?key=YOUR_API_KEY" async with websockets.connect(uri) as ws: await ws.send(json.dumps({ "action": "subscribe", "type": "chainlink" })) async for message in ws: msg = json.loads(message) if msg.get("type") == "price_feed": d = msg["data"] print(f"{d['feed']}: ${d['price']}") asyncio.run(main()) ``` ```rust Rust theme={null} use polynode::PolyNodeWS; #[tokio::main] async fn main() { let mut ws = PolyNodeWS::connect("YOUR_API_KEY").await.unwrap(); ws.subscribe_price_feeds(None).await.unwrap(); // None = all feeds while let Some(event) = ws.next_event().await { println!("{}: ${}", event.feed, event.price); } } ``` ```bash wscat theme={null} wscat -c "wss://ws.polynode.dev/ws?key=YOUR_API_KEY" # Then send: {"action": "subscribe", "type": "chainlink"} ``` ### Filter to specific feeds Only receive the feeds you care about: ```json theme={null} { "action": "subscribe", "type": "chainlink", "filters": { "feeds": ["BTC/USD", "ETH/USD"] } } ``` Omit `feeds` to get all 7. ## Event format Every price update arrives as a `price_feed` event: ```json theme={null} { "type": "price_feed", "feed": "BTC/USD", "timestamp": 1774672588, "data": { "feed": "BTC/USD", "price": 66225.49, "bid": 66221.69, "ask": 66229.46, "timestamp": 1774672588 } } ``` See [Event Format](/crypto/event-format) for the full field reference. ## Combining with other streams Crypto prices run on the same WebSocket connection as settlements, orderbook, and oracle streams. Send multiple subscribe messages: ```javascript JavaScript theme={null} // Settlements + crypto prices on the same connection ws.on("open", () => { ws.send(JSON.stringify({ action: "subscribe", type: "settlements" })); ws.send(JSON.stringify({ action: "subscribe", type: "chainlink" })); }); ws.on("message", (data) => { const msg = JSON.parse(data); if (msg.type === "price_feed") { // crypto price tick console.log(`${msg.data.feed}: $${msg.data.price}`); } else if (msg.type === "settlement") { // polymarket settlement console.log(`${msg.data.market_title}: ${msg.data.outcome}`); } }); ``` ```python Python theme={null} await ws.send(json.dumps({"action": "subscribe", "type": "settlements"})) await ws.send(json.dumps({"action": "subscribe", "type": "chainlink"})) async for message in ws: msg = json.loads(message) if msg.get("type") == "price_feed": d = msg["data"] print(f"Price: {d['feed']} ${d['price']}") elif msg.get("type") == "settlement": d = msg["data"] print(f"Settlement: {d['market_title']}: {d['outcome']}") ``` Use the `type` field (`"price_feed"` vs `"settlement"`) to route events. ## REST Endpoints In addition to the WebSocket stream, crypto data is available via REST. See the [REST API reference](/crypto/rest-api) for full details. | Endpoint | Description | | ------------------------------------------------------------- | -------------------------------------------------- | | `GET /v1/crypto/markets` | All crypto prediction markets | | `GET /v1/crypto/candles?symbol=BTC` | 5-min OHLC candles from Chainlink oracle | | `GET /v1/crypto/ticks?symbol=BTC&from={unix_ms}&to={unix_ms}` | 1-second historical price ticks for chart backfill | | `GET /v1/crypto/price?symbol=BTC&window={epoch}` | Open/close price for a market window | | `GET /v1/crypto/active` | Currently active 5-minute markets for all 7 coins | | `GET /v1/crypto/series` | Recurring crypto market series | All endpoints require an API key via `?key=` or `x-api-key` header. ## Short-Form Crypto Markets **These prices power prediction markets.** All 7 coins have active 5-minute, 15-minute, and 1-hour "Up or Down" prediction markets on Polymarket. The Chainlink price feed determines the opening price (price-to-beat), and the closing price at window end decides the outcome. Hundreds of trades happen every minute on these markets. Here's how it works: a BTC 5-minute market opens at 10:00 with BTC at $66,800. If BTC is above $66,800 at 10:05, "Up" wins. If it's below, "Down" wins. A new market opens at 10:05 with the new price as the target. This repeats every 5 minutes, 15 minutes, and every hour, across all 7 coins. ### What you can do with this | Stream | What it gives you | How to get it | | -------------------------- | -------------------------------------------------- | --------------------------------------------------- | | **Chainlink prices** | Underlying asset price at \~1/sec | `subscribe('chainlink')` on the main WS | | **Historical price ticks** | 1-second chart backfill for an already-open window | `GET /v1/crypto/ticks` | | **Short-form settlements** | Every trade on 5m/15m/1h markets | SDK `shortForm('15m')` auto-rotates between windows | | **Short-form orderbook** | Bid/ask depth on crypto markets | `OrderbookEngine` with `clobTokenIds` from rotation | | **Price-to-beat** | Chainlink opening price per window | Included in SDK rotation events | | **Odds & liquidity** | Market-implied probabilities | Included in SDK rotation events | ### Quick example Track BTC price alongside 15-minute market odds: ```javascript theme={null} import { PolyNodeWS } from 'polynode-sdk'; const ws = new PolyNodeWS('pn_live_...', 'wss://ws.polynode.dev/ws'); // Live BTC price from Chainlink const prices = await ws.subscribe('chainlink') .feeds(['BTC/USD']) .send(); prices.on('price_feed', (msg) => { console.log(`BTC: $${msg.price}`); }); // 15-minute market rotation + settlements const stream = ws.shortForm('15m', { coins: ['btc'] }); stream.on('rotation', (r) => { const m = r.markets[0]; console.log(`Window: beat $${m.priceToBeat} | ${(m.upOdds * 100).toFixed(0)}% up | ${r.timeRemaining}s left`); }); stream.on('settlement', (e) => { console.log(`Trade: ${e.outcome} $${e.taker_size}`); }); ``` The SDK handles market discovery, slug computation, token resolution, and automatic rotation between windows. See [Short-Form Markets](/sdks/short-form) for the full reference including the interactive slug calculator, all 14 market fields, and how to connect the orderbook. ### Market slug patterns Slugs are deterministic, based on the window's Unix epoch timestamp: | Interval | Pattern | Example | | -------- | --------------------------- | ----------------------------------------- | | 5 min | `{coin}-updown-5m-{epoch}` | `btc-updown-5m-1775399100` | | 15 min | `{coin}-updown-15m-{epoch}` | `eth-updown-15m-1775397600` | | 1 hour | Human-readable | `bitcoin-up-or-down-april-5-2026-10am-et` | The epoch timestamp is always aligned to the interval boundary: `Math.floor(now / intervalSeconds) * intervalSeconds`. ## Use cases * **Short-form market trading** -- track the underlying asset price alongside 5m/15m/1h crypto markets * **Portfolio valuation** -- combine real-time crypto prices with Polymarket positions for live PnL * **Hedging signals** -- correlate crypto price moves with prediction market settlements * **Dashboards** -- display live crypto prices alongside prediction market data * **Trading bots** -- trigger Polymarket trades based on crypto price thresholds # REST API Source: https://docs.polynode.dev/crypto/rest-api REST endpoints for crypto market discovery, oracle candles, historical ticks, price-to-beat, and active short-form markets. REST endpoints for crypto prediction market data. All endpoints require an API key. ## Authentication Pass your API key via query parameter or header: ```bash theme={null} # Query parameter curl "https://api.polynode.dev/v1/crypto/markets?key=YOUR_API_KEY" # Header curl -H "x-api-key: YOUR_API_KEY" "https://api.polynode.dev/v1/crypto/markets" ``` *** ## GET /v1/crypto/markets All crypto prediction markets with liquidity, volume, and open interest. **Cache:** 3 minutes ```bash theme={null} curl "https://api.polynode.dev/v1/crypto/markets?key=YOUR_API_KEY" ``` ```json theme={null} { "events": [ { "id": "238474", "slug": "what-price-will-bitcoin-hit-in-march-2026", "title": "What price will Bitcoin hit in March?", "startDate": "2026-03-01T05:20:27.791222Z", "endDate": "2026-04-01T04:00:00Z", "active": true, "volume": 88886711.74, "volume24hr": 5733750.73, "liquidity": 6074250.39, "openInterest": 12827944.10, "seriesSlug": "bitcoin-hit-price-monthly", "markets": [ { "id": "1629442", "question": "$100,000?", "conditionId": "0x...", "outcomes": ["Yes", "No"], "outcomePrices": [0.045, 0.955], "volume": 24084104.74, "liquidity": 2028310.67, "active": true, "closed": false, "groupItemTitle": "↑ 150,000" } ] } ] } ``` *** ## GET /v1/crypto/candles 5-minute OHLC candles from PolyNode's live Chainlink tick archive. Returns \~2.5 hours of history (30 candles). **Cache:** \~5 seconds | Parameter | Required | Description | | --------- | -------- | ----------------------------------------------------------------------------------------------------- | | `symbol` | Yes | Asset symbol or feed name: `BTC`, `ETH`, `SOL`, `BNB`, `XRP`, `DOGE`, `HYPE`, or names like `BTC/USD` | ```bash theme={null} curl "https://api.polynode.dev/v1/crypto/candles?symbol=BTC&key=YOUR_API_KEY" ``` REST candle symbols accept both bare asset symbols like `BTC` and feed names like `BTC/USD`. WebSocket Chainlink subscriptions use feed names such as `BTC/USD`. ```json theme={null} { "candles": [ { "time": 1774665300, "open": 65954.03, "high": 65992.01, "low": 65947.14, "close": 65949.69 } ] } ``` *** ## GET /v1/crypto/ticks Historical 1-second ticks for the same crypto feeds available on the `price_feed` WebSocket stream. Use this to backfill a chart window before switching to live WebSocket updates. **Range:** up to 24 hours per request | Parameter | Required | Description | | --------- | -------- | -------------------------------------------------------------------------- | | `symbol` | Yes | Asset symbol: `BTC`, `ETH`, `SOL`, `BNB`, `XRP`, `DOGE`, `HYPE` | | `from` | Yes | Start of range as Unix milliseconds | | `to` | Yes | End of range as Unix milliseconds | | `limit` | No | Max ticks to return. Default 10000, max 100000 | | `source` | No | Optional source filter: `chainlink_data_streams` or `polymarket_chainlink` | ```bash theme={null} curl -H "x-api-key: YOUR_API_KEY" \ "https://api.polynode.dev/v1/crypto/ticks?symbol=BTC&from=1780458961000&to=1780459082000&limit=5" ``` ```json theme={null} { "symbol": "BTC", "feed": "BTC/USD", "from": 1780458961000, "to": 1780459082000, "limit": 5, "count": 5, "truncated": true, "source": null, "ticks": [ { "id": "1780458962000-0", "type": "price_feed", "feed": "BTC/USD", "timestamp": 1780458962, "timestamp_ms": 1780458962000, "source": "chainlink_data_streams", "data": { "feed": "BTC/USD", "price": 65869.02124677686, "bid": 65865.65936734258, "ask": 65870.4120597498, "timestamp": 1780458962 } } ] } ``` If `truncated` is `true`, request a narrower range or increase `limit`. BTC/USD can include more than one source for the same second; use the `source` parameter or deduplicate by `timestamp_ms` if your chart needs exactly one point per second. *** ## GET /v1/crypto/price Open and close price for a specific crypto market window. Use this to get the "price to beat" for a short-form market. **Cache:** 10 seconds | Parameter | Required | Description | | ---------- | -------- | --------------------------------------------------------------- | | `symbol` | Yes | Asset symbol: `BTC`, `ETH`, `SOL`, `BNB`, `XRP`, `DOGE`, `HYPE` | | `window` | Yes | Unix epoch timestamp of the market window start | | `interval` | No | Market interval variant (e.g. `5m`, `15m`, `1h`, `4h`) | ```bash theme={null} curl "https://api.polynode.dev/v1/crypto/price?symbol=BTC&window=1774674000&key=YOUR_API_KEY" ``` ```json theme={null} { "openPrice": 66285.01, "closePrice": 66238.61, "timestamp": 1774674466843, "completed": false, "incomplete": true, "cached": false } ``` The `openPrice` is the oracle price at market open. `closePrice` updates in real time until the window completes. `completed: true` means the market has resolved. *** ## GET /v1/crypto/active Currently active 5-minute up-or-down markets for all 7 coins. Returns live market data with token IDs, outcomes, and current odds. **Cache:** 30 seconds ```bash theme={null} curl "https://api.polynode.dev/v1/crypto/active?key=YOUR_API_KEY" ``` ```json theme={null} { "markets": [ { "id": "312746", "slug": "btc-updown-5m-1774674300", "title": "Bitcoin Up or Down - March 28, 1:05AM-1:10AM ET", "active": true, "closed": false, "startDate": "2026-03-27T05:16:04.211285Z", "endDate": "2026-03-28T05:10:00Z", "markets": [ { "question": "Bitcoin Up or Down - March 28, 1:05AM-1:10AM ET", "conditionId": "0x...", "outcomes": "[\"Up\", \"Down\"]", "outcomePrices": [0.465, 0.535], "tokenId": "12345...", "active": true, "closed": false } ] } ], "count": 7, "windowStart": 1774674300 } ``` The `windowStart` field is the epoch timestamp of the current 5-minute window. New markets rotate every 5 minutes. Slug pattern is deterministic: `{coin}-updown-5m-{windowStart}`. You can compute the current window yourself with `Math.floor(Date.now() / 1000 / 300) * 300`. The SDK's `shortForm()` method handles this automatically with auto-rotation, enriched market data, and settlement streaming. See [Short-Form Markets](/sdks/short-form) for the interactive slug calculator and full reference. *** ## GET /v1/crypto/series Recurring crypto market series (5m, 15m, 1h, 4h, daily, weekly, monthly patterns). **Cache:** 5 minutes ```bash theme={null} curl "https://api.polynode.dev/v1/crypto/series?key=YOUR_API_KEY" ``` ```json theme={null} { "series": [ { "id": "10422", "slug": "xrp-up-or-down-15m", "title": "XRP Up or Down 15m", "recurrence": "15m", "active": true, "eventCount": null }, { "id": "10065", "slug": "ethereum-neg-risk-weekly", "title": "Ethereum Neg Risk Weekly", "recurrence": "weekly", "active": true, "eventCount": null } ], "count": 7 } ``` # Builder Detail Source: https://docs.polynode.dev/data/builders/detail GET /v3/builders/{code} Get stats and public profile metadata for a single builder by hex code. Returns fill count, volume, fees, normalized side-aware volume, and public profile metadata for a specific builder. ## Request ``` GET /v3/builders/{code} ``` ### Path parameters | Parameter | Type | Description | | --------- | ------ | -------------------------------------------- | | `code` | string | Builder hex code (with or without 0x prefix) | ## Example ```bash theme={null} curl https://api.polynode.dev/v3/builders/0xd1d9dd6983c40006b0dc8eab84a41ac9a4f27643296178479ffbebbc01ab7bde ``` ```json theme={null} { "builder": "0xd1d9dd6983c40006b0dc8eab84a41ac9a4f27643296178479ffbebbc01ab7bde", "builder_code": "0xd1d9dd6983c40006b0dc8eab84a41ac9a4f27643296178479ffbebbc01ab7bde", "builder_logo": "https://polymarket-upload.s3.us-east-2.amazonaws.com/profile-image-5148537-b5dbf263-8d5f-41d7-bba1-387bfa17075c.png", "builder_name": "MagicMarkets", "builder_verified": true, "buy_volume_e6": "8805777867302", "buy_volume_usdc": "8805777.867302000000", "elapsed_ms": 3, "external_active_users": 3, "external_rank": 23, "external_volume": "18380636.129236996", "fee_semantics": "Builder-attributed trader-paid fees. This is not a builder rebate payout.", "fill_count": 23076, "first_fill": "1777382110", "last_fill": "1782307555", "sell_volume_e6": "0", "sell_volume_usdc": "0.000000000000000000000000", "source": "builder_fee_stats", "stats_checked_at": "2026-06-24 13:28:05.26204+00", "stats_pending": false, "stats_refreshed_at": "2026-06-24 13:28:03.133082+00", "stats_source": "local_order_filled_event_index", "total_fees": "116885095300", "total_fees_usdc": "116885.095300000000", "total_maker_volume": "8805777867302", "total_maker_volume_usdc": "8805777.867302000000", "total_taker_volume": "18512535904523", "total_taker_volume_usdc": "18512535.904523000000", "volume_e6": "8805777867302", "volume_usdc": "8805777.867302000000" } ``` ## Response fields Same fields as the [Builder Leaderboard](/data/builders/list) response, without `rank`. | Field | Type | Description | | ------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `builder` | string | Builder code (`0x`-prefixed hex, 32 bytes) | | `builder_code` | string | Same builder code, included for profile-style naming consistency | | `builder_name` | string \| null | Public builder profile name when available | | `builder_logo` | string \| null | Public builder profile image URL when available | | `builder_verified` | boolean \| null | Whether the builder profile is verified, when available | | `fill_count` | integer | Total fills attributed to this builder | | `first_fill` | string | Unix timestamp of first fill | | `last_fill` | string | Unix timestamp of most recent fill | | `source` | string | Legacy source label. `builder_fee_stats` means local builder stats are present; `polymarket_builder_metadata` means only profile metadata is present so far. | | `stats_source` | string \| null | Local stats source, currently `local_order_filled_event_index` when local stats are populated | | `total_fees` | string | Total fees generated in raw 6-decimal USDC units | | `total_fees_usdc` | string \| null | Total fees normalized to decimal USDC | | `total_maker_volume` | string | Sum of raw maker-side amount fields in 6-decimal units, kept for compatibility | | `total_maker_volume_usdc` | string \| null | Maker-side total normalized to decimal USDC | | `total_taker_volume` | string | Sum of raw taker-side amount fields in 6-decimal units, kept for compatibility | | `total_taker_volume_usdc` | string \| null | Taker-side total normalized to decimal USDC | | `volume_e6` | string \| null | Side-aware builder volume in raw 6-decimal USDC units. For buys this uses maker collateral; for sells this uses taker collateral. | | `volume_usdc` | string \| null | `volume_e6` normalized to decimal USDC. Prefer this for displayed builder volume. | | `buy_volume_e6` | string \| null | Buy-side normalized-volume numerator in raw 6-decimal USDC units | | `buy_volume_usdc` | string \| null | Buy-side normalized volume in decimal USDC | | `sell_volume_e6` | string \| null | Sell-side normalized-volume numerator in raw 6-decimal USDC units | | `sell_volume_usdc` | string \| null | Sell-side normalized volume in decimal USDC | | `external_rank` | integer \| null | Polymarket's public builder leaderboard rank, when available | | `external_volume` | string \| null | Polymarket's public builder leaderboard volume, when available | | `external_active_users` | integer \| null | Polymarket's public active-user count, when available | | `stats_refreshed_at` | string \| null | Time PolyNode last rewrote local stats for this builder | | `stats_checked_at` | string \| null | Time PolyNode last checked this builder, including no-op refreshes | | `stats_pending` | boolean | `true` when profile metadata exists but the local stats row has not been populated yet | | `fee_semantics` | string | Reminder that builder fees are trader-paid fees attributed to builder order flow, not a builder rebate payout | | `elapsed_ms` | integer | Server-side query time in milliseconds | ## Related endpoints | Endpoint | Description | | -------------------------------- | --------------------------------- | | `GET /v3/builders` | Builder leaderboard | | `GET /v3/builders/{code}/trades` | Trades attributed to this builder | # Builder Leaderboard Source: https://docs.polynode.dev/data/builders/list GET /v3/builders Rank Polymarket builders by fill count, normalized volume, fees, and enriched builder profile metadata. Polymarket V2 trades include a `builder` field identifying which order routing service executed the trade. This endpoint ranks builders by attributed fills and includes profile metadata when a builder has a public builder profile. Builder profile metadata is synced from Polymarket's public builder metadata. Local fill, fee, and normalized volume stats come from PolyNode's indexed `OrderFilled` events. Builders can appear with metadata before their local stats row is refreshed; in that case `stats_pending` is `true` and normalized local volume fields are `null`. ## Request ``` GET /v3/builders ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ---------------------------------- | | `sort` | string | `fills` | Sort by: `fills`, `volume`, `fees` | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/builders?sort=volume&limit=1" ``` ```json theme={null} { "builders": [ { "builder": "0xd1d9dd6983c40006b0dc8eab84a41ac9a4f27643296178479ffbebbc01ab7bde", "builder_code": "0xd1d9dd6983c40006b0dc8eab84a41ac9a4f27643296178479ffbebbc01ab7bde", "builder_logo": "https://polymarket-upload.s3.us-east-2.amazonaws.com/profile-image-5148537-b5dbf263-8d5f-41d7-bba1-387bfa17075c.png", "builder_name": "MagicMarkets", "builder_verified": true, "external_active_users": 3, "external_rank": 23, "external_volume": "18380636.129236996", "fill_count": 23076, "first_fill": "1777382110", "last_fill": "1782307555", "rank": 1, "source": "local_order_filled_event_index", "stats_checked_at": "2026-06-24 13:28:05.26204+00", "stats_pending": false, "stats_refreshed_at": "2026-06-24 13:28:03.133082+00", "buy_volume_e6": "8805777867302", "buy_volume_usdc": "8805777.867302000000", "sell_volume_e6": "0", "sell_volume_usdc": "0.000000000000000000000000", "total_fees": "116885095300", "total_fees_usdc": "116885.095300000000", "total_maker_volume": "8805777867302", "total_maker_volume_usdc": "8805777.867302000000", "total_taker_volume": "18512535904523", "total_taker_volume_usdc": "18512535.904523000000", "volume_e6": "8805777867302", "volume_usdc": "8805777.867302000000" } ], "elapsed_ms": 8, "has_more": true, "limit": 1, "offset": 0, "rows_returned": 1 } ``` ## Response fields | Field | Type | Description | | ------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `builder` | string | Builder code (`0x`-prefixed hex, 32 bytes) | | `builder_code` | string | Same builder code, included for profile-style naming consistency | | `builder_name` | string \| null | Public builder profile name when available | | `builder_logo` | string \| null | Public builder profile image URL when available | | `builder_verified` | boolean \| null | Whether the builder profile is verified, when available | | `fill_count` | integer | Total fills attributed to this builder | | `first_fill` | string | Unix timestamp of first fill | | `last_fill` | string | Unix timestamp of most recent fill | | `rank` | integer | Position in the leaderboard | | `source` | string | `local_order_filled_event_index` when local stats are present, otherwise `polymarket_builder_metadata` | | `total_fees` | string | Total fees generated in raw 6-decimal USDC units | | `total_fees_usdc` | string \| null | Total fees normalized to decimal USDC | | `total_maker_volume` | string | Sum of raw maker-side amount fields in 6-decimal units, kept for compatibility | | `total_maker_volume_usdc` | string \| null | Maker-side total normalized to decimal USDC | | `total_taker_volume` | string | Sum of raw taker-side amount fields in 6-decimal units, kept for compatibility | | `total_taker_volume_usdc` | string \| null | Taker-side total normalized to decimal USDC | | `volume_e6` | string \| null | Side-aware builder volume in raw 6-decimal USDC units. For buys this uses maker collateral; for sells this uses taker collateral. | | `volume_usdc` | string \| null | `volume_e6` normalized to decimal USDC. Prefer this for displayed builder volume. | | `buy_volume_e6` | string \| null | Buy-side normalized-volume numerator in raw 6-decimal USDC units | | `buy_volume_usdc` | string \| null | Buy-side normalized volume in decimal USDC | | `sell_volume_e6` | string \| null | Sell-side normalized-volume numerator in raw 6-decimal USDC units | | `sell_volume_usdc` | string \| null | Sell-side normalized volume in decimal USDC | | `external_rank` | integer \| null | Polymarket's public builder leaderboard rank, when available | | `external_volume` | string \| null | Polymarket's public builder leaderboard volume, when available | | `external_active_users` | integer \| null | Polymarket's public active-user count, when available | | `stats_refreshed_at` | string \| null | Time PolyNode last rewrote local stats for this builder | | `stats_checked_at` | string \| null | Time PolyNode last checked this builder, including no-op refreshes | | `stats_pending` | boolean | `true` when profile metadata exists but the local stats row has not been populated yet | `builder_name`, `builder_logo`, `builder_verified`, and the normalized local-volume fields are nullable. `external_volume` is Polymarket's public leaderboard value; `volume_usdc` is PolyNode's local indexed side-aware volume. Use `external_active_users` for the current public builder user-count signal. ## Related endpoints | Endpoint | Description | | -------------------------------- | ------------------------------------------------------------------ | | `GET /v3/builders/{code}` | Stats for a single builder | | `GET /v3/builders/{code}/trades` | Trades attributed to a builder, with market and profile enrichment | # Builder Trades Source: https://docs.polynode.dev/data/builders/trades GET /v3/builders/{code}/trades Get trades attributed to a specific builder, with builder profile and market enrichment. Returns fills attributed to a specific builder, enriched with builder profile metadata and market context. Use token, market, event, side, size, and time filters to narrow the trade feed. ## Request ``` GET /v3/builders/{code}/trades ``` ### Path parameters | Parameter | Type | Description | | --------- | ------ | -------------------------------------------- | | `code` | string | Builder hex code (with or without 0x prefix) | ### Query parameters | Parameter | Type | Default | Description | | -------------- | ------- | ------- | ------------------------------------------------------------------------------------------ | | `token_id` | string | -- | Filter trades involving this outcome token | | `condition_id` | string | -- | Filter by market condition ID (resolves to token IDs) | | `market_slug` | string | -- | Filter by market slug (resolves to token IDs) | | `event_slug` | string | -- | Filter by event slug (resolves to token IDs across the event) | | `side` | string | -- | `buy` or `sell`, based on the maker order side | | `min_amount` | integer | -- | Minimum `maker_amount_filled` (raw 6-decimal) | | `category` | string | -- | Filter by market category. Broad categories may return `400` asking for a narrower filter. | | `tag_slug` | string | -- | Filter by market tag slug. Broad tags may return `400` asking for a narrower filter. | | `after` | integer | 0 | Start of time range (Unix timestamp) | | `before` | integer | -- | End of time range | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/builders/0x4898df15ec6590495dc6c0fedf951ade3e64001d47f9caf44a64e86fc11959df/trades?market_slug=dota2-tundra-xtreme-2026-05-22-game1&limit=1" ``` ```json theme={null} { "builder": "0x4898df15ec6590495dc6c0fedf951ade3e64001d47f9caf44a64e86fc11959df", "builder_code": "0x4898df15ec6590495dc6c0fedf951ade3e64001d47f9caf44a64e86fc11959df", "builder_logo": "https://polymarket-upload.s3.us-east-2.amazonaws.com/profile-image-4043800-ced7ecde-0754-46a1-8a16-bb6c338fbf39.png", "builder_name": "PolyCop", "builder_verified": true, "elapsed_ms": 12, "has_more": true, "limit": 1, "offset": 0, "rows_returned": 1, "trades": [ { "amount": 1.101, "amount_usd": 1.08999, "asset": "21578184864194020862792310253337084708609281967112790860038966988826118995357", "builder": "0x4898df15ec6590495dc6c0fedf951ade3e64001d47f9caf44a64e86fc11959df", "builder_code": "0x4898df15ec6590495dc6c0fedf951ade3e64001d47f9caf44a64e86fc11959df", "builder_logo": "https://polymarket-upload.s3.us-east-2.amazonaws.com/profile-image-4043800-ced7ecde-0754-46a1-8a16-bb6c338fbf39.png", "builder_name": "PolyCop", "builder_verified": true, "condition_id": "0x97269cbb95bd572b3dde34cdd619be278c8f13ba5e43e2e6c2d8639411128a86", "direction": "BUY", "fee": 0.00032, "id": "0x0d57b8bd2cad026ccac902cee781becb358387c6fed98558802e0464434d4c9c_1060", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/dota2-7ffacddb21.jpg", "maker": "0x952d11ebff81d6bd3185e608ed3515b94618ab8a", "maker_amount": 1.08999, "maker_asset_id": "0", "market": "Dota 2: Tundra Esports vs Xtreme Gaming - Game 1 Winner", "order_hash": "0x52d42aa621bd4c451151e97eb9b8a9ec4154f76da440ba31b73cebe918331ac2", "outcome": "Tundra Esports", "outcome_index": 0, "price": 0.99, "role": "maker", "side": 0, "size": 1.101, "slug": "dota2-tundra-xtreme-2026-05-22-game1", "taker": "0xe111180000d2663c0091e4f400237545b87b996b", "taker_amount": 1.101, "taker_asset_id": "21578184864194020862792310253337084708609281967112790860038966988826118995357", "timestamp": "1779448364", "token_id": "21578184864194020862792310253337084708609281967112790860038966988826118995357", "transaction_hash": "0x0d57b8bd2cad026ccac902cee781becb358387c6fed98558802e0464434d4c9c" } ] } ``` ## Response fields ### Response envelope | Field | Type | Description | | ------------------ | --------------- | ---------------------------------------------------------------- | | `builder` | string | Builder code requested in the path | | `builder_code` | string | Same builder code, included for profile-style naming consistency | | `builder_name` | string \| null | Public builder profile name when available | | `builder_logo` | string \| null | Public builder profile image URL when available | | `builder_verified` | boolean \| null | Whether the builder profile is verified, when available | | `trades` | array | Trade rows attributed to this builder | | `rows_returned` | integer | Number of trade rows returned | | `has_more` | boolean | Whether another page is available | | `offset` | integer | Pagination offset used | | `limit` | integer | Page size used | | `elapsed_ms` | integer | Server-side query time in milliseconds | ### Trade data Each trade contains the same enriched fields as [Wallet Trades](/data/wallets/trades), including market context, computed price/size, order hash, and direction. Builder metadata is also repeated on each row for clients that stream or store trade rows independently. | Field | Type | Description | | ------------------ | --------------- | ------------------------------------------------------- | | `builder_code` | string | Builder code for this trade row | | `builder_name` | string \| null | Public builder profile name when available | | `builder_logo` | string \| null | Public builder profile image URL when available | | `builder_verified` | boolean \| null | Whether the builder profile is verified, when available | ## Filter examples ### Trades for a specific token ```bash theme={null} curl "https://api.polynode.dev/v3/builders/0x4898df15.../trades?token_id=21578184864194020862792310253337084708609281967112790860038966988826118995357&limit=10" ``` ### Trades for a specific market ```bash theme={null} curl "https://api.polynode.dev/v3/builders/0x4898df15.../trades?market_slug=dota2-tundra-xtreme-2026-05-22-game1&limit=10" ``` ### Maker-side buys or sells ```bash theme={null} curl "https://api.polynode.dev/v3/builders/0x4898df15.../trades?side=buy&limit=10" curl "https://api.polynode.dev/v3/builders/0x4898df15.../trades?side=sell&limit=10" ``` ### Combine tag/category with a narrow market filter ```bash theme={null} curl "https://api.polynode.dev/v3/builders/0x4898df15.../trades?market_slug=dota2-tundra-xtreme-2026-05-22-game1&tag_slug=sports&limit=10" curl "https://api.polynode.dev/v3/builders/0x4898df15.../trades?market_slug=dota2-tundra-xtreme-2026-05-22-game1&category=Sports&limit=10" ``` Builder categories are derived from the first market tag. Broad `category` and `tag_slug` filters can be too large to run directly; add `market_slug`, `condition_id`, or `token_id` to use the indexed intersection path. ```json theme={null} { "error": "tag_slug filter too broad for builder trades; add market_slug, condition_id, token_id, or time bounds" } ``` ## Related endpoints | Endpoint | Description | | ------------------------- | ------------------------------------------- | | `GET /v3/builders` | Builder leaderboard | | `GET /v3/builders/{code}` | Stats and profile metadata for this builder | # Combo Activity Source: https://docs.polynode.dev/data/combos/activity GET /v3/combos/activity Query combo lifecycle activity by market, condition, position, or wallet. Returns combo lifecycle activity across wallets. At least one of `market_id`, `condition_id`, or `position_id` is required. Add `wallet` to narrow results to a single wallet. ## Request ``` GET /v3/combos/activity ``` ### Query parameters | Parameter | Type | Default | Description | | -------------- | ------- | ------- | ------------------------------------------------------------------------ | | `market_id` | string | -- | Alias for `condition_id` | | `condition_id` | string | -- | Combo condition ID | | `position_id` | string | -- | Combo position ID | | `wallet` | string | -- | Wallet address | | `event_kind` | string | -- | Filter by event kind, for example `PositionsSplit` or `PositionRedeemed` | | `limit` | integer | 100 | Results per page, max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/combos/activity?condition_id=0x03d98f6ac4c108c5ca66f79f34908a1d820000000000000000000000000000&limit=10" ``` ```json theme={null} { "activity": [ { "position_type": "combo", "event_kind": "PositionsSplit", "wallet_address": "0x63613e3b96f418332d43cd2af8dc321014d15907", "combo_condition_id": "0x03d98f6ac4c108c5ca66f79f34908a1d820000000000000000000000000000", "amount_usdc": "0.050750", "tx_hash": "0xf3a8985f04bf869483ef4163a185f296c834eb827b5e5ae3db5bd44558121d51", "block_number": 88276713, "timestamp": 1781120055 } ], "rows_returned": 10, "has_more": true, "position_type": "combo", "source": "v3.combos.activity" } ``` ## Notes * Use `/v3/wallets/{address}/combos/activity` when the wallet address is already known. * Use this endpoint when you are exploring all lifecycle activity for a combo market or position. * The activity feed is on-chain derived and can include split, merge, redemption, and transfer-related lifecycle rows as those events are observed. * Public combo redemption rows are normalized to Polymarket's combo activity shape: router and auto-redeemer redemption paths are returned as `event_kind: "PositionRedeemed"` with `side: "Redeem"`, a module kind such as `Combinatorial`, `Binary`, or `NegRisk`, and `payout_usdc` when available. * When leg metadata is available, resolved combo activity includes leg statuses such as `RESOLVED_WIN` or `RESOLVED_LOSS` and `leg_current_price` values. # Combo Markets Source: https://docs.polynode.dev/data/combos/markets GET /v3/combos/markets List observed Polymarket combo markets with legs and activity counters. Returns observed combo markets. Each market includes its combo condition, status, activity counters, volume, and a `legs` array when leg metadata is available. ## Request ``` GET /v3/combos/markets ``` ### Query parameters | Parameter | Type | Default | Description | | -------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------- | | `market_id` | string | -- | Alias for `condition_id` | | `condition_id` | string | -- | Combo condition ID | | `status` | string | -- | `observed`, `open`, `closed`, `resolved`, `resolved_win`, `resolved_loss`, `redeemable`, `redeemed`, or `all` | | `limit` | integer | 100 | Results per page, max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/combos/markets?limit=2" ``` ```json theme={null} { "markets": [ { "combo_condition_id": "0x03c58e66825a6501dfffbf7dd0e802c3470000000000000000000000000000", "module_name": "CombinatorialModule", "status": "observed", "observed_execution_count": 10, "observed_lifecycle_count": 16, "observed_transfer_count": 32, "volume_usdc": "427.01", "last_activity_at": "2026-06-11T02:07:18+00:00", "legs": [ { "leg_index": 0, "leg_position_id": "770259566746216134461634134886555299946416955842760322422283095048103919616", "leg_status": "OPEN", "market_slug": "fifwc-gha-pan-2026-06-17-spread-away-1pt5", "market_title": "Panama (-1.5)", "event_title": "Ghana vs. Panama - More Markets", "leg_current_price": "0.0000" } ] } ], "rows_returned": 2, "has_more": true, "position_type": "combo", "source": "v3.combos.markets", "elapsed_ms": 148 } ``` ## Response fields | Field | Type | Description | | -------------------------- | ------- | ------------------------------------------------ | | `combo_condition_id` | string | Combo condition ID | | `module_name` | string | Combo module name, usually `CombinatorialModule` | | `status` | string | Observed lifecycle status | | `observed_execution_count` | integer | Observed combo execution count | | `observed_lifecycle_count` | integer | Observed split/merge/lifecycle count | | `observed_redeem_count` | integer | Observed combo redemption count | | `observed_transfer_count` | integer | Observed combo position transfer count | | `volume_usdc` | string | Observed combo volume formatted in USDC | | `last_activity_at` | string | Last observed combo activity timestamp | | `legs` | array | Market legs that define the combo | Resolved leg metadata is refreshed from Polymarket combo metadata when available. Leg rows can include `leg_status` values such as `RESOLVED_WIN` or `RESOLVED_LOSS`, `leg_resolved_at`, and `leg_current_price`. # Combos Overview Source: https://docs.polynode.dev/data/combos/overview Query Polymarket combo markets, combo wallet positions, combo trades, and combo lifecycle activity. Combos are Polymarket's combinatorial positions: one position is defined by multiple market legs. PolyNode indexes those on-chain combo positions separately from standard CTF positions, then exposes them through dedicated combo endpoints and opt-in wallet aggregates. Use the dedicated combo endpoints when you want combo-only data. Use `include_combos=true` on supported wallet endpoints when you want the wallet-level response to include both standard market data and combo data. ## Base URL ``` https://api.polynode.dev/v3 ``` ## Dedicated combo endpoints | Endpoint | Description | | -------------------------------------------- | -------------------------------------------------------------------------- | | `GET /v3/combos/markets` | List observed combo markets with leg metadata | | `GET /v3/combos/activity` | List combo lifecycle activity for a market, condition, position, or wallet | | `GET /v3/wallets/{address}/combos/positions` | Combo positions for one wallet | | `GET /v3/wallets/{address}/combos/trades` | Combo trades for one wallet | | `GET /v3/wallets/{address}/combos/activity` | Combo lifecycle activity for one wallet | | `GET /v3/wallets/{address}/combos/summary` | Combo-only P\&L summary for one wallet | ## Additive wallet support These wallet endpoints support `include_combos=true`: | Endpoint | Behavior | | ------------------------------------- | -------------------------------------------------------------- | | `GET /v3/wallets/{address}` | Adds combo P\&L/counts to the all-time wallet summary | | `GET /v3/wallets/{address}/pnl` | Adds combo P\&L/counts to all-time wallet P\&L | | `GET /v3/wallets/{address}/positions` | Appends combo positions to the position list | | `GET /v3/wallets/{address}/trades` | Adds a `combo_trades` branch alongside the standard trade list | If a wallet has no combo exposure, `include_combos=true` still returns `200` with the normal wallet response and a zero combo contribution or empty combo branch. Clients do not need to know ahead of time whether a wallet has traded combos. ## Identifier notes | Identifier | Description | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `combo_condition_id` | Combo condition ID. Returned as a hex string. | | `position_id` / `combo_position_id` | ERC-1155 position ID for the combo leg or combo position. Returned as a string because it can exceed JavaScript integer precision. | | `legs` | Array of market legs that define the combo position. Legs may include market slug/title, event slug/title, outcome label, current price, and leg status when metadata is available. | ## P\&L semantics Combo P\&L uses the same high-level model as standard Polymarket positions: weighted-average cost basis, realized P\&L from closes/redeems/merges, and unrealized P\&L for open balances when a current price is available. When a combo is redeemed, PolyNode settles the redeemed size against weighted-average entry basis. Fully redeemed winners return `RESOLVED_WIN` with zero open entry cost and realized P\&L from the redemption payout; fully redeemed losers return `RESOLVED_LOSS`. Combo redemption activity is normalized to Polymarket's public `PositionRedeemed` shape even when the on-chain path used the router or auto-redeemer. All aggregate P\&L fields are returned as decimal USD numbers. Event-level combo amount fields use decimal strings such as `"0.050750"` for exact USDC-style display. # Wallet Combo Activity Source: https://docs.polynode.dev/data/combos/wallet-activity GET /v3/wallets/{address}/combos/activity List combo lifecycle activity for one wallet, including split and merge activity. Returns combo lifecycle activity for a wallet. This is the combo-only counterpart to wallet splits, merges, redemptions, and lifecycle activity. ## Request ``` GET /v3/wallets/{address}/combos/activity ``` ### Query parameters | Parameter | Type | Default | Description | | -------------- | ------- | ------- | ------------------------------------------------------------------------ | | `market_id` | string | -- | Alias for `condition_id` | | `condition_id` | string | -- | Combo condition ID | | `position_id` | string | -- | Combo position ID | | `event_kind` | string | -- | Filter by event kind, for example `PositionsSplit` or `PositionRedeemed` | | `limit` | integer | 100 | Results per page, max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/combos/activity?limit=2" ``` ```json theme={null} { "address": "0x63613e3b96f418332d43cd2af8dc321014d15907", "activity": [ { "position_type": "combo", "event_kind": "PositionsSplit", "side": "Split", "user_address": "0x63613e3b96f418332d43cd2af8dc321014d15907", "combo_condition_id": "0x03d98f6ac4c108c5ca66f79f34908a1d820000000000000000000000000000", "combo_position_id": "1741334187009265192213210063949860811096650382021683265628751751539647840256", "amount_usdc": "0.050750", "tx_hash": "0xf3a8985f04bf869483ef4163a185f296c834eb827b5e5ae3db5bd44558121d51", "log_index": 890, "block_number": 88276713, "timestamp": 1781120055, "legs": [ { "leg_index": 0, "leg_position_id": "547325449395903582555711510844460161809002726131102493368605951180083822592" } ] } ], "rows_returned": 2, "has_more": true, "position_type": "combo", "source": "v3.wallet_combos.activity" } ``` ## Response fields | Field | Type | Description | | -------------------- | -------------- | ----------------------------------------------------------------- | | `event_kind` | string | Public lifecycle event kind | | `side` | string | Human-readable lifecycle side, such as `Split` or `Merge` | | `combo_condition_id` | string | Combo condition ID | | `combo_position_id` | string | Combo position ID | | `amount_usdc` | string | Amount formatted in USDC | | `payout_usdc` | string or null | Redemption payout formatted in USDC when the activity is a redeem | | `tx_hash` | string | Polygon transaction hash | | `block_number` | integer | Polygon block number | | `timestamp` | integer | Unix timestamp in seconds | | `legs` | array | Leg position IDs involved in the lifecycle event | ## Redemption semantics Combo redemptions are normalized to match Polymarket's public combo activity shape. Router and auto-redeemer paths are returned as `event_kind: "PositionRedeemed"` with `side: "Redeem"` and the underlying module kind, such as `Combinatorial`, `Binary`, or `NegRisk`. The raw router/auto-redeemer contract path is used internally for indexing but is not the public event kind. When leg metadata is available, redemption rows include resolved leg statuses and `leg_current_price` values in the `legs` array. # Wallet Combo Positions Source: https://docs.polynode.dev/data/combos/wallet-positions GET /v3/wallets/{address}/combos/positions List combo positions for one wallet with legs, entry basis, balances, and P&L. Returns combo positions for a wallet. Each row includes balance, entry basis, realized/unrealized/total P\&L, status, and leg metadata when available. For a combined standard + combo position list, call `GET /v3/wallets/{address}/positions?include_combos=true`. ## Request ``` GET /v3/wallets/{address}/combos/positions ``` ### Query parameters | Parameter | Type | Default | Description | | -------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------- | | `market_id` | string | -- | Alias for `condition_id` | | `condition_id` | string | -- | Combo condition ID | | `position_id` | string | -- | Combo position ID | | `status` | string | -- | `open`, `closed`, `resolved`, `resolved_win`, `resolved_loss`, `redeemable`, `redeemed`, `observed`, or `all` | | `limit` | integer | 100 | Results per page, max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/combos/positions?limit=1" ``` ```json theme={null} { "address": "0x63613e3b96f418332d43cd2af8dc321014d15907", "positions": [ { "position_type": "combo", "wallet_address": "0x63613e3b96f418332d43cd2af8dc321014d15907", "combo_condition_id": "0x030002fa8781d9f445d838e60524d70bf30000000000000000000000000000", "combo_position_id": "1356959103499736670017337806334234879289930581423500836189165811753797287936", "shares_balance": "0.000100", "entry_avg_price_usdc": "0.2285", "entry_cost_usdc": "0.00", "realized_pnl_usdc": "0.000000", "total_pnl_usdc": "0.000000", "status": "open", "legs_total": 3, "legs": [ { "leg_index": 0, "leg_outcome_label": "Yes", "leg_status": "OPEN", "market": { "slug": "fifwc-ger-civ-2026-06-20", "event_title": "Germany vs. Cote d'Ivoire" } } ] } ], "rows_returned": 1, "has_more": true, "position_type": "combo", "source": "v3.wallet_combos.positions" } ``` ## Response fields | Field | Type | Description | | ---------------------- | -------------- | ------------------------------------------ | | `combo_condition_id` | string | Combo condition ID | | `combo_position_id` | string | Combo ERC-1155 position ID | | `shares_balance` | string | Current combo share balance | | `entry_avg_price_usdc` | string | Weighted-average entry price | | `entry_cost_usdc` | string | Entry cost for current/lifetime accounting | | `realized_pnl_usdc` | string | Realized P\&L | | `unrealized_pnl_usdc` | string or null | Unrealized P\&L when available | | `total_pnl_usdc` | string | Realized + unrealized P\&L | | `status` | string | Current combo position status | | `legs` | array | Leg metadata for the combo | ## Resolution semantics Redeemed combo positions are settled into weighted-average cost basis as soon as PolyNode observes the on-chain combo redemption. A fully redeemed winning combo returns `status: "RESOLVED_WIN"`, `shares_balance: "0.000000"`, `entry_cost_usdc: "0.00"`, and realized/total P\&L equal to redemption payout minus entry basis. A fully redeemed losing combo returns `status: "RESOLVED_LOSS"` with zero open entry cost. `entry_avg_price_usdc` remains the historical weighted-average entry price after close. Leg metadata is refreshed from Polymarket combo metadata. Resolved legs expose `leg_status` values such as `RESOLVED_WIN` or `RESOLVED_LOSS`, `leg_resolved_at`, and `leg_current_price` when available. `legs_resolved` and `legs_pending` are derived from those leg statuses. # Wallet Combo Summary Source: https://docs.polynode.dev/data/combos/wallet-summary GET /v3/wallets/{address}/combos/summary Get combo-only P&L and position counts for one wallet. Returns a combo-only summary for a wallet. Use this endpoint when you want to inspect combo exposure separately from the standard wallet summary. For an all-in wallet summary, call `GET /v3/wallets/{address}?include_combos=true`. ## Request ``` GET /v3/wallets/{address}/combos/summary ``` ### Path parameters | Parameter | Type | Description | | --------- | ------ | -------------- | | `address` | string | Wallet address | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/combos/summary" ``` ```json theme={null} { "address": "0x63613e3b96f418332d43cd2af8dc321014d15907", "summary": { "position_type": "combo", "wallet_address": "0x63613e3b96f418332d43cd2af8dc321014d15907", "open_combo_count": 1495, "closed_combo_count": 19995, "balance_count": 21492, "realized_pnl_usdc": "113.510513", "unrealized_pnl_usdc": null, "total_pnl_usdc": "113.510513", "last_activity_at": "2026-06-11T02:04:19.887367+00:00" }, "rows_returned": 1, "position_type": "combo", "source": "v3.wallet_combos.summary", "elapsed_ms": 13 } ``` ## Response fields | Field | Type | Description | | --------------------- | -------------- | ----------------------------------------------- | | `open_combo_count` | integer | Open combo positions | | `closed_combo_count` | integer | Closed combo positions | | `balance_count` | integer | Total combo balance rows tracked for the wallet | | `realized_pnl_usdc` | string | Realized combo P\&L in USD | | `unrealized_pnl_usdc` | string or null | Current unrealized combo P\&L when available | | `total_pnl_usdc` | string | Realized + unrealized combo P\&L | | `last_activity_at` | string | Last observed combo activity for this wallet | # Wallet Combo Trades Source: https://docs.polynode.dev/data/combos/wallet-trades GET /v3/wallets/{address}/combos/trades List combo trades for one wallet with side, size, price, role, counterparty, and transaction data. Returns combo trades where the wallet participated as maker or taker. Use this endpoint for combo-only trade history. ## Request ``` GET /v3/wallets/{address}/combos/trades ``` ### Query parameters | Parameter | Type | Default | Description | | -------------- | ------- | ------- | ------------------------- | | `market_id` | string | -- | Alias for `condition_id` | | `condition_id` | string | -- | Combo condition ID | | `position_id` | string | -- | Combo position ID | | `side` | string | -- | `BUY`, `SELL`, or `all` | | `limit` | integer | 100 | Results per page, max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/combos/trades?limit=2" ``` ```json theme={null} { "address": "0x63613e3b96f418332d43cd2af8dc321014d15907", "trades": [ { "position_type": "combo", "wallet_role": "maker", "wallet_address": "0x63613e3b96f418332d43cd2af8dc321014d15907", "counterparty_address": "0xe3333700ca9d93003f00f0f71f8515005f6c00aa", "position_id": "1741334187009265192213210063949860811096650382021683265628751751539647840256", "side": "BUY", "price": "0.0197", "size": "0.050750", "fee": "0.000020", "tx_hash": "0xf3a8985f04bf869483ef4163a185f296c834eb827b5e5ae3db5bd44558121d51", "log_index": 896, "block_number": 88276713, "timestamp": 1781120055 } ], "rows_returned": 2, "has_more": true, "position_type": "combo", "source": "v3.wallet_combos.trades", "elapsed_ms": 23 } ``` ## Response fields | Field | Type | Description | | ---------------------- | ------- | -------------------------------- | | `wallet_role` | string | `maker` or `taker` | | `counterparty_address` | string | Counterparty address | | `position_id` | string | Combo position ID | | `side` | string | `BUY` or `SELL` | | `price` | string | Fill price | | `size` | string | Combo shares traded | | `fee` | string | Fee amount | | `tx_hash` | string | Polygon transaction hash | | `log_index` | integer | Log index within the transaction | | `block_number` | integer | Polygon block number | | `timestamp` | integer | Unix timestamp in seconds | # Builder Fees Source: https://docs.polynode.dev/data/fees/builder-fees Rank builders by attributed fee generation and inspect their fee-bearing fills. Builder fee data is based on the `builder` field on attributed fills. `total_fees` is protocol fee generated by attributed fills, not the builder's off-chain rev-share payout. Canonical endpoint references: * [Builder Leaderboard](/data/builders/list) * [Builder Detail](/data/builders/detail) * [Builder Trades](/data/builders/trades) ## Rank Builders by Fees ```bash theme={null} curl "https://api.polynode.dev/v3/builders?sort=fees&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ## Inspect Builder Trades ```bash theme={null} curl "https://api.polynode.dev/v3/builders/0x4898df15ec6590495dc6c0fedf951ade3e64001d47f9caf44a64e86fc11959df/trades?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ## Builder Trade Filters | Parameter | Purpose | | ----------------- | --------------------------- | | `token_id` | One outcome token | | `condition_id` | All outcomes in a condition | | `market_slug` | Market URL slug | | `event_slug` | All markets in an event | | `side` | `buy` or `sell` | | `min_amount` | Minimum raw maker amount | | `category` | Market category | | `tag_slug` | Market tag slug | | `after`, `before` | Time range | Builder codes are 32-byte hex tags, not wallet addresses. # Fees by Receiver Source: https://docs.polynode.dev/data/fees/fees-by-receiver Fetch fee charged events received by one address. Use this when you know the receiver address and want only fee events paid to that receiver. Canonical endpoint reference: [Fee Events](/data/global/fees). ## Query ```bash theme={null} curl "https://api.polynode.dev/v3/fees/0x115f48dc2a731aa16251c6d6e1befc42f92accc9?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ## Filters | Parameter | Purpose | | --------- | ------------------ | | `after` | Start timestamp | | `before` | End timestamp | | `limit` | Page size, max 300 | | `offset` | Pagination offset | ## Returned Data Rows match the global fee event shape, with the receiver implied by the path. # Global Fee Events Source: https://docs.polynode.dev/data/fees/global-fees Browse fee charged events across Polymarket. Use this when you want the global stream of fee charged events rather than wallet-owned fills. Canonical endpoint reference: [Fee Events](/data/global/fees). ## Query ```bash theme={null} curl "https://api.polynode.dev/v3/fees?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ## Filters | Parameter | Purpose | | --------- | ------------------ | | `after` | Start timestamp | | `before` | End timestamp | | `limit` | Page size, max 300 | | `offset` | Pagination offset | ## Returned Data Rows include the fee receiver, raw amount, transaction hash, and timestamp. Amounts are raw 6-decimal USDC values unless a page says otherwise. # Maker Rebates Source: https://docs.polynode.dev/data/fees/maker-rebates Query Polymarket-reported maker rebate records for one maker/date. Maker rebates are Polymarket accounting credits. They are not the same thing as trader-paid fill fees. Canonical endpoint reference: [Wallet Maker Rebates](/data/wallets/rebates). ## Query ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63ce342161250d705dc0b16df89036c8e5f9ba9a/rebates" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ## Optional Date ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63ce342161250d705dc0b16df89036c8e5f9ba9a/rebates?date=2026-06-17" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ## Notes * Rebate lookups are keyed by CLOB maker/signing address. * Passing a Safe or proxy wallet can return an empty `rebates` array with a hint. * This endpoint can return zero rows for a valid wallet/date. # Fees Overview Source: https://docs.polynode.dev/data/fees/overview Where to query trader-paid fees, fee receiver events, builder-attributed fees, maker rebates, and reward market configuration. PolyNode exposes fee data in a few different forms. Use this section when you know you need fee or rebate data but are not sure which endpoint matches the accounting question. Fees, rebates, rewards, and builder payouts are separate concepts. Trader-paid fees come from executed fills. Fee events show receiver-level on-chain fee charges. Maker rebates and reward configuration are Polymarket accounting surfaces. Builder rev share is paid off-chain and is not the same as `total_fees`. ## Fee Surfaces | Need | Endpoint | Use this when | | --------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------- | | Wallet all-time fee summary | `GET /v3/wallets/{address}?include_accounting_summary=true` | You want the exact all-time trader-paid fee total without paging raw fills | | Wallet fee-bearing fills | `GET /v3/wallets/{address}/fees-paid` | You want trades where a wallet paid a nonzero fill fee | | Global fee events | `GET /v3/fees` | You want fee charged events across the platform | | Fees by receiver | `GET /v3/fees/{receiver}` | You want fee events received by one address | | Builder fee ranking | `GET /v3/builders?sort=fees` | You want builders ranked by fees generated on attributed fills | | Builder fee trades | `GET /v3/builders/{code}/trades` | You want the underlying attributed fills, including per-fill `fee` | | Maker rebates | `GET /v3/wallets/{address}/rebates` | You want Polymarket-reported maker rebates for one maker/date | | Reward configs | `GET /v3/rewards/markets` | You want public reward-market configuration, not wallet-earned rewards | ## Quick Examples ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63ce342161250d705dc0b16df89036c8e5f9ba9a?include_accounting_summary=true" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63ce342161250d705dc0b16df89036c8e5f9ba9a/fees-paid?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ```bash theme={null} curl "https://api.polynode.dev/v3/fees?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ```bash theme={null} curl "https://api.polynode.dev/v3/builders?sort=fees&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ## Related Pages | Page | Canonical reference | | --------------------------------------------------------- | --------------------------------------------- | | [Wallet Summary](/data/wallets/summary) | [Wallet Summary](/data/wallets/summary) | | [Wallet Fees Paid](/data/fees/wallet-fees-paid) | [Wallet Fees Paid](/data/wallets/fees-paid) | | [Global Fee Events](/data/fees/global-fees) | [Fee Events](/data/global/fees) | | [Fees by Receiver](/data/fees/fees-by-receiver) | [Fee Events](/data/global/fees) | | [Builder Fees](/data/fees/builder-fees) | [Builder Leaderboard](/data/builders/list) | | [Maker Rebates](/data/fees/maker-rebates) | [Wallet Maker Rebates](/data/wallets/rebates) | | [Reward Market Configs](/data/fees/reward-market-configs) | [Reward Markets](/data/rewards/markets) | # Reward Market Configs Source: https://docs.polynode.dev/data/fees/reward-market-configs Query public Polymarket reward-market configuration. Reward market configuration describes markets with reward programs. It is not a per-wallet earned rewards ledger. Canonical endpoint references: * [Reward Markets](/data/rewards/markets) * [Reward Market Detail](/data/rewards/market) ## List Reward Markets ```bash theme={null} curl "https://api.polynode.dev/v3/rewards/markets" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ## One Condition ```bash theme={null} curl "https://api.polynode.dev/v3/rewards/markets/0x0001cb8c0b39aeb614ab9a43867595317f06ede9c011661513065c638fbbefda" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ## Filters | Parameter | Purpose | | ------------- | ---------------------------------- | | `sponsored` | Filter to sponsored reward markets | | `next_cursor` | Provider cursor pagination | Use this for public market reward configuration. Do not treat it as arbitrary-wallet earned LP rewards. # Wallet Fees Paid Source: https://docs.polynode.dev/data/fees/wallet-fees-paid Query fee-bearing executed fills for one wallet. Use this when you want the fills where a specific wallet paid a nonzero trader fee. Canonical endpoint reference: [Wallet Fees Paid](/data/wallets/fees-paid). For the exact all-time total, use [Wallet Summary](/data/wallets/summary) with `include_accounting_summary=true` and read `accounting_summary.fees_paid.amount`. ## Query ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63ce342161250d705dc0b16df89036c8e5f9ba9a/fees-paid?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ## Filters | Parameter | Purpose | | --------- | ----------------------------- | | `after` | Start timestamp, Unix seconds | | `before` | End timestamp, Unix seconds | | `order` | `asc` or `desc` | | `limit` | Page size, max 300 | | `offset` | Pagination offset | ## Returned Data The response includes a `fees` array with the same market-enriched fill fields as wallet trades, plus: | Field | Meaning | | ---------------------- | ----------------------------------------------------- | | `fee` | Fee in USD | | `fee_paid` | Same value as `fee`, named for fee-specific summaries | | `fee_paid_raw` | Raw 6-decimal amount | | `page_total_fees_paid` | Sum for the returned page only | | `fee_semantics` | Reminder that this is trader-paid fill fees | `page_total_fees_paid` is page-scoped. Use `GET /v3/wallets/{address}?include_accounting_summary=true` when you need the all-time total. # Condition Detail Source: https://docs.polynode.dev/data/global/conditions GET /v3/conditions/{condition_id} Get full market condition data including payouts, outcomes, metadata, and resolution status. Returns full condition data for a market, including payout structure, outcomes, and enriched market metadata. ## Request ``` GET /v3/conditions/{condition_id} ``` ### Path parameters | Parameter | Type | Description | | -------------- | ------ | --------------------------------- | | `condition_id` | string | Market condition ID (0x-prefixed) | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/conditions/0x6c4d221b3cf2c8d17467c70a8aa3c714e30299b6e57cd3e4269dc8a41d2b0cd8?key=YOUR_KEY" ``` ```json theme={null} { "id": "0x6c4d221b3cf2c8d17467c70a8aa3c714e30299b6e57cd3e4269dc8a41d2b0cd8", "position_ids": [ "30698649476690694814108493145041085718609914990967907518412053225237708492787", "43362318061877762261164265810725209343672429810065542703856317173848260953021" ], "payout_numerators": [0, 1], "payout_denominator": "1", "question": "Will Trump pardon Joe Exotic \"The Tiger King\" in 2025?", "slug": "will-trump-pardon-joe-exotic-the-tiger-king-in-2025-784-928-365", "outcomes": ["Yes", "No"], "end_date": "2025-12-31 12:00:00+00", "active": true, "closed": true, "neg_risk": false, "event_slug": "who-will-trump-pardon-in-2025", "event_title": "Who will Trump pardon in 2025?", "category": null, "liquidity": "0", "volume": "99993.937724000003072433173656463623046875", "tags": ["Politics", "Trump", "SBF", "cz", "Trump Presidency", "Ghislaine Maxwell"], "winning_outcome_index": 1, "elapsed_ms": 6 } ``` `position_ids` are **decimal strings**, not JSON numbers. Polymarket token IDs are 78-digit values that exceed IEEE 754 float precision — any JSON parser that decodes them as floats will corrupt the lower digits. Always treat them as strings. `volume` and `liquidity` are returned at full database precision (up to \~50 digits after the decimal). Round them on the client; do not store as a JS `Number`. ## Response fields | Field | Type | Nullable | Description | | ----------------------- | ---------------- | -------- | ----------------------------------------------------------- | | `id` | string | no | Condition ID | | `position_ids` | array\ | no | Outcome token IDs (decimal-string-encoded — see note above) | | `payout_numerators` | array\ | no | Payout numerators per outcome (non-zero after resolution) | | `payout_denominator` | string | no | Payout denominator | | `question` | string | no | Market question | | `slug` | string | no | Market URL slug | | `outcomes` | array\ | no | Outcome labels | | `end_date` | string | yes | Market end date (Postgres timestamp with offset) | | `active` | boolean | no | Currently active | | `closed` | boolean | no | No longer accepting orders | | `neg_risk` | boolean | no | Multi-outcome (neg-risk) market | | `event_slug` | string | yes | Parent event slug | | `event_title` | string | yes | Parent event title | | `category` | string | yes | Optional category label | | `liquidity` | string (decimal) | no | Current liquidity, full-precision string | | `volume` | string (decimal) | no | All-time volume, full-precision string | | `tags` | array\ | yes | Category tags in their canonical (Title Case) form | | `winning_outcome_index` | integer | yes | Winning outcome (null if unresolved) | # Fee Events Source: https://docs.polynode.dev/data/global/fees GET /v3/fees Browse fee charged events across all Polymarket trades. Returns fee events from the Polymarket exchange. Each fee event records the receiver, amount, and associated transaction. ## Request ``` GET /v3/fees ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ------------------------------------ | | `after` | integer | -- | Start of time range (Unix timestamp) | | `before` | integer | -- | End of time range | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ### By receiver ``` GET /v3/fees/{receiver} ``` Returns fees received by a specific address. ## Example ```bash theme={null} curl https://api.polynode.dev/v3/fees?limit=1 ``` ```json theme={null} { "data": [ { "id": "0x1e20bc6a2107b61fa179b3ab17047b43569031e238f2b4ac92d9fa25a2822e3d_633", "timestamp": "1778962419", "receiver": "0x115f48dc2a731aa16251c6d6e1befc42f92accc9", "amount": "318480", "transaction_hash": "0x1e20bc6a2107b61fa179b3ab17047b43569031e238f2b4ac92d9fa25a2822e3d" } ], "rows_returned": 1, "has_more": true, "offset": 0, "limit": 1, "elapsed_ms": 3 } ``` When fetching `GET /v3/fees/{receiver}`, the `receiver` field is omitted from each row (it's redundant — every row matches the path parameter). ## Response fields | Field | Type | Description | | ------------------ | ------ | --------------------------- | | `id` | string | Unique fee event ID | | `timestamp` | string | Unix timestamp | | `receiver` | string | Fee receiver address | | `amount` | string | Fee amount (6-decimal USDC) | | `transaction_hash` | string | On-chain transaction hash | # Global Positions Source: https://docs.polynode.dev/data/global/positions GET /v3/positions Browse the latest positions across all Polymarket wallets. Filter by market, status, or size. Returns the most recent position changes across all wallets, enriched with market metadata and current prices. Defaults to most recent positions first. ## Request ``` GET /v3/positions ``` ### Query parameters | Parameter | Type | Default | Description | | -------------- | ------- | ------- | --------------------------------------------------------------- | | `status` | string | -- | Filter: `open`, `closed`, `redeemable`, `redeemed` | | `sort` | string | recent | Sort by: `pnl`, `size`, `volume`. Default is most recent first. | | `order` | string | `desc` | `asc` or `desc` | | `token_id` | string | -- | Filter by outcome token ID | | `condition_id` | string | -- | Filter by market condition ID | | `market_slug` | string | -- | Filter by market slug | | `min_size` | number | -- | Minimum position size in USD | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ### Position statuses Global positions use the same lifecycle statuses as wallet positions: | Status | Meaning | | ------------ | --------------------------------------------------------------- | | `open` | Wallet holds shares in an active (unresolved) market | | `closed` | Position fully sold before market resolved | | `redeemable` | Market resolved, wallet still holds shares that can be redeemed | | `redeemed` | Market resolved and shares have been redeemed | ## Examples ### Latest position changes ```bash theme={null} curl https://api.polynode.dev/v3/positions?limit=5 ``` ### Biggest open positions ```bash theme={null} curl https://api.polynode.dev/v3/positions?sort=size&status=open&limit=5 ``` ### Who holds positions in a specific market ```bash theme={null} curl "https://api.polynode.dev/v3/positions?condition_id=0xdd22472e...&sort=pnl&limit=10" ``` ### Unclaimed redeemable positions ```bash theme={null} curl "https://api.polynode.dev/v3/positions?status=redeemable&min_size=100&sort=size&limit=10" ``` ## Response fields (per row in `positions`) This endpoint returns a leaner field set than [`/v3/wallets/{addr}/positions`](/data/wallets/positions). For full enrichment (`last_trade_at`, `tag_slugs`, `event_slug`, `market_status`, `opposite_asset`, `price_source`, `redeemable_payout`, `won`, etc.), use the per-wallet endpoint. | Field | Type | Nullable | Description | | ---------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `wallet` | string | no | Wallet address | | `token_id` | string | no | Outcome token ID (decimal-string-encoded — see [Identifiers](/data/overview#identifiers-and-large-numbers)) | | `size` | number | no | Current shares held (USD) | | `avg_price` | number | no | Weighted-average entry price (USD per outcome token) | | `realized_pnl` | number | no | Realized P\&L (USD) | | `unrealized_pnl` | number | no | Unrealized P\&L (USD) — `(current_price - avg_price) * size` | | `total_pnl` | number | no | `realized_pnl + unrealized_pnl` (USD) | | `current_price` | number | no | Latest token price (USD). Falls back to `0` if no price is known yet. | | `total_bought` | number | no | Total USD spent buying this position | | `resolved` | boolean | no | Whether the market has settled | | `status` | string | no | `open`, `closed`, `redeemable`, or `redeemed` | | `market` | string | yes | Market question (null if the token's metadata hasn't been backfilled) | | `slug` | string | yes | Market URL slug | | `outcome` | string | yes | Outcome label | | `outcome_index` | integer | yes | Outcome position (0 or 1) | | `image` | string | yes | Market image URL | | `condition_id` | string | yes | Market condition ID | | `neg_risk` | boolean | yes | Neg-risk (multi-outcome) market flag | # Market Resolutions Source: https://docs.polynode.dev/data/global/resolutions GET /v3/resolutions Browse recent market resolutions with payout data and market metadata. Returns market resolution events, sorted by most recent. Each resolution includes the payout structure and enriched market metadata. ## Request ``` GET /v3/resolutions ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ----------------- | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl https://api.polynode.dev/v3/resolutions?limit=1 ``` ```json theme={null} { "data": [ { "category": null, "condition_id": "0xbd7f5df4a14a1b0ec35354ed910acf8c538d5e556c8389fcb58bb6a45c32e5b6", "created_at": "2026-05-15 22:16:02.633047", "outcomes": [ "Over", "Under" ], "payout_denominator": "1", "payout_numerators": [ "0", "1" ], "question": "Chicago Cubs vs. Chicago White Sox: O/U 11.5", "resolved_at": "1778883350000", "slug": "mlb-chc-cws-2026-05-15-total-11pt5" } ], "elapsed_ms": 6, "has_more": true, "limit": 1, "offset": 0, "rows_returned": 1 } ``` ## Response fields | Field | Type | Description | | -------------------- | ------ | --------------------------------------------------------------- | | `condition_id` | string | Market condition ID | | `payout_numerators` | array | Payout for each outcome (e.g. `["0", "1"]` means outcome 1 won) | | `payout_denominator` | string | Payout denominator (usually `"1"`) | | `resolved_at` | string | Resolution timestamp (milliseconds) | | `created_at` | string | When the resolution was recorded | | `question` | string | Market question | | `slug` | string | Market slug | | `outcomes` | array | Outcome labels (e.g. `["Yes", "No"]`) | | `category` | string | Market category | # Platform Stats Source: https://docs.polynode.dev/data/global/stats GET /v3/stats Global aggregates for the entire Polymarket database. Total fills, wallets, volume, and P&L. Returns platform-wide aggregates from the `global_stats` materialized view. Instant response. ## Request ``` GET /v3/stats ``` ## Example ```bash theme={null} curl https://api.polynode.dev/v3/stats ``` ```json theme={null} { "total_fills": 1227599616, "total_redemptions": 160535696, "total_wallets": 2676595, "total_positions": "227857354", "total_realized_pnl": "2240414321986960", "total_gross_profit": "13881389085858405", "total_gross_loss": "-11640974763871445", "total_volume": "2149019620148621138", "elapsed_ms": 0 } ``` ## Related global endpoints | Endpoint | Description | | ------------------------- | ------------------------------------------------------------------- | | `GET /v3/trades` | Global trade feed. `?after=`, `?before=`, `?min_amount=`, `?order=` | | `GET /v3/fees` | Fee events. `?after=`, `?before=` | | `GET /v3/fees/{receiver}` | Fees by receiver address | | `GET /v3/resolutions` | Recent market resolutions with metadata | | `GET /v3/conditions/{id}` | Condition with payouts and market metadata | # Global Trades Source: https://docs.polynode.dev/data/global/trades GET /v3/trades Browse the full Polymarket trade history with market enrichment, computed prices, and order hashes. Returns all Polymarket fills across all wallets, enriched with market metadata. Supports time range filtering, minimum amount, and token filtering. ## Request ``` GET /v3/trades ``` ### Query parameters | Parameter | Type | Default | Description | | ------------ | ------- | ------- | --------------------------------------------- | | `after` | integer | -- | Start of time range (Unix timestamp) | | `before` | integer | -- | End of time range (Unix timestamp) | | `token_id` | string | -- | Filter by outcome token | | `builder` | string | -- | Filter by builder code (hex) | | `min_amount` | integer | -- | Minimum maker\_amount\_filled (raw 6-decimal) | | `order` | string | `desc` | `asc` or `desc` | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl https://api.polynode.dev/v3/trades?limit=1 ``` ```json theme={null} { "trades": [ { "id": "0xd4064c6d77afc87390ae9c371bb87d7f4687ac8f2cb1e600ad695928397c1dce_1483", "maker": "0x84cfffc3f16dcc353094de30d4a45226eccd2f63", "taker": "0xe2222d279d744050d28e00520010520000310f59", "maker_asset_id": "0", "taker_asset_id": "9593514921851392841100196009218771406639098519115981476479629056691973100726", "maker_amount": 49.301, "taker_amount": 70.43, "fee": 0.4437, "price": 0.7, "size": 70.43, "timestamp": "1778674687", "transaction_hash": "0xd4064c6d77afc87390ae9c371bb87d7f4687ac8f2cb1e600ad695928397c1dce", "order_hash": "0xad6a2b6ebf9537d72374fe77a41a7fc285906827a1adddda28948d37c09a6640", "builder": "0x0000000000000000000000000000000000000000000000000000000000000000", "side": 0, "role": "maker", "direction": "BUY", "market": "Will Racing Club de Lens win on 2026-05-13?", "slug": "fl1-rcl-psg-2026-05-13-rcl", "outcome": "No", "outcome_index": 1, "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/league-fl1.png", "condition_id": "0x8e4ef7519b5dc20cebc4c19a07abae587e4abf889e415169ec6b1477d796e1b5" } ], "rows_returned": 1, "has_more": true, "offset": 0, "limit": 1, "elapsed_ms": 2 } ``` ## Response fields Each trade contains the same fields as [Wallet Trades](/data/wallets/trades), including market enrichment (market, slug, outcome, image, condition\_id), computed price and size in USD, order\_hash, and direction. On global trades (no wallet context), `role` is always `"maker"` and `direction` reflects the maker's action. Use the [Wallet Trades](/data/wallets/trades) endpoint for wallet-specific perspective. # Leaderboard Source: https://docs.polynode.dev/data/leaderboard/global GET /v3/leaderboard Rank all Polymarket wallets by P&L, volume, profit, or win count. Supports one primary market filter dimension per request. All amounts in USD. Ranks wallets by realized P\&L, total P\&L (realized + unrealized), gross profit, volume, or win count. All monetary values are in USD. Leaderboard filters are designed around one primary dimension per request. Use one of `category`, `tags`/`tag_slug`, `market`/`market_slug`, `event_slug`, or `condition_id`. For compound analysis, run separate leaderboard calls or filter client-side. ## Request ``` GET /v3/leaderboard ``` ### Query parameters | Parameter | Type | Default | Description | | -------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `sort` | string | `total` | Sort by: `total`, `total_pnl`, `realized`, `profit`, `loss`, `volume`, `wins`, `positions`, `unrealized` | | `category` | string | -- | Filter to one market category, case-insensitive | | `tags` | string | -- | Filter to one tag slug. Comma-separated multi-tag filters may be accepted for specific cases, but one tag is the recommended leaderboard shape. | | `tag_slug` | string | -- | Alias for a single `tags` value | | `market` | string | -- | Filter by condition ID or market slug | | `market_slug` | string | -- | Filter by market slug | | `event_slug` | string | -- | Filter by parent event slug | | `condition_id` | string | -- | Filter by market condition ID | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl https://api.polynode.dev/v3/leaderboard?limit=2&sort=total ``` ### Filter by one dimension ```bash theme={null} curl "https://api.polynode.dev/v3/leaderboard?category=crypto&limit=20" curl "https://api.polynode.dev/v3/leaderboard?tag_slug=us-election&limit=20" curl "https://api.polynode.dev/v3/leaderboard?market_slug=will-donald-trump-win-the-popular-vote-in-the-2024-presidential-election&limit=20" curl "https://api.polynode.dev/v3/leaderboard?event_slug=presidential-election-popular-vote-winner-2024&limit=20" ``` ```json theme={null} { "leaderboard": [ { "rank": 1, "wallet": "0x56687bf447db6ffa42ffe2204a05edaa20f55839", "net_realized_pnl": 22053845.825455, "gross_profit": 22057977.181649, "gross_loss": -4131.356194, "unrealized_pnl": 0.000688755, "total_pnl": 22053845.826143753, "wins": 18, "losses": 4, "position_count": 22, "open_positions": 1, "total_volume": 43013258.515682 } ], "rows_returned": 1, "has_more": true, "offset": 0, "limit": 2, "elapsed_ms": 1028 } ``` ## Response fields | Field | Type | Description | | ------------------ | ------- | ----------------------------------------- | | `rank` | integer | Position in the leaderboard | | `wallet` | string | Wallet address | | `net_realized_pnl` | number | Net realized P\&L (USD) | | `gross_profit` | number | Sum of winning positions (USD) | | `gross_loss` | number | Sum of losing positions (USD, negative) | | `unrealized_pnl` | number | Paper P\&L from open positions (USD) | | `total_pnl` | number | `net_realized_pnl + unrealized_pnl` (USD) | | `wins` | integer | Winning position count | | `losses` | integer | Losing position count | | `position_count` | integer | Total positions | | `open_positions` | integer | Currently held positions | | `total_volume` | number | Total volume traded (USD) | # Tag Leaderboard Source: https://docs.polynode.dev/data/leaderboard/tag-leaderboard GET /v3/tags/{slug}/leaderboard Rank wallets by P&L within a specific market category (tag). Works for niche tags. Returns a P\&L leaderboard filtered to markets with a specific tag. This direct tag route is best for smaller/niche tags. For broad tags or categories such as `crypto`, `sports`, or `politics`, use the projected global leaderboard with `?tags=` or `?category=`. It is the preferred route for high-cardinality leaderboard filters. ## Request ``` GET /v3/tags/{slug}/leaderboard ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | --------- | ---------------------------------------------------------- | | `sort` | string | `net_pnl` | Sort by `net_pnl` (default), `volume`, `profit`, or `loss` | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ### Slug handling The tag slug is matched case-insensitively. URL-style hyphens are also converted to spaces, so `/v3/tags/us-election/leaderboard` resolves to the stored tag `US Election`. ### Broad tag alternative ```bash theme={null} curl "https://api.polynode.dev/v3/leaderboard?tags=crypto&limit=20" curl "https://api.polynode.dev/v3/leaderboard?category=crypto&limit=20" ``` Use [`GET /v3/leaderboard`](/data/leaderboard/global) for broad tag/category rankings or pick a more specific tag for this route. ## Example ```bash theme={null} curl https://api.polynode.dev/v3/tags/taylor-swift/leaderboard?limit=1 ``` ```json theme={null} { "data": [ { "wallet": "0x4aec3e3c555ecbeec58adba532e8ea8b1c76e9b6", "net_pnl": "60212.914000000000", "gross_profit": "60212.914000000000", "gross_loss": "0.000000000000000000", "total_volume": "62721.000000000000", "position_count": 3 } ], "rows_returned": 1, "has_more": true, "offset": 0, "limit": 1, "elapsed_ms": 26 } ``` ## Response fields (per row in `data`) | Field | Type | Description | | ---------------- | ---------------- | ----------------------------------------------- | | `wallet` | string | Wallet address | | `net_pnl` | string (decimal) | Net realized P\&L in this tag (USD) | | `gross_profit` | string (decimal) | Sum of winning positions (USD) | | `gross_loss` | string (decimal) | Sum of losing positions (USD, negative or zero) | | `total_volume` | string (decimal) | Total volume traded in this tag (USD) | | `position_count` | integer | Number of positions in markets with this tag | # Tag Markets Source: https://docs.polynode.dev/data/leaderboard/tag-markets GET /v3/tags/{slug} List markets that belong to a specific tag (category), ordered by all-time volume or other metrics. Returns every market tagged with the given slug. Useful for building category landing pages or filtering search results. ## Request ``` GET /v3/tags/{slug} ``` ### Path parameters | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | `slug` | string | Tag slug. Matched case-insensitively. URL-style hyphens are converted to spaces, so `us-election` resolves to the stored tag `US Election`. | ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ----------------- | -------------------------------------------------------- | | `sort` | string | `volume_all_time` | Sort by `volume_all_time`, `liquidity`, or `created_at`. | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/tags/ufc?limit=2&key=YOUR_KEY" ``` ```json theme={null} { "data": [ { "condition_id": "0x61ef853c0ecc72a583da722cf6434681f9b92d10652cded44d8f889beb923418", "question": "Dvalishvili vs. Sandhagen", "slug": "ufc-dvalishvili-vs-sandhagen-2025-10-04", "outcomes": ["Dvalishvili", "Sandhagen"], "category": null, "active": true, "closed": true, "neg_risk": false, "volume_all_time": "999731.6550259999930858612060546875", "liquidity": "0", "end_date": "2025-10-11 18:00:00+00", "event_slug": "ufc-320-ankalaev-vs-pereira-2", "event_title": "UFC 320: Ankalaev vs. Pereira 2", "winning_outcome_index": 0 } ], "rows_returned": 1, "has_more": true, "offset": 0, "limit": 1, "elapsed_ms": 42 } ``` ## Response fields (per row in `data`) | Field | Type | Description | | ----------------------- | ---------------- | ------------------------------------------------------ | | `condition_id` | string | Market condition ID | | `question` | string | Market question text | | `slug` | string | Market URL slug | | `outcomes` | array\ | Outcome labels | | `category` | string \| null | Optional category label | | `active` | boolean | Market is currently active | | `closed` | boolean | Market is closed (no longer accepting orders) | | `neg_risk` | boolean | Neg-risk (multi-outcome) market | | `volume_all_time` | string (decimal) | All-time USD volume | | `liquidity` | string (decimal) | Current liquidity in USD | | `end_date` | string \| null | Market resolution deadline (ISO timestamp with offset) | | `event_slug` | string \| null | Parent event slug | | `event_title` | string \| null | Parent event title | | `winning_outcome_index` | integer \| null | Winning outcome (null if unresolved) | ## Errors If no tag matches the slug (case-insensitive, hyphen-converted), the endpoint returns `404` with: ```json theme={null} {"error": "tag not found", "tag": "notarealtag"} ``` For a wallet-level P\&L leaderboard scoped to a tag, see [Tag Leaderboard](/data/leaderboard/tag-leaderboard). # Tags Source: https://docs.polynode.dev/data/leaderboard/tags GET /v3/tags List all market tags with the number of markets in each category. Returns all market tag categories ranked by market count. ## Request ``` GET /v3/tags ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ----------------- | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/tags?limit=3&key=YOUR_KEY" ``` ```json theme={null} { "tags": [ { "tag": "Sports", "market_count": 575318 }, { "tag": "Games", "market_count": 522716 }, { "tag": "Hide From New", "market_count": 447811 } ], "rows_returned": 3, "has_more": true, "elapsed_ms": 1241 } ``` ## Response fields | Field | Type | Description | | -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------- | | `tag` | string | Canonical tag label as stored in the database. Title Case, spaces preserved (e.g. `"US Election"`, `"Trump Presidency"`). | | `market_count` | integer | Number of markets with this tag | ## Using tag values in URLs The `tag` value above is the canonical (Title Case) form. When passing a tag in a URL path (e.g. [`/v3/tags/{slug}`](/data/leaderboard/tag-markets) or [`/v3/tags/{slug}/leaderboard`](/data/leaderboard/tag-leaderboard)), use a lowercase, hyphen-separated form — the API resolves it back to the canonical tag: | URL form | Resolves to | | ------------------- | ------------------- | | `sports` | `Sports` | | `us-election` | `US Election` | | `primary-elections` | `primary elections` | The match is case-insensitive and converts hyphens to spaces. If no tag matches, you get `404 {"error":"tag not found","tag":...}`. # Market by Condition Source: https://docs.polynode.dev/data/markets/by-condition GET /v3/markets/condition/{condition_id} Get market metadata, token IDs, and outcomes by condition ID. Returns full market metadata for a condition, including all token IDs and their outcome labels. ## Request ``` GET /v3/markets/condition/{condition_id} ``` ### Path parameters | Parameter | Type | Description | | -------------- | ------ | --------------------------------- | | `condition_id` | string | Market condition ID (0x-prefixed) | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/markets/condition/0x6c4d221b3cf2c8d17467c70a8aa3c714e30299b6e57cd3e4269dc8a41d2b0cd8?key=YOUR_KEY" ``` ```json theme={null} { "data": [ { "condition_id": "0x6c4d221b3cf2c8d17467c70a8aa3c714e30299b6e57cd3e4269dc8a41d2b0cd8", "question": "Will Trump pardon Joe Exotic \"The Tiger King\" in 2025?", "slug": "will-trump-pardon-joe-exotic-the-tiger-king-in-2025-784-928-365", "outcomes": ["Yes", "No"], "category": null, "active": true, "closed": true, "neg_risk": false, "volume_all_time": "99993.937724000003072433173656463623046875", "liquidity": "0", "end_date": "2025-12-31 12:00:00+00", "event_slug": "who-will-trump-pardon-in-2025", "event_title": "Who will Trump pardon in 2025?", "tag_slugs": ["Politics", "Trump", "SBF", "cz", "Trump Presidency", "Ghislaine Maxwell"], "token_ids": [ "30698649476690694814108493145041085718609914990967907518412053225237708492787", "43362318061877762261164265810725209343672429810065542703856317173848260953021" ], "token_outcomes": ["Yes", "No"], "winning_outcome_index": 1 } ], "rows_returned": 1, "has_more": false, "offset": 0, "limit": 1, "elapsed_ms": 3 } ``` The response is wrapped in the standard `data: [{...}]` envelope. If no condition matches, `data` is `[]` and `rows_returned` is `0`. `volume_all_time` and `liquidity` are full-precision decimal strings — round on the client. `token_ids` are decimal strings, not numbers — they are 78-digit values and lose precision if parsed as floats. ## Response fields (per row in `data`) | Field | Type | Nullable | Description | | ----------------------- | ---------------- | -------- | -------------------------------------------------------------- | | `condition_id` | string | no | Market condition ID | | `question` | string | no | Market question | | `slug` | string | no | URL slug | | `outcomes` | array\ | no | Outcome labels | | `token_ids` | array\ | no | Outcome token IDs (decimal-string-encoded) | | `token_outcomes` | array\ | no | Outcome label for each token, in the same order as `token_ids` | | `active` | boolean | no | Currently active | | `closed` | boolean | no | No longer accepting orders | | `neg_risk` | boolean | no | Multi-outcome (neg-risk) market | | `volume_all_time` | string (decimal) | no | Full-precision volume in USD | | `liquidity` | string (decimal) | no | Full-precision current liquidity in USD | | `end_date` | string | yes | Market end date (Postgres timestamp with offset) | | `event_slug` | string | yes | Parent event slug | | `event_title` | string | yes | Parent event title | | `category` | string | yes | Optional category label | | `tag_slugs` | array\ | yes | Category tags in their canonical (Title Case) form | | `winning_outcome_index` | integer | yes | Which outcome won (null if unresolved) | # Market by Slug Source: https://docs.polynode.dev/data/markets/by-slug GET /v3/markets/slug/{slug}/trades Get trades and positions for a market by its URL slug. Look up a market by its slug (the URL path on Polymarket) and get trades or positions across all outcomes. ## Trades by slug ``` GET /v3/markets/slug/{slug}/trades ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ------------------------------------ | | `after` | integer | -- | Start of time range (Unix timestamp) | | `before` | integer | -- | End of time range | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ### Example ```bash theme={null} curl "https://api.polynode.dev/v3/markets/slug/dota2-aur1-liquid-2026-05-13/trades?limit=1" ``` Returns the same enriched trade format as [Wallet Trades](/data/wallets/trades). ## Positions by slug ``` GET /v3/markets/slug/{slug}/positions ``` Returns every wallet holding (or that has ever held) any outcome of this market, across V1 + V2, with realized P\&L computed from the V2 adapter pipeline. ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | -------------- | ------------------------------------------------------------------------------------------ | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | | `sort` | string | `realized_pnl` | One of `amount`, `volume`, `realized_pnl`, `pnl` | | `status` | string | -- | `open` (current holders only) or `closed` (zeroed-out positions only). Omit to return all. | For this market-scoped endpoint, `status` is a holder-state filter on the current raw balance. `open` means `amount > 0`; `closed` means `amount = 0`. It is not the wallet/global lifecycle status split, where resolved nonzero positions are `redeemable` and resolved zero-balance positions are `redeemed`. ### Example ```bash theme={null} curl "https://api.polynode.dev/v3/markets/slug/dota2-aur1-liquid-2026-05-13/positions?limit=1" ``` ```json theme={null} { "data": [ { "user": "0xca70278943df14029a0f819f9baf060f67860908", "token_id": "2241710011255999173993483096156975147842635212627942653724495466212043852479", "amount": "0", "avg_price": "500000", "total_bought": "2000000", "realized_pnl": "998000" } ], "rows_returned": 1, "has_more": true, "offset": 0, "limit": 1, "elapsed_ms": 179 } ``` ### Response fields | Field | Type | Description | | -------------- | ------ | ------------------------------------------------------------------------- | | `user` | string | Wallet address (lowercased) | | `token_id` | string | Outcome token ID this row is about | | `amount` | string | Current balance in raw 6-decimal USDC (`0` if fully exited) | | `avg_price` | string | Time-weighted average buy price in raw 6-decimal USDC (`500000` = \$0.50) | | `total_bought` | string | Cumulative cost-basis of all purchases in raw 6-decimal USDC | | `realized_pnl` | string | Realized P\&L on closed-out portion of the position in raw 6-decimal USDC | # Market Candles Source: https://docs.polynode.dev/data/markets/candles Trade-built OHLCV candles for V3 Polymarket outcome tokens, condition IDs, and market slugs. Build OHLCV candles from V3 Polymarket trade fills. Candles are bucketed by trade timestamp and include price, volume, VWAP, trade count, and first/last fill markers for each bucket. Use this endpoint for V3 charting when you already have an outcome token ID, condition ID, or market slug. ## Endpoints ```http theme={null} GET /v3/markets/{token_id}/candles GET /v3/markets/condition/{condition_id}/candles GET /v3/markets/slug/{slug}/candles ``` The token route returns one candle series for one outcome token. The condition and slug routes resolve the market's outcome tokens and return a `series` array, one entry per outcome token. Pass `token_id` on the condition or slug route to return only one outcome. ## Authentication ```bash theme={null} curl "https://api.polynode.dev/v3/markets/{token_id}/candles?resolution=5m&limit=120" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ## Query parameters | Parameter | Type | Default | Description | | ------------ | ------: | -------: | ------------------------------------------------------------------------------------------------------------- | | `resolution` | string | `1h` | Bucket size. One of `1m`, `5m`, `15m`, `1h`, `4h`, `1d`. | | `limit` | integer | `120` | Maximum number of candle buckets in the requested window. Clamped by resolution. | | `direction` | string | `before` | `before` returns the window ending at `anchor_ts` or now. `after` returns the window starting at `anchor_ts`. | | `anchor_ts` | integer | now | Unix timestamp in seconds. Required when `direction=after`. | | `start` | integer | -- | Explicit window start timestamp in seconds. Alias: `start_time`, `after`. | | `end` | integer | -- | Explicit window end timestamp in seconds. Alias: `end_time`, `before`. | | `order` | string | `asc` | Candle order. Use `desc` to return newest bucket first. | | `gap_fill` | boolean | `false` | When true, empty buckets are filled with flat carry-forward candles after a prior price exists. | | `token_id` | string | -- | Condition/slug routes only. Filters the resolved market to one outcome token. | Timestamp parameters are Unix seconds, not milliseconds. If you pass an explicit range, the range must fit inside the resolution's bucket limit. ## Bucket limits `limit` is a candle-bucket count, not a trade count. | Resolution | Max candles per request | | ---------- | ----------------------: | | `1m` | 120 | | `5m` | 180 | | `15m` | 240 | | `1h` | 300 | | `4h` | 360 | | `1d` | 500 | For more history, walk backward with `pagination.older_end_ts`: ```bash theme={null} # First page: latest 120 one-minute buckets curl "https://api.polynode.dev/v3/markets/$TOKEN_ID/candles?resolution=1m&limit=120" \ -H "x-api-key: $POLYNODE_API_KEY" # Older page: use pagination.older_end_ts from the previous response curl "https://api.polynode.dev/v3/markets/$TOKEN_ID/candles?resolution=1m&limit=120&anchor_ts=$OLDER_END_TS" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ## Token route response ```json theme={null} { "token_id": "7936379438884929999943146941289093559746194477224187754527972073168717543990", "resolution": "1m", "count": 1, "candles": [ { "time": 1781659920, "time_unix": 1781659920, "time_ms": 1781659920000, "open": 0.525, "high": 0.535, "low": 0.515, "close": 0.53, "volume": 233.135659, "volume_usd": 233.135659, "volume_shares": 444.019955, "volume_buy": 121.504, "volume_sell": 111.631659, "trades": 13, "vwap": 0.52504954, "first_trade_ts": 1781659922, "last_trade_ts": 1781659978, "first_block": 88620000, "last_block": 88620030, "first_log_index": 320, "last_log_index": 881, "gap_filled": false } ], "question": "Spread: Argentina (-1.5)", "slug": "fifwc-arg-alg-2026-06-16-spread-home-1pt5", "outcome": "Argentina", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/soccer ball-bba4025f77.png", "condition_id": "0x1eb724a708f3720c2b7ef63d7ffc7483ecc14deed6df2e7e9dc1dfa724a98bd6", "outcome_index": 0, "window": { "start_ts": 1781652840, "end_ts": 1781659981, "first_bucket": 1781652840, "last_bucket": 1781659980, "duration_seconds": 7142, "bucket_count": 120, "resolution_seconds": 60, "older_end_ts": 1781652839, "newer_start_ts": 1781660040, "has_older": true, "has_newer": true }, "pagination": { "limit": 120, "max_limit": 120, "direction": "before", "order": "asc", "older_end_ts": 1781652839, "newer_start_ts": 1781660040, "has_more": true, "has_older": true, "has_newer": true }, "gap_fill": false, "elapsed_ms": 27 } ``` Sparse markets can return `count: 0` and `candles: []` for a window with no trades. That is a valid empty chart window, not a missing market. ## Condition and slug responses ```bash theme={null} curl "https://api.polynode.dev/v3/markets/condition/$CONDITION_ID/candles?resolution=5m&limit=2" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ```json theme={null} { "condition_id": "0x1eb724a708f3720c2b7ef63d7ffc7483ecc14deed6df2e7e9dc1dfa724a98bd6", "resolution": "5m", "series_count": 2, "series": [ { "token_id": "7936379438884929999943146941289093559746194477224187754527972073168717543990", "resolution": "5m", "count": 0, "candles": [], "question": "Spread: Argentina (-1.5)", "slug": "fifwc-arg-alg-2026-06-16-spread-home-1pt5", "outcome": "Argentina", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/soccer ball-bba4025f77.png", "condition_id": "0x1eb724a708f3720c2b7ef63d7ffc7483ecc14deed6df2e7e9dc1dfa724a98bd6", "outcome_index": 0 } ], "window": { "start_ts": 1781668800, "end_ts": 1781669399, "first_bucket": 1781668800, "last_bucket": 1781669100, "duration_seconds": 600, "bucket_count": 2, "resolution_seconds": 300, "older_end_ts": 1781668799, "newer_start_ts": 1781669400, "has_older": true, "has_newer": true }, "pagination": { "limit": 2, "max_limit": 180, "direction": "before", "order": "asc", "older_end_ts": 1781668799, "newer_start_ts": 1781669400, "has_more": true, "has_older": true, "has_newer": true }, "gap_fill": false, "elapsed_ms": 205 } ``` For a single outcome from a condition or slug: ```bash theme={null} curl "https://api.polynode.dev/v3/markets/condition/$CONDITION_ID/candles?token_id=$TOKEN_ID&resolution=15m&limit=96" \ -H "x-api-key: $POLYNODE_API_KEY" ``` If a condition or slug resolves to more than 50 outcome tokens, pass `token_id` to request one outcome series. ## Candle fields | Field | Type | Description | | ----------------- | --------------- | --------------------------------------------------------------------- | | `time` | integer | Candle bucket start time in Unix seconds. | | `time_unix` | integer | Same as `time`. | | `time_ms` | integer | Candle bucket start time in Unix milliseconds for charting libraries. | | `open` | number | First trade price in the bucket. | | `high` | number | Highest trade price in the bucket. | | `low` | number | Lowest trade price in the bucket. | | `close` | number | Last trade price in the bucket. | | `volume` | number | Total USD volume in the bucket. Alias of `volume_usd`. | | `volume_usd` | number | Total USD volume in the bucket. | | `volume_shares` | number | Total outcome-share volume in the bucket. | | `volume_buy` | number | USD volume where the fill bought this outcome token. | | `volume_sell` | number | USD volume where the fill sold this outcome token. | | `trades` | integer | Number of fills in the bucket. | | `vwap` | number | Volume-weighted average price. | | `first_trade_ts` | integer or null | Timestamp of the first fill in the bucket. | | `last_trade_ts` | integer or null | Timestamp of the last fill in the bucket. | | `first_block` | integer or null | Polygon block of the first fill in the bucket. | | `last_block` | integer or null | Polygon block of the last fill in the bucket. | | `first_log_index` | integer or null | Log index of the first fill in the bucket. | | `last_log_index` | integer or null | Log index of the last fill in the bucket. | | `gap_filled` | boolean | `true` only for carry-forward buckets created by `gap_fill=true`. | ## Errors and guards | Status | Condition | | -----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | Invalid `resolution`, invalid `direction`, invalid `order`, millisecond timestamp, range larger than the resolution limit, or `token_id` that does not belong to a condition/slug route. | | `404` | Unknown token, condition, or slug. | Unknown token IDs return: ```json theme={null} { "error": "market token not found", "token_id": "0", "hint": "candles are available for Polymarket outcome token IDs, not collateral or arbitrary asset IDs" } ``` # Market Positions (Condition) Source: https://docs.polynode.dev/data/markets/condition-positions GET /v3/markets/condition/{condition_id}/positions Get wallet positions across every outcome token in a market condition. Returns wallet-position rows for every outcome token in a market condition. Use `sort=amount` to fetch the largest current holders first across all outcomes in the condition. ## Request ``` GET /v3/markets/condition/{condition_id}/positions ``` ### Path parameters | Parameter | Type | Description | | -------------- | ------ | -------------------------------------------------------------------------------------------------------------------------- | | `condition_id` | string | Market condition ID (`0x` + 64 hex characters). The response includes positions for every outcome token in this condition. | ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ----------------------------------------------------------------------------------------------------- | | `sort` | string | `pnl` | Sort order. One of `pnl` (realized P\&L), `amount` (current shares held), or `volume` (total bought). | | `limit` | integer | 100 | Rows per page. Requests are capped by API key tier: Starter `300`, Growth `1000`, Enterprise `2000`. | | `offset` | integer | 0 | Number of rows to skip for pagination. | If `limit` is above your tier cap, PolyNode normalizes it to the maximum allowed value instead of rejecting the request. Every response includes the applied `limit`, plus `rows_returned`, `offset`, and `has_more`. ## Sorting Use `sort=amount` when you need the largest current holders for a condition. The sort is descending, so the first rows are the wallets with the highest current `amount` across the condition's outcome tokens. ## Example ```bash theme={null} curl -H "x-api-key: $POLYNODE_API_KEY" \ "https://api.polynode.dev/v3/markets/condition/0x713641f745d71f6ec61f906237ffca3c8583f251e49384429a63ceb0ccdb2d37/positions?sort=amount&limit=1&offset=0" ``` ### Example response ```json theme={null} { "data": [ { "amount": "6660546928621", "avg_price": null, "realized_pnl": "0", "token_id": "1770840559776249239623005379825945674336282130390798724203946923853499387834", "total_bought": "0", "user": "0xa5ef39c3d3e10d0b270233af41cac69796b12966" } ], "elapsed_ms": 3, "has_more": true, "limit": 1, "offset": 0, "rows_returned": 1 } ``` ## Response columns | Column | Type | Description | | -------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------- | | `user` | string | Wallet address (lowercased) | | `token_id` | string | Outcome token this row covers (each condition has multiple outcomes; a wallet can appear once per outcome it has touched) | | `amount` | string | Current shares held in 6-decimal token units (`0` if the wallet has exited this outcome) | | `avg_price` | string \| null | Average entry price in 6-decimal USDC units (`500000` = \$0.50). May be `null` when cost basis is not available. | | `realized_pnl` | string | Realized P\&L on the closed portion of the position in 6-decimal USDC units | | `total_bought` | string | Cumulative purchase cost basis in 6-decimal USDC units | # Market Positions (Token) Source: https://docs.polynode.dev/data/markets/positions GET /v3/markets/{token_id}/positions Get all wallets holding positions in a specific outcome token, sorted by P&L or size. Returns all wallets that hold or have held positions in a specific outcome token. ## Request ``` GET /v3/markets/{token_id}/positions ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ---------------------------------- | | `sort` | string | `pnl` | Sort by: `pnl`, `amount`, `volume` | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl https://api.polynode.dev/v3/markets/75783394880030392863380883800697645018418815910449662777195626260206142035810/positions?limit=1 ``` ```json theme={null} { "data": [ { "amount": "0", "avg_price": "490000", "realized_pnl": "9999999", "total_bought": "19607842", "user": "0xa435d9eb84bf50f5d72d88023a77e8c7fd1d572d" } ], "elapsed_ms": 204, "has_more": true, "limit": 1, "offset": 0, "rows_returned": 1 } ``` ## Response columns | Column | Type | Description | | -------------- | ------ | ---------------------------------------- | | `user` | string | Wallet address | | `amount` | string | Current shares held (6-decimal) | | `avg_price` | string | Weighted-average entry price (6-decimal) | | `realized_pnl` | string | Realized P\&L (6-decimal USDC) | | `total_bought` | string | Total volume bought (6-decimal USDC) | # Token Price Source: https://docs.polynode.dev/data/markets/price GET /v3/markets/{token_id}/price Get the current price, resolution status, and price source for any outcome token. Returns the latest price for an outcome token, including where the price was sourced from and whether the market has resolved. ## Request ``` GET /v3/markets/{token_id}/price ``` ### Path parameters | Parameter | Type | Description | | ---------- | ------ | ---------------- | | `token_id` | string | Outcome token ID | ## Example ```bash theme={null} curl https://api.polynode.dev/v3/markets/75783394880030392863380883800697645018418815910449662777195626260206142035810/price ``` ```json theme={null} { "token_id": "75783394880030392863380883800697645018418815910449662777195626260206142035810", "price": 1.0, "resolved": true, "source": "settlement", "updated_at": "2026-05-13 12:27:28.558218", "elapsed_ms": 1 } ``` ## Response fields | Field | Type | Description | | ------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `token_id` | string | Outcome token ID | | `price` | number | Token price in USD. Range `0`–`1` for unresolved markets, exactly `0` or `1` for resolved. | | `resolved` | boolean | Whether the market has settled | | `source` | string | Price source: `settlement` (final payout), `clob_mid` (CLOB orderbook midpoint), `v1_condition` (V1 condition payout), `last_fill` (most recent trade price) | | `updated_at` | string | When the price was last updated | ### Price priority Prices follow this priority chain (higher sources are never overwritten by lower ones): 1. `settlement` -- final payout after market resolution (`0` or `1`) 2. `clob_mid` -- live CLOB orderbook midpoint (updated every 5 minutes) 3. `v1_condition` -- V1 condition payout data 4. `last_fill` -- price from the most recent trade # Search Markets Source: https://docs.polynode.dev/data/markets/search GET /v3/markets/search Full-text search across all Polymarket markets. Returns matching markets with metadata. Search market questions by keyword. Returns matching markets with metadata, volume, and resolution status. ## Request ``` GET /v3/markets/search ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `q` | string | required | Search query. Case-insensitive substring match against the `question`, `slug`, and `event_title` fields. Multiple words are AND-matched. | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ### How matching works * `?q=bitcoin` matches any market whose question, slug, or event title contains the substring `bitcoin` (any case). * `?q=trump pardon` matches questions containing both substrings, in any order. * Punctuation in the query is preserved (`?q=$72,000` matches the literal characters). * Results are ordered by `volume_all_time DESC` so higher-traded markets surface first. ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/markets/search?q=bitcoin&limit=1" ``` ```json theme={null} { "data": [ { "active": true, "category": null, "closed": true, "condition_id": "0x58c931c00e31fd20d02aade0c1e287e46a15b6da514c3ac3624e956e41bc45d0", "question": "Will the price of Bitcoin be above $72,000 on February 13?", "slug": "bitcoin-above-72k-on-february-13", "tag_slugs": [ "Crypto", "Bitcoin", "Crypto Prices", "Recurring", "Hide From New", "Weekly", "Multi Strikes" ], "volume_all_time": "999989.65550899994559586048126220703125", "winning_outcome_index": 1 } ], "elapsed_ms": 668, "has_more": true, "limit": 1, "offset": 0, "rows_returned": 1 } ``` ## Response fields (per row in `data`) | Field | Type | Nullable | Description | | ----------------------- | ---------------- | -------- | -------------------------------------- | | `condition_id` | string | no | Market condition ID | | `question` | string | no | Market question text | | `slug` | string | no | URL slug | | `category` | string | yes | Optional category label | | `volume_all_time` | string (decimal) | no | Total volume in USD at full precision | | `active` | boolean | no | Currently active | | `closed` | boolean | no | No longer accepting orders | | `winning_outcome_index` | integer | yes | Which outcome won (null if unresolved) | | `tag_slugs` | array\ | yes | Tags in canonical (Title Case) form | # Market Trades Source: https://docs.polynode.dev/data/markets/trades GET /v3/markets/{token_id}/trades Get all trades for a specific outcome token or market slug, with full enrichment. Returns fills involving a specific outcome token, enriched with market metadata, computed prices, and order hashes. Can look up by token ID or market slug. ## By token ID ``` GET /v3/markets/{token_id}/trades ``` ## By market slug ``` GET /v3/markets/slug/{slug}/trades ``` Resolves the slug to all token IDs for that market and returns trades across all outcomes. ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ------------------------------------ | | `after` | integer | -- | Start of time range (Unix timestamp) | | `before` | integer | -- | End of time range | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example (by token ID) ```bash theme={null} curl https://api.polynode.dev/v3/markets/75783394880030392863380883800697645018418815910449662777195626260206142035810/trades?limit=1 ``` ```json theme={null} { "token_id": "75783394880030392863380883800697645018418815910449662777195626260206142035810", "trades": [ { "id": "0xc6708a8b57ab936d8bd50be18b0bb55b64d27b5224b7c48ce487fef16fa0de0a_1077", "maker": "0x3248e74f1dafd9acb332fd6a511fe7e4600baad4", "taker": "0xf8f3c0269b1bff87dba772666864737b168f12a9", "maker_asset_id": "0", "taker_asset_id": "75783394880030392863380883800697645018418815910449662777195626260206142035810", "maker_amount": 31.130816, "taker_amount": 61.040815, "fee": 0.0, "price": 0.51, "size": 61.040815, "timestamp": "1778674995", "transaction_hash": "0xc6708a8b57ab936d8bd50be18b0bb55b64d27b5224b7c48ce487fef16fa0de0a", "order_hash": "0xf91a35be0abe447ed8c7d5e4c7511db22e0707136f3850b6708487bf2e6d834c", "builder": "0x0000000000000000000000000000000000000000000000000000000000000000", "side": 0, "role": "maker", "direction": "BUY", "market": "Dota 2: Aurora vs Team Liquid (BO3) - DreamLeague Group A", "slug": "dota2-aur1-liquid-2026-05-13", "outcome": "Aurora", "outcome_index": 0, "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/dota2-7ffacddb21.jpg", "condition_id": "0x4f05dbc6273b89aed46bb79a961c1d8771c01925d92d439e9a81fa6241900661" } ], "rows_returned": 1, "has_more": true, "elapsed_ms": 30 } ``` ## Example (by slug) ```bash theme={null} curl https://api.polynode.dev/v3/markets/slug/dota2-aur1-liquid-2026-05-13/trades?limit=5 ``` ## Response fields Each trade contains the same fields as [Wallet Trades](/data/wallets/trades), including all market enrichment fields. # API Overview Source: https://docs.polynode.dev/data/overview polynode API reference. Real-time streaming, historical data, P&L analytics, market data, and more. The polynode API provides two main surfaces: 1. **V3 Historical Data** — query the complete Polymarket V1 + V2 historical dataset. 1.2 billion fills, 228 million positions, 2.7 million wallets. Dual P\&L (realized + unrealized), position tracking with settlement status, builder analytics, market search, and leaderboards. 2. **Real-time Streaming** — live WebSocket feeds for settlements, trades, price updates, position changes, and more. See the [WebSocket tab](/websocket/overview). The V3 endpoints are **live** and provide the most comprehensive Polymarket dataset available. ## V3 Data API — Base URL ``` https://api.polynode.dev/v3 ``` ## Authentication Every `/v3/*` endpoint requires an API key. Pass it either as a `key` query parameter or as a bearer token in the `Authorization` header: ```bash theme={null} # Query string curl "https://api.polynode.dev/v3/stats?key=pn_live_..." # Bearer header curl -H "Authorization: Bearer pn_live_..." https://api.polynode.dev/v3/stats ``` Get a key from the [dashboard](https://polynode.dev/dashboard). All examples in this section assume `?key=YOUR_KEY` is appended (omitted only for brevity in the code blocks). ## Dataset Scale Query `GET /v3/stats` for live counts. Current scale: | Metric | Count | | --------------- | ------------ | | Fills (trades) | 1+ billion | | Positions | 200+ million | | Unique wallets | 2.5+ million | | Redemptions | 160+ million | | Splits | 30+ million | | Merges | 14+ million | | Markets tracked | 1.07 million | | Token prices | 2 million | ## Key features * **Dual P\&L** — realized and unrealized profit/loss for every wallet, computed from the full event history using weighted-average cost basis math * **Position status** — every position is tagged as `open`, `closed`, `redeemable`, or `redeemed` based on live settlement data * **Redeemable payouts** — see exactly how much USDC a wallet can claim from resolved markets * **Builder attribution** — 77 million fills attributed to 1,502 unique builders * **Batch queries** — query up to 100 wallets in a single call * **Time-range filters** — filter trades, redemptions, and activity by Unix timestamp * **Market search** — full-text search across 1 million+ market questions ## Amounts and precision Monetary fields come in two conventions depending on the endpoint: **Decimal USD (JSON number).** Used by aggregated P\&L responses (wallet summary, batch, pnl, positions) and by token-price responses (`/v3/markets/{token_id}/price`, `/v3/tokens/{token_id}`): ```json theme={null} { "net_realized_pnl": 22053845.825455, "total_volume": 43013258.515682 } { "price": 0.42 } ``` `net_realized_pnl: 22053845.825455` means **$22,053,845.83 USD**. `price: 0.42` means **$0.42 per share** (Polymarket outcome prices are always in the range 0–1). **Raw 6-decimal USDC (JSON string).** Used by event-level rows — fees, splits, merges, redemptions, NRC, activity, tag-leaderboard rollups, market-positions rows: ```json theme={null} { "amount": "120469388830" } ``` Divide by `1_000_000` to get USD. `"120469388830"` is **120,469.38883 USDC**. **Full-precision decimal (JSON string).** Used by raw subgraph fields like `volume_all_time`, `liquidity`, `price`: ```json theme={null} { "volume_all_time": "99993.937724000003072433173656463623046875" } ``` These are stored at full Postgres `numeric` precision (up to \~50 digits). Round on the client; do not parse as a JS `Number`. Each endpoint's response-fields table explicitly notes which convention applies. ## Identifiers and large numbers Polymarket `token_id` and `position_id` values are 78-digit decimal integers (uint256). They are returned as **JSON strings** so they survive standard JSON parsers — IEEE 754 doubles cannot represent them. Treat them as opaque strings; do not parse them as numbers. ## Nullable fields Many fields can be `null` in real responses even when the example shows a populated value — typically when a row was created before the metadata pipeline backfilled it, or when a market hasn't resolved yet. Each endpoint's response-fields table has a **Nullable** column. Defensive clients should accept `null` for any field marked nullable, plus for any field whose row predates indexing. ## Response envelopes Endpoints fall into three envelope shapes: | Shape | Endpoints | Top-level data key | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | | Single-object | `GET /v3/wallets/{addr}`, `GET /v3/wallets/{addr}/pnl`, `GET /v3/markets/{token_id}/price`, `GET /v3/conditions/{cid}`, `GET /v3/builders/{code}`, `GET /v3/stats` | Fields at top level (no wrapper) | | Named list | `/v3/trades`, `/v3/wallets/{addr}/trades`, `/v3/markets/{token_id}/trades`, `/v3/markets/slug/{slug}/trades`, `/v3/builders/{code}/trades` | `"trades": [...]` | | Named list | `/v3/positions`, `/v3/wallets/{addr}/positions` | `"positions": [...]` | | Named list | `/v3/leaderboard` | `"leaderboard": [...]` | | Named list | `/v3/tags`, `/v3/builders` | `"tags": [...]`, `"builders": [...]` | | Named list | `POST /v3/wallets/batch` | `"wallets": [...]` | | Generic list | Everything else (fees, resolutions, splits, merges, NRC, activity, redemptions, tag-leaderboard, tag-markets, market-positions, condition-positions, market-by-condition, market-search, tokens/info) | `"data": [...]` | All list responses also include `rows_returned`, `has_more`, `offset`, `limit`, `elapsed_ms`. ## Timestamps All Unix timestamps in responses are in **seconds** (e.g., `"1778674056"`). The `resolved_at` field in resolutions uses **milliseconds** (e.g., `"1778674836000"`). Datetime strings like `updated_at` are in UTC. ## Rate limits V3 REST data endpoints require a paid API key and are rate limited per key. | Plan | Standard V3 REST | Heavy V3 trade endpoints | | ---------- | ------------------------------------------ | ------------------------------------------ | | Starter | 1,000 req/min | 1,000 req/min | | Growth | 2,000 req/min | 1,500 req/min | | Enterprise | 4,000 req/min default; custom by agreement | 1,500 req/min default; custom by agreement | See [Rate Limits](/guides/rate-limits) for the full tier table and heavy-endpoint details. ## Pagination All list endpoints support: * `?limit=N` — results per page (default 100, max 300) * `?offset=N` — skip N results Responses include the actual `limit` used, plus `has_more: true/false` to indicate if more pages exist. For deep trade-history walks, prefer `after` and `before` time windows instead of very large offsets. ## Response format Every response includes `elapsed_ms` showing server-side query time in milliseconds. ```json theme={null} { "data": [...], "rows_returned": 100, "has_more": true, "offset": 0, "limit": 100, "elapsed_ms": 12 } ``` ## Endpoint categories | Category | Endpoints | What you can do | | --------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------ | | [Wallet P\&L](/data/wallets/summary) | 4 | Wallet summary, dual P\&L breakdown, batch queries, enriched positions | | [Wallet Activity](/data/wallets/trades) | 6 | Trades, redemptions, splits, merges, NRC, combined activity | | [Combos](/data/combos/overview) | 7 | Combo markets, wallet combo positions, combo trades, lifecycle activity, and additive wallet summaries | | [Markets](/data/markets/search) | 8 | Search, lookup by condition/token/slug, trades, positions, prices, candles | | [Leaderboard](/data/leaderboard/global) | 3 | Global P\&L rankings, tag discovery, tag leaderboards | | [Builders](/data/builders/list) | 3 | Builder rankings, stats, attributed trades | | [Global](/data/global/stats) | 5 | Platform stats, trade feed, fees, resolutions, conditions | | [Tokens](/data/tokens/info) | 1 | Token price and market metadata | ## Explorer Browse the data interactively with pre-built SQL recipes at [explorer.polynode.dev](https://explorer.polynode.dev). # Builders Query Catalog Source: https://docs.polynode.dev/data/query-catalog/builders Builder recipes for rankings, profiles, fee sorting, attributed trades, and filtered builder feeds. Use these recipes when building builder dashboards, attribution analytics, or builder-specific trade feeds. Rank builders by fills, volume, fees, makers, or takers. Stats and public profile metadata for one builder. Trades attributed to one builder code. Builder-attributed trades inside one market. Indexed builder tag/category filters paired with market, condition, or token filters. Buy/sell and amount filters for builder trades. # Builder Detail Source: https://docs.polynode.dev/data/query-catalog/builders/detail Get stats and public profile metadata for one builder. Canonical reference: [Builder Detail](/data/builders/detail) ```bash theme={null} curl "https://api.polynode.dev/v3/builders/0x4898df15ec6590495dc6c0fedf951ade3e64001d47f9caf44a64e86fc11959df" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Builder Leaderboard Source: https://docs.polynode.dev/data/query-catalog/builders/leaderboard Rank builders by fills, volume, fees, makers, or takers. Canonical reference: [Builder Leaderboard](/data/builders/list) ```bash theme={null} curl "https://api.polynode.dev/v3/builders?sort=fees&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Sort options include `fills`, `volume`, `fees`, `makers`, and `takers`. # Builder Trades Source: https://docs.polynode.dev/data/query-catalog/builders/trades Get trades attributed to one builder code. Canonical reference: [Builder Trades](/data/builders/trades) ```bash theme={null} curl "https://api.polynode.dev/v3/builders/0x4898df15ec6590495dc6c0fedf951ade3e64001d47f9caf44a64e86fc11959df/trades?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Builder Trades by Market Source: https://docs.polynode.dev/data/query-catalog/builders/trades-by-market Filter builder-attributed trades to one market slug. Canonical reference: [Builder Trades](/data/builders/trades) ```bash theme={null} curl "https://api.polynode.dev/v3/builders/0x4898df15ec6590495dc6c0fedf951ade3e64001d47f9caf44a64e86fc11959df/trades?market_slug=dota2-tundra-xtreme-2026-05-22-game1&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` You can also use `condition_id` or `token_id` when you already have those identifiers. # Builder Trades by Side and Size Source: https://docs.polynode.dev/data/query-catalog/builders/trades-by-side-size Filter builder-attributed trades by side and minimum amount. Canonical reference: [Builder Trades](/data/builders/trades) ```bash theme={null} curl "https://api.polynode.dev/v3/builders/0x4898df15ec6590495dc6c0fedf951ade3e64001d47f9caf44a64e86fc11959df/trades?side=buy&min_amount=1000000&limit=10" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use `side=buy` or `side=sell`. `min_amount` is useful for large-trade filters. # Builder Trades by Tag or Category Source: https://docs.polynode.dev/data/query-catalog/builders/trades-by-tag-category Filter builder trades by tag or category on a narrow market, condition, or token scope. Canonical reference: [Builder Trades](/data/builders/trades) ```bash theme={null} curl "https://api.polynode.dev/v3/builders/0x4898df15ec6590495dc6c0fedf951ade3e64001d47f9caf44a64e86fc11959df/trades?market_slug=dota2-tundra-xtreme-2026-05-22-game1&tag_slug=sports&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ```bash theme={null} curl "https://api.polynode.dev/v3/builders/0x4898df15ec6590495dc6c0fedf951ade3e64001d47f9caf44a64e86fc11959df/trades?market_slug=dota2-tundra-xtreme-2026-05-22-game1&category=Sports&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Pair broad tag or category filters with `market_slug`, `condition_id`, or `token_id` for the indexed query path. # Combos Query Catalog Source: https://docs.polynode.dev/data/query-catalog/combos Combo recipes for markets, activity, wallet summaries, combo positions, combo trades, and combined wallet views. Use these recipes when building combo market explorers, combo wallet dashboards, or lifecycle activity feeds. List observed combo markets with leg metadata. Lifecycle activity by combo market, condition, position, or wallet. Combo-only P\&L and position counts for one wallet. Combo positions for one wallet. Combo trades for one wallet. Combo lifecycle activity for one wallet. Add combo branches to standard wallet P\&L, positions, and trades. # Combo Activity Source: https://docs.polynode.dev/data/query-catalog/combos/activity Query combo lifecycle activity by market, condition, position, or wallet. Canonical reference: [Combo Activity](/data/combos/activity) ```bash theme={null} curl "https://api.polynode.dev/v3/combos/activity?condition_id=0x03d98f6ac4c108c5ca66f79f34908a1d820000000000000000000000000000&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Include Combos in Wallet Views Source: https://docs.polynode.dev/data/query-catalog/combos/include-combos Add combo branches to standard wallet P&L, positions, and trades. Canonical reference: [Combos Overview](/data/combos/overview) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/pnl?include_combos=true" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/positions?include_combos=true&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/trades?include_combos=true&limit=10" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Combo Markets Source: https://docs.polynode.dev/data/query-catalog/combos/markets List observed combo markets with leg metadata. Canonical reference: [Combo Markets](/data/combos/markets) ```bash theme={null} curl "https://api.polynode.dev/v3/combos/markets?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Wallet Combo Activity Source: https://docs.polynode.dev/data/query-catalog/combos/wallet-activity List combo lifecycle activity for one wallet. Canonical reference: [Wallet Combo Activity](/data/combos/wallet-activity) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/combos/activity?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Wallet Combo Positions Source: https://docs.polynode.dev/data/query-catalog/combos/wallet-positions List combo positions for one wallet. Canonical reference: [Wallet Combo Positions](/data/combos/wallet-positions) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/combos/positions?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Wallet Combo Summary Source: https://docs.polynode.dev/data/query-catalog/combos/wallet-summary Get combo-only P&L and position counts for one wallet. Canonical reference: [Wallet Combo Summary](/data/combos/wallet-summary) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/combos/summary" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Wallet Combo Trades Source: https://docs.polynode.dev/data/query-catalog/combos/wallet-trades List combo trades for one wallet. Canonical reference: [Wallet Combo Trades](/data/combos/wallet-trades) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/combos/trades?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Markets Query Catalog Source: https://docs.polynode.dev/data/query-catalog/markets Market recipes for search, condition lookup, slug lookup, token info, prices, tags, and resolutions. Use these recipes when building market search, market detail pages, token pages, tag pages, or resolution feeds. Full-text market search. Market metadata, outcomes, and token IDs from a condition ID. Resolution and payout data for one condition. Use a Polymarket slug to retrieve market trades or holders. Outcome-token metadata, price, category, tags, and resolution status. Current price and resolution status for one outcome token. OHLCV candles from V3 trade fills. List tags and find markets by tag. Recent market resolution feed. # Market by Condition Source: https://docs.polynode.dev/data/query-catalog/markets/by-condition Get market metadata and outcome tokens from a condition ID. Canonical reference: [Market by Condition](/data/markets/by-condition) ```bash theme={null} curl "https://api.polynode.dev/v3/markets/condition/0x97269cbb95bd572b3dde34cdd619be278c8f13ba5e43e2e6c2d8639411128a86" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use this when your app stores market condition IDs. # Trades and Positions by Slug Source: https://docs.polynode.dev/data/query-catalog/markets/by-slug Use a market slug to fetch trades or positions. Canonical reference: [Market by Slug](/data/markets/by-slug) ```bash theme={null} curl "https://api.polynode.dev/v3/markets/slug/dota2-tundra-xtreme-2026-05-22-game1/trades?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ```bash theme={null} curl "https://api.polynode.dev/v3/markets/slug/dota2-tundra-xtreme-2026-05-22-game1/positions?sort=amount&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Market Candles Source: https://docs.polynode.dev/data/query-catalog/markets/candles Build OHLCV candles for a V3 market token, condition ID, or slug. Canonical reference: [Market Candles](/data/markets/candles) ```bash theme={null} curl "https://api.polynode.dev/v3/markets/{token_id}/candles?resolution=5m&limit=120" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use this for V3 charting. Candles are built from trade fills and support `1m`, `5m`, `15m`, `1h`, `4h`, and `1d` buckets. # Condition Detail Source: https://docs.polynode.dev/data/query-catalog/markets/condition-detail Get resolution and payout data for a condition. Canonical reference: [Condition Detail](/data/global/conditions) ```bash theme={null} curl "https://api.polynode.dev/v3/conditions/0x97269cbb95bd572b3dde34cdd619be278c8f13ba5e43e2e6c2d8639411128a86" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use this for settlement, payout, and resolution status views. # Market Resolutions Source: https://docs.polynode.dev/data/query-catalog/markets/resolutions Browse recent resolved markets with payout data. Canonical reference: [Market Resolutions](/data/global/resolutions) ```bash theme={null} curl "https://api.polynode.dev/v3/resolutions?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use this for settlement feeds and post-resolution workflows. # Search Markets Source: https://docs.polynode.dev/data/query-catalog/markets/search-markets Search Polymarket markets by text. Canonical reference: [Search Markets](/data/markets/search) ```bash theme={null} curl "https://api.polynode.dev/v3/markets/search?q=dota&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use market search when a user enters free text or when you need to discover slugs and condition IDs. # Tags and Markets by Tag Source: https://docs.polynode.dev/data/query-catalog/markets/tags List tags and discover markets under a tag. Canonical references: [Tags](/data/leaderboard/tags), [Tag Markets](/data/leaderboard/tag-markets) ```bash theme={null} curl "https://api.polynode.dev/v3/tags?limit=3" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ```bash theme={null} curl "https://api.polynode.dev/v3/tags/ufc?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Tag values are case-insensitive. URL hyphens are treated like spaces. # Token Info Source: https://docs.polynode.dev/data/query-catalog/markets/token-info Get metadata, price, category, tags, and resolution status for one outcome token. Canonical reference: [Token Info](/data/tokens/info) ```bash theme={null} curl "https://api.polynode.dev/v3/tokens/21578184864194020862792310253337084708609281967112790860038966988826118995357" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use this when you have an outcome token ID and need the market context around it. # Token Price Source: https://docs.polynode.dev/data/query-catalog/markets/token-price Get the current price and resolution status for one outcome token. Canonical reference: [Token Price](/data/markets/price) ```bash theme={null} curl "https://api.polynode.dev/v3/markets/21578184864194020862792310253337084708609281967112790860038966988826118995357/price" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use token price for lightweight price checks when you do not need full market metadata. # Query Catalog Source: https://docs.polynode.dev/data/query-catalog/overview Intent-based catalog of the most useful PolyNode V3 query shapes. The API reference is organized by endpoint. The query catalog is organized by what you are trying to build: trade feeds, wallet dashboards, P\&L screens, market pages, fee accounting, builder analytics, and combo views. All examples use the public V3 API and an API key. Set `POLYNODE_API_KEY` once, then copy any example. ```bash theme={null} export POLYNODE_API_KEY="pn_live_..." ``` Global trades, wallet trades, market trades, builder-attributed trades, grouped orders, and combo trades. Wallet positions, open positions, global holders, token holders, condition holders, slug holders, and combo positions. Wallet P\&L, time-windowed P\&L, tag/market P\&L, P\&L events, batch reads, and leaderboards. Search, condition lookup, condition detail, token info, price, tags, tag markets, and resolutions. Unified activity, splits, merges, redemptions, neg-risk conversions, PolyUSD flows, fees, rebates, and rewards. Wallet-paid fees, global fee events, receiver fees, builder fee ranking, rebates, and reward configs. Builder leaderboard, builder profiles, builder trades, market filters, side/size filters, tag filters, and category filters. Combo markets, combo activity, combo wallet summary, combo positions, combo trades, and additive wallet views. ## Granular Recipes The left navigation includes both category pages and individual query recipes. Use the category pages for orientation, then jump to the exact query shape you need. # P&L Query Catalog Source: https://docs.polynode.dev/data/query-catalog/pnl P&L recipes for wallet totals, time windows, tag and market filters, events, batches, and leaderboards. Use these recipes when building wallet performance pages, time-series charts, filtered rankings, and leaderboard views. Recovered all-time wallet P\&L with realized, unrealized, total, wins, losses, and volume. Realized P\&L for periods like 1d, 7d, 30d, and 1y. Wallet P\&L filtered to a tag or category. Wallet P\&L filtered to one condition or market slug. Bucketed P\&L events for charts. Read P\&L for multiple wallets in one request. Rank wallets by total P\&L, realized P\&L, volume, or win count, with one primary filter dimension when needed. Rank wallets inside a tag/category; broad filters should use the global leaderboard filter. # Batch Wallet P&L Source: https://docs.polynode.dev/data/query-catalog/pnl/batch-wallet-pnl Query P&L for multiple wallets in one request. Canonical reference: [Batch Wallet P\&L](/data/wallets/batch) ```bash theme={null} curl -X POST "https://api.polynode.dev/v3/wallets/batch" \ -H "content-type: application/json" \ -H "x-api-key: $POLYNODE_API_KEY" \ -d '{"wallets":["0x56687bf447db6ffa42ffe2204a05edaa20f55839","0x952d11ebff81d6bd3185e608ed3515b94618ab8a"]}' ``` Use this when a dashboard needs several wallet summaries at once. # Global P&L Leaderboard Source: https://docs.polynode.dev/data/query-catalog/pnl/global-leaderboard Rank wallets by P&L, volume, profit, or win count. Canonical reference: [Leaderboard](/data/leaderboard/global) ```bash theme={null} curl "https://api.polynode.dev/v3/leaderboard?sort=total&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use `sort=total`, `realized`, `volume`, `profit`, or `wins` depending on the leaderboard view. For filtered leaderboards, use one primary market dimension per request: ```bash theme={null} curl "https://api.polynode.dev/v3/leaderboard?category=crypto&sort=total&limit=20" \ -H "x-api-key: $POLYNODE_API_KEY" curl "https://api.polynode.dev/v3/leaderboard?tag_slug=us-election&sort=volume&limit=20" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Supported dimensions include `category`, `tags`/`tag_slug`, `market`/`market_slug`, `event_slug`, and `condition_id`. # Tag P&L Leaderboard Source: https://docs.polynode.dev/data/query-catalog/pnl/tag-leaderboard Rank wallets inside a tag or category. Canonical reference: [Tag Leaderboard](/data/leaderboard/tag-leaderboard) ```bash theme={null} curl "https://api.polynode.dev/v3/tags/ufc/leaderboard?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use direct tag leaderboards for niche tags, sport-specific pages, or event-family rankings. For broad tags/categories, prefer the projected global leaderboard filter: ```bash theme={null} curl "https://api.polynode.dev/v3/leaderboard?tags=crypto&limit=20" \ -H "x-api-key: $POLYNODE_API_KEY" curl "https://api.polynode.dev/v3/leaderboard?category=crypto&limit=20" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Wallet All-Time P&L Source: https://docs.polynode.dev/data/query-catalog/pnl/wallet-all-time Get recovered all-time wallet P&L. Canonical reference: [Wallet P\&L](/data/wallets/pnl) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x56687bf447db6ffa42ffe2204a05edaa20f55839/pnl" \ -H "x-api-key: $POLYNODE_API_KEY" ``` The response includes realized P\&L, unrealized P\&L, total P\&L, gross profit, gross loss, wins, losses, open positions, and volume. # Wallet P&L by Market Source: https://docs.polynode.dev/data/query-catalog/pnl/wallet-by-market Filter wallet P&L to one condition ID or market slug. Canonical reference: [Wallet P\&L](/data/wallets/pnl) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/pnl?market=0x97269cbb95bd572b3dde34cdd619be278c8f13ba5e43e2e6c2d8639411128a86" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use `market` for a condition ID, or `market_slug` for a Polymarket slug. `event_slug` and `condition_id` are also accepted for focused market/event P\&L views. # Wallet P&L by Tag Source: https://docs.polynode.dev/data/query-catalog/pnl/wallet-by-tag Filter wallet P&L to a tag or category. Canonical reference: [Wallet P\&L](/data/wallets/pnl) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/pnl?tags=sports" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use comma-separated `tags` for multiple tags where supported. `category` filters the first market tag, and `tag_slug` is accepted as an alias for single-tag filters. # Wallet P&L Events Source: https://docs.polynode.dev/data/query-catalog/pnl/wallet-events Get bucketed wallet P&L events for charts. Canonical reference: [Wallet P\&L Time Series](/data/wallets/pnl-events) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/pnl/events?period=7d&group=day&limit=10" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use `group=hour`, `day`, `week`, or `month` depending on the chart resolution. # Wallet Time-Windowed P&L Source: https://docs.polynode.dev/data/query-catalog/pnl/wallet-time-window Get wallet P&L for a period such as 30 days. Canonical reference: [Wallet P\&L](/data/wallets/pnl) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/pnl?period=30d&include_unrealized=true" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Supported period shortcuts are `1d`, `7d`, `30d`, and `1y`. You can also use `after` and `before` Unix timestamps. # Positions Query Catalog Source: https://docs.polynode.dev/data/query-catalog/positions Position recipes for wallet positions, open positions, holders, market positions, and combo positions. Use these recipes when building wallet dashboards, holder tables, market pages, or position discovery views. All standard positions for one wallet. Current open positions for one wallet. Platform-wide open-position discovery. Holders for one outcome token. Holders across all outcomes in one market condition. Holders for a market URL slug. Combo-only and combined wallet position views. # Positions by Condition Source: https://docs.polynode.dev/data/query-catalog/positions/by-condition Get holders across all outcomes in one market condition. Canonical reference: [Market Positions by Condition](/data/markets/condition-positions) ```bash theme={null} curl "https://api.polynode.dev/v3/markets/condition/0x97269cbb95bd572b3dde34cdd619be278c8f13ba5e43e2e6c2d8639411128a86/positions?sort=amount&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use condition-level positions when a market has multiple outcome tokens and you want the full holder table. # Positions by Market Slug Source: https://docs.polynode.dev/data/query-catalog/positions/by-market-slug Get holders for a market URL slug. Canonical reference: [Market by Slug](/data/markets/by-slug) ```bash theme={null} curl "https://api.polynode.dev/v3/markets/slug/dota2-tundra-xtreme-2026-05-22-game1/positions?sort=amount&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use this when your app stores or receives Polymarket slugs. # Positions by Market Token Source: https://docs.polynode.dev/data/query-catalog/positions/by-market-token Get holders for one outcome token. Canonical reference: [Market Positions](/data/markets/positions) ```bash theme={null} curl "https://api.polynode.dev/v3/markets/21578184864194020862792310253337084708609281967112790860038966988826118995357/positions?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use this when you already know the outcome token ID. # Positions by Wallet Source: https://docs.polynode.dev/data/query-catalog/positions/by-wallet Get all standard positions for one wallet. Canonical reference: [Wallet Positions](/data/wallets/positions) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/positions?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use this for wallet position tables with market context, current prices, unrealized P\&L, and settlement status. # Combo Positions Source: https://docs.polynode.dev/data/query-catalog/positions/combo-positions Get combo-only wallet positions or include combos in the standard wallet position view. Canonical references: [Wallet Combo Positions](/data/combos/wallet-positions), [Wallet Positions](/data/wallets/positions) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/combos/positions?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/positions?include_combos=true&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Global Positions Source: https://docs.polynode.dev/data/query-catalog/positions/global-positions Browse platform-wide open positions. Canonical reference: [Global Positions](/data/global/positions) ```bash theme={null} curl "https://api.polynode.dev/v3/positions?status=open&sort=size&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use this for holder discovery across the platform. Add `condition_id`, `token_id`, `status`, `min_size`, and `sort` to narrow the result. # Open Wallet Positions Source: https://docs.polynode.dev/data/query-catalog/positions/open-wallet-positions Get current open positions for one wallet. Canonical reference: [Wallet Positions](/data/wallets/positions) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/positions?status=open&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use `status=open` when you only want active exposure. # Tags and Categories Query Catalog Source: https://docs.polynode.dev/data/query-catalog/tags-categories Tag and category recipes for discovery, market lists, P&L filters, leaderboards, and builder filters. Tags are market labels. In V3 market and token responses, the first tag is also the category used by category-style query filters. Discover available tag values. List markets under a tag. Rank wallets inside a tag/category; broad tags should use the global leaderboard filter. Filter wallet P\&L to a tag/category. Filter builder-attributed trades by tag/category on an indexed market, condition, or token scope. # Trades Query Catalog Source: https://docs.polynode.dev/data/query-catalog/trades Trade feed recipes for global, wallet, market, builder, grouped-order, and combo trade queries. Use these recipes when building trade feeds, market tapes, wallet histories, builder analytics, or order-level views. Global trade feed with pagination and time filters. Trades where one wallet was maker or taker. Trades for one outcome token. Trades for all outcomes in one market slug. One wallet's trades inside one market condition. Order-level grouping for wallet trades. Trades attributed to a builder code. Combo-only and combined wallet trade views. # All Trades Source: https://docs.polynode.dev/data/query-catalog/trades/all-trades Browse the global Polymarket trade feed. Canonical reference: [Global Trades](/data/global/trades) ```bash theme={null} curl "https://api.polynode.dev/v3/trades?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use `after`, `before`, `limit`, `offset`, `order`, and `min_amount` to page and filter the feed. # Builder Trades Source: https://docs.polynode.dev/data/query-catalog/trades/by-builder Get trades attributed to a builder code. Canonical reference: [Builder Trades](/data/builders/trades) ```bash theme={null} curl "https://api.polynode.dev/v3/builders/0x4898df15ec6590495dc6c0fedf951ade3e64001d47f9caf44a64e86fc11959df/trades?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Builder trades can be narrowed by `market_slug`, `condition_id`, `token_id`, `side`, `min_amount`, `tag_slug`, and `category`. # Trades by Market Slug Source: https://docs.polynode.dev/data/query-catalog/trades/by-market-slug Get trades for all outcome tokens in one market slug. Canonical reference: [Market by Slug](/data/markets/by-slug) ```bash theme={null} curl "https://api.polynode.dev/v3/markets/slug/dota2-tundra-xtreme-2026-05-22-game1/trades?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use this when your application works from Polymarket URLs or slugs instead of token IDs. # Trades by Market Token Source: https://docs.polynode.dev/data/query-catalog/trades/by-market-token Get trades for one outcome token. Canonical reference: [Market Trades](/data/markets/trades) ```bash theme={null} curl "https://api.polynode.dev/v3/markets/21578184864194020862792310253337084708609281967112790860038966988826118995357/trades?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use this when you already know the ERC-1155 outcome token ID. # Trades by Wallet Source: https://docs.polynode.dev/data/query-catalog/trades/by-wallet Get trades where a wallet participated as maker or taker. Canonical reference: [Wallet Trades](/data/wallets/trades) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/trades?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Useful filters include `after`, `before`, `side`, `condition_id`, `token_id`, `market_slug`, `group_by=order_hash`, and `include_combos=true`. # Combo Wallet Trades Source: https://docs.polynode.dev/data/query-catalog/trades/combo-wallet-trades Get combo-only wallet trades or include combo trades in the standard wallet trade view. Canonical references: [Wallet Combo Trades](/data/combos/wallet-trades), [Wallet Trades](/data/wallets/trades) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/combos/trades?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/trades?include_combos=true&limit=10" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Wallet Trades by Condition Source: https://docs.polynode.dev/data/query-catalog/trades/wallet-by-condition Filter one wallet's trades to a specific market condition. Canonical reference: [Wallet Trades](/data/wallets/trades) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/trades?condition_id=0x97269cbb95bd572b3dde34cdd619be278c8f13ba5e43e2e6c2d8639411128a86&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use `condition_id` when you want both outcomes for one market. Use `token_id` when you want one specific outcome. # Wallet Trades Grouped by Order Source: https://docs.polynode.dev/data/query-catalog/trades/wallet-grouped-by-order Group wallet fills by order hash. Canonical reference: [Wallet Trades](/data/wallets/trades) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/trades?group_by=order_hash&limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Use this for order-level analytics where multiple fills share the same order hash. # Wallet Activity Query Catalog Source: https://docs.polynode.dev/data/query-catalog/wallet-activity Wallet activity recipes for lifecycle events, PolyUSD flows, fees, rebates, and rewards. Use these recipes when building wallet timelines, cash movement views, settlement history, or fee accounting pages. Splits, merges, redemptions, and neg-risk conversions in one feed. USDC converted into outcome tokens. Outcome tokens converted back into USDC. Claimed payouts from resolved markets. Neg-risk conversion events for a wallet. PolyUSD deposits and withdrawals. Fee-bearing fills for one wallet. Polymarket-reported maker rebates and reward surfaces. # Wallet Fees Paid Source: https://docs.polynode.dev/data/query-catalog/wallets/fees-paid Get fee-bearing fills for one wallet. Canonical reference: [Wallet Fees Paid](/data/wallets/fees-paid) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63ce342161250d705dc0b16df89036c8e5f9ba9a/fees-paid?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` This is trader-paid fill fee data. It is separate from maker rebates and reward configuration. # Wallet Merges Source: https://docs.polynode.dev/data/query-catalog/wallets/merges Get merge events where outcome tokens were converted back into USDC. Canonical reference: [Wallet Merges](/data/wallets/merges) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x01a23b7408650ba910b11740a814071e57fbfbe3/merges?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Neg-Risk Conversions Source: https://docs.polynode.dev/data/query-catalog/wallets/neg-risk-conversions Get neg-risk conversion activity for one wallet. Canonical reference: [Wallet NRC](/data/wallets/nrc) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x4ce73141dbfce41e65db3723e31059a730f0abad/nrc?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # PolyUSD Flows Source: https://docs.polynode.dev/data/query-catalog/wallets/polyusd-flows Get PolyUSD deposits and withdrawals for one wallet. Canonical reference: [Wallet PolyUSD Flows](/data/wallets/polyusd-flows) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x2a1f579283C87c4574102bbF6E4B39F7A12fe77E/polyusd-flows?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Wallet Rebates and Rewards Source: https://docs.polynode.dev/data/query-catalog/wallets/rebates-rewards Query maker rebates and wallet reward surfaces. Canonical references: [Wallet Maker Rebates](/data/wallets/rebates), [Reward Markets](/data/rewards/markets) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x8ecb4b228e07b6ddc58a32997093032a6907b8f6/rebates" \ -H "x-api-key: $POLYNODE_API_KEY" ``` ```bash theme={null} curl "https://api.polynode.dev/v3/rewards/markets" \ -H "x-api-key: $POLYNODE_API_KEY" ``` Rebates, rewards, and fill fees are distinct accounting surfaces. # Wallet Redemptions Source: https://docs.polynode.dev/data/query-catalog/wallets/redemptions Get redemption events where a wallet claimed resolved-market payouts. Canonical reference: [Wallet Redemptions](/data/wallets/redemptions) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x56687bf447db6ffa42ffe2204a05edaa20f55839/redemptions?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Wallet Splits Source: https://docs.polynode.dev/data/query-catalog/wallets/splits Get split events where USDC was converted into outcome tokens. Canonical reference: [Wallet Splits](/data/wallets/splits) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x0f35109cc3ceaf729609c3109310791bd616c84a/splits?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` # Unified Wallet Activity Source: https://docs.polynode.dev/data/query-catalog/wallets/unified-activity Get non-trade wallet activity in one feed. Canonical reference: [Wallet Activity](/data/wallets/activity) ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x56687bf447db6ffa42ffe2204a05edaa20f55839/activity?limit=1" \ -H "x-api-key: $POLYNODE_API_KEY" ``` This feed includes split, merge, redemption, and neg-risk conversion activity. # Reward Market Detail Source: https://docs.polynode.dev/data/rewards/market GET /v3/rewards/markets/{condition_id} Get public Polymarket reward configuration for one condition ID. Returns Polymarket's public reward-market configuration for a single condition ID. This describes market reward settings; it is not per-wallet earned LP rewards. ## Request ``` GET /v3/rewards/markets/{condition_id} ``` ### Path parameters | Parameter | Type | Description | | -------------- | ------ | -------------------------- | | `condition_id` | string | `0x` + 64 hex condition ID | ### Query parameters | Parameter | Type | Default | Description | | ------------- | ------- | ------- | ---------------------------------- | | `sponsored` | boolean | `false` | Filter to sponsored reward markets | | `next_cursor` | string | -- | Pagination cursor | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/rewards/markets/0x0001cb8c0b39aeb614ab9a43867595317f06ede9c011661513065c638fbbefda" ``` ```json theme={null} { "source": "reward_market_config_detail", "reward_semantics": "public Polymarket reward market configuration; this is not per-wallet earned LP rewards", "data": { "count": 1, "data": [ { "condition_id": "0x0001cb8c0b39aeb614ab9a43867595317f06ede9c011661513065c638fbbefda", "native_daily_rate": 1, "rewards_config": [ { "asset_address": "0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB", "rate_per_day": 1, "start_date": "2026-02-17", "end_date": "2500-12-31" } ] } ] }, "elapsed_ms": 236 } ``` ## Response fields | Field | Type | Description | | ------------------ | ------- | ---------------------------------------- | | `source` | string | Dataset label | | `reward_semantics` | string | Meaning note | | `data` | object | Reward-market configuration response | | `elapsed_ms` | integer | Server-side request time in milliseconds | ## Errors | HTTP | When | | ----- | --------------------------------------- | | `400` | Invalid condition ID format | | `401` | Missing or invalid PolyNode API key | | `502` | Temporary rewards data provider failure | # Reward Markets Source: https://docs.polynode.dev/data/rewards/markets GET /v3/rewards/markets List public Polymarket reward market configurations. Returns Polymarket's public reward-market configuration feed. This describes markets with active reward programs; it is not per-wallet earned LP rewards. ## Request ``` GET /v3/rewards/markets ``` ### Query parameters | Parameter | Type | Default | Description | | ------------- | ------- | ------- | ---------------------------------- | | `sponsored` | boolean | `false` | Filter to sponsored reward markets | | `next_cursor` | string | -- | Pagination cursor | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/rewards/markets" ``` ```json theme={null} { "source": "reward_market_config", "reward_semantics": "public Polymarket reward market configuration; this is not per-wallet earned LP rewards", "data": { "count": 500, "data": [ { "condition_id": "0x0001cb8c0b39aeb614ab9a43867595317f06ede9c011661513065c638fbbefda", "native_daily_rate": 1, "rewards_config": [ { "asset_address": "0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB", "end_date": "2500-12-31", "id": 0, "rate_per_day": 1, "start_date": "2026-02-17", "total_rewards": 0 } ], "rewards_max_spread": 4.5, "rewards_min_size": 50, "total_daily_rate": 1 } ], "next_cursor": "..." }, "elapsed_ms": 447 } ``` ## Response fields | Field | Type | Description | | ------------------ | ------- | ---------------------------------------- | | `source` | string | Dataset label | | `reward_semantics` | string | Meaning note | | `data` | object | Reward-market configuration response | | `elapsed_ms` | integer | Server-side request time in milliseconds | ## Errors | HTTP | When | | ----- | --------------------------------------- | | `401` | Missing or invalid PolyNode API key | | `502` | Temporary rewards data provider failure | # Token Info Source: https://docs.polynode.dev/data/tokens/info GET /v3/tokens/{token_id} Get full details for an outcome token: price, resolution status, market metadata, and tags. Returns price, resolution status, and market context for a specific outcome token. ## Request ``` GET /v3/tokens/{token_id} ``` ### Path parameters | Parameter | Type | Description | | ---------- | ------ | ---------------- | | `token_id` | string | Outcome token ID | ## Example ```bash theme={null} curl https://api.polynode.dev/v3/tokens/75783394880030392863380883800697645018418815910449662777195626260206142035810 ``` ```json theme={null} { "data": [ { "category": null, "condition_id": "0xaa08b43f2e39a28c52278738d456dfce516fcd966d43f157aab3727457cd4b6c", "outcome": "Down", "outcome_index": 1, "price": 1.0, "question": "Bitcoin Up or Down - March 29, 1:10AM-1:15AM ET", "resolved": true, "slug": "btc-updown-5m-1774761000", "source": "v1_condition", "tag_slugs": [ "Crypto", "Bitcoin", "Crypto Prices", "Recurring", "Up or Down", "Hide From New", "5M" ], "token_id": "40671034664065625078050343379339320067070367341089437265715901074008336896448", "updated_at": "2026-05-16 11:42:10.666489" } ], "elapsed_ms": 3, "has_more": false, "limit": 1, "offset": 0, "rows_returned": 1 } ``` ## Response columns | Column | Type | Description | | --------------- | ------- | ------------------------------------------------------------------------------ | | `token_id` | string | Outcome token ID | | `price` | number | Current price in USD (`0`–`1` for unresolved, exactly `0` or `1` for resolved) | | `resolved` | boolean | Whether the market has settled | | `source` | string | Price source: `settlement`, `clob_mid`, `v1_condition`, `last_fill` | | `updated_at` | string | When the price was last updated | | `condition_id` | string | Market condition ID | | `outcome` | string | Outcome label (e.g. "Aurora", "Yes") | | `outcome_index` | integer | Outcome position (0 or 1) | | `question` | string | Market question | | `slug` | string | Market URL slug | | `category` | string | Market category | | `tag_slugs` | array | Category tags | # Wallet Activity Source: https://docs.polynode.dev/data/wallets/activity GET /v3/wallets/{address}/activity Get all non-trade activity for a wallet: splits, merges, neg-risk conversions, and redemptions. Returns a unified feed of all non-trade events for a wallet, sorted by timestamp. Includes splits, merges, neg-risk conversions, and redemptions. This endpoint is the conditional-token activity feed for splits, merges, neg-risk conversions, and redemptions. PolyUSD deposits and withdrawals are available separately through [Wallet PolyUSD Flows](/data/wallets/polyusd-flows). ## Request ``` GET /v3/wallets/{address}/activity ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ------------------------------------ | | `after` | integer | -- | Start of time range (Unix timestamp) | | `before` | integer | -- | End of time range | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0x56687bf447db6ffa42ffe2204a05edaa20f55839/activity?limit=1 ``` ```json theme={null} { "data": [ { "amount": "706561610", "event_type": "redemption", "extra": "{1,2}", "id": "0x7a7494699af0d7bf9734db8c803fc9ecad0330df97ebc7893bb05a81bc3c93bf_0x353", "ref_id": "0xfbf0af69aa98b4662b95ff5ffb6dde560ec2e3da723a1980a6ef3f919cbeddea", "timestamp": "1777356700" } ], "elapsed_ms": 8, "has_more": true, "limit": 1, "offset": 0, "rows_returned": 1 } ``` ## Response fields | Field | Type | Description | | ------------ | ------ | ---------------------------------------------------------------------- | | `event_type` | string | `split`, `merge`, `nrc` (neg-risk conversion), or `redemption` | | `id` | string | Event ID | | `timestamp` | string | Unix timestamp | | `ref_id` | string | Condition ID or neg-risk market ID | | `amount` | string | Amount (6-decimal USDC) | | `extra` | string | Additional data (index\_sets for redemptions, question\_count for NRC) | # Batch Wallet P&L Source: https://docs.polynode.dev/data/wallets/batch POST /v3/wallets/batch Query P&L for up to 100 wallets in a single request. All amounts in USD. Query multiple wallets at once. Returns the same summary data as the single wallet endpoint, but for up to 100 wallets in one call. All monetary values are in USD. ## Request ``` POST /v3/wallets/batch ``` ### Request body ```json theme={null} { "wallets": [ "0x56687bf447db6ffa42ffe2204a05edaa20f55839", "0xa9857c7bcb9bcfafd2c132ab053f34f678610058" ] } ``` | Field | Type | Description | | --------- | ----- | -------------------------- | | `wallets` | array | Up to 100 wallet addresses | ## Example ```bash theme={null} curl -X POST https://api.polynode.dev/v3/wallets/batch \ -H 'Content-Type: application/json' \ -d '{"wallets":["0x56687bf447db6ffa42ffe2204a05edaa20f55839"]}' ``` ```json theme={null} { "count": 1, "elapsed_ms": 1, "wallets": [ { "wallet": "0x56687bf447db6ffa42ffe2204a05edaa20f55839", "net_realized_pnl": 22053845.825455, "gross_profit": 22057977.181649, "gross_loss": -4131.356194, "unrealized_pnl": 0.000688755, "total_pnl": 22053845.826143753, "wins": 18, "losses": 4, "position_count": 22, "open_positions": 1, "total_volume": 43013258.515682 } ] } ``` ## Response fields Same fields as [Wallet Summary](/data/wallets/summary), with `wallet` instead of `address`. All amounts in USD. # Wallet Fees Paid Source: https://docs.polynode.dev/data/wallets/fees-paid GET /v3/wallets/{address}/fees-paid Get fee-bearing order fills for a wallet. Returns fills where the wallet was the order owner and `OrderFilled.fee` was greater than zero. Each row is enriched with the same market context used by wallet trade history. This is fees paid by the trader on executed order fills. It is not the same as maker rebates, LP rewards, or builder payouts. `page_total_fees_paid` is the sum for the returned page only. For the exact all-time fee total, use `GET /v3/wallets/{address}?include_accounting_summary=true` and read `accounting_summary.fees_paid.amount`. ## Request ``` GET /v3/wallets/{address}/fees-paid ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------------ | --------------------------------------------- | | `after` | integer | `0` | Start of time range (Unix seconds, inclusive) | | `before` | integer | `9999999999` | End of time range (Unix seconds, inclusive) | | `order` | string | `desc` | `asc` or `desc` | | `limit` | integer | `100` | Max 300 | | `offset` | integer | `0` | Pagination offset | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/fees-paid?limit=1&after=1779700000" ``` ```json theme={null} { "address": "0xbddf61af533ff524d27154e589d2d7a81510c684", "fees": [ { "id": "0x4f8447fda6ed5b200d876ffc677fedd853d524c9a2751d055d7578c1575124dc_847", "maker": "0xbddf61af533ff524d27154e589d2d7a81510c684", "taker": "0xe111180000d2663c0091e4f400237545b87b996b", "fee": 352.774, "fee_paid": 352.774, "fee_paid_raw": "352774000", "price": 0.45, "size": 47511.65, "direction": "BUY", "market": "Spread: Thunder (-2.5)", "slug": "nba-sas-okc-2026-05-26-spread-home-2pt5", "condition_id": "0x7bcc428d368834c6dfbb4925f699079f65925fef69ad259274b88c5671aaa29e", "timestamp": "1779839786", "source": "wallet_fees_paid" } ], "rows_returned": 1, "has_more": true, "page_total_fees_paid": 352.774, "fee_semantics": "Trader-paid fees on executed order fills where this wallet was the order owner. Includes supported Polymarket exchange versions.", "elapsed_ms": 14 } ``` ## Response fields | Field | Type | Description | | ------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------ | | `address` | string | Wallet address, lowercased | | `fees` | array | Fee-bearing fill rows | | `fees[].fee` | number | Fee in USD | | `fees[].fee_paid` | number | Same value as `fee`, included for category-breakdown clarity | | `fees[].fee_paid_raw` | string | Raw 6-decimal fee amount | | `fees[].source` | string | Dataset label | | `rows_returned` | integer | Number of rows returned | | `has_more` | boolean | Whether another page exists | | `offset` | integer | Offset used for this page | | `limit` | integer | Limit used for this page | | `page_total_fees_paid` | number | Sum of `fee_paid` for this page only. Use wallet summary `include_accounting_summary=true` for the all-time total. | | `page_total_fees_paid_raw_note` | string | Reminder that the total is page-scoped | | `fee_semantics` | string | Attribution note | | `after` | string | Applied start timestamp | | `before` | string | Applied end timestamp | | `elapsed_ms` | integer | Server-side query time in milliseconds | The fill objects also include standard trade fields such as `maker_asset_id`, `taker_asset_id`, `maker_amount`, `taker_amount`, `token_id`, `asset`, `amount`, `amount_usd`, `transaction_hash`, `order_hash`, `builder`, `side`, `role`, `outcome`, `outcome_index`, and `image` when available. ## Errors | HTTP | When | | ----- | ----------------------------------- | | `400` | Invalid wallet address | | `400` | Invalid `order` value | | `401` | Missing or invalid PolyNode API key | # Wallet Merges Source: https://docs.polynode.dev/data/wallets/merges GET /v3/wallets/{address}/merges Get all merge events for a wallet. Merges occur when outcome tokens are converted back into USDC. Returns merge events where outcome tokens were converted back into USDC for a market condition. ## Request ``` GET /v3/wallets/{address}/merges ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ------------------------------------ | | `after` | integer | -- | Start of time range (Unix timestamp) | | `before` | integer | -- | End of time range | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0xada100874d00e3331d00f2007a9c336a65009718/merges?limit=1 ``` ```json theme={null} { "data": [ { "id": "0x56e84b69492dff41fae8aa932695eec8b90931aa286ce09d9565932d72e4a1f4_0x612", "timestamp": "1778482999", "condition": "0x649340b2a3e39f7d0de414d6891a14b6e1969293f8fec3ccb481572fd2375e34", "amount": "20000000" } ], "rows_returned": 1, "has_more": true, "elapsed_ms": 2 } ``` ## Response fields | Field | Type | Description | | ----------- | ------ | -------------------------------- | | `id` | string | Merge event ID | | `timestamp` | string | Unix timestamp | | `condition` | string | Market condition ID | | `amount` | string | USDC amount received (6-decimal) | # Wallet NRC Source: https://docs.polynode.dev/data/wallets/nrc GET /v3/wallets/{address}/nrc Get neg-risk conversion events for a wallet. NRC events convert between multi-outcome market positions. Returns neg-risk conversion events for a wallet. These occur when positions in multi-outcome (neg-risk) markets are converted between outcome tokens. ## Request ``` GET /v3/wallets/{address}/nrc ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ------------------------------------ | | `after` | integer | -- | Start of time range (Unix timestamp) | | `before` | integer | -- | End of time range | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0x4ce73141dbfce41e65db3723e31059a730f0abad/nrc?limit=1 ``` ```json theme={null} { "data": [], "elapsed_ms": 2, "has_more": false, "limit": 1, "offset": 0, "rows_returned": 0 } ``` ## Response fields | Field | Type | Description | | -------------------- | ------- | -------------------------------------------------------- | | `id` | string | NRC event ID | | `timestamp` | string | Unix timestamp | | `neg_risk_market_id` | string | Parent neg-risk market ID | | `amount` | string | Amount converted (6-decimal USDC) | | `index_set` | string | Bit-packed index indicating which outcomes were involved | | `question_count` | integer | Total number of outcomes in the neg-risk market | # Wallet P&L Source: https://docs.polynode.dev/data/wallets/pnl GET /v3/wallets/{address}/pnl Get profit and loss for a wallet in USD. Supports time-windowed P&L with ?period=1d|7d|30d|1y plus tag, category, market, event, and condition filters. Returns P\&L data for a wallet in USD. Without a `period` parameter, returns all-time P\&L. With `period`, `after`, or `before`, returns realized P\&L for that time window. The two modes have different counting semantics: all-time `wins` and `losses` are position counts; time-windowed `wins`, `losses`, and `events` are realized P\&L event counts. Do not compare the counts directly. Wallet P\&L supports focused market filters such as `category`, `tags`/`tag_slug`, `market`/`market_slug`, `event_slug`, and `condition_id`. For leaderboard views, use one primary filter dimension per request. ## Request ``` GET /v3/wallets/{address}/pnl ``` ### Query parameters | Parameter | Type | Default | Description | | -------------------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `period` | string | -- | Time window: `1d`, `7d`, `30d`, `1y` | | `after` | integer | -- | Start timestamp (Unix seconds). Time-windowed queries clamp values below `1` to `1`. | | `before` | integer | -- | End timestamp (Unix seconds) | | `category` | string | -- | Filter to one market category, case-insensitive | | `tags` | string | -- | Comma-separated tag slugs to filter by, case-insensitive (e.g. `politics,crypto`, `nfl`, `Iran`) | | `tag_slug` | string | -- | Alias for a single `tags` value | | `market` | string | -- | Filter by condition ID or market slug | | `market_slug` | string | -- | Filter by market slug | | `event_slug` | string | -- | Filter by parent event slug | | `condition_id` | string | -- | Filter by market condition ID | | `include_unrealized` | string | `false` | Set to `true` to include current unrealized P\&L alongside the realized event-window result. Only applies to time-filtered queries. | | `include_combos` | boolean | `false` | Add combo position P\&L to all-time wallet P\&L. If the wallet has no combo exposure, the response remains 200 with a zero combo contribution. | ## Examples ### All-time P\&L ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0x56687bf447db6ffa42ffe2204a05edaa20f55839/pnl ``` ### All-time P\&L including combos ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/pnl?include_combos=true" ``` ```json theme={null} { "address": "0x63613e3b96f418332d43cd2af8dc321014d15907", "net_realized_pnl": 113.510513, "realized_pnl": 113.510513, "gross_profit": 184.901354, "gross_loss": -71.390841, "unrealized_pnl": 0.0, "total_pnl": 113.510513, "position_count": 21496, "open_positions": 1495, "total_volume": 129.837476, "include_combos": true, "included_position_types": ["market", "combo"], "combo_pnl": { "position_type": "combo", "included": true, "realized_pnl": 113.510513, "unrealized_pnl": 0.0, "total_pnl": 113.510513, "position_count": 21492, "open_positions": 1495 } } ``` For time-windowed or tag-filtered P\&L, the response stays successful. If combo P\&L cannot be safely added to that filtered view, `combo_pnl.included` is `false` and the standard market P\&L fields remain unchanged. ```json theme={null} { "address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839", "net_realized_pnl": 22053845.825455, "gross_profit": 22057977.181649, "gross_loss": -4131.356194, "unrealized_pnl": 0.000688755, "total_pnl": 22053845.826143753, "position_count": 22, "open_positions": 1, "total_volume": 43013258.515682, "wins": 18, "losses": 4, "elapsed_ms": 41 } ``` ### 30-day P\&L ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/pnl?period=30d ``` ```json theme={null} { "address": "0xbddf61af533ff524d27154e589d2d7a81510c684", "realized_pnl": 1815261.28, "gross_profit": 1918402.27, "gross_loss": -103140.98, "wins": 33039, "losses": 58867, "events": 91906, "source": "realized_pnl_series", "period": "30d", "after": 1776134996, "before": 1778726996, "elapsed_ms": 5902 } ``` ### Filtered P\&L ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/pnl?category=crypto" curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/pnl?tag_slug=us-election&period=30d" curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/pnl?market_slug=will-donald-trump-win-the-popular-vote-in-the-2024-presidential-election" curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/pnl?event_slug=presidential-election-popular-vote-winner-2024" ``` ### 30-day P\&L with unrealized Pass `include_unrealized=true` to include current unrealized P\&L alongside the realized event-window result. ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/pnl?period=30d&include_unrealized=true ``` ```json theme={null} { "address": "0xbddf61af533ff524d27154e589d2d7a81510c684", "realized_pnl": 1815261.28, "gross_profit": 1918402.27, "gross_loss": -103140.98, "unrealized_pnl": 14714.71, "total_pnl": 1829975.99, "open_positions_at_date": 363, "wins": 33039, "losses": 58867, "events": 91906, "source": "realized_pnl_series", "period": "30d", "after": 1776135002, "before": 1778727002, "elapsed_ms": 3803 } ``` ## Response fields (all-time) | Field | Type | Description | | ------------------------- | ------- | ------------------------------------------------- | | `net_realized_pnl` | number | Net realized P\&L (USD) | | `gross_profit` | number | Sum of winning positions (USD) | | `gross_loss` | number | Sum of losing positions (USD, negative) | | `unrealized_pnl` | number | Paper P\&L from open positions (USD) | | `total_pnl` | number | `net_realized_pnl + unrealized_pnl` (USD) | | `wins` | integer | Winning position count | | `losses` | integer | Losing position count | | `position_count` | integer | Total positions | | `open_positions` | integer | Currently held positions | | `total_volume` | number | Total volume traded (USD) | | `include_combos` | boolean | Present when `include_combos=true` was requested | | `included_position_types` | array | Position families included in aggregate totals | | `combo_pnl` | object | Combo-only contribution to the aggregate response | ## Response fields (with period) | Field | Type | Description | | -------------- | ------- | -------------------------------------------------- | | `realized_pnl` | number | Realized P\&L in the time window (USD) | | `gross_profit` | number | Sum of winning events in the window (USD) | | `gross_loss` | number | Sum of losing events in the window (USD, negative) | | `wins` | integer | Winning event count | | `losses` | integer | Losing event count | | `events` | integer | Total P\&L events in the window | | `period` | string | The requested period | | `after` | integer | Start timestamp (Unix seconds) | | `before` | integer | End timestamp (Unix seconds) | | `source` | string | Dataset label for time-windowed queries | ### Additional fields (with `include_unrealized=true`) | Field | Type | Description | | ------------------------ | ------- | ------------------------------------------------------------------------------------------------------ | | `unrealized_pnl` | number | Current unrealized P\&L for open positions (USD), returned alongside the realized event-window result. | | `total_pnl` | number | `realized_pnl + unrealized_pnl` (USD) | | `open_positions_at_date` | integer | Current open position count returned with the unrealized P\&L estimate | # Wallet P&L Time Series Source: https://docs.polynode.dev/data/wallets/pnl-events GET /v3/wallets/{address}/pnl/events Realized P&L bucketed by hour, day, week, or month — chart-ready time series for a wallet. Returns wallet P\&L buckets for charting. With `period`, `after`, or `before`, each row is a fixed time window (hour, day, week, or month). A running `cumulative_pnl` is included so the response can be plotted directly. Without an explicit time filter, the endpoint returns a single all-time summary bucket that matches [`GET /v3/wallets/{address}/pnl`](/data/wallets/pnl). In event-series mode, `wins`, `losses`, and `events` count realized P\&L events in the selected window. In default summary mode, counts are position counts and the response includes `count_type: "positions"` and `summary_bucket: true`. Rows at timestamp `0` or earlier are ignored in event-series mode, so explicit event windows start at Unix second `1`. ## Request ``` GET /v3/wallets/{address}/pnl/events ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ------------------------------------------------------------------------------- | | `group` | string | `day` | Bucket size: `hour`, `day`, `week`, `month` | | `period` | string | -- | Shortcut for `after`: `1d`, `7d`, `30d`, `1y` | | `after` | integer | `1` | Start timestamp (Unix seconds, inclusive). Values below `1` are treated as `1`. | | `before` | integer | now | End timestamp (Unix seconds, inclusive) | | `limit` | integer | `5000` | Max buckets returned, clamped 1-10000 | If `after` and `period` are both set, `after` wins. ## Examples ### All-time summary bucket ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/pnl/events" ``` ```json theme={null} { "address": "0xbddf61af533ff524d27154e589d2d7a81510c684", "granularity": "day", "bucket_count": 1, "buckets": [ { "bucket": 1779908613, "realized_pnl": 29035964.931344, "cumulative_pnl": 29035964.931344, "gross_profit": 29818047.886063, "gross_loss": -782082.954719, "wins": 482, "losses": 33, "events": 1016, "position_count": 1016, "open_positions": 511, "unrealized_pnl": -26917534.501466, "total_pnl": 2118430.429878, "total_volume": 177203290.120101 } ], "total_realized_pnl": 29035964.931344, "total_events": 1016, "total_wins": 482, "total_losses": 33, "total_positions": 1016, "open_positions": 511, "unrealized_pnl": -26917534.501466, "total_pnl": 2118430.429878, "source": "wallet_summary", "count_type": "positions", "summary_bucket": true, "elapsed_ms": 3 } ``` ### Daily P\&L for the last 7 days ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x44c1dfe43260c94ed4f1d00de2e1f80fb113ebc1/pnl/events?period=7d&group=day" ``` ```json theme={null} { "address": "0x44c1dfe43260c94ed4f1d00de2e1f80fb113ebc1", "granularity": "day", "after": 1778478745, "before": 1779083545, "bucket_count": 3, "buckets": [ { "bucket": 1778889600, "realized_pnl": 0.000525, "cumulative_pnl": 0.000525, "gross_profit": 0.000525, "gross_loss": 0, "wins": 1, "losses": 0, "events": 1 }, { "bucket": 1778976000, "realized_pnl": 611.069702, "cumulative_pnl": 611.070227, "gross_profit": 618.41811, "gross_loss": -7.348408, "wins": 23, "losses": 3, "events": 26 }, { "bucket": 1779062400, "realized_pnl": 11.774724, "cumulative_pnl": 622.844951, "gross_profit": 11.774724, "gross_loss": 0, "wins": 2, "losses": 0, "events": 2 } ], "total_realized_pnl": 622.844951, "total_events": 29, "total_wins": 26, "total_losses": 3, "source": "realized_pnl_series", "elapsed_ms": 4 } ``` ### Hourly P\&L for an explicit window ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x44c1dfe43260c94ed4f1d00de2e1f80fb113ebc1/pnl/events?after=1778976000&before=1779062400&group=hour" ``` ### Weekly P\&L for the last year ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x44c1dfe43260c94ed4f1d00de2e1f80fb113ebc1/pnl/events?period=1y&group=week" ``` ## Response fields | Field | Type | Description | | -------------------------- | ------- | ------------------------------------------------------------------------------ | | `address` | string | The wallet address (lowercased) | | `granularity` | string | Resolved bucket size (`hour`, `day`, `week`, `month`) | | `after` | integer | Start of the window (Unix seconds, inclusive) | | `before` | integer | End of the window (Unix seconds, inclusive) | | `bucket_count` | integer | Number of buckets returned | | `buckets` | array | Time-series rows, ordered oldest → newest | | `buckets[].bucket` | integer | Bucket start timestamp (Unix seconds, UTC, truncated to bucket boundary) | | `buckets[].realized_pnl` | number | Realized P\&L in that bucket (USD) | | `buckets[].cumulative_pnl` | number | Running sum of `realized_pnl` over the returned series (USD) | | `buckets[].gross_profit` | number | Sum of winning events in that bucket (USD) | | `buckets[].gross_loss` | number | Sum of losing events in that bucket (USD, negative) | | `buckets[].wins` | integer | Count of winning events in that bucket | | `buckets[].losses` | integer | Count of losing events in that bucket | | `buckets[].events` | integer | Total P\&L events in event-series mode; position count in default summary mode | | `buckets[].position_count` | integer | Position count. Present in default summary mode. | | `buckets[].open_positions` | integer | Open position count. Present in default summary mode. | | `buckets[].unrealized_pnl` | number | Current unrealized P\&L. Present in default summary mode. | | `buckets[].total_pnl` | number | `realized_pnl + unrealized_pnl`. Present in default summary mode. | | `buckets[].total_volume` | number | Wallet volume from the summary source. Present in default summary mode. | | `total_realized_pnl` | number | Sum of `realized_pnl` across all returned buckets (USD) | | `total_events` | integer | Sum of `events` across all returned buckets | | `total_wins` | integer | Sum of `wins` across all returned buckets | | `total_losses` | integer | Sum of `losses` across all returned buckets | | `source` | string | Dataset label | | `count_type` | string | `positions` in default summary mode | | `summary_bucket` | boolean | `true` in default summary mode | | `elapsed_ms` | integer | Server-side query time in milliseconds | ## Errors | HTTP | When | | ----- | ----------------------------------------------------------------- | | `400` | Invalid wallet address (must be `0x` + 40 hex) | | `400` | Invalid `group` value (must be `hour`, `day`, `week`, or `month`) | | `400` | `after` greater than `before` | # Wallet PolyUSD Flows Source: https://docs.polynode.dev/data/wallets/polyusd-flows GET /v3/wallets/{address}/polyusd-flows Get PolyUSD deposits and withdrawals for a wallet. Returns PolyUSD deposit and withdrawal history for one wallet. Deposits are incoming PolyUSD mints to the wallet. Withdrawals are outgoing PolyUSD movements that redeem back to USDC or USDC.e. This endpoint is focused on user cash movement. It excludes split, merge, and trading settlement mechanics, which are available through the wallet activity and trade endpoints. ## Request ``` GET /v3/wallets/{address}/polyusd-flows ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | --------- | --------------------------------------------- | | `kind` | string | `all` | `all`, `deposit`, or `withdrawal` | | `after` | integer | `0` | Start of time range (Unix seconds, inclusive) | | `before` | integer | unlimited | End of time range (Unix seconds, inclusive) | | `order` | string | `desc` | `asc` or `desc` | | `limit` | integer | `100` | Max 300 | | `offset` | integer | `0` | Pagination offset | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x2a1f579283C87c4574102bbF6E4B39F7A12fe77E/polyusd-flows?limit=2" ``` ```json theme={null} { "address": "0x2a1f579283c87c4574102bbf6e4b39f7a12fe77e", "flows": [ { "event_type": "withdrawal", "direction": "out", "amount": 2690.200068, "amount_usdc": 2690.200068, "amount_raw": "2690200068", "asset": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", "asset_symbol": "USDC.e", "ramp": "CollateralOfframp", "source_event": "Transfer+Unwrapped", "transaction_hash": "0xed54095cbf5c5f434cd3c12d7d226e8922bb40d597d0c61a623412567b5f0e45" }, { "event_type": "deposit", "direction": "in", "amount": 1500, "amount_usdc": 1500, "amount_raw": "1500000000", "asset": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", "asset_symbol": "USDC.e", "ramp": "CollateralOnramp", "source_event": "Wrapped", "transaction_hash": "0x1d516fa44c127232be3f8aad9dc974fca73e2f1e1c8716914a8ef6c8222cbf25" } ], "rows_returned": 2, "total_matching_rows": 21, "has_more": true, "total_deposited": 12854.05392, "total_withdrawn": 5930.200068, "net_deposited": 6923.853852, "source": "polyusd_flows" } ``` ## Response fields | Field | Type | Description | | -------------------------- | ------- | ---------------------------------------------------- | | `address` | string | Wallet address, lowercased | | `flows` | array | Deposit and withdrawal rows for the requested page | | `flows[].event_type` | string | `deposit` or `withdrawal` | | `flows[].direction` | string | `in` for deposits, `out` for withdrawals | | `flows[].amount` | number | 6-decimal normalized USD amount | | `flows[].amount_raw` | string | Raw PolyUSD amount | | `flows[].asset` | string | Underlying asset address | | `flows[].asset_symbol` | string | `USDC.e`, `USDC`, or `unknown` | | `flows[].ramp` | string | Ramp contract used | | `flows[].recipient` | string | Recipient of minted PolyUSD or unwrapped USDC/USDC.e | | `flows[].transaction_hash` | string | Polygon transaction hash | | `flows[].block_number` | integer | Polygon block number | | `flows[].block_timestamp` | integer | Polygon block timestamp | | `rows_returned` | integer | Number of rows returned | | `total_matching_rows` | integer | Total rows after filters before pagination | | `has_more` | boolean | Whether another page exists | | `total_deposited` | number | Sum of matching deposit rows | | `total_withdrawn` | number | Sum of matching withdrawal rows | | `net_deposited` | number | Deposits minus withdrawals | | `source` | string | Dataset label | ## Errors | HTTP | When | | ----- | ------------------------------------------ | | `400` | Invalid wallet address, `kind`, or `order` | | `401` | Missing or invalid PolyNode API key | | `502` | Temporary data provider failure | # Wallet Positions Source: https://docs.polynode.dev/data/wallets/positions GET /v3/wallets/{address}/positions Get all positions for a wallet with market context, current prices, unrealized P&L, and settlement status. Returns every position a wallet has ever held, enriched with market metadata, live token prices, computed unrealized P\&L, settlement status, and redemption data. All amounts are in USD. ## Request ``` GET /v3/wallets/{address}/positions ``` ### Path parameters | Parameter | Type | Description | | --------- | ------ | ---------------------------------------------- | | `address` | string | Wallet address (0x-prefixed, case-insensitive) | ### Query parameters | Parameter | Type | Default | Description | | ---------------- | ------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `status` | string | all | Filter: `open`, `closed`, `redeemable`, `redeemed` | | `sort` | string | `realized_pnl` | Sort by: `realized_pnl` (default), `amount` (current shares), `volume` / `total_bought`, `avg_price`, `recent` (last trade time) | | `order` | string | `desc` | Sort direction: `asc` or `desc` | | `condition_id` | string | -- | Filter by market condition ID | | `market_slug` | string | -- | Filter by market slug | | `min_size` | number | -- | Minimum position size in USD (e.g. `min_size=10.0`) | | `include_combos` | boolean | `false` | Append combo positions to the response. Combo rows have `position_type: "combo"` and include `legs` metadata. | | `limit` | integer | 100 | Results per page (max 300) | | `offset` | integer | 0 | Pagination offset | ### Position statuses Wallet and global position endpoints use lifecycle statuses: | Status | Meaning | | ------------ | --------------------------------------------------------------- | | `open` | Wallet holds shares in an active (unresolved) market | | `closed` | Position fully sold before market resolved | | `redeemable` | Market resolved, wallet still holds shares that can be redeemed | | `redeemed` | Market resolved and shares have been redeemed | Market-scoped holder endpoints may use `open`/`closed` differently when explicitly documented as holder-state filters: `open` means the current balance is nonzero, and `closed` means the current balance is zero. ## Examples ### All positions ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0xa9857c7bcb9bcfafd2c132ab053f34f678610058/positions?limit=1 ``` ```json theme={null} { "address": "0xa9857c7bcb9bcfafd2c132ab053f34f678610058", "positions": [ { "token_id": "56311531524793560667677947517355066819344622894461317832902534277368408457717", "size": 0.003332, "avg_price": 0.479999, "realized_pnl": 0.083202, "unrealized_pnl": 0.0, "total_pnl": 0.083202, "current_price": 0.0, "total_bought": 2.083332, "initial_value": 0.001599, "redeemable_payout": 0.0, "resolved": true, "price_source": "settlement", "status": "redeemable", "market": "Bitcoin Up or Down - May 13, 6:55AM-7:00AM ET", "slug": "btc-updown-5m-1778669700", "outcome": "Down", "outcome_index": 1, "opposite_asset": "75458928898922591833639657019011228747535375591777673334858165385124723756977", "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/BTC+fullsize.png", "condition_id": "0xa1118bf183058190b9b47529d94eed53d0210c65531990019c09b893cb64b73b", "event_slug": "btc-updown-5m-1778669700", "neg_risk": false, "market_status": "live", "tag_slugs": ["Hide From New", "Recurring", "Up or Down", "Crypto Prices", "Crypto", "Bitcoin", "5M"], "last_trade_at": "1778670064", "resolved_at": "0", "winning_outcome_index": null, "won": null } ], "rows_returned": 1, "has_more": true, "offset": 0, "limit": 1, "elapsed_ms": 45 } ``` ### Only open positions ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0x.../positions?status=open ``` ### Only redeemable (resolved but not yet claimed) ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0x.../positions?status=redeemable ``` The `redeemable_payout` field shows the exact USD amount the wallet can claim. ### Sort by most recent trade ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x.../positions?sort=recent" ``` ### Sort by current position size ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x.../positions?sort=amount" ``` ### Include combo positions ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/positions?include_combos=true&limit=2" ``` When combo rows are present, they are appended with `position_type: "combo"` and a combo-specific field set: ```json theme={null} { "position_type": "combo", "combo_condition_id": "0x030002fa8781d9f445d838e60524d70bf30000000000000000000000000000", "combo_position_id": "1356959103499736670017337806334234879289930581423500836189165811753797287936", "shares_balance": "0.000100", "entry_avg_price_usdc": "0.2285", "entry_cost_usdc": "0.00", "realized_pnl_usdc": "0.000000", "total_pnl_usdc": "0.000000", "status": "open", "legs": [ { "leg_index": 0, "leg_position_id": "943244391856361009754982642766252708422485833075867696330382296106234020352", "leg_outcome_label": "Yes", "leg_status": "OPEN" } ] } ``` ## Response fields ### Top-level combo fields | Field | Type | Description | | ------------------------- | ------- | ---------------------------------------------------------------- | | `include_combos` | boolean | Present when `include_combos=true` was requested | | `included_position_types` | array | Includes `market` and `combo` when combo inclusion was requested | | `combo_position_count` | integer | Number of combo position rows returned in this page | ### Position data | Field | Type | Description | | ------------------- | ------- | ------------------------------------------------------------------- | | `token_id` | string | Outcome token ID | | `size` | number | Current shares held in USD (0 if closed/redeemed) | | `avg_price` | number | Weighted-average entry price in USD | | `realized_pnl` | number | Realized P\&L from closed trades (USD) | | `unrealized_pnl` | number | Paper P\&L: `(current_price - avg_price) * size` | | `total_pnl` | number | `realized_pnl + unrealized_pnl` | | `current_price` | number | Latest token price (USD, from settlement, CLOB mid, or last fill) | | `total_bought` | number | Total USD spent buying this position | | `initial_value` | number | `avg_price * size` at current holdings | | `redeemable_payout` | number | USD claimable if redeemed now (0 for losing outcomes) | | `resolved` | boolean | Whether the market has settled | | `price_source` | string | Price source: `settlement`, `clob_mid`, `v1_condition`, `last_fill` | | `status` | string | `open`, `closed`, `redeemable`, or `redeemed` | ### Market context | Field | Type | Description | | ---------------- | ------- | ---------------------------------------------------- | | `market` | string | Market question text | | `slug` | string | Market URL slug | | `outcome` | string | Outcome label (e.g. "Yes", "No", "Trump") | | `outcome_index` | integer | Outcome position (0 or 1) | | `opposite_asset` | string | Token ID of the other outcome in this market | | `image` | string | Market image URL | | `condition_id` | string | Market condition ID | | `event_slug` | string | Parent event slug | | `neg_risk` | boolean | Whether this is a neg-risk (multi-outcome) market | | `market_status` | string | `live`, `closed`, `resolved-win`, or `resolved-loss` | | `tag_slugs` | array | Category tags for this market | ### Timestamps and resolution | Field | Type | Description | | ----------------------- | ------- | ------------------------------------------------------- | | `last_trade_at` | string | Unix timestamp of the most recent fill for this token | | `resolved_at` | string | Unix timestamp when the market was resolved | | `winning_outcome_index` | integer | Which outcome won (0 or 1), null if unresolved | | `won` | boolean | Whether this position's outcome won, null if unresolved | ## Use cases * **Portfolio dashboard** — show a trader's open positions with live P\&L and market context * **Redemption alerts** — find wallets with unclaimed payouts using `?status=redeemable` * **Performance analysis** — sort by P\&L to see best and worst positions * **Copy trading** — see what a wallet is currently holding with `?status=open&sort=amount` # Wallet Maker Rebates Source: https://docs.polynode.dev/data/wallets/rebates GET /v3/wallets/{address}/rebates Get Polymarket-reported maker rebates for a wallet and date. Returns maker rebate records reported by Polymarket for one maker address and one date. Maker rebates are Polymarket accounting credits. They are not the same value as trader fees paid. Polymarket rebate lookups are keyed by CLOB maker/signing address. If you pass a Safe or proxy wallet that Polymarket does not accept as a maker address, PolyNode returns an empty `rebates` array with an explanatory `hint`. ## Request ``` GET /v3/wallets/{address}/rebates ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------ | ---------------- | --------------------------- | | `date` | string | current UTC date | Date in `YYYY-MM-DD` format | ## Example ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x8ecb4b228e07b6ddc58a32997093032a6907b8f6/rebates" ``` ```json theme={null} { "address": "0x8ecb4b228e07b6ddc58a32997093032a6907b8f6", "date": "2026-05-27", "rebates": [], "rows_returned": 0, "total_rebated_fees_usdc": 0, "source": "polymarket_rebates", "rebate_semantics": "Maker rebates reported for one maker/date. Separate from trader-paid fees.", "elapsed_ms": 271 } ``` ## Response fields | Field | Type | Description | | ------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------- | | `address` | string | Wallet or maker address, lowercased | | `date` | string | Date used for the rebate lookup | | `rebates` | array | Polymarket rebate records | | `rows_returned` | integer | Number of rebate records returned | | `total_rebated_fees_usdc` | number | Sum of `rebated_fees_usdc` across returned records when present | | `source` | string | Dataset label | | `rebate_semantics` | string | Accounting note | | `provider_status` | integer | Present when the rebate data provider returned an invalid maker-address response that PolyNode converted to an empty result | | `provider_warning` | object | Present with the provider warning body for invalid maker-address responses | | `hint` | string | Present when a Safe/proxy address may need to be resolved to a maker EOA | | `elapsed_ms` | integer | Server-side request time in milliseconds | The objects inside `rebates` follow Polymarket's rebate record format. When a record includes `rebated_fees_usdc`, PolyNode includes it in `total_rebated_fees_usdc`. ## Errors | HTTP | When | | ----- | -------------------------------------- | | `400` | Invalid wallet address | | `400` | Invalid `date` format | | `401` | Missing or invalid PolyNode API key | | `502` | Temporary rebate data provider failure | # Wallet Redemptions Source: https://docs.polynode.dev/data/wallets/redemptions GET /v3/wallets/{address}/redemptions Get all redemption events for a wallet. Redemptions occur when a wallet claims payouts from resolved markets. Returns redemption events where the wallet claimed payouts from resolved market conditions. Rows are enriched with market and resolution metadata when available so clients can identify the winning outcome without a second market lookup. The original response fields remain present and unchanged. ## Request ``` GET /v3/wallets/{address}/redemptions ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ------------------------------------ | | `after` | integer | -- | Start of time range (Unix timestamp) | | `before` | integer | -- | End of time range | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0x56687bf447db6ffa42ffe2204a05edaa20f55839/redemptions?limit=1 ``` ```json theme={null} { "data": [ { "id": "0x6173027f191670852e049df6ded0b353069a531f5ea647220bdcb64ab634348e_cac", "timestamp": "1776731610", "condition": "0x19be7c46e28b61455ec766679ee1122fba39eb0d5c85d0878e4b203ab8b18406", "payout": "4078910900", "index_sets": ["1", "2"], "condition_id": "0x19be7c46e28b61455ec766679ee1122fba39eb0d5c85d0878e4b203ab8b18406", "payout_e6": "4078910900", "payout_usdc": 4078.9109, "market_title": "Will John Ternus be the next CEO of Apple?", "market_slug": "will-john-ternus-be-the-next-ceo-of-apple", "market_image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/next-ceo-of-apple-leUfWn-_USCO.jpg", "event_title": "Next CEO of Apple?", "event_slug": "next-ceo-of-apple", "neg_risk": true, "token_ids": [ "92311850691119989700494243314039291208974625045063458908184129720108035114164", "14954786464025422340381119759227166561985048022429770302259194276382916274663" ], "outcomes": ["Yes", "No"], "payouts": [1.0, 0.0], "outcome_prices": [1.0, 0.0], "winning_outcome_index": 0, "winning_outcome": "Yes", "winning_token_id": "92311850691119989700494243314039291208974625045063458908184129720108035114164", "resolved_at": "2026-05-28T04:49:07.589252+00:00" } ], "rows_returned": 1, "has_more": true, "elapsed_ms": 3 } ``` ## Response fields | Field | Type | Description | | ----------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------- | | `id` | string | Redemption event ID | | `timestamp` | string | Unix timestamp | | `condition` | string | Market condition ID that was redeemed | | `payout` | string | Exact raw payout amount in six-decimal integer units. Preserved for backwards compatibility. | | `index_sets` | array | Which outcome index sets were redeemed | | `condition_id` | string | Alias of `condition`, matching the WebSocket redemption payload naming. | | `payout_e6` | string | Alias of `payout`; exact six-decimal payout amount for accounting-safe comparisons. | | `payout_usdc` | number | Human-readable payout amount in USDC. | | `market_title` | string \| null | Human-readable market question. | | `market_slug` | string \| null | Market slug. | | `market_image` | string \| null | Market image URL. | | `event_title` | string \| null | Parent event title, when available. | | `event_slug` | string \| null | Parent event slug, when available. | | `neg_risk` | boolean \| null | Whether the market uses Polymarket negative-risk settlement. | | `token_ids` | string\[] \| null | Ordered market token IDs. Same order as `outcomes`, `payouts`, and `outcome_prices`. | | `outcomes` | string\[] \| null | Ordered outcome labels for the market. | | `payouts` | number\[] \| null | Resolved payout numerators for each outcome, when resolution metadata is available. | | `outcome_prices` | number\[] \| null | Payouts normalized by the payout denominator. Winners are usually `1.0`; losers are usually `0.0`. | | `winning_outcome_index` | number \| null | Index of the winning outcome when exactly one outcome has a non-zero payout, or when metadata already provides the winner. | | `winning_outcome` | string \| null | Label of the winning outcome. | | `winning_token_id` | string \| null | Token ID of the winning outcome. | | `resolved_at` | string \| null | ISO timestamp for when the market resolution metadata indicates the condition became resolved. | # Wallet Splits Source: https://docs.polynode.dev/data/wallets/splits GET /v3/wallets/{address}/splits Get all split events for a wallet. Splits occur when USDC is converted into outcome tokens. Returns split events where USDC was converted into outcome tokens for a market condition. ## Request ``` GET /v3/wallets/{address}/splits ``` ### Query parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ------------------------------------ | | `after` | integer | -- | Start of time range (Unix timestamp) | | `before` | integer | -- | End of time range | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0xada100874d00e3331d00f2007a9c336a65009718/splits?limit=1 ``` ```json theme={null} { "data": [ { "id": "0x48a4ffe402ea660ccb9dea5f802ca4ea5d22ed395ae07d249fca0ec94b79d325_0x566", "timestamp": "1778492385", "condition": "0xd863a481323564b64f431d4c8fa8f0237f56075b2bb4b6f3ec9be8986ec292f0", "amount": "33927075" } ], "rows_returned": 1, "has_more": true, "elapsed_ms": 3 } ``` ## Response fields | Field | Type | Description | | ----------- | ------ | ----------------------------- | | `id` | string | Split event ID | | `timestamp` | string | Unix timestamp | | `condition` | string | Market condition ID | | `amount` | string | USDC amount split (6-decimal) | # Wallet Summary Source: https://docs.polynode.dev/data/wallets/summary GET /v3/wallets/{address} Get a complete P&L summary for any Polymarket wallet in USD. Realized and unrealized profit/loss, win rate, position count, and total volume. Returns a full profile for a single wallet in USD. Includes realized P\&L, unrealized P\&L from open positions, gross profit and loss, win/loss record, and total trading volume. ## Request ``` GET /v3/wallets/{address} ``` ### Path parameters | Parameter | Type | Description | | --------- | ------ | ---------------------------------------------- | | `address` | string | Wallet address (0x-prefixed, case-insensitive) | ### Query parameters | Parameter | Type | Default | Description | | ---------------------------- | ------- | ------- | -------------------------------------------------------------------------------------------------------- | | `include_combos` | boolean | `false` | Add combo P\&L and combo position counts to the all-time wallet summary. | | `include_accounting_summary` | boolean | `false` | Add the all-time wallet accounting summary, including exact trader-paid fees from indexed onchain fills. | ## Example ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0x56687bf447db6ffa42ffe2204a05edaa20f55839 ``` ### Summary including combos ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907?include_combos=true" ``` ```json theme={null} { "address": "0x63613e3b96f418332d43cd2af8dc321014d15907", "net_realized_pnl": 113.510513, "realized_pnl": 113.510513, "total_pnl": 113.510513, "position_count": 21496, "open_positions": 1495, "include_combos": true, "included_position_types": ["market", "combo"], "combo_pnl": { "position_type": "combo", "included": true, "realized_pnl": 113.510513, "total_pnl": 113.510513, "position_count": 21492, "open_positions": 1495 } } ``` ### Summary including accounting Use this when you need the exact all-time fee summary without paging through raw fill rows. ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x7553e42ce1b37727b819dfe4bac495b3968f65ca?include_accounting_summary=true" ``` ```json theme={null} { "address": "0x7553e42ce1b37727b819dfe4bac495b3968f65ca", "net_realized_pnl": -138570.0369464795, "gross_profit": 524921.0508558493, "gross_loss": -671088.850081543, "unrealized_pnl": -7597.762279214215, "total_pnl": -146167.79922569369, "position_count": 915, "open_positions": 0, "total_volume": 2718779.615141, "accounting_summary": { "available": true, "loaded": true, "has_order_filled_rows": true, "source": "api.wallet_accounting_summary_current", "fees_paid": { "available": true, "loaded": true, "amount": 99973.115598, "raw": "99973115598", "fee_fill_count": 2984, "order_owner_fill_count": 5122, "first_fee_block": "84892414", "last_fee_block": "88904629" }, "maker_rebates": { "available": false, "loaded": false, "status": "not_loaded", "amount": null, "raw": null }, "rewards": { "available": false, "loaded": false, "status": "not_loaded", "amount": null, "raw": null }, "order_filled_through_block": "88978163", "projector_status": "ready" }, "elapsed_ms": 2 } ``` ```json theme={null} { "address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839", "net_realized_pnl": 22053845.825455, "gross_profit": 22057977.181649, "gross_loss": -4131.356194, "unrealized_pnl": 0.000688755, "total_pnl": 22053845.826143753, "wins": 18, "losses": 4, "position_count": 22, "open_positions": 1, "total_volume": 43013258.515682, "elapsed_ms": 1 } ``` ## Response fields | Field | Type | Description | | ----------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------ | | `net_realized_pnl` | number | Net realized P\&L in USD | | `gross_profit` | number | Sum of all winning position P\&L (USD) | | `gross_loss` | number | Sum of all losing position P\&L (USD, negative) | | `unrealized_pnl` | number | Unrealized P\&L from open positions based on current prices (USD) | | `total_pnl` | number | `net_realized_pnl + unrealized_pnl` (USD) | | `wins` | integer | Number of positions closed with positive P\&L | | `losses` | integer | Number of positions closed with negative P\&L | | `position_count` | integer | Total positions (open + closed) | | `open_positions` | integer | Positions with shares > 0 | | `total_volume` | number | Total volume traded (USD) | | `combo_pnl` | object | Present when `include_combos=true`; combo-only contribution to the wallet summary | | `accounting_summary` | object | Present when `include_accounting_summary=true`; all-time compact wallet accounting summary | | `accounting_summary.fees_paid.amount` | number | Exact all-time trader-paid fees in USD | | `accounting_summary.fees_paid.raw` | string | Exact all-time trader-paid fees in 6-decimal raw units | | `accounting_summary.fees_paid.fee_fill_count` | integer | Number of order-owner fills with a positive fee | | `accounting_summary.fees_paid.order_owner_fill_count` | integer | Number of indexed order-owner fills for the wallet | | `accounting_summary.maker_rebates.status` | string | Maker rebate projection status. `not_loaded` means do not treat the amount as zero. | | `accounting_summary.rewards.status` | string | Reward projection status. `not_loaded` means do not treat the amount as zero. | | `accounting_summary.order_filled_through_block` | string | Latest block covered by the order-filled accounting projector | | `accounting_summary.projector_status` | string | Current projector status | # Wallet Trades Source: https://docs.polynode.dev/data/wallets/trades GET /v3/wallets/{address}/trades Get all trades for a wallet with market context, computed price, direction, and order hash. Returns fills where the wallet participated as maker, taker, or both. Each trade is enriched with market metadata, computed price/size in USD, buy/sell direction, and the order hash. ## Request ``` GET /v3/wallets/{address}/trades ``` ### Query parameters | Parameter | Type | Default | Description | | ---------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `side` | string | `both` | `maker`, `taker`, or `both` | | `token_id` | string | -- | Filter trades involving this token | | `condition_id` | string | -- | Filter by market condition ID (resolves to token IDs) | | `market_slug` | string | -- | Filter by market slug (resolves to token IDs) | | `min_amount` | integer | -- | Minimum `maker_amount_filled` (raw 6-decimal) | | `sort_by` | string | -- | `order_hash` groups fills by limit order visually (individual fill rows, sorted by most recent order) | | `group_by` | string | -- | `user_trade` returns Polymarket-style user-visible trade rows; `transaction_hash` is an alias; `order_hash` aggregates wallet-visible fills per order | | `after` | integer | -- | Start of time range (Unix timestamp) | | `before` | integer | -- | End of time range (Unix timestamp) | | `include_combos` | boolean | `false` | Add combo trades in a separate `combo_trades` branch. If the wallet has no combo trades, the response remains 200 with an empty combo branch. | | `order` | string | `desc` | `asc` or `desc` | | `limit` | integer | 100 | Max 300 | | `offset` | integer | 0 | Pagination offset | ## Example ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0xa9857c7bcb9bcfafd2c132ab053f34f678610058/trades?limit=1 ``` ```json theme={null} { "address": "0xa9857c7bcb9bcfafd2c132ab053f34f678610058", "trades": [ { "id": "0x284b61d18c8e0a60333bfe883288c7d9861c9c07a410050f537550940038a713_951", "maker": "0xa9857c7bcb9bcfafd2c132ab053f34f678610058", "taker": "0xe111180000d2663c0091e4f400237545b87b996b", "maker_asset_id": "0", "taker_asset_id": "75783394880030392863380883800697645018418815910449662777195626260206142035810", "maker_amount": 0.999999, "taker_amount": 1.694914, "fee": 0.01229, "price": 0.59, "size": 1.694914, "timestamp": "1778674056", "transaction_hash": "0x284b61d18c8e0a60333bfe883288c7d9861c9c07a410050f537550940038a713", "order_hash": "0x21245a1d81ff19d7effcdb7e3b78d5fe66098708a7dc7cad2267f81d237acc7f", "builder": "0x0000000000000000000000000000000000000000000000000000000000000000", "side": 0, "role": "maker", "direction": "BUY", "market": "Dota 2: Aurora vs Team Liquid (BO3) - DreamLeague Group A", "slug": "dota2-aur1-liquid-2026-05-13", "outcome": "Aurora", "outcome_index": 0, "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/dota2-7ffacddb21.jpg", "condition_id": "0x4f05dbc6273b89aed46bb79a961c1d8771c01925d92d439e9a81fa6241900661" } ], "rows_returned": 1, "has_more": true, "offset": 0, "limit": 1, "elapsed_ms": 40 } ``` ### Maker-only trades ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0x.../trades?side=maker&limit=10 ``` ### Trades for a specific token ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0x.../trades?token_id=75783394...&limit=10 ``` ### Trades in a time window ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0x.../trades?after=1778000000&before=1778604000 ``` ### Include combo trades ```bash theme={null} curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/trades?include_combos=true&limit=5" ``` Combo trades are returned in `combo_trades` so existing consumers of the standard `trades` array do not need to change parsing logic. ```json theme={null} { "address": "0x63613e3b96f418332d43cd2af8dc321014d15907", "trades": [], "rows_returned": 0, "include_combos": true, "included_position_types": ["market", "combo"], "combo_trade_count": 5, "combo_has_more": true, "combo_source": "v3.wallet_combos.trades", "combo_trades": [ { "position_type": "combo", "wallet_role": "maker", "wallet_address": "0x63613e3b96f418332d43cd2af8dc321014d15907", "counterparty_address": "0xe3333700ca9d93003f00f0f71f8515005f6c00aa", "combo_condition_id": "0x0310c8c1e924b0ddffd987430c42fc26e10000000000000000000000000000", "position_id": "1741334187009265192213210063949860811096650382021683265628751751539647840256", "side": "BUY", "price": "0.0197", "size": "0.050750", "fee": "0.000020", "tx_hash": "0xf3a8985f04bf869483ef4163a185f296c834eb827b5e5ae3db5bd44558121d51", "block_number": 88276713, "timestamp": 1781120055, "source": "v3.wallet_combos.trades" } ] } ``` ## Response fields ### Top-level combo fields | Field | Type | Description | | ------------------------- | ------- | ---------------------------------------------------------------- | | `include_combos` | boolean | Present when `include_combos=true` was requested | | `included_position_types` | array | Includes `market` and `combo` when combo inclusion was requested | | `combo_trades` | array | Combo trade rows for the wallet | | `combo_trade_count` | integer | Number of combo trade rows returned in this page | | `combo_has_more` | boolean | Whether more combo trade rows are available | | `combo_source` | string | Source label for the combo trade branch | ### Trade data | Field | Type | Description | | ------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `id` | string | Unique fill ID (transaction\_hash + log index) | | `maker` | string | Maker wallet address | | `taker` | string | Taker wallet address | | `maker_asset_id` | string | Asset the maker provided (`0` = USDC) | | `taker_asset_id` | string | Asset the taker provided (`0` = USDC) | | `maker_amount` | number | USD amount the maker provided | | `taker_amount` | number | USD amount the taker provided | | `fee` | number | Fee charged (USD) | | `token_id` | string | Outcome token ID for the visible trade leg | | `asset` | string | Alias for `token_id`, included for Polymarket compatibility | | `amount` | number | Alias for `size`, included for Polymarket compatibility | | `price` | number | Computed price per outcome token (maker\_amount / taker\_amount or inverse) | | `size` | number | Number of outcome tokens traded (USD) | | `timestamp` | string | Unix timestamp | | `transaction_hash` | string | On-chain transaction hash | | `order_hash` | string | Unique order hash for this fill | | `builder` | string | Builder attribution code (hex) | | `side` | integer | Maker's limit order direction: `0` = maker was buying tokens, `1` = maker was selling tokens, `null` for V1 trades. This is the maker's side, not the queried wallet's action. Use `direction` instead for the wallet's perspective. | | `role` | string | `maker` or `taker` -- which side the queried wallet was on in this fill | | `direction` | string | `BUY` or `SELL` -- whether the queried wallet bought or sold outcome tokens. This is the field you want for understanding what the wallet did. | ### Market context | Field | Type | Description | | --------------- | ------- | ------------------------------------ | | `market` | string | Market question text | | `slug` | string | Market URL slug | | `outcome` | string | Outcome label (e.g. "Yes", "Aurora") | | `outcome_index` | integer | Outcome position (0 or 1) | | `image` | string | Market image URL | | `condition_id` | string | Market condition ID | ### Sort fills by order hash ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0xa9857c.../trades?sort_by=order_hash&limit=10 ``` Groups all fills belonging to the same limit order together visually, sorted by most recent order first. Returns individual fill rows. ### User-visible trades ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0xa9857c.../trades?market_slug=dota2-aur1-liquid-2026-05-13&group_by=user_trade ``` Returns one row per user-visible trade, matching the Polymarket-style wallet trade feed. This removes complementary order-side rows that can appear in raw CTF fills and merges fills that represent the same user action. Use this grouping for UI trade history and user-facing volume math. `group_by=transaction_hash` is accepted as an alias for this behavior. ### Aggregate fills by order hash ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0x61b0ea32aff59e9893e867a2ab196476ab22dd96/trades?group_by=order_hash ``` Returns one row per wallet-visible order with aggregated totals. Complementary order-side rows are collapsed from the queried wallet's perspective before order aggregation, so UI volume and share counts do not double-count both sides of the same user action. Use `group_by=user_trade` when you want one row per user-visible fill without order-level rollup. ```json theme={null} { "trades": [ { "order_hash": "0xea91f0d67086be...", "fill_count": 5, "avg_price": 0.57, "total_amount_usd": 669.11, "total_usd": 669.11, "total_shares": 1173.88, "total_fee": 0.0, "total_maker_amount": 669.11, "total_taker_amount": 1173.88, "first_fill_at": 1778100000, "last_fill_at": 1778200000, "tx_hashes": ["0xabc...", "0xdef..."], "market": "MegaETH market cap (FDV) >$1.5B one day after TGE?", "slug": "megaeth-fdv-1-5b-one-day-after-tge", "outcome": "Yes", "direction": "BUY" } ], "rows_returned": 6, "grouped_by": "order_hash" } ``` #### Grouped response fields | Field | Type | Description | | -------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------ | | `order_hash` | string | The limit order hash | | `fill_count` | integer | Number of fills in this order | | `avg_price` | number | Volume-weighted average price (`total_amount_usd / total_shares`) | | `total_amount_usd` | number | Total USD value across all fills. Alias of `total_usd`, included for compatibility with grouped onchain trade responses. | | `total_usd` | number | Total USD value across all fills | | `total_shares` | number | Total outcome tokens across all fills | | `total_fee` | number | Total fees (USD) | | `total_maker_amount` | number | Raw sum of maker amounts (side-dependent) | | `total_taker_amount` | number | Raw sum of taker amounts (side-dependent) | | `first_fill_at` | integer | Earliest fill timestamp (Unix seconds) | | `last_fill_at` | integer | Latest fill timestamp (Unix seconds) | | `timestamp` | string | Same as `last_fill_at` but as a string. Present for consistency with ungrouped trade rows. | | `tx_hashes` | array\ | Unique transaction hashes contributing to this order | | `market` | string \| null | Market question | | `slug` | string \| null | Market slug | | `outcome` | string \| null | Outcome label | | `outcome_index` | integer \| null | Outcome position (0 or 1) | | `image` | string \| null | Market image URL | | `condition_id` | string \| null | Market condition ID | | `direction` | string | `BUY` or `SELL` (queried wallet's perspective) | | `role` | string | `maker` or `taker` (which side the queried wallet was on) | | `side` | integer \| null | Maker's limit-order direction (raw on-chain value). Use `direction` instead. | | `maker` | string | Maker wallet address | | `taker` | string | Taker wallet address | ### Filter by market condition ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0xa9857c.../trades?condition_id=0x4f05dbc6... ``` ### Filter by market slug ```bash theme={null} curl https://api.polynode.dev/v3/wallets/0xa9857c.../trades?market_slug=dota2-aur1-liquid-2026-05-13 ``` # Best Practices Source: https://docs.polynode.dev/guides/best-practices Patterns for correctly handling the pending-to-confirmed lifecycle, snapshots, and reconnection. ## The pending-to-confirmed lifecycle PolyNode detects Polymarket settlements **before** they confirm on-chain. This means every settlement goes through two stages, delivered as **two separate WebSocket events**: 1. **`settlement`** — the trade is detected pre-chain (`status: "pending"`) 2. **`status_update`** — the trade confirms in a Polygon block (typically 2–4 seconds later) Link them together by `tx_hash`: ```javascript theme={null} const pending = new Map(); // tx_hash → settlement data ws.onmessage = (event) => { const msg = JSON.parse(event.data); if (msg.type === "settlement" && msg.data.status === "pending") { pending.set(msg.data.tx_hash, { ...msg.data, received_at: Date.now(), }); console.log(`PENDING: ${msg.data.taker_side} $${msg.data.taker_size}`); } if (msg.type === "status_update") { const original = pending.get(msg.data.tx_hash); if (original) { const leadMs = msg.data.confirmed_at - original.received_at; console.log(`CONFIRMED: ${msg.data.latency_ms}ms server lead, ${leadMs}ms client lead`); pending.delete(msg.data.tx_hash); } } }; ``` These are **two separate events**, not an update to the original event. If you only listen for `settlement` events, you'll see pending trades but never know when they confirm. ## Handle the snapshot correctly When you subscribe, PolyNode sends a **snapshot** of recent events before streaming live data. The snapshot contains both `settlement` and `status_update` events mixed together. **The gotcha**: Settlement events in the snapshot always have their **original** status (`pending`), even if they were confirmed seconds ago. The corresponding `status_update` is a separate entry in the same snapshot. If you only process `settlement` events from the snapshot, you'll load stale "pending" settlements that are actually already confirmed — and their confirmation event will never arrive as a live message because it was already sent in the snapshot you ignored. ### Correct snapshot handling ```javascript theme={null} ws.onmessage = (event) => { const msg = JSON.parse(event.data); if (msg.type === "snapshot") { // Step 1: Collect all status updates from the snapshot const confirmations = new Map(); for (const ev of msg.events) { if (ev.type === "status_update" && ev.data) { confirmations.set(ev.data.tx_hash, ev.data); } } // Step 2: Process settlements, checking for matching confirmations for (const ev of msg.events) { if (ev.type === "settlement" && ev.data) { const confirmation = confirmations.get(ev.data.tx_hash); if (confirmation) { // This settlement is already confirmed — use the confirmation data handleConfirmedSettlement(ev.data, confirmation); } else if (ev.data.status === "pending") { // Genuinely pending — will receive a live status_update soon handlePendingSettlement(ev.data); } } } return; } // Live events — standard handling if (msg.type === "settlement") handlePendingSettlement(msg.data); if (msg.type === "status_update") handleStatusUpdate(msg.data); }; ``` ## Add a stuck-pending timeout In rare cases, a `status_update` might be missed (e.g., network interruption, reconnection timing). Add a timeout so stale pending items don't get stuck forever: ```javascript theme={null} const PENDING_TIMEOUT_MS = 15_000; // 15 seconds setInterval(() => { const now = Date.now(); for (const [hash, data] of pending) { if (now - data.received_at > PENDING_TIMEOUT_MS) { console.log(`Timeout: ${hash} — no confirmation after 15s`); pending.delete(hash); } } }, 5_000); ``` Under normal conditions, confirmations arrive within 2–5 seconds. A 15-second timeout is generous enough to never fire in normal operation but catches edge cases. ## Reconnection with state recovery When reconnecting, use `snapshot_count` to recover recent state. The snapshot fills in events you missed during the disconnection: ```javascript theme={null} function connect() { const ws = new WebSocket("wss://ws.polynode.dev/ws?key=pn_live_YOUR_KEY"); let delay = 1000; ws.onopen = () => { delay = 1000; ws.send(JSON.stringify({ action: "subscribe", type: "settlements", filters: { snapshot_count: 100 }, // Catch up on missed events })); }; ws.onclose = () => { setTimeout(connect, Math.min(delay *= 2, 30000)); }; ws.onmessage = (event) => { const msg = JSON.parse(event.data); if (msg.type === "heartbeat") return; // Handle snapshot + live events as above }; } ``` The snapshot includes both `settlement` and `status_update` events from the recent buffer. Processing both event types from the snapshot (as shown above) ensures you have accurate state immediately after reconnection. ## Connection health The server sends a WebSocket Ping frame and a text heartbeat (`{"type": "heartbeat"}`) every 30 seconds. If the server receives no activity from the client within 5 minutes, the connection is closed. Activity that resets the server's liveness timer: * **Pong frames** (automatic response to server Ping, handled by most WS libraries) * **Any text message** you send: subscribe, unsubscribe, or `{"action": "ping"}` For most clients, this works automatically. All standard WebSocket libraries respond to Ping with Pong: * **Browser `WebSocket`** — handled by the browser * **Node.js `ws`** — automatic by default * **Python `websockets`** — automatic by default * **Go `gorilla/websocket`** — automatic with `SetPongHandler` (default) **Cloud-hosted clients (Railway, Render, Heroku, fly.io, AWS ALB):** Many cloud platforms run a reverse proxy in front of your container that intercepts WebSocket Ping/Pong frames at the proxy layer. Your application never sees the server's Ping, so it never sends a Pong, and the server eventually drops the connection for inactivity. **Symptoms:** Connection works on subscribe, receives a snapshot, then drops after 1-5 minutes with an empty error or no close frame. Reconnects immediately but the cycle repeats. **Fix:** Send `{"action": "ping"}` every 30 seconds from your application code. This is a regular JSON text frame that passes through any proxy. See the [WebSocket Overview](/websocket/overview#application-level-keepalive) for full code examples. If your read loop is blocked (e.g. doing heavy synchronous work before reading the next message), the client cannot send Pong frames and the server will eventually drop the connection. Keep your read loop running — offload heavy processing to a separate thread or task. ## Summary | Pattern | Why | | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | | Process **both** `settlement` and `status_update` events | Settlements and confirmations are separate events linked by `tx_hash` | | Process **both event types from the snapshot** | Snapshot settlements keep their original `pending` status — confirmations are separate entries | | Add a **stuck-pending timeout** (15s) | Safety net for missed confirmations during reconnection | | Use **`snapshot_count`** on reconnect | Recovers events missed during disconnection | | Keep read loop running | Server drops connections with no activity after 5 minutes | | Send `{"action": "ping"}` every 30s **if cloud-hosted** | Keeps connection alive through reverse proxies that strip Ping/Pong frames | # Copy Trading Pipeline Source: https://docs.polynode.dev/guides/copy-trading Private documentation for copy trading integration partners