# Cohort Stats
Source: https://docs.predexon.com/api-reference/analytics/cohort-stats
GET /v2/polymarket/cohorts/stats
Compare performance across trading style cohorts
Understand which trading styles perform best. Cohorts are based on `primary_trading_style`: WHALE, MARKET\_MAKER, ACTIVE\_TRADER, BUY\_AND\_HOLD, DEGEN, HIGH\_CONVICTION, CONTRARIAN, VALUE\_HUNTER.
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
See [Trading Labels](/api-reference/trading-labels) for how each cohort is defined, the criteria behind each label, and minimum requirements for classification.
**Rolling total PnL accuracy timeline** - Total PnL (realized + unrealized - net fees) uses rolling windows that need time to accumulate full data:
* **All-time total PnL**: accurate immediately (February 10, 2026)
* **1-day rolling total PnL**: fully accurate starting February 11, 2026
* **7-day rolling total PnL**: fully accurate starting February 17, 2026
* **30-day rolling total PnL**: fully accurate starting March 12, 2026
# Filter Wallets by Markets
Source: https://docs.predexon.com/api-reference/analytics/filter-wallets
GET /v2/polymarket/wallets/filter
Filter wallets by market trades with AND/OR logic
Find wallets that traded specific markets with configurable logic. Use AND to find wallets that traded *all* specified markets, or OR to find wallets that traded *any* of them. Filter by combined performance metrics across those markets.
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
**Rolling total PnL accuracy timeline** - Total PnL (realized + unrealized - net fees) uses rolling windows that need time to accumulate full data:
* **All-time total PnL**: accurate immediately (February 10, 2026)
* **1-day rolling total PnL**: fully accurate starting February 11, 2026
* **7-day rolling total PnL**: fully accurate starting February 17, 2026
* **30-day rolling total PnL**: fully accurate starting March 12, 2026
| Constraint | Value |
| - | - |
| `limit` | 1–100 (default 100) |
# Global Leaderboard
Source: https://docs.predexon.com/api-reference/analytics/leaderboard
GET /v2/polymarket/leaderboard
Get the global leaderboard of smart wallets
Ranked list of wallets by trading performance. Supports filtering by trading style (WHALE, MARKET\_MAKER, ACTIVE\_TRADER, etc.), PnL range, minimum volume, win rate, and more.
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
Each wallet exposes individual boolean columns (`is_whale`, `is_contrarian`, etc.) so you can filter by any combination of labels - not just `primary_trading_style`. See [Trading Labels](/api-reference/trading-labels) for the full list and criteria.
**Rolling total PnL accuracy timeline** - Total PnL (realized + unrealized - net fees) uses rolling windows that need time to accumulate full data:
* **All-time total PnL**: accurate immediately (February 10, 2026)
* **1-day rolling total PnL**: fully accurate starting February 11, 2026
* **7-day rolling total PnL**: fully accurate starting February 17, 2026
* **30-day rolling total PnL**: fully accurate starting March 12, 2026
| Constraint | Value |
| - | - |
| `limit` | 1–100 (default 100) |
# Market Leaderboard
Source: https://docs.predexon.com/api-reference/analytics/market-leaderboard
GET /v2/polymarket/leaderboard/market/{condition_id}
Leaderboard for a specific market
Wallet rankings for trading performance within a single market. Filter by entry/sell prices, edge, and volume to find the best-performing traders on any market.
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
**Rolling total PnL accuracy timeline** - Total PnL (realized + unrealized - net fees) uses rolling windows that need time to accumulate full data:
* **All-time total PnL**: accurate immediately (February 10, 2026)
* **1-day rolling total PnL**: fully accurate starting February 11, 2026
* **7-day rolling total PnL**: fully accurate starting February 17, 2026
* **30-day rolling total PnL**: fully accurate starting March 12, 2026
| Constraint | Value |
| - | - |
| `limit` | 1–100 (default 100) |
# Position Top Holders
Source: https://docs.predexon.com/api-reference/analytics/position-top-holders
GET /v2/polymarket/position/{token_id}/top-holders
Top holders of a specific Polymarket token, ranked by position size
Get the largest holders of a single Polymarket CTF token (one outcome side of a market), ranked by position size. Use this when you have a specific `token_id` rather than a `condition_id` — for whole-market top holders across both Yes and No, use [Market Top Holders](/api-reference/analytics/top-holders).
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
### Pagination
Pass `include_count=true` to get the total holder count alongside the page. Use the `pagination_key` cursor from each response to fetch the next page.
| Constraint | Value |
| - | - |
| `limit` | Results per page. |
| `pagination_key` | Cursor returned by the previous response. |
# Market Top Holders
Source: https://docs.predexon.com/api-reference/analytics/top-holders
GET /v2/polymarket/market/{condition_id}/top-holders
Top holders for a specific market ranked by position size
Get the largest position holders in any market. Use the `side` parameter to filter by Yes or No holders and see their position value, entry price, and PnL.
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
**Rolling total PnL accuracy timeline** - Total PnL (realized + unrealized - net fees) uses rolling windows that need time to accumulate full data:
* **All-time total PnL**: accurate immediately (February 10, 2026)
* **1-day rolling total PnL**: fully accurate starting February 11, 2026
* **7-day rolling total PnL**: fully accurate starting February 17, 2026
* **30-day rolling total PnL**: fully accurate starting March 12, 2026
| Constraint | Value |
| - | - |
| `limit` | 1–100 (default 100) |
# Candles
Source: https://docs.predexon.com/api-reference/binance/candles
GET /v2/binance/candles/{symbol}
Get OHLCV candlestick data for a Binance trading pair
Fetch OHLCV candlestick data derived from Binance book ticker mid-prices. Supported intervals: `1s`, `1m`, `5m`, `15m`, `1h`, `4h`, `1d`.
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
| Constraint | Value |
| - | - |
| `limit` | 1–1,500 (default 500) |
### Time Range Limits
| Interval | Max Range |
| - | - |
| `1s` | 1 hour |
| `1m` | 7 days |
| `5m` | 30 days |
| `15m` | 90 days |
| `1h` | 180 days |
| `4h` | 365 days |
| `1d` | 730 days |
Useful for backtesting crypto up/down prediction markets:
* `15m` for 15-minute markets
* `1h` for 1-hour markets
* `4h` and `1d` for longer timeframes
# Ticks
Source: https://docs.predexon.com/api-reference/binance/ticks
GET /v2/binance/ticks/{symbol}
Get raw book ticker data at microsecond granularity
Fetch raw book ticker data for a Binance trading pair. Returns bid/ask/mid/spread for each tick with microsecond precision.
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
| Constraint | Value |
| - | - |
| `limit` | 1–10,000 (default 10,000) |
| Time range | No limit |
Use cursor-based pagination to iterate through large time ranges (e.g. a 15-minute window is \~63K ticks).
# Builder Markets
Source: https://docs.predexon.com/api-reference/builders/builder-markets
GET /v2/polymarket/builders/{builder_code}/markets
Top markets attributed to a builder over a rolling window
Top markets attributed to a single builder over the window, ranked by attributed volume. Each row carries `volume_usd`, `builder_fee_usd`, `trade_count`, and `unique_traders` for that `(builder, condition_id)`.
**V2-only.** Builder attribution is a Polymarket V2 concept - V1 fills, the zero-sentinel builder, and reversed (`sign != 1`) fills are excluded.
### Windows
`24h`, `7d` (default), `30d`, or `all`.
| Constraint | Value |
| - | - |
| `limit` | 1–500 (default 50) |
# Builder Stats
Source: https://docs.predexon.com/api-reference/builders/builder-stats
GET /v2/polymarket/builders/{builder_code}
Single-builder summary plus full fee-rate history
Single-builder summary over a rolling window, plus the full history of `(taker_bps, maker_bps)` rate changes for that builder.
**V2-only.** Builder attribution is a Polymarket V2 concept - V1 fills are excluded from all metrics.
### Windows
`24h`, `7d` (default), `30d`, or `all`.
### Fee-rate history
The `fee_rate_history` array returns every `(taker_bps, maker_bps)` rate the builder has used, in order, with `valid_from` and `valid_to` Unix timestamps. The current rate has a far-future `valid_to`.
| Field | Range |
| - | - |
| `taker_bps` | 0–100 |
| `maker_bps` | 0–50 |
# Builder Timeseries
Source: https://docs.predexon.com/api-reference/builders/builder-timeseries
GET /v2/polymarket/builders/{builder_code}/timeseries
Bucketed volume and fee revenue for a builder over a rolling window
Bucketed `volume_usd`, `builder_fee_usd`, and `trade_count` for a single builder, returned as an ordered array of `(timestamp, ...)` points. Use this for charting builder activity over time.
**V2-only.** Builder attribution is a Polymarket V2 concept - V1 fills are excluded.
### Buckets
| `bucket` | Use with |
| - | - |
| `day` (default) | `window=30d` or `all` |
| `hour` | `window=24h` or `7d` |
### Windows
`24h`, `7d`, `30d` (default), or `all`. Each `timestamp` is the Unix-seconds start of the bucket.
# Builder Leaderboard
Source: https://docs.predexon.com/api-reference/builders/leaderboard
GET /v2/polymarket/builders/leaderboard
Ranked list of V2 builders by attributed volume or fee revenue
Ranks Polymarket V2 builders over a rolling window by either attributed trade volume (default) or builder-fee revenue. Each row carries the builder's volume, fee revenue, trade count, unique traders/markets, and the current `(taker_bps, maker_bps)` rate.
**V2-only.** Builder attribution is a Polymarket V2 concept - V1 fills, the zero-sentinel builder, and reversed (`sign != 1`) fills are excluded.
### Sorting
| `sort_by` | Behavior |
| - | - |
| `volume` (default) | Ranks by `volume_usd` - surfaces the highest-throughput builders, many of which charge 0 bps. |
| `fee` | Ranks by `builder_fee_usd` - more useful when you care about revenue-earning builders. |
### Windows
`24h`, `7d` (default), `30d`, or `all`.
| Constraint | Value |
| - | - |
| `limit` | 1–500 (default 50) |
# Wallet Builder Fees
Source: https://docs.predexon.com/api-reference/builders/wallet-builder-fees
GET /v2/polymarket/builders/wallet/{wallet}
Total builder fees a wallet has paid, with a per-builder breakdown
Total builder fees a single wallet has paid over a rolling window, plus a per-builder breakdown. Each fill is scored at the `(taker_bps, maker_bps)` rate that was actually in effect at the fill's `block_timestamp` (ASOF join against the builder fee-rate history), so historical rate changes are accounted for correctly.
**V2-only.** Builder attribution is a Polymarket V2 concept - V1 fills are excluded from all metrics.
### Time range
By default the endpoint uses a rolling `window`: `24h`, `7d`, `30d`, or `all` (default). To score fees over an exact interval instead, pass `start_time` and/or `end_time` as Unix seconds — when either is set it **overrides** `window`.
| Parameter | Description |
| - | - |
| `window` | Rolling window: `24h`, `7d`, `30d`, or `all` (default). Ignored if `start_time`/`end_time` are provided. |
| `start_time` | Unix seconds, inclusive. Overrides `window`. |
| `end_time` | Unix seconds, exclusive. Overrides `window`. |
| `builder_code` | Restrict the result to a single builder (bytes32 hex). |
| `limit` | Caps the number of per-builder breakdown rows returned. |
The response echoes the effective `start_time`, `end_time`, and `builder_code` that were applied to the query.
# List Categories
Source: https://docs.predexon.com/api-reference/discovery/categories
GET /v2/polymarket/categories
Top-level categories with market counts, volume, and liquidity
Top-level Polymarket categories ordered by activity, each with its market count, total and rolling volume, liquidity, and all-time trade count. Use this to build a top-level navigation surface before drilling into [tags](/api-reference/discovery/tags) or [events](/api-reference/markets/events).
Per-category open interest is not exposed — a category-wide OI sum is unreliable. Use `total_volume_usd`, the windowed `rolling_metrics`, and `liquidity_usd` for activity ranking.
# Unified Search
Source: https://docs.predexon.com/api-reference/discovery/search
GET /v2/polymarket/search
Cross-surface search over Polymarket markets, events, and tags
A single query that searches across Polymarket markets, events, and tags, returning results grouped by type. Use it to power a search box or autocomplete.
| Parameter | Value |
| - | - |
| `q` | Search query (required). |
| `types` | Comma-separated subset of `markets`, `events`, `tags`. Defaults to all three. |
| `limit` | Max results per type. 1–50 (default 10). |
# List Series
Source: https://docs.predexon.com/api-reference/discovery/series
GET /v2/polymarket/series
Recurring market groupings, ordered by open-event count
List Polymarket series — recurring market groupings (e.g. daily/weekly/monthly markets) — ordered by open-event count.
| Parameter | Value |
| - | - |
| `recurrence` | Filter by recurrence: `daily`, `weekly`, or `monthly`. |
| `active` | Filter by active status. Defaults to active-only; pass `active=false` to include inactive series. |
| `limit` | 1–200 (default 50). |
| `offset` | Offset for pagination (default 0). |
The crypto up/down feature is just a named series. Fetch its current events with [Get Series Detail](/api-reference/discovery/series-detail) — e.g. `/series/btc-up-or-down-daily`.
# Get Series Detail
Source: https://docs.predexon.com/api-reference/discovery/series-detail
GET /v2/polymarket/series/{slug}
A single series plus its events (keyset-paginated)
A single series by `slug` plus its events, with keyset pagination. The crypto up/down feature is a named series — e.g. `/series/btc-up-or-down-daily` returns the current Bitcoin up/down events.
| Parameter | Value |
| - | - |
| `status` | Filter the series' events by status. |
| `limit` | Number of events to return. 1–200 (default 50). |
| `pagination_key` | Cursor returned from the previous response. |
# Get Tag Detail
Source: https://docs.predexon.com/api-reference/discovery/tag
GET /v2/polymarket/tag/{slug}
Metadata and rolled-up metrics for a single curated tag
A single curated tag by `slug`: metadata (label, image, description) plus its rolled-up volume and liquidity metrics (per-tag open interest is not exposed).
For the tag's events use [List Events](/api-reference/markets/events) with `?tag=`. For its child subtags use [Get Tag Subtags](/api-reference/discovery/tag-subtags).
# Get Tag Subtags
Source: https://docs.predexon.com/api-reference/discovery/tag-subtags
GET /v2/polymarket/tag/{slug}/subtags
Ranked child subtags for a major tag, each with an event count
Ranked child subtags for a major tag, each with an event count — powers the category sidebar (e.g. `/tag/politics/subtags` → Trump, Midterms, Primaries…). Each `count` equals what [List Events](/api-reference/markets/events) returns for `?tag=&status=`.
| Parameter | Value |
| - | - |
| `status` | Scope for the per-subtag event count (default `open`). |
| `limit` | Max subtags to return. 1–100 (default 30). |
# List Tags
Source: https://docs.predexon.com/api-reference/discovery/tags
GET /v2/polymarket/tags
Curated tags with rolled-up volume and liquidity metrics
List curated Polymarket tags with rolled-up volume (total + rolling windows), liquidity, and all-time trade count, ordered by the chosen metric. This drives the tag navigation surface. `force_hide` tags are excluded unless you pass `include_hidden=true`.
Per-tag open interest is not exposed — a tag-wide OI sum is unreliable. Rank tags by `volume_7d` (default) or the other `rolling_metrics`, `total_volume_usd`, and `liquidity_usd`.
| Parameter | Value |
| - | - |
| `sort` | Sort metric (default `volume_7d`). |
| `limit` | 1–200 (default 50). |
| `offset` | Offset for pagination (default 0). |
| `include_hidden` | Include `force_hide` tags. Default `false`. |
To fetch the events under a tag, use [List Events](/api-reference/markets/events) with `?tag=`. For a tag's child subtags, use [Get Tag Subtags](/api-reference/discovery/tag-subtags).
# All Endpoints
Source: https://docs.predexon.com/api-reference/endpoints
Complete directory of every Predexon API endpoint - markets, prices, orderbooks, trades, wallets, and smart money across every venue
**Base URL**
* **Data API:** `https://api.predexon.com`
All endpoints authenticate via the `x-api-key` header. [Get your API key →](https://dashboard.predexon.com)
***
## Data API - 58 Endpoints
### Markets & Pricing
| Method | Endpoint | Description |
| - | - | - |
| `GET` | [`/v2/polymarket/markets/keyset`](/api-reference/markets/list-markets) | **List markets** - search, filter, sort with cursor pagination |
| `GET` | [`/v2/polymarket/events/keyset`](/api-reference/markets/events) | **List events** - top-level event groupings with cursor pagination |
| `GET` | [`/v2/polymarket/crypto-updown`](/api-reference/markets/crypto-updown) | **Crypto up/down** - BTC, ETH, SOL, XRP, HYPE, DOGE, BNB across 5m–daily |
| `GET` | [`/v2/polymarket/market-price/{token_id}`](/api-reference/markets/market-price) | **Market price** - current or historical |
| `GET` | [`/v2/polymarket/candlesticks/{condition_id}`](/api-reference/markets/candlesticks) | **Candlesticks** - OHLCV at 1m/1h/1d intervals, normalized to YES side |
| `GET` | [`/v2/polymarket/candlesticks/token/{token_id}`](/api-reference/markets/candlesticks-by-token) | **Candlesticks by token** - per-token OHLCV with un-normalized NO prices |
| `GET` | [`/v2/polymarket/markets/{token_id}/volume`](/api-reference/markets/volume) | **Volume time series** - cumulative by buy/sell |
| `GET` | [`/v2/polymarket/volume-chart/{condition_id}`](/api-reference/markets/volume-chart) | **Volume chart** - per-period with YES/NO breakdown |
| `GET` | [`/v2/polymarket/markets/{condition_id}/open_interest`](/api-reference/markets/open-interest) | **Open interest** - historical OI tracking |
| `GET` | [`/v2/polymarket/orderbooks`](/api-reference/markets/orderbooks) | **Orderbooks** - historical snapshots at ms resolution |
### Discovery & Taxonomy
| Method | Endpoint | Description |
| - | - | - |
| `GET` | [`/v2/polymarket/search`](/api-reference/discovery/search) | **Unified search** - markets, events, and tags in one query |
| `GET` | [`/v2/polymarket/categories`](/api-reference/discovery/categories) | **Categories** - top-level categories with counts + active volume/OI |
| `GET` | [`/v2/polymarket/tags`](/api-reference/discovery/tags) | **Tags** - curated tags with rolled-up volume/OI metrics |
| `GET` | [`/v2/polymarket/tag/{slug}`](/api-reference/discovery/tag) | **Tag detail** - metadata + metrics for one tag |
| `GET` | [`/v2/polymarket/tag/{slug}/subtags`](/api-reference/discovery/tag-subtags) | **Tag subtags** - ranked child subtags with event counts |
| `GET` | [`/v2/polymarket/series`](/api-reference/discovery/series) | **Series** - recurring market groupings (daily/weekly/monthly) |
| `GET` | [`/v2/polymarket/series/{slug}`](/api-reference/discovery/series-detail) | **Series detail** - one series plus its events (keyset) |
### Trades & Activity
| Method | Endpoint | Description |
| - | - | - |
| `GET` | [`/v2/polymarket/trades`](/api-reference/trading/trades) | **Trades** - by market, token, or wallet |
| `GET` | [`/v2/polymarket/activity`](/api-reference/trading/activity) | **Activity** - splits, merges, redeems |
| `GET` | [`/v2/polymarket/positions`](/api-reference/trading/positions) | **Positions** - cross-wallet position scanner |
### Wallet Analytics
| Method | Endpoint | Description |
| - | - | - |
| `GET` | [`/v2/polymarket/wallet/{wallet}`](/api-reference/wallet/wallet-profile) | **Profile** - full metrics across all time windows |
| `GET` | [`/v2/polymarket/wallets/profiles`](/api-reference/wallet/wallet-profiles-batch) | **Batch profiles** - up to 20 wallets at once |
| `GET` | [`/v2/polymarket/wallet/positions/{wallet}`](/api-reference/wallet/positions) | **Positions** - open + historical with P\&L |
| `GET` | [`/v2/polymarket/wallet/pnl/{wallet}`](/api-reference/wallet/pnl) | **P\&L** - realized, unrealized, total |
| `GET` | [`/v2/polymarket/wallet/{wallet}/markets`](/api-reference/wallet/wallet-markets) | **Market breakdown** - per-market performance |
| `GET` | [`/v2/polymarket/wallet/{wallet}/similar`](/api-reference/wallet/similar-wallets) | **Similar wallets** - portfolio overlap detection |
| `GET` | [`/v2/polymarket/wallet/volume-chart/{wallet}`](/api-reference/wallet/volume-chart) | **Volume chart** - BUY/SELL breakdown |
| `GET` | [`/v2/polymarket/wallet/identity/{wallet}`](/api-reference/wallet/identity) | **Identity** - display name, X handle, badges, wallet type, USDC balance |
| `POST` | [`/v2/polymarket/wallet/identities`](/api-reference/wallet/identities-batch) | **Identities (batch)** - up to 200 addresses, proxy or signer EOA |
| `GET` | [`/v2/polymarket/wallet/{address}/cluster`](/api-reference/wallet/cluster) | **Connected wallets** - on-chain transfer graph + identity-proof clusters |
### Smart Money & Analytics
Requires **Dev** plan or higher.
| Method | Endpoint | Description |
| - | - | - |
| `GET` | [`/v2/polymarket/markets/smart-activity`](/api-reference/smart-money/smart-activity) | **Smart activity** - markets where top wallets trade |
| `GET` | [`/v2/polymarket/market/{condition_id}/smart-money`](/api-reference/smart-money/smart-money-market) | **Smart money** - net positioning for a market |
| `GET` | [`/v2/polymarket/leaderboard`](/api-reference/analytics/leaderboard) | **Leaderboard** - global wallet rankings |
| `GET` | [`/v2/polymarket/leaderboard/market/{condition_id}`](/api-reference/analytics/market-leaderboard) | **Market leaderboard** - per-market rankings |
| `GET` | [`/v2/polymarket/market/{condition_id}/top-holders`](/api-reference/analytics/top-holders) | **Top holders** - largest positions |
| `GET` | [`/v2/polymarket/position/{token_id}/top-holders`](/api-reference/analytics/position-top-holders) | **Position top holders** - largest holders of one token |
| `GET` | [`/v2/polymarket/wallets/filter`](/api-reference/analytics/filter-wallets) | **Filter wallets** - by market + performance |
| `GET` | [`/v2/polymarket/cohorts/stats`](/api-reference/analytics/cohort-stats) | **Cohort stats** - aggregate by trading style |
### UMA
| Method | Endpoint | Description |
| - | - | - |
| `GET` | [`/v2/polymarket/uma/markets`](/api-reference/uma/list-markets) | **List UMA markets** - non-sports UMA questions, sorted by `last_event_at` |
| `GET` | [`/v2/polymarket/uma/market/{condition_id}`](/api-reference/uma/get-market) | **Get UMA market** - current oracle status + event timeline |
### Builders
**V2-only.** Builder attribution excludes V1 fills and the zero-sentinel builder.
| Method | Endpoint | Description |
| - | - | - |
| `GET` | [`/v2/polymarket/builders/leaderboard`](/api-reference/builders/leaderboard) | **Builder leaderboard** - V2 builders ranked by volume or fee revenue |
| `GET` | [`/v2/polymarket/builders/{builder_code}`](/api-reference/builders/builder-stats) | **Builder stats** - single-builder summary + full `(taker_bps, maker_bps)` history |
| `GET` | [`/v2/polymarket/builders/{builder_code}/markets`](/api-reference/builders/builder-markets) | **Builder markets** - top markets attributed to a builder over a rolling window |
| `GET` | [`/v2/polymarket/builders/{builder_code}/timeseries`](/api-reference/builders/builder-timeseries) | **Builder timeseries** - bucketed volume + fee revenue over a rolling window |
| `GET` | [`/v2/polymarket/builders/wallet/{wallet}`](/api-reference/builders/wallet-builder-fees) | **Wallet builder fees** - total builder fees a wallet paid, per-builder breakdown |
| Method | Endpoint | Description |
| - | - | - |
| `GET` | [`/v2/kalshi/markets`](/api-reference/kalshi/list-markets) | **List markets** - filter by ticker, event, status, price range |
| `GET` | [`/v2/kalshi/trades`](/api-reference/kalshi/trades) | **Trades** - historical with taker side |
| `GET` | [`/v2/kalshi/orderbooks`](/api-reference/kalshi/orderbooks) | **Orderbooks** - historical snapshots, prices in cents |
Requires **Dev** plan or higher.
| Method | Endpoint | Description |
| - | - | - |
| `GET` | [`/v2/binance/ticks/{symbol}`](/api-reference/binance/ticks) | **Ticks** - raw book ticker at microsecond granularity |
| `GET` | [`/v2/binance/candles/{symbol}`](/api-reference/binance/candles) | **Candles** - OHLCV from 1s to 1d intervals |
### Limitless
| Method | Endpoint | Description |
| - | - | - |
| `GET` | [`/v2/limitless/markets`](/api-reference/limitless/markets) | **Markets** - with trade type filter |
| `GET` | [`/v2/limitless/orderbooks`](/api-reference/limitless/orderbooks) | **Orderbooks** - historical snapshots |
### Opinion
| Method | Endpoint | Description |
| - | - | - |
| `GET` | [`/v2/opinion/markets`](/api-reference/opinion/markets) | **Markets** - with event grouping |
| `GET` | [`/v2/opinion/orderbooks`](/api-reference/opinion/orderbooks) | **Orderbooks** - historical snapshots |
### Predict.Fun
| Method | Endpoint | Description |
| - | - | - |
| `GET` | [`/v2/predictfun/markets`](/api-reference/predictfun/markets) | **Markets** - with event grouping |
| `GET` | [`/v2/predictfun/orderbooks`](/api-reference/predictfun/orderbooks) | **Orderbooks** - historical snapshots |
***
## Access Tiers
All Polymarket market data plus Kalshi markets, trades, and orderbooks. No credit card required.
Wallet analytics, smart money, leaderboards, Binance data, and WebSocket streaming.
See [pricing](https://predexon.com/pricing) for full details, or [get a free API key](https://dashboard.predexon.com) to start.
# List Markets
Source: https://docs.predexon.com/api-reference/kalshi/list-markets
GET /v2/kalshi/markets
Query Kalshi markets with filtering, sorting, and pagination
Fetch Kalshi prediction markets with filtering, sorting, and pagination. Does not include Multi Variate Event (MVE) markets like parlays.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
| Constraint | Value |
| - | - |
| `limit` | 1–100 (default 20) |
Prices are returned as decimals (0–1), not cents.
# Orderbook History
Source: https://docs.predexon.com/api-reference/kalshi/orderbooks
GET /v2/kalshi/orderbooks
Fetch historical orderbook snapshots for a Kalshi market
Fetch historical orderbook snapshots for a Kalshi market. Data starts from **January 7th, 2026**.
**This endpoint will be deprecated soon.** Kalshi prices markets in sub-cent
increments; this endpoint rounds prices to whole cents, which widens spreads and
merges neighboring price levels. Use the
[Sub-Cent Orderbook History endpoint](/api-reference/kalshi/orderbooks-subcent)
instead. The deprecation date will be announced in the [changelog](/changelog).
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
| Constraint | Value |
| - | - |
| `limit` | 1–2000 (default 100) |
Timestamps are in **milliseconds**. Prices are in **cents** (1–99).
# Orderbook History (Sub-Cent)
Source: https://docs.predexon.com/api-reference/kalshi/orderbooks-subcent
GET /v2/kalshi/orderbooks-subcent
Fetch historical sub-cent orderbook snapshots for a Kalshi market
Fetch historical orderbook snapshots for a Kalshi market with **full sub-cent price
precision**. Data starts from **January 8th, 2026**.
Kalshi prices markets in sub-cent increments. This endpoint reflects those prices
exactly: a level at 57.5 cents is reported as `"57.5"`, spreads are as tight as they
really are, and sizes include fractional contracts. The
[legacy orderbook endpoint](/api-reference/kalshi/orderbooks) rounds everything to
whole cents, which widens spreads and merges neighboring price levels into one.
For example, in a live market where this endpoint reported a best bid/ask of
`57.50 / 57.60` (a 0.1-cent spread), the legacy endpoint reported `57 / 58` for the
same moment, and folded the `57.5`, `57.1`, and `57.0` bid levels into a single `57`
bucket.
**The legacy endpoint will be deprecated soon.** Use this endpoint for all
integrations. The deprecation date will be announced in the
[changelog](/changelog).
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
| Constraint | Value |
| - | - |
| `limit` | 1–2000 (default 100) |
Timestamps are in **milliseconds**. Prices and sizes are exact decimal **strings**
(`"36.50"`, `"196.52"`). Parse them as decimals, not floats, if exactness matters.
Sizes may be fractional (Kalshi fractional contracts).
## Differences from the legacy endpoint
| | `/v2/kalshi/orderbooks` (legacy) | `/v2/kalshi/orderbooks-subcent` |
| - | - | - |
| Prices | integer cents (`36`) | decimal-cent strings (`"36.50"`) |
| Sizes | integers | strings, may be fractional |
| Extra fields | none | `source` per snapshot, `sources` filter |
| Status | **deprecated soon** | active development |
## The `source` field and `sources` parameter
Snapshots are assembled from several capture streams, reported per snapshot in
`source`:
* `websocket`: the primary capture spine (bulk of the data)
* `api`: REST resyncs
* `live_early`: trade-triggered early capture of brand-new markets
The default `sources=spine` serves the standard capture streams. `sources=websocket`
(or any comma-separated list) narrows to specific streams. `sources=all` returns the
raw table including experimental capture streams, and is intended for debugging
rather than analytics.
## Migrating from the legacy endpoint
* Prices arrive as strings with decimal scale. `legacy_price == round(float(subcent_price))`
does **not** hold in general, because sub-cent levels have no integer-cent
equivalent.
* Pagination works identically (`pagination_key` cursor over `timestamp`, `sequence`).
* Most consumers can ignore `source`.
# Trades History
Source: https://docs.predexon.com/api-reference/kalshi/trades
GET /v2/kalshi/trades
Fetch historical Kalshi trade data
Fetch historical trade data from Kalshi markets. Requires at least one of `ticker` or `event_ticker`.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
| Constraint | Value |
| - | - |
| `limit` | 1–500 (default 100) |
# List Markets
Source: https://docs.predexon.com/api-reference/limitless/markets
GET /v2/limitless/markets
List Limitless markets with filtering and sorting
List Limitless markets with filtering and sorting. Prices are normalized to 0-1 range. Data covers markets active from **March 7th, 2026**.
| Constraint | Value |
| - | - |
| `limit` | 1–100 (default 20) |
### Trade Types
* `amm` - Automated market maker. `market_address` is populated with the AMM contract address.
* `clob` - Central limit order book. `exchange_address` is populated.
### Notes
* Prices are normalized to 0-1 range (AMM raw values may be 0-100 internally).
* `open_interest_usd` is currently available only for AMM markets. For CLOB markets, it is returned as `null` because Limitless does not expose CLOB open interest through the indexed market API today.
* `collateral` shows the settlement token (currently all USDC on Base).
* No event grouping - all markets are standalone.
# Orderbook History
Source: https://docs.predexon.com/api-reference/limitless/orderbooks
GET /v2/limitless/orderbooks
Fetch historical orderbook snapshots for a Limitless market
Fetch historical orderbook snapshots for a Limitless market. Data starts from **March 7th, 2026**.
YES-side orderbook only.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
| Constraint | Value |
| - | - |
| `limit` | 1–200 (default 100) |
Timestamps are in **milliseconds** for this endpoint.
# Candlesticks
Source: https://docs.predexon.com/api-reference/markets/candlesticks
GET /v2/polymarket/candlesticks/{condition_id}
Get historical OHLCV candlestick data for a market
Fetch OHLCV candlestick data for charting. Prices normalized to YES side (NO prices converted as 1 - price).
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
### Intervals
| Interval | Value | Max Range |
| - | - | - |
| Auto | `0` (default) | Picks the best interval based on your time range |
| 1 minute | `1` | 7 days |
| 5 minutes | `5` | 14 days |
| 15 minutes | `15` | 30 days |
| 1 hour | `60` | 90 days |
| 1 day | `1440` | Unlimited (all-time) |
The response includes `first_trade_ts` and `last_trade_ts` - the Unix timestamps of the first and last trades in the market, useful for determining the full available data range. It also echoes back the resolved `interval` (in minutes) and `interval_seconds` (bucket width), so you know which bucket size was picked when requesting `interval=0`.
# Candlesticks by Token
Source: https://docs.predexon.com/api-reference/markets/candlesticks-by-token
GET /v2/polymarket/candlesticks/token/{token_id}
Get historical OHLCV candlestick data for a single outcome token
Fetch OHLCV candlestick data for one specific outcome token. Unlike the [condition-level candlesticks endpoint](/api-reference/markets/candlesticks), prices are **not** normalized - NO-token prices stay as NO prices (e.g. \~\$0.30 in a 70/30 market). Volume is only from trades of this token; summing the YES and NO token volumes over the same window approximates the condition-level volume.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
Use this when you need to chart each side of a binary market separately, or when working with a specific token ID rather than a condition ID.
### Intervals
| Interval | Value | Max Range |
| - | - | - |
| Auto | `0` (default) | Picks the best interval based on your time range |
| 1 minute | `1` | 7 days |
| 5 minutes | `5` | 14 days |
| 15 minutes | `15` | 30 days |
| 1 hour | `60` | 90 days |
| 1 day | `1440` | Unlimited (all-time) |
The response includes `first_trade_ts` and `last_trade_ts` - the Unix timestamps of the first and last trades for this token. It also echoes back the resolved `interval` (in minutes) and `interval_seconds` (bucket width), so you know which bucket size was picked when requesting `interval=0`.
# Crypto Up/Down Markets
Source: https://docs.predexon.com/api-reference/markets/crypto-updown
GET /v2/polymarket/crypto-updown
List crypto up/down prediction markets
Bitcoin, Ethereum, Solana, XRP, HYPE, Dogecoin, and BNB up/down markets across all timeframes (5m, 15m, 1h, 4h, daily). Filter by asset, timeframe, status, and time range.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
| Constraint | Value |
| - | - |
| `limit` | 1–200 (default 50) |
| `offset` | No limit |
# List Events
Source: https://docs.predexon.com/api-reference/markets/events
GET /v2/polymarket/events/keyset
List events with filtering, sorting, and cursor-based pagination
Events are top-level groupings that contain one or more markets. Use `status=open` for active events only.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
### Nesting markets and filtering by liquidity
Pass `include_markets=true` to nest each event's markets inline (same shape as [List Markets](/api-reference/markets/list-markets)), and `markets_per_event` (1–50, default 10) to cap how many are nested per event. Filter to liquid events with `min_open_interest` (current OI in USD) and `min_volume` (7-day volume in USD).
### Pagination
This endpoint uses cursor-based (keyset) pagination, which stays stable for large backfills and long-running crawls and does not return a `total` count. To fetch the next page, pass the returned `pagination.pagination_key` as the next request's `pagination_key`, keeping the same filter and sort parameters across pages.
| Constraint | Value |
| - | - |
| `limit` | 1–100 (default 20) |
| `pagination_key` | Cursor returned from the previous response |
# List Markets
Source: https://docs.predexon.com/api-reference/markets/list-markets
GET /v2/polymarket/markets/keyset
Query markets with filtering, sorting, and cursor-based pagination
Fetch Polymarket prediction markets with filtering, sorting, and pagination.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
### Pagination
This endpoint uses cursor-based (keyset) pagination, which stays stable for large backfills and long-running crawls and does not return a `total` count. To fetch the next page, pass the returned `pagination.pagination_key` as the next request's `pagination_key`, keeping the same filter and sort parameters across pages.
| Constraint | Value |
| - | - |
| `limit` | 1–100 (default 20) |
| `pagination_key` | Cursor returned from the previous response |
# Market Price
Source: https://docs.predexon.com/api-reference/markets/market-price
GET /v2/polymarket/market-price/{token_id}
Get the current or historical price for a specific token
Fetch current or historical price for a token. Uses weighted average of recent trades when stable, or last trade price when volatile.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
# Open Interest Time Series
Source: https://docs.predexon.com/api-reference/markets/open-interest
GET /v2/polymarket/markets/{condition_id}/open_interest
Get historical open interest data for a market
Fetch historical open interest for a market. Open interest represents total outstanding positions (can increase or decrease, unlike cumulative volume).
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
# Orderbook History
Source: https://docs.predexon.com/api-reference/markets/orderbooks
GET /v2/polymarket/orderbooks
Get historical orderbook snapshots for a token
Fetch historical orderbook snapshots for a token. Data starts from **January 1st, 2026**.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
| Constraint | Value |
| - | - |
| `limit` | 1–200 (default 100) |
Timestamps are in **milliseconds** for this endpoint.
# Volume Time Series
Source: https://docs.predexon.com/api-reference/markets/volume
GET /v2/polymarket/markets/{token_id}/volume
Get historical cumulative volume data for a market
Fetch historical cumulative trading volume for a token.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
# Volume Chart
Source: https://docs.predexon.com/api-reference/markets/volume-chart
GET /v2/polymarket/volume-chart/{condition_id}
Fetch volume chart data for a market
Returns per-period total, YES, and NO volume aggregated from trade events, broken down by side.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
### Time Range Limits
| Interval | Max Range |
| - | - |
| `hour` | 90 days |
| `day` | 1 year |
| `week` | 3 years |
# List Markets
Source: https://docs.predexon.com/api-reference/opinion/markets
GET /v2/opinion/markets
List Opinion markets with filtering and sorting
List Opinion markets with filtering and sorting. Returns tradeable markets only (standalone binaries + categorical outcomes). Categorical parent markets are excluded.
| Constraint | Value |
| - | - |
| `limit` | 1–100 (default 20) |
### Event Grouping
* `event_id`, `event_slug`, and `event_title` come from the parent market for categorical outcomes. These are `null` for standalone binaries.
* Opinion calls these "parent markets" natively - Predexon normalizes to "event" for cross-venue consistency.
### Notes
* `collection` is non-null for recurring markets (e.g. hourly crypto). Contains title, symbol, and frequency.
* `price` in outcomes is `null` when the indexer hasn't populated prices yet.
# Orderbook History
Source: https://docs.predexon.com/api-reference/opinion/orderbooks
GET /v2/opinion/orderbooks
Fetch historical orderbook snapshots for an Opinion market
Fetch historical orderbook snapshots for an Opinion market. Data starts from **March 10th, 2026**.
YES-side orderbook only.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
| Constraint | Value |
| - | - |
| `limit` | 1–200 (default 100) |
Timestamps are in **milliseconds** for this endpoint.
# List Markets
Source: https://docs.predexon.com/api-reference/predictfun/markets
GET /v2/predictfun/markets
List Predict.Fun markets with filtering and sorting
List Predict.fun markets with filtering and sorting. Markets sharing the same `event_slug` belong to the same event.
| Constraint | Value |
| - | - |
| `limit` | 1–100 (default 20) |
### Event Grouping
Multiple markets sharing the same `event_slug` are outcomes under one event (e.g. all teams in "2026-nba-champion"). Use `event_slug` as a query parameter to fetch all outcomes.
### Market Variants
| Variant | Description |
| - | - |
| `DEFAULT` | General markets |
| `CRYPTO_UP_DOWN` | Crypto price direction |
| `SPORTS_TEAM_MATCH` | Team vs team |
| `SPORTS_MATCH` | Match outcome |
| `TWEET_COUNT` | Social media |
### Notes
* Outcomes use `best_bid`/`best_ask` instead of `price` (CLOB-style pricing). Currently `null` for most markets.
* `is_neg_risk` indicates negative risk markets (event-grouped, shares one slug).
* `is_yield_bearing` indicates yield-bearing collateral.
* Use `bid_depth`/`ask_depth` for liquidity signals (no volume/OI columns).
# Orderbook History
Source: https://docs.predexon.com/api-reference/predictfun/orderbooks
GET /v2/predictfun/orderbooks
Fetch historical orderbook snapshots for a Predict.Fun market
Fetch historical orderbook snapshots for a Predict.Fun market. Data starts from **March 11th, 2026**.
YES-side orderbook only.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
| Constraint | Value |
| - | - |
| `limit` | 1–200 (default 100) |
Timestamps are in **milliseconds** for this endpoint.
# Smart Activity Markets
Source: https://docs.predexon.com/api-reference/smart-money/smart-activity
GET /v2/polymarket/markets/smart-activity
Discover markets where high-performing wallets are active
Find alpha by seeing where profitable traders are putting their money. Define "smart wallets" using filter criteria (min PnL, win rate, etc.), then see which markets they're trading and rank by smart wallet activity.
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
**Rolling total PnL accuracy timeline** - Total PnL (realized + unrealized - net fees) uses rolling windows that need time to accumulate full data:
* **All-time total PnL**: accurate immediately (February 10, 2026)
* **1-day rolling total PnL**: fully accurate starting February 11, 2026
* **7-day rolling total PnL**: fully accurate starting February 17, 2026
* **30-day rolling total PnL**: fully accurate starting March 12, 2026
| Constraint | Value |
| - | - |
| `limit` | 1–100 (default 100) |
### Market Status Filter
Use the optional `status` parameter to filter by trading status:
| Value | Description |
| - | - |
| `open` | Currently tradable markets only |
| `closed` | Resolved/settled markets only |
| *(omitted)* | All markets (default) |
Filtering by `status=open` improves query performance by skipping resolved markets.
### Filter Requirements
At least **one** strong filter must meet threshold:
| Parameter | Minimum |
| - | - |
| `min_realized_pnl` | ≥ 5,000 |
| `min_total_pnl` | ≥ 5,000 |
| `min_roi` | ≥ 0.15 |
| `min_trades` | ≥ 100 |
| `min_volume` | ≥ 10,000 |
`min_win_rate` and `min_profit_factor` require `min_trades >= 50` to be valid.
# Smart Money Positioning
Source: https://docs.predexon.com/api-reference/smart-money/smart-money-market
GET /v2/polymarket/market/{condition_id}/smart-money
Smart money positioning for a specific market
See what profitable traders are doing on a specific market. Returns how many smart wallets are net buyers vs net sellers, average entry prices, and total volume and PnL from smart wallets.
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
### Filter Requirements
At least **one** strong filter must meet threshold:
| Parameter | Minimum |
| - | - |
| `min_realized_pnl` | ≥ 1,000 |
| `min_total_pnl` | ≥ 1,000 |
| `min_roi` | ≥ 0.15 |
| `min_trades` | ≥ 100 |
| `min_volume` | ≥ 10,000 |
`min_win_rate` and `min_profit_factor` require `min_trades >= 50` to be valid.
**Rolling total PnL accuracy timeline** - Total PnL (realized + unrealized - net fees) uses rolling windows that need time to accumulate full data:
* **All-time total PnL**: accurate immediately (February 10, 2026)
* **1-day rolling total PnL**: fully accurate starting February 11, 2026
* **7-day rolling total PnL**: fully accurate starting February 17, 2026
* **30-day rolling total PnL**: fully accurate starting March 12, 2026
# Trading Labels
Source: https://docs.predexon.com/api-reference/trading-labels
How Predexon classifies wallet trading behavior and entry styles
## Overview
Every wallet on Predexon is automatically classified with **trading labels** based on its on-chain activity. These labels power the wallet profile, cohort stats, leaderboard filtering, and smart money analytics.
Labels fall into two categories: **behavioral labels** (how a wallet trades) and **entry price labels** (what prices a wallet buys at).
**Don't like the defaults?** Every label is exposed as an individual boolean column (e.g. `is_whale`, `is_contrarian`), so you can ignore `primary_trading_style` entirely and build your own composite filters using the leaderboard or filter endpoints.
## Behavioral Labels
These describe *how* a wallet trades based on trade frequency, sizing, and win rate.
| Label | Column | Criteria |
| - | - | - |
| **WHALE** | `is_whale` | Average trade size at least 1,000 USD |
| **MARKET\_MAKER** | `is_market_maker` | Buy ratio 40–60% AND at least 10 trades per position |
| **ACTIVE\_TRADER** | `is_active_trader` | More than 4 trades per position |
| **BUY\_AND\_HOLD** | `is_buy_and_hold` | 2 or fewer trades per position |
| **DEGEN** | `is_degen` | Win rate below 35% AND at least 50 closed positions |
## Entry Price Labels
These describe *what prices* a wallet enters positions at, revealing conviction and risk appetite.
| Label | Column | Criteria |
| - | - | - |
| **HIGH\_CONVICTION** | `is_high_conviction` | Avg buy price at or above 0.80 (buys heavy favorites) |
| **CONTRARIAN** | `is_contrarian` | Avg buy price at or below 0.25 (takes unpopular sides) |
| **VALUE\_HUNTER** | `is_value_hunter` | Avg buy price between 0.35–0.55 (enters near 50/50 odds) |
## Primary Trading Style
Each wallet is assigned a single `primary_trading_style` label, derived by priority order:
```
WHALE > MARKET_MAKER > ACTIVE_TRADER > DEGEN > BUY_AND_HOLD > MIXED > UNKNOWN
```
This means a wallet that qualifies as both a WHALE and an ACTIVE\_TRADER will be classified as **WHALE**. If no behavioral label applies, the wallet is labeled **MIXED** (has some activity but no dominant style) or **UNKNOWN** (insufficient data).
Entry price labels (HIGH\_CONVICTION, CONTRARIAN, VALUE\_HUNTER) are independent and do not affect the primary trading style. A wallet can be a WHALE *and* a CONTRARIAN at the same time.
## Minimum Requirements
Labels are only assigned once a wallet meets minimum activity thresholds to ensure statistical significance:
| Requirement | Threshold |
| - | - |
| General classification | At least **5 trades** and **3 days** of wallet age |
| Entry price labels | At least **100 USD** in buy volume |
| DEGEN label | At least **50 closed positions** |
Wallets below these thresholds will have `primary_trading_style: "UNKNOWN"` and all boolean label columns set to `false`.
## Custom Filtering
The `primary_trading_style` field is a convenient default, but you are not limited to it. Every label is available as an individual boolean field, so you can combine them however you like.
For example, to find wallets that are both whales *and* contrarians, filter the [leaderboard](/api-reference/analytics/leaderboard) with:
```
GET /v2/polymarket/leaderboard?is_whale=true&is_contrarian=true
```
Or use the [cohort stats](/api-reference/analytics/cohort-stats) endpoint to compare aggregate performance across any label.
## Next Steps
See all labels for a specific wallet
Compare performance across trading styles
Filter the leaderboard by any label combination
Find wallets by market activity and labels
# Activity
Source: https://docs.predexon.com/api-reference/trading/activity
GET /v2/polymarket/activity
Fetch trading activity (merges, splits, redeems) for a user
Query non-trade wallet activity. Includes `SPLIT` (deposit collateral to create YES+NO pairs), `MERGE` (combine pairs back to collateral), `REDEEM` (redeem winning tokens after resolution), and `CONVERT` (NegRisk position conversions - sell NO tokens on a subset of conditions, buy YES tokens on the remainder).
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
**`CONVERT` rows behave differently.** Conversions are a market-level operation, not a per-outcome one. On `CONVERT` rows:
* `condition_id` is an **empty string** and `market_id` is `null` - filtering by either will not match.
* The parent identifier lives in the new `neg_risk_market_id` field (32-byte hex, matches Gamma's `Event.negRiskMarketID`).
* `index_set` carries the NegRisk indexSet bitmask (decimal string) of which outcomes were involved.
* `market_slug`/`title` may be empty.
For SPLIT/MERGE/REDEEM, `market_id` and `condition_id` are populated as before; `neg_risk_market_id` and `index_set` are `null`.
| Constraint | Value |
| - | - |
| `limit` | 1–500 (default 100) |
| `offset` | No limit |
# All Positions
Source: https://docs.predexon.com/api-reference/trading/positions
GET /v2/polymarket/positions
Fetch all user positions with filtering and pagination
Designed for analytics and streaming use cases - backfill historical position data or stream new updates as they come in.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
Use `min_block` / `max_block` or `min_timestamp` / `max_timestamp` to scope your query, and `pagination_key` to page through results.
| Constraint | Value |
| - | - |
| `limit` | 1–200 (default 100) |
# Trades History
Source: https://docs.predexon.com/api-reference/trading/trades
GET /v2/polymarket/trades
Fetch historical trade data
Query historical trade data with flexible filtering by market, token, wallet, and time range. All filters are optional - omit them to browse the global trade feed.
All trades mode (no filters) only supports `order=desc` (newest first). Using `order=asc` without a market or wallet filter returns HTTP 400.
**Free & Unlimited.** This endpoint does not count toward your monthly usage limits on any plan.
### V2 fields
Every trade row now carries Polymarket exchange version metadata:
* `exchange_version` - `1` (V1 CTF/NegRiskCTF) or `2` (V2). Defaults to `1`.
* `builder` - V2 builder attribution tag (bytes32 hex). Empty for V1 fills or V2 fills with no builder.
* `metadata` - V2 opaque metadata tag (bytes32 hex). Empty for V1 fills or V2 fills with no metadata.
Use the `builder` query parameter to filter to fills attributed to a specific builder tag.
### Multi-wallet filtering
The `wallet` parameter accepts a single address or a comma-separated list of up to 50 addresses:
```
GET /v2/polymarket/trades?wallet=0xabc...,0xdef...,0x123...&limit=100
```
Returns one merged feed of all listed wallets' trades, time-ordered, with standard cursor pagination — designed for tracking a list of followed wallets. Address order doesn't matter and duplicates are ignored: the same list in any order returns identical results and pagination keys. Trades match on the maker side, same as the single-wallet filter, and the list combines with all other parameters.
### Point lookups by hash
Two optional parameters (each `0x` + 64 hex chars) support full-history point lookups, back to October 2022:
| Parameter | Returns |
| - | - |
| `tx_hash` | all fills executed in that transaction |
| `order_hash` | all fills of that order (an order can fill across multiple transactions) |
Usable standalone or combined with each other and with any wallet/market/time filters. Invalid hash format returns HTTP 400.
| Constraint | Value |
| - | - |
| `limit` | 1–500 (default 100) |
| `wallet` | up to 50 comma-separated addresses (>50 or any invalid address returns HTTP 400, identifying which one) |
# Get UMA Market
Source: https://docs.predexon.com/api-reference/uma/get-market
GET /v2/polymarket/uma/market/{condition_id}
Get current UMA oracle status and event timeline for a market
Returns the current UMA oracle status for a (non-sports) market plus its event timeline. Set `events_limit=0` if you only want the status header.
# List UMA Markets
Source: https://docs.predexon.com/api-reference/uma/list-markets
GET /v2/polymarket/uma/markets
List UMA questions filtered by oracle state
Returns regular non-sports UMA questions sorted by `last_event_at DESC`.
# Connected Wallets
Source: https://docs.predexon.com/api-reference/wallet/cluster
GET /v2/polymarket/wallet/{address}/cluster
Discover wallets connected to a seed address via on-chain transfers and identity proofs
Returns sibling wallets discovered via the on-chain transfer graph (USDC, pUSD, CTF) plus identity-proof signals (shared signer, shared X username, shared first funder). Use this to surface likely-related accounts behind a single operator - alt wallets, hot/cold splits, multi-account farming patterns.
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
## Two response shapes
This endpoint can return either a **`200 OK`** or a **`202 Accepted`** depending on cache state. **Clients must handle both.**
Returned on a fresh cache hit. The body is the full `WalletClusterResponse`.
```json theme={null}
{
"seed": "0x89b5cdaaa4866c1e738406712012a630b4078beb",
"siblings": [
{
"address": "0x07e18557cf1b62b5b613359badb39bbb8df93fb4",
"confidence": 100,
"min_hops": 2,
"evidence": {
"hops": { "out": 2, "in": 3 },
"bidirectional": true,
"class": null,
"is_verified_trader": true,
"direct_flow": [
{ "amount_str": "10000000", "tx_count": 1, "direction": "out" }
],
"shared_peers": {
"destinations": [],
"sources": [{ "addr": "0xabc..." }],
"count": 31
}
}
}
],
"computed_at": 1779154544
}
```
Returned when the cache is empty, stale (>24h old), or `recompute=true` was passed. Background BFS has been kicked off - retry after the indicated interval.
```http theme={null}
HTTP/1.1 202 Accepted
Retry-After: 60
Content-Type: application/json
{
"status": "compute_pending",
"retry_after_seconds": 60,
"reason": "miss",
"message": "Cluster compute is running in the background. Retry after the indicated interval to receive the full result."
}
```
`reason` values:
| Value | Meaning |
| - | - |
| `miss` | Seed has never been computed; a background job was just spawned. |
| `stale` | Cached result is older than 24h; a refresh was spawned. |
| `forced` | Caller passed `recompute=true`. |
| `another_pod_computing` | A job for this seed is already running on another API pod (Redis-coordinated). |
**Why 202?** Hub-wallet clusters can take 60–180 seconds to compute (a popular seed can reach tens of thousands of wallets) - far longer than any sane HTTP timeout. We compute out-of-band and write the result to a Postgres cache; the next call after `Retry-After` returns 200 from cache.
## Evidence sub-fields
The `evidence` object is free-form, but typical sibling entries include:
| Field | Description |
| - | - |
| `hops` | `{out, in}` BFS depths from seed to sibling on each direction of the transfer graph. |
| `bidirectional` | `true` when seed and sibling have sent funds both ways. |
| `direct_flow` | Array of direct USDC/pUSD/CTF transfers with `amount_str`, `tx_count`, and `direction`. |
| `shared_peers` | Low-degree wallets (`degree < 50`) that both seed and sibling transacted with. `count ≥ 2` is a strong signal. |
| `same_signer` | Cryptographic match - sibling is controlled by the same EOA as the seed. |
| `same_x_username` | Self-asserted match - sibling set the same X / Twitter handle. |
| `shared_first_funder` | Both seed and sibling were initially funded by the same small wallet. |
| `is_verified_trader` | Sibling has trading activity on Polymarket. |
| `class` | Wallet classification (`magic_link`, `contract`, etc.) or `null`. |
A sibling at `confidence: 100` typically means bidirectional direct flow + verified trader + ≥2 shared peers, **or** an identity-proof match.
## How siblings are discovered
1. **Bidirectional BFS** over the USDC / pUSD / CTF transfer graph from the seed (max depth 3, fanout 100, filters out high-degree pivots and shared-service wallets).
2. **Identity proofs** - wallets sharing the seed's cryptographic signer or self-asserted X username.
3. **Shared-peer discovery** - wallets that both seed and the candidate transacted with through a small (`degree < 50`) intermediary.
4. **Scoring** - combines hop distance, direct-flow tx count / amount, bidirectional flag, shared peers, shared first funder, verified-trader flag, and wallet class into a 0–100 confidence score.
## Caching & limits
* Results persist in Postgres (`wallet_cluster` table) and serve from cache for **24 hours**.
* After 24h the next request returns 202 and triggers a refresh.
* Background compute is deduplicated across API pods via Redis - only one BFS runs per seed at a time.
* A typical cache-hit response returns in **\~100–500 ms** including JSON encoding of up to 1000 siblings.
* Up to **1000 siblings** are persisted per seed (sorted by confidence, descending). Direct bidirectional neighbors with multi-tx evidence are always prioritized - clients see the top 1000 strongest connections rather than the full graph.
| Constraint | Value |
| - | - |
| `min_confidence` | 0–100 (default 70) |
| `limit` | 1–1000 (default 50) |
## Recommended client pattern
```python theme={null}
import time, requests
def get_cluster(addr, min_conf=70, limit=50, max_wait=300):
deadline = time.time() + max_wait
while time.time() < deadline:
r = requests.get(
f"https://api.predexon.com/v2/polymarket/wallet/{addr}/cluster",
params={"min_confidence": min_conf, "limit": limit},
headers={"x-api-key": API_KEY},
)
if r.status_code == 200:
return r.json()
if r.status_code == 202:
retry = int(r.headers.get("Retry-After", 30))
time.sleep(retry)
continue
r.raise_for_status()
raise TimeoutError(f"Cluster for {addr} not ready within {max_wait}s")
```
# Wallet Identities (Batch)
Source: https://docs.predexon.com/api-reference/wallet/identities-batch
POST /v2/polymarket/wallet/identities
Bulk lookup of wallet identity profiles
Accepts up to 200 addresses (proxy or signer EOA) and returns a map of lowercase address → profile. Both the `id` and `signer` of each matched row appear as keys, so callers can match on either address form.
# Wallet Identity
Source: https://docs.predexon.com/api-reference/wallet/identity
GET /v2/polymarket/wallet/identity/{wallet}
Fetch identity and profile metadata for a wallet
Returns display name, pseudonym, profile image, X handle, verified/creator/mod badges, wallet type (proxy/safe/eoa), signer, USDC balance, and timestamps. Looks up by either proxy or signer EOA address.
# Wallet P&L
Source: https://docs.predexon.com/api-reference/wallet/pnl
GET /v2/polymarket/wallet/pnl/{wallet}
Get P&L summary and realized P&L time series for a wallet
Returns both a **summary** (realized, unrealized, fees) and a **time series** of cumulative realized P\&L.
**Summary fields** (current snapshot):
* `realized_pnl` - Total realized P\&L from sells and redemptions
* `unrealized_pnl` - Unrealized P\&L computed live from open positions
* `total_pnl` - `realized_pnl + unrealized_pnl`
* `fees_paid` / `fees_refunded` - Taker fees paid and maker rebates received
**Time series** (`pnl_over_time`):
* Shows **realized P\&L only** - does not include unrealized gains/losses
* Each data point is cumulative realized P\&L at that timestamp
Use `condition_id` query param to filter to a specific market. This correctly handles split/merge operations by aggregating both sides.
**Rolling total PnL accuracy timeline** - Total PnL (realized + unrealized - net fees) uses rolling windows that need time to accumulate full data:
* **All-time total PnL**: accurate immediately (February 10, 2026)
* **1-day rolling total PnL**: fully accurate starting February 11, 2026
* **7-day rolling total PnL**: fully accurate starting February 17, 2026
* **30-day rolling total PnL**: fully accurate starting March 12, 2026
# Wallet Positions
Source: https://docs.predexon.com/api-reference/wallet/positions
GET /v2/polymarket/wallet/positions/{wallet}
Get open and historical positions for a wallet address
Fetch positions for a Polymarket wallet with cost basis, current value, and P\&L.
**Rolling total PnL accuracy timeline** - Total PnL (realized + unrealized - net fees) uses rolling windows that need time to accumulate full data:
* **All-time total PnL**: accurate immediately (February 10, 2026)
* **1-day rolling total PnL**: fully accurate starting February 11, 2026
* **7-day rolling total PnL**: fully accurate starting February 17, 2026
* **30-day rolling total PnL**: fully accurate starting March 12, 2026
| Constraint | Value |
| - | - |
| `limit` | 1–200 (default 100) |
| `include_closed` | `false` by default - only open (non-zero-balance) positions are returned. Pass `include_closed=true` to include closed/zero-balance positions. |
# Similar Wallets
Source: https://docs.predexon.com/api-reference/wallet/similar-wallets
GET /v2/polymarket/wallet/{wallet}/similar
Find wallets with similar market portfolios
Detect potential copy-trading patterns and discover traders with similar strategies. Finds all markets the target wallet has traded, then ranks other wallets by overlap percentage and count.
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
**Rolling total PnL accuracy timeline** - Total PnL (realized + unrealized - net fees) uses rolling windows that need time to accumulate full data:
* **All-time total PnL**: accurate immediately (February 10, 2026)
* **1-day rolling total PnL**: fully accurate starting February 11, 2026
* **7-day rolling total PnL**: fully accurate starting February 17, 2026
* **30-day rolling total PnL**: fully accurate starting March 12, 2026
| Constraint | Value |
| - | - |
| `limit` | 1–100 (default 50) |
# Wallet Volume Chart
Source: https://docs.predexon.com/api-reference/wallet/volume-chart
GET /v2/polymarket/wallet/volume-chart/{wallet}
Fetch volume chart data for a wallet
Returns per-period total, BUY, and SELL volume for a wallet, aggregated from trade events where the wallet was the maker.
# Wallet Markets
Source: https://docs.predexon.com/api-reference/wallet/wallet-markets
GET /v2/polymarket/wallet/{wallet}/markets
Get per-market performance breakdown for a wallet
Shows performance metrics for each market the wallet has traded in. Sort by PnL, volume, ROI, or other metrics to find a wallet's best and worst trades.
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
**Rolling total PnL accuracy timeline** - Total PnL (realized + unrealized - net fees) uses rolling windows that need time to accumulate full data:
* **All-time total PnL**: accurate immediately (February 10, 2026)
* **1-day rolling total PnL**: fully accurate starting February 11, 2026
* **7-day rolling total PnL**: fully accurate starting February 17, 2026
* **30-day rolling total PnL**: fully accurate starting March 12, 2026
| Constraint | Value |
| - | - |
| `limit` | 1–100 (default 100) |
# Wallet Profile
Source: https://docs.predexon.com/api-reference/wallet/wallet-profile
GET /v2/polymarket/wallet/{wallet}
Get a complete smart wallet profile
Returns all metrics across all time windows and style classifications for a wallet. The response includes boolean label columns (`is_whale`, `is_market_maker`, `is_contrarian`, etc.) and a computed `primary_trading_style`.
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
See [Trading Labels](/api-reference/trading-labels) for how each label is calculated, priority rules, and minimum thresholds.
You can use the individual boolean columns to build your own custom label combinations instead of relying on the default `primary_trading_style`.
**Rolling total PnL accuracy timeline** - Total PnL (realized + unrealized - net fees) uses rolling windows that need time to accumulate full data:
* **All-time total PnL**: accurate immediately (February 10, 2026)
* **1-day rolling total PnL**: fully accurate starting February 11, 2026
* **7-day rolling total PnL**: fully accurate starting February 17, 2026
* **30-day rolling total PnL**: fully accurate starting March 12, 2026
# Wallet Profiles (Batch)
Source: https://docs.predexon.com/api-reference/wallet/wallet-profiles-batch
GET /v2/polymarket/wallets/profiles
Get wallet profiles for multiple addresses in a single request
Returns the same data as the [single wallet profile](/api-reference/wallet/wallet-profile) endpoint, but for up to 20 wallets at once. Wallets not found are omitted from the response.
**Requires Dev or Pro tier.** This endpoint is not available on the Free tier.
# Authentication
Source: https://docs.predexon.com/authentication
How to authenticate with the Predexon API
## API Key
All requests require an API key passed in the `x-api-key` header. Sign up free at [dashboard.predexon.com](https://dashboard.predexon.com).
```bash theme={null}
curl -H "x-api-key: YOUR_API_KEY" \
"https://api.predexon.com/v2/polymarket/markets?limit=10"
```
***
## Pay-per-call (x402)
The Data API can also be accessed via x402, the HTTP 402 Payment Required protocol. Pay per request, no signup or subscription required. Useful for agents and one-off integrations.
**Facilitator:** [Blockrun marketplace](https://blockrun.ai/marketplace/predexon)
***
## Plans (summary)
| Tier | Rate Limit | Monthly Requests | Price |
| - | - | - | - |
| Free | 1 req/sec | 1,000 | \$0/month |
| Dev | 20 req/sec | 1,000,000 | \$49/month |
| Pro | 100 req/sec | 5,000,000 | \$249/month |
| Enterprise | Custom | Unlimited | \$499+/month |
**Most data is free.** Core market data, historical trades, orderbooks, and pricing endpoints are **free and unlimited** on all plans.
For the full matrix — WebSocket limits, gated features, free-endpoint list, overage pricing — see [Rate Limits & Plans](/rate-limits) (the canonical reference) or [predexon.com/pricing](https://predexon.com/pricing).
Exceeding rate limits returns `429 Too Many Requests`. Implement exponential backoff.
***
## Best Practices
Many endpoints return data that doesn't change frequently. Implement caching to reduce API calls:
```python theme={null}
import requests, time
BASE_URL = "https://api.predexon.com"
cache = {}
CACHE_TTL = 300 # 5 minutes
def get_market(condition_id: str, api_key: str) -> dict:
cache_key = f"market:{condition_id}"
now = time.time()
if cache_key in cache and cache[cache_key]["expires"] > now:
return cache[cache_key]["data"]
response = requests.get(
f"{BASE_URL}/v2/polymarket/markets",
params={"condition_id": condition_id},
headers={"x-api-key": api_key}
)
data = response.json()
cache[cache_key] = {"data": data, "expires": now + CACHE_TTL}
return data
```
Implement retries with exponential backoff for transient errors:
```python theme={null}
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = requests.Session()
retries = Retry(
total=3,
backoff_factor=0.5,
status_forcelist=[500, 502, 503, 504]
)
session.mount("https://", HTTPAdapter(max_retries=retries))
```
Reuse connections for better performance:
```python theme={null}
BASE_URL = "https://api.predexon.com"
session = requests.Session()
session.headers.update({"x-api-key": "your_api_key"})
# Reuse for all requests
response = session.get(f"{BASE_URL}/v2/polymarket/markets")
```
***
## CORS
The API supports CORS for browser-based applications:
```javascript theme={null}
fetch("https://api.predexon.com/v2/polymarket/markets", {
headers: { "x-api-key": "your_api_key" }
})
.then(res => res.json())
.then(data => console.log(data));
```
## Health Check
```bash theme={null}
curl "https://api.predexon.com/health"
# {"status": "healthy"}
```
# Changelog
Source: https://docs.predexon.com/changelog
Updates and new features for the Predexon API and WebSocket service
## Kalshi book snapshots for tick replay
`GET /v2/data/ticks` has a new dataset, **`venue=kalshi_snapshots`**: full order-book snapshots for every Kalshi market, one row per resting level, in the same `(side, price_dollars)` terms as the `kalshi` delta files. Seed a book from the latest snapshot at or before your start time, then apply deltas forward — the delta files alone could not be replayed because a market's opening book only ever appears in its subscribe snapshot. Data begins **2026-09-18** (served daily for T-1); priced at \$80.00 / GiB with **no minimum charge**. Schema, the replay recipe, and the `timestamp` anchor semantics are on the [Kalshi ticks page](/data-signals/ticks/kalshi).
Also: the `schema` advertised by `venue=kalshi` now lists all eight columns the files have carried since 2026-03-03 (`price_dollars`, `delta_fp`, and `delta` as a double). The files themselves are unchanged.
## Market-matching endpoints removed
The cross-venue market-matching endpoints, deprecated on July 14, have been sunset and now return `410 Gone`:
* `GET /v2/matching-markets`
* `GET /v2/matching-markets/pairs`
* `GET /v2/markets`
* `GET /v2/markets/listings`
* `GET /v2/outcomes/{predexon_id}`
Sports live-state updates have also concluded with the end of the FIFA World Cup: the `sports_live` and `sports_fifa_world_cup` WebSocket channels no longer emit events, and `/v2/sports/fifa-world-cup/live-games` continues to serve final game states.
All venue-specific data endpoints, discovery, analytics, tick history, and the WebSocket service are unaffected. Questions? [team@predexon.com](mailto:team@predexon.com).
## Sub-cent Kalshi orderbook history
New endpoint: [`GET /v2/kalshi/orderbooks-subcent`](/api-reference/kalshi/orderbooks-subcent). Kalshi prices markets in sub-cent increments, and this endpoint reflects those prices exactly: decimal price levels, spreads as tight as they really are, fractional contract sizes, plus per-snapshot `source` provenance and a `sources` filter. History from January 8th, 2026. Free and unlimited like the legacy endpoint.
**The legacy integer-cent [`GET /v2/kalshi/orderbooks`](/api-reference/kalshi/orderbooks) will be deprecated soon.** It rounds prices to whole cents, which widens spreads and merges neighboring price levels. Switch to the sub-cent endpoint now; the deprecation date will be announced here. The legacy `limit` cap was also raised to 2000 (docs previously said 200).
## Market-matching endpoints deprecated
The cross-venue market-matching endpoints are deprecated as of July 14, 2026 and scheduled for sunset:
* `GET /v2/matching-markets`
* `GET /v2/matching-markets/pairs`
* `GET /v2/markets`
* `GET /v2/markets/listings`
* `GET /v2/outcomes/{predexon_id}`
The endpoints remain available during a transition window. `GET /v2/markets/search` (cross-venue text search) is **not** affected, and all venue-specific data endpoints, sports endpoints, and the WebSocket service continue unchanged.
If your integration depends on the deprecated endpoints, please contact us to discuss alternatives.
## Trading disabled — withdrawal-only mode in effect
As announced, the Trading API is now **withdrawal-only**. Order placement (`POST /api/accounts/{accountId}/orders`) returns `403 trading_disabled` on every venue. Cancel, redeem, balance, positions, transfers, and withdrawals keep working.
* **July 17, 2026** — final withdrawal deadline. Cancel open orders, redeem resolved positions, and withdraw all funds by this date.
These dates supersede the June 25 / June 29 dates in the June 21 entry below — the wind-down timeline was extended to July 10 / July 17.
Questions, or need help withdrawing? Reach out at [team@predexon.com](mailto:team@predexon.com).
## Trades: multi-wallet filtering and hash lookups
Two additions to [`GET /v2/polymarket/trades`](/api-reference/trading/trades). No breaking changes: all existing requests, response shapes, and pagination cursors are unaffected.
### Multi-wallet filtering
The `wallet` parameter now accepts a comma-separated list of up to 50 addresses (previously one):
```
GET /v2/polymarket/trades?wallet=0xabc...,0xdef...,0x123...&limit=100
```
* Returns one merged feed of all listed wallets' trades, time-ordered (newest first by default), with standard cursor pagination — designed for tracking a list of followed wallets.
* Address order doesn't matter and duplicates are ignored; the same list in any order returns identical results and pagination keys.
* Trades match on the maker side, same as the existing single-wallet filter.
* Combines with all existing parameters (`market_slug` / `condition_id` / `token_id`, `start_time` / `end_time`, `min_total`, `order`, `pagination_key`).
* Errors: more than 50 addresses returns `400`; any invalid address returns `400` identifying which one.
* Fully backward compatible: single-address requests behave exactly as before, and existing pagination keys remain valid.
### Order hash and transaction hash filters
Two new optional parameters (each `0x` + 64 hex chars):
| Parameter | Returns |
| - | - |
| `tx_hash` | all fills executed in that transaction |
| `order_hash` | all fills of that order (an order can fill across multiple transactions) |
```
GET /v2/polymarket/trades?tx_hash=0x52e2...42e8
GET /v2/polymarket/trades?order_hash=0xdd14...33b1
```
* Usable standalone (full-history point lookup, back to October 2022) or combined with each other and with any wallet/market/time filters.
* Invalid hash format returns `400`.
## Exact lifetime buy totals: `total_bought_usd`
Position responses now include a new field, `total_bought_usd`: the exact total USD ever spent buying a position, cumulative over its lifetime and preserved even after redemption. It is computed from on-chain fills and verified against them.
Available on both position endpoints:
| Endpoint | Docs |
| - | - |
| `GET /v2/polymarket/wallet/positions/{wallet}` | [Wallet Positions](/api-reference/wallet/positions) |
| `GET /v2/polymarket/positions` | [Positions](/api-reference/trading/positions) |
### `total_shares_bought` is deprecated
Use `total_bought_usd` instead. The share-count derivation behind `total_shares_bought` is approximate for positions that were fully closed and later reopened at a different price, which can make it diverge significantly from the true buy total. The field is **not** being removed and its values are unchanged — existing clients see one extra JSON key and nothing else changes.
### Orderbook snapshot limit raised
`GET /v2/polymarket/orderbooks` and `GET /v2/kalshi/orderbooks` now accept `limit` up to **2000** (previously 200). The default remains 100.
## Trading API is being retired
The custodial **Trading API** (Unified Execution Infra) is winding down. The read-only **[Data & Signals API](/data-signals/overview)** is unaffected.
* **June 25, 2026** — trading stops. Sell or close any open positions before this date. After it, the Trading API is withdrawal-only (cancel, redeem, withdraw).
* **June 29, 2026** — final withdrawal deadline. Redeem resolved positions and withdraw all funds by this date.
New account creation and API key issuance are already closed — `POST /api/accounts/create` returns `403`.
### Order Router removed
The Unified Order Router (`/api/accounts/{accountId}/router/*`) has been retired and its documentation removed. Until trading stops on June 25, place orders directly with [venue-specific orders](/trading-api/accounts/place-order).
Questions, or need help withdrawing? Reach out at [team@predexon.com](mailto:team@predexon.com).
## Dflow endpoints removed
The Dflow venue is deprecated. The three Data API endpoints below have been removed and now return `404`. The corresponding MCP tools have been removed as well.
| Endpoint | MCP tool |
| - | - |
| `GET /v2/dflow/trades` | `get_dflow_trades` |
| `GET /v2/dflow/wallet/positions/{wallet}` | `get_dflow_positions` |
| `GET /v2/dflow/wallet/pnl/{wallet}` | `get_dflow_pnl` |
If you were depending on Dflow data, reach out at [team@predexon.com](mailto:team@predexon.com) so we can scope a replacement path.
## New endpoints
`GET /v2/polymarket/builders/wallet/{wallet}`. Total builder fees a wallet has paid over a rolling window or an exact `start_time`/`end_time` range, with a per-builder breakdown and optional `builder_code` filter. Each fill is scored at the rate in effect at the time it filled.
`GET /v2/polymarket/position/{token_id}/top-holders`. Largest holders of a single token (one outcome side), ranked by position size, with cursor pagination.
## Polymarket Discovery & Taxonomy
Seven new endpoints for browsing and navigating Polymarket — categories, tags, series, and a unified search. These power navigation surfaces and autocomplete on top of the markets and events data.
`GET /v2/polymarket/search`. One query across markets, events, and tags, grouped by type.
`GET /v2/polymarket/categories`. Top-level categories with market counts, volume, and liquidity.
`GET /v2/polymarket/tags`, `GET /v2/polymarket/tag/{slug}`, and `GET /v2/polymarket/tag/{slug}/subtags`. Curated tags with rolled-up volume/liquidity metrics, single-tag detail, and ranked child subtags.
`GET /v2/polymarket/series` and `GET /v2/polymarket/series/{slug}`. Recurring market groupings (daily/weekly/monthly) plus a series' events. The crypto up/down feature is just a named series.
## Improvements
[`GET /v2/polymarket/events`](/api-reference/markets/events) (keyset) gained optional `include_markets` (nest each event's markets inline, capped by `markets_per_event`) plus `min_open_interest` and `min_volume` filters to return only liquid events. Events also now include an all-time trade count (`trades_all_time`).
Per-tag and per-category open interest has been removed from the [categories](/api-reference/discovery/categories) and [tags](/api-reference/discovery/tags) endpoints — a tag/category-wide OI sum was unreliable. Rank by volume (`total_volume_usd`, `rolling_metrics`) and `liquidity_usd` instead. Both surfaces also gained `trades_all_time`.
## Behavioral changes
A few request defaults and constraints changed. Set parameters explicitly if you were relying on the previous defaults.
| Endpoint | Parameter | Before | Now |
| - | - | - | - |
| [`GET /v2/markets`](/api-reference/canonical/markets), [`GET /v2/markets/listings`](/api-reference/canonical/listings) | `status` | no default (all statuses) | defaults to `open` — also accepts `closed`, `resolved`, `all` |
| [`GET /v2/markets`](/api-reference/canonical/markets), [`GET /v2/markets/listings`](/api-reference/canonical/listings) | `limit` | default `20` | default `50` (max `200`) |
| [`GET /v2/markets`](/api-reference/canonical/markets), [`GET /v2/markets/listings`](/api-reference/canonical/listings) | `search` | any length | must be 3–200 characters |
| [`GET /v2/polymarket/wallet/positions/{wallet}`](/api-reference/wallet/positions) | `include_closed` | default `true` | default `false` — pass `include_closed=true` for closed/zero-balance positions |
**Pagination shape change on the Polymarket offset list endpoints.** `GET /v2/polymarket/markets` and `GET /v2/polymarket/events` now return `pagination: { has_more, limit, count, pagination_key }` — the `offset` and `total` fields have been removed. Migrate to the cursor-based [List Markets](/api-reference/markets/list-markets) / [List Events](/api-reference/markets/events) flow using `pagination_key`.
## Canonical Markets API
Four new venue-agnostic endpoints exposing Predexon's canonical outcome graph. These are the data layer the Order Router quotes against and the entry point for any cross-venue workflow.
`GET /v2/markets`. Cross-venue canonical market containers with outcome-level `predexon_id`s.
`GET /v2/markets/listings`. Flatten canonical markets into venue-native executable listings (token IDs, condition IDs, Kalshi tickers, market slugs).
`GET /v2/outcomes/{predexon_id}`. Resolve one canonical outcome ID to its full venue-equivalent listings.
`GET /v2/markets/search`. Keyword search returning interleaved results across Polymarket, Kalshi, Limitless, Opinion, and Predict.fun.
All four canonical endpoints are **free and unlimited** on every plan. Category and league filters supported. Cursor-based pagination.
## Order Router
One canonical outcome, one order, every venue. The router quotes across Polymarket, Predict.fun, Opinion, Limitless, and Hyperliquid, splits the order fee-aware, and dispatches each leg to its matching venue.
**Endpoints:**
* [`POST /api/accounts/{accountId}/router/quote`](/trading-api/router/quote). Pre-trade quote: project the best fill across every matched venue.
* [`POST /api/accounts/{accountId}/router/orders`](/trading-api/router/place-order). Place a routed order.
* [`GET /api/accounts/{accountId}/router/orders`](/trading-api/router/list-orders). List routed orders.
* [`GET /api/accounts/{accountId}/router/orders/{routerOrderId}`](/trading-api/router/get-order). Get a routed order.
* [`DELETE /api/accounts/{accountId}/router/orders/{routerOrderId}`](/trading-api/router/cancel-order). Cancel a routed order.
The router uses Predexon's canonical outcome graph to identify equivalent markets across venues. See the [Order Router guide](/trading-api/guides/order-router) for the full flow.
## Unified Transfers
`POST /api/accounts/{accountId}/transfers` replaces separate deposit and withdraw flows. One verb covers movement between the deposit wallet, venue trading wallets, and external addresses.
* `POST /api/accounts/{accountId}/transfers/quote`. Project route and fees before committing.
* `POST /api/accounts/{accountId}/transfers`. Execute the transfer.
* [`GET /api/accounts/{accountId}/transfers`](/trading-api/funds/list-transfers). List transfers with status.
* `GET /api/accounts/{accountId}/transfers/{transferId}`. Status of a single transfer.
**Beta.** Router and Transfers endpoints are marked beta. Existing per-venue order endpoints (`POST /api/accounts/{accountId}/orders`) continue to work unchanged.
## Other improvements
* **\$1 bridge minimum** documented on Across-routed deposits.
* **Router 4xx errors** moved from `502` to `400` in the OpenAPI spec to match shipped behavior.
* **Deposit-wallet behavior** synced with deployed router.
## Connected Wallets: Async Compute + Larger Clusters
The [Connected Wallets endpoint](/api-reference/wallet/cluster) now returns **either `200` or `202`** depending on cache state - clients must handle both.
**Behavior change.** Hub-wallet clusters can take 60–180 seconds to compute, far beyond any HTTP timeout. On a cache miss, stale cache (>24h), or `recompute=true`, the endpoint now returns `202 Accepted` with a `Retry-After` header and a `{status, retry_after_seconds, reason, message}` body. Background BFS runs out-of-band; the next call after `Retry-After` returns `200` from cache.
**`reason` values on 202:** `miss`, `stale`, `forced`, `another_pod_computing` (cross-pod Redis lock).
**Other changes:**
* `limit` upper bound bumped **200 → 1000**. Up to 1000 siblings persisted per seed.
* Cluster cache TTL is now **24 hours**.
* `min_hops` is now described as `1 = direct neighbor` (previously `0 = direct`).
* Richer `evidence` documentation: `hops`, `bidirectional`, `direct_flow`, `shared_peers`, `same_signer`, `same_x_username`, `shared_first_funder`, `is_verified_trader`, `class`.
See the [recommended client polling pattern](/api-reference/wallet/cluster#recommended-client-pattern) in the docs.
## Polymarket V2 Builder Analytics
Four new endpoints exposing Polymarket V2 builder-attribution data - leaderboards, per-builder summaries, top markets per builder, and bucketed time-series.
Ranked list of builders by attributed volume or fee revenue over 24h / 7d / 30d / all.
Summary metrics for a single builder plus its full `(taker_bps, maker_bps)` rate history.
Top markets attributed to a builder, ranked by volume.
Bucketed (`day` or `hour`) volume and fee revenue for charting.
V2-only. V1 fills, the zero-sentinel builder, and reversed (`sign != 1`) rows are excluded. The leaderboard's `sort_by=fee` is useful when looking at revenue-earning builders, since the highest-volume builders often charge 0 bps.
## List Markets: `end_after` / `end_before` Filters
[`GET /v2/polymarket/markets`](/api-reference/markets/list-markets) now accepts two new optional Unix-timestamp (seconds) query parameters - `end_after` and `end_before` - to filter markets by `end_time`. Useful for listing markets that close in a specific window (e.g. "this week's resolutions").
## Cross-Platform Matching: Predict and Opinion Support
[Find Matching Markets](/api-reference/matching/find-matches) and [Get Matched Pairs](/api-reference/matching/matched-pairs) now include Predict.fun and Opinion canonical equivalents alongside Polymarket, Kalshi, and Limitless.
**Changes:**
* `/v2/matching-markets`: accepts Predict and Opinion identifiers (`market_id`, `condition_id`, or `token_id`) and returns exact canonical equivalents when available.
* `/v2/matching-markets/pairs`: returns `PREDICT` and `OPINION` venue blocks by default when canonical equivalents exist.
* `venue` filtering now supports `polymarket`, `kalshi`, `limitless`, `predict`, and `opinion`.
Predict.fun and Opinion matching is powered by the canonical matching graph. Existing Polymarket, Kalshi, and Limitless response fields remain backward-compatible.
## Polymarket Markets: Keyset Pagination
List Polymarket markets with the same filters and sort options as `/v2/polymarket/markets`, but use cursor-based `pagination_key` instead of `offset`.
Use [List Markets (Keyset Pagination)](/api-reference/markets/list-markets) for large backfills and deep pagination. The existing offset endpoint remains unchanged.
## Polymarket Events: Keyset Pagination
List Polymarket events with the same filters and sort options as `/v2/polymarket/events`, but use cursor-based `pagination_key` instead of `offset`.
Use [List Events (Keyset Pagination)](/api-reference/markets/events) for large event crawls. The existing offset endpoint remains unchanged.
## Limitless: CLOB Open Interest
Limitless CLOB markets now return `open_interest_usd: null` instead of `0` because Limitless does not currently expose CLOB open interest through the indexed market API. AMM market open interest remains available.
See [Limitless Markets](/api-reference/limitless/markets) for the field note.
## Trading API: Hyperliquid (HIP-4) Support
Trade Hyperliquid HIP-4 prediction markets through the Predexon Trading API. Limit and market orders, full position lifecycle, and managed wallet provisioning - same shape as Polymarket, Predict, Opinion, and Limitless.
**Trading**
* New `venue: "hyperliquid"` accepted on every `/api/accounts/*` endpoint (place order, cancel, positions, balance, redeem, withdraw, enable).
* Markets are identified by `assetId` in the `market` bag - a single integer that pins down a specific HIP-4 outcome side. See [Place Order](/trading-api/accounts/place-order) for the per-venue identification table.
* \$10 minimum order notional (`size × price ≥ 10`) - applies to all orders. Smaller orders are rejected.
* Market BUY takes `size` (not `amount`) - fills as many shares as possible at the best ask up to the requested quantity.
**Funding**
* Bridge USDC to your Hyperliquid wallet via [Across](https://across.to). Select Hyperliquid (HyperCore) as the destination chain. Funds arrive as USDH (HL's USDC-pegged stablecoin) in your trading balance in \~1-2 minutes; `/balance` returns `asset: "usdh"`.
**Withdrawals**
* Deliver USDC on Arbitrum to any address. **\$10 minimum withdrawal.** A **\$1 USDC fee** is deducted before delivery - recipient receives `amount - 1`. Settlement takes \~3-4 minutes.
**Available now** for any API key. No allowlist - call `POST /api/accounts/{accountId}/enable {"venue": "hyperliquid"}` to provision a wallet.
## WebSocket: Crypto Prices Channel
Real-time crypto price ticks relayed from [Chainlink Data Streams](https://docs.chain.link/data-streams). Subscribe with `platform: "chainlink"`, `type: "crypto"` and a `feeds` filter - wildcard supported. See [Crypto Prices](/websocket/crypto-prices).
**Supported feeds:** BTC/USD, ETH/USD, SOL/USD, XRP/USD, BNB/USD, DOGE/USD, HYPE/USD.
```json theme={null}
{
"action": "subscribe",
"platform": "chainlink",
"version": 1,
"type": "crypto",
"filters": { "feeds": ["BTC/USD", "ETH/USD"] }
}
```
Each tick is a `price_feed` event with `price`, `bid`, `ask`, and `observed_at` (Unix seconds). One event per upstream observation - no batching. Plan limits and wildcard rules are the same as the existing channels.
## WebSocket: Explicit `role` on order\_filled
Every `order_filled` event on the [trades channel](/websocket/trades) now carries a `role` field - `"maker"` or `"taker"` - telling you which OrderFilled emission you're looking at:
* `role: "maker"` - per-maker fill (one per maker matched in the tx). `taker` is the real taker user address. **This is what you want by default.**
* `role: "taker"` - synthetic taker-aggregate emission (one per `matchOrders` tx, summing across all makers). `taker` equals the exchange contract address.
**Don't sum volume across both roles** - the taker-aggregate row covers the same shares as the per-maker rows. Filter on `role === "maker"` for maker-attribution counts, or `role === "taker"` for one-row-per-taker-action dashboards. Previously consumers had to derive this implicitly by checking `taker` against the five exchange contract addresses; now it's a first-class field.
The field is fully additive - existing clients ignore it unchanged.
## Connected Wallets Endpoint
Discover sibling wallets behind a seed address via the on-chain transfer graph (USDC, pUSD, CTF) plus identity-proof signals - shared signer, shared X username, shared first funder. Each sibling carries a `confidence` score (0–100); the default `min_confidence=70` returns strong-evidence links only.
Use this to surface likely-related accounts behind a single operator - alt wallets, hot/cold splits, multi-account farming patterns. Results are cached; pass `recompute=true` to force a fresh BFS. **Requires Dev or Pro tier.**
## Trades: V2 Exchange Fields
Every row in the [`/v2/polymarket/trades`](/api-reference/trading/trades) response now carries Polymarket exchange version metadata:
* `exchange_version` - `1` (V1 CTF/NegRiskCTF) or `2` (V2).
* `builder` - V2 builder attribution tag (bytes32 hex). Empty for V1 fills or V2 fills with no builder.
* `metadata` - V2 opaque metadata tag (bytes32 hex). Empty for V1 fills or V2 fills with no metadata.
The endpoint also accepts a new `builder` query parameter to filter fills attributed to a specific builder tag.
## Cross-Platform Matching: Limitless Support
[Find Matching Markets](/api-reference/matching/find-matches) and [Get Matched Pairs](/api-reference/matching/matched-pairs) now cover Limitless alongside Polymarket and Kalshi.
**Changes:**
* `/v2/matching-markets`: new `limitless_slug` query param - pass a Limitless slug to find equivalent markets on Polymarket or Kalshi.
* `/v2/matching-markets/pairs`: new `venue` filter - restrict results to a specific venue pairing.
* `MatchedPair` schema: `kalshi` block is now **optional**, and a new optional `limitless` block (using `LimitlessPairInfo`) is included when a Limitless equivalent exists.
Existing clients reading `pair.kalshi` directly should null-check before access - pairs without a Kalshi equivalent will omit the field.
## Per-Token Candlesticks
Fetch raw OHLCV candles for a single outcome token. Unlike the [condition-level endpoint](/api-reference/markets/candlesticks), prices are **not** normalized - NO-token prices stay as NO prices, and volume reflects only that token's trades.
Use this when you need to chart each side of a binary market separately, or when you only have a token ID. Same intervals, same auto mode, same response shape - the response now also includes a top-level `token_id` (and `condition_id` becomes optional, set only on the condition-level endpoint).
## Activity: `CONVERT` Events + NegRisk Fields
The activity endpoint now returns `CONVERT` events alongside `SPLIT`, `MERGE`, and `REDEEM` - these are NegRisk position conversions (sell NO on a subset of conditions, buy YES on the remainder).
**New fields on the `Activity` schema:**
* `neg_risk_market_id` - NegRisk parent market identifier (32-byte hex, matches Gamma's `Event.negRiskMarketID`). Populated only on `CONVERT` rows.
* `index_set` - NegRisk indexSet bitmask (decimal string) of which outcomes were involved. Populated only on `CONVERT` rows.
* `market_id` is now nullable - it's `null` on `CONVERT` rows.
On `CONVERT` rows, `condition_id` is an **empty string** and `market_id` is `null` - filtering by either will not match conversion rows. Use `neg_risk_market_id` instead. Conversions are a market-level operation, not a per-outcome one.
## WebSocket: Conversion Events & Pending Trade Gating
**New `conversion` event type on the activity channel**
V2 NegRisk "No" position conversions are now streamed as a new `conversion` event on the activity channel. Includes `neg_risk_event_id` (NegRiskAdapter marketId) and `index_set` (uint256 bitmap of converted question indices). See [Activity Events](/websocket/activity#conversion).
**Two things to be aware of - neither affects most clients:**
* `ActivityAlert.condition_id` is `null` only for the new `conversion` event type. Clients with strict typed generators (`condition_id: string`) should widen to `string | null` if they want to consume conversion events. Clients that filter by `event_type` in `{"split", "merge", "redeem"}` are unaffected.
* Pending trade events (`filters.status: "all"` or `"pending"`) are now gated to **Dev+** plans. Free-tier clients that don't request pending (default `"confirmed"`) continue to work unchanged.
## WebSocket: Token IDs on `condition_prepared`
The `condition_prepared` lifecycle event now includes derived token IDs for binary markets (`outcome_slot_count === 2`) in the `tokens` array. Previously clients had to query Gamma or CLOB after receiving the event - now token IDs are available immediately from the WebSocket payload.
* Binary markets: `tokens: [{ token_id, label: "Yes" }, { token_id, label: "No" }]` - derived from `condition_id` + `is_neg_risk` using the CTF Gnosis formula.
* Non-binary markets: `tokens: null` (unchanged).
* **Labels are placeholders** - resolve canonical outcome labels from Gamma/CLOB if needed.
## WebSocket: Polymarket V2 Support
The WebSocket service now handles Polymarket V2 contracts alongside V1. **Fully backwards-compatible** - existing clients require zero code changes. See the [V2 Migration Guide](/websocket/v2-migration) for details.
**Additive changes:**
* New `version` field (`1` or `2`) on trade, activity, and lifecycle events
* New `builder` and `metadata` fields on V2 `order_filled` events
* New `collateral` channel for pUSD deposit/withdrawal events (`polyusd_wrapped` / `polyusd_unwrapped`)
* New `condition_prepared` event type on the lifecycle channel (covers V1 + V2 market creation)
**Breaking for token discovery:** V2 exchanges no longer emit `TokenRegistered`. Clients using `token_registered` as a "new market" signal should switch to `condition_prepared` to cover both V1 and V2 markets.
## Candlesticks: New Intervals & Auto Mode
The candlestick endpoint now supports **5-minute** and **15-minute** intervals, plus an **auto** mode (`interval=0`) that picks the best interval based on your time range. Auto is now the default.
The response also includes `first_trade_ts` and `last_trade_ts` fields - the timestamps of the first and last trades in the market.
| Interval | Value | Max Range |
| - | - | - |
| Auto | `0` (default) | Best fit for your range |
| 1m | `1` | 7 days |
| 5m | `5` | 7 days |
| 15m | `15` | 30 days |
| 1h | `60` | 30 days |
| 1d | `1440` | Unlimited (all-time) |
## Universal Predexon IDs on All List Market Endpoints
`predexon_id` is now included in outcome objects across all five list market endpoints: Polymarket, Kalshi, Opinion, Limitless, and Predict.Fun. All five endpoints also support a `predexon_id` query parameter to filter markets by outcome ID.
A Predexon ID is a stable, deterministic identifier for each tradeable outcome (`px-` followed by a 12-character base32 hash), consistent across all venues.
## Wallet Positions: Per-Position Net Fees
The `PositionDetails` schema now includes `net_fees_usd` - net taker fees charged for each individual position (charged minus refunded, in USD). Previously, fee data was only available at the wallet summary level.
## `fees_refunded` Deprecated
The `fees_refunded` field now always returns `0` across all schemas. Refunds are already netted into `fees_paid`, making the separate field redundant.
| Schema | Field |
| - | - |
| `PositionsSummary` | `fees_refunded` |
| `AllTimeMetrics` | `fees_refunded` |
| `WindowMetrics` | `fees_refunded` |
| `CohortStats` | `avg_fees_refunded`, `total_fees_refunded` |
| `WalletPnLResponse` | `fees_refunded` |
## Candlesticks: All-Time Range for Daily Interval
The `1d` candlestick interval no longer has a range limit. Omit `start_time` and `end_time` to fetch the full history.
| Interval | Max Range |
| - | - |
| `1m` | 7 days |
| `1h` | 30 days |
| `1d` | **Unlimited (all-time)** |
## WebSocket: Oracle Channel
Real-time UMA oracle resolution events for Polymarket markets - proposals, settlements, disputes, and resets.
* Subscribe by `condition_ids` or `market_slugs`. Wildcard (`["*"]`) supported.
* Event types: `proposal`, `settled`, `dispute`, `reset`.
* Enrichment fields: `title`, `market_slug`, `image`, `outcomes`, `tokens`, `is_neg_risk`.
## WebSocket: Orderbook Channel
Real-time L2 orderbook data from Polymarket's CLOB - snapshots, price changes, and last trades.
* Subscribe by `token_ids`, `condition_ids`, or `market_slugs`. Wildcard (`["*"]`) supported.
* Targeted subscriptions deliver individual events. Wildcard delivers 250ms batched events with price-level conflation.
* Event types: `book_snapshot`, `price_change`, `last_trade`, `tick_size_change`, `snapshots_done`, `event_batch`, `resync`.
## New Endpoints: Opinion, Limitless, Predict.Fun Markets
List Opinion markets with filtering, sorting, and event grouping.
List Limitless markets with filtering, sorting, and trade type filter.
List Predict.Fun markets with filtering, sorting, and event grouping.
All three support status filtering, text search, cursor pagination, and platform-specific filters.
## Smart Activity: Market Status Filter
`GET /v2/polymarket/markets/smart-activity` now supports an optional `status` query parameter.
| Value | Description |
| - | - |
| `open` | Currently tradable markets only |
| `closed` | Resolved/settled markets only |
| *(omitted)* | All markets (default) |
## New Endpoints: Cross-Platform Orderbooks
Historical orderbook snapshots for Limitless.
Historical orderbook snapshots for Opinion.
Historical orderbook snapshots for Predict.Fun.
All return YES-side orderbook data with cursor pagination. Timestamps in milliseconds.
**Kalshi data gap:** Orderbook data unavailable between **March 12 08:00 UTC** and **March 14 18:10 UTC** due to an upstream API format change.
## WebSocket: Pending Trades Channel
Detect trade events from the Polygon mempool **3–5 seconds before** on-chain confirmation.
## WebSocket: New Trade Event Fields
The `order_filled` event on the trades channel now includes four new fields:
| Field | Type | Description |
| - | - | - |
| `outcome_index` | number \| null | `0` = first outcome (Yes/Over/Up), `1` = second (No/Under/Down) |
| `complement_token_id` | string \| null | Token ID of the opposite outcome |
| `complement_token_label` | string \| null | Label of the opposite outcome |
| `is_neg_risk` | boolean \| null | `true` for neg-risk markets |
All fields are additive and nullable - existing clients are unaffected.
## List Markets: Token ID Filter
`GET /v2/polymarket/markets` now supports a `token_id` query parameter (up to 50 IDs).
## Crypto Up/Down: 5-Minute Timeframe
`GET /v2/polymarket/crypto-updown` now supports `5m` as a timeframe filter.
## New Endpoint: Batch Wallet Profiles
`GET /v2/polymarket/wallets/profiles` - fetch up to 20 wallet profiles in a single request via comma-separated `addresses`.
## Total PnL & Rolling Window Metrics
Total PnL (`realized + unrealized - net fees`) is now available across all analytics, leaderboard, smart money, and wallet endpoints.
**Rolling total PnL accuracy timeline:**
* **All-time**: accurate immediately (February 10, 2026)
* **1-day rolling**: fully accurate February 11, 2026
* **7-day rolling**: fully accurate February 17, 2026
* **30-day rolling**: fully accurate March 12, 2026
| Endpoint | New Parameters |
| - | - |
| `GET /v2/polymarket/leaderboard` | `min_total_pnl`, `max_total_pnl`, `max_volume`, `max_trades`, `max_roi`, `max_profit_factor`, `max_win_rate`, `max_avg_trade_usd`, `max_wallet_age_days` |
| `GET /v2/polymarket/leaderboard/market/{condition_id}` | `min_total_pnl`, `max_total_pnl`, `max_trades`, `max_volume` |
| `GET /v2/polymarket/wallets/filter` | `min_total_pnl`, `max_total_pnl` |
| `GET /v2/polymarket/markets/smart-activity` | `min_total_pnl` |
| `GET /v2/polymarket/market/{condition_id}/smart-money` | `min_total_pnl` |
| Schema | Change |
| - | - |
| `WindowMetrics` | Added `total_pnl` field |
| `LeaderboardEntry` | Added `total_pnl` and `first_trade_at`; `total_pnl` available as sort option |
| `SmartActivityMarket` | Added `smart_total_pnl`; available as sort option |
| `SmartMoneyPositioning` | Added `total_smart_total_pnl` |
| `GET /v2/polymarket/positions` | Default sort changed from `desc` to `asc` |
## New Endpoints
Bulk position fetching with block/timestamp filtering and cursor pagination.
Per-wallet volume chart with BUY/SELL breakdown by period.
Top holders for a market ranked by position size.
| Schema | Change |
| - | - |
| `GET /v2/polymarket/markets/smart-activity` | `min_realized_pnl` threshold increased \$1K → \$5K |
| `GET /v2/binance/candles/{symbol}` | `start_time` and `end_time` now optional |
| `PositionDetails` | Added `total_shares_bought` field |
| `WalletProfileResponse` | Added `total_pnl` and `unrealized_pnl` fields |
## API v2 Launch
14 new endpoints, improved pagination, and expanded analytics. v1 remains fully supported.
**Base path:** `/v2/` (e.g., `https://api.predexon.com/v2/polymarket/markets`)
Real-time and historical Binance book ticker data for backtesting crypto up/down markets.
Track where profitable wallets are trading, filter by PnL/win rate, and discover alpha.
Full wallet profiles, trading style classification, market exposure, and similar wallet discovery.
Events grouping, crypto up/down markets, and per-period volume charts.
| Category | Endpoint | Description |
| - | - | - |
| Binance | `GET /v2/binance/ticks/{symbol}` | Raw book ticker data at microsecond granularity |
| Binance | `GET /v2/binance/candles/{symbol}` | OHLCV candles from book ticker mid-prices (1s–1d) |
| Smart Money | `GET /v2/polymarket/markets/smart-activity` | Markets ranked by smart wallet activity |
| Smart Money | `GET /v2/polymarket/market/{condition_id}/smart-money` | Smart money positioning for a market |
| Analytics | `GET /v2/polymarket/leaderboard` | Global wallet leaderboard (22 filter/sort params) |
| Analytics | `GET /v2/polymarket/leaderboard/market/{condition_id}` | Per-market leaderboard |
| Analytics | `GET /v2/polymarket/cohorts/stats` | Aggregate stats for wallet cohorts |
| Analytics | `GET /v2/polymarket/wallets/filter` | Filter wallets by market participation and performance |
| Wallets | `GET /v2/polymarket/wallet/{wallet}` | Full wallet profile with metrics across all windows |
| Wallets | `GET /v2/polymarket/wallet/{wallet}/markets` | Markets a wallet has traded |
| Wallets | `GET /v2/polymarket/wallet/{wallet}/similar` | Find wallets with similar trading patterns |
| Markets | `GET /v2/polymarket/events` | List events with grouped markets |
| Markets | `GET /v2/polymarket/crypto-updown` | Crypto up/down prediction markets |
| Markets | `GET /v2/polymarket/volume-chart/{condition_id}` | Per-period volume breakdown |
**Fee refund events** - the trades channel now delivers `order_filled` and `fee_refund` event types. Correlate refunds to trades via `order_hash`.
**Plan tiers restructured** (Free/Pro/Desk → Free/Dev/Pro/Enterprise):
| Limit | Free | Dev | Pro | Enterprise |
| - | - | - | - | - |
| WebSocket access | No | Yes | Yes | Yes |
| Subscriptions / connection | - | 10 | 100 | Custom |
| Items / subscription | - | 10 | 500 | Custom |
| Total items | - | 100 | 50,000 | Custom |
| Wildcard | - | No | 2 / channel | Yes |
| Priority routing | - | No | No | Yes |
**Wildcard rules:** tracked per-channel, cannot mix wildcard and regular subs on same channel, Pro allows 2 wildcard connections per channel.
**Filter restrictions:** `market_slugs` only on trades channel, `users` not on lifecycle channel. All channels support `condition_ids` and `["*"]`.
**New error code:** `RATE_LIMIT`. **Keepalive:** 30s ping, 60s pong timeout, 2min idle timeout, 1MB drop / 4MB disconnect buffer limits.
These changes only affect v2 endpoints. v1 endpoints remain unchanged.
**Path parameter renamed:** `{wallet_address}` → `{wallet}` on all wallet endpoints.
**Pagination:** switched from `offset` to cursor-based `pagination_key`:
| Endpoint | Change |
| - | - |
| `/v2/polymarket/trades` | `offset` → `pagination_key` |
| `/v2/dflow/trades` | `offset` → `pagination_key` |
| `/v2/kalshi/markets` | `offset` → `pagination_key` |
| `/v2/kalshi/trades` | `offset` → `pagination_key` |
**Parameter renames:** `user` → `wallet` on `/v2/polymarket/trades` and `/v2/polymarket/activity`.
**Removed parameters:** `token_id` from `/v2/polymarket/markets` (use `condition_id`), `match_type` from `/v2/matching-markets/pairs`.
| Endpoint | New Parameters |
| - | - |
| `/v2/polymarket/markets` | `min_volume_1d`, `min_volume_7d`, `min_volume_30d`, `min_trades_1d`, `min_trades_7d`, `min_trades_30d` |
| `/v2/polymarket/trades` | `min_total`, `wallet` |
| `/v2/polymarket/activity` | `sort_by`, `wallet` |
## Matching Markets: Enhanced Filtering
New query parameters and response fields for cross-platform market matching.
| Change | Details |
| - | - |
| New query params | `polymarket_condition_id`, `polymarket_market_id`, `match_type` (`exact`, `related`, `all`) |
| New response fields | `condition_id`, `market_id` for Polymarket markets |
| Pairs endpoint | Now includes `match_type` field per pair |
## Trading API
Place orders, view positions, check balances, and redeem resolved positions on Polymarket and Kalshi.
* `POST /api/users/{userId}/orders` - Place buy or sell orders
* `GET /api/users/{userId}/positions` - Get current positions with PnL
* `GET /api/users/{userId}/balance` - Get available balances
* `POST /api/users/{userId}/redeem` - Redeem resolved positions
## Schema Updates
| Endpoint | Change |
| - | - |
| `/v1/kalshi/markets` | Added `yes_subtitle` and `no_subtitle` fields |
| `/v1/dflow/wallet/positions/{wallet_address}` | Added `yes_subtitle`, `no_subtitle`, `market_title`, `outcome`, `status`, `total_cost_basis`, `total_qty_bought`, `total_qty_sold`, `first_trade_at`, `last_trade_at` |
## Dflow Support
Three new endpoints for the Dflow prediction market platform.
* `GET /v1/dflow/trades` - Trade history for a wallet
* `GET /v1/dflow/wallet/positions/{wallet_address}` - Positions with cost basis and PnL
* `GET /v1/dflow/wallet/pnl/{wallet_address}` - Realized PnL history
## Breaking Change: Candlesticks Response
The `/v1/polymarket/candlesticks/{condition_id}` response structure changed from nested `[data, metadata]` tuples to a flat structure:
```json theme={null}
{
"condition_id": "0x1234...",
"candlesticks": [
{
"end_period_ts": 1704070800,
"price": { "open": 0.55, "high": 0.57, ... },
"volume": 125000.50,
"trades_count": 847
}
]
}
```
Schema updates: `CandlestickData` now fully typed, `CandlesticksResponse` has `condition_id` field, new `PriceData` schema with dollar-formatted strings.
# How It Works
Source: https://docs.predexon.com/concepts/how-it-works
How Predexon turns raw blockchain and venue data into the fastest, most accurate prediction-market datasets.
**Concepts learning path · 3 of 3.** You know what prediction markets are and what smart money means. This page covers the machinery — how Predexon captures, indexes, and serves the data you read.
## The pipeline
Predexon runs its own indexing pipeline end to end — from our own blockchain nodes to the API response you read. Nothing is proxied from a venue's public feed, which is why the data is both faster and more complete than what the venues publish.
```mermaid theme={null}
flowchart LR
NODES[Own nodes
+ mempool access] --> IDX[Custom indexers
per venue]
VENUES[Venue APIs
+ orderbook feeds] --> IDX
IDX --> NORM[Normalization
one schema across venues]
NORM --> STORE[(Historical store
billions of rows, since 2020)]
NORM --> RT[Real-time layer]
STORE --> REST[Data API
REST]
STORE --> TICKS[Tick history
Parquet downloads]
STORE --> ANA[Analytics
P&L · smart money · leaderboards]
RT --> WSS[WebSocket
wss.predexon.com]
ANA --> REST
```
## Stage by stage
We run our own blockchain nodes rather than paying an RPC provider. That gives us the **mempool**: pending transactions are visible before they confirm, which is how the [pending-trades channel](/websocket/pending-trades) streams fills up to **5 seconds ahead of Polymarket's RTDS**. In parallel, dedicated recorders capture every orderbook update from each venue, 24/7 — the raw stream behind [tick history](/data-signals/ticks/data-credits).
Each venue gets its own indexer: contract ABIs decoded, chain reorgs handled, gaps backfilled. When a protocol upgrades, we update the decoder — you never see the seam. This is the part that quietly breaks in self-built pipelines; it's our full-time job.
Polymarket prices are 0–1 decimals; Kalshi's are 0–100 cents. Field names, timestamps, and identifiers differ everywhere. The normalization layer maps everything into one consistent shape, so `GET /v2/kalshi/trades` reads like `GET /v2/polymarket/trades`.
Wallet P\&L, smart-money classification, leaderboards, and cohort stats are computed from the complete on-chain history — every trade since 2020, not a sample. That's why a wallet's P\&L on Predexon matches what the chain says, block for block.
The same underlying data reaches you three ways: [REST](/data-signals/overview) for state, [WebSocket](/websocket/overview) for change, and [bulk Parquet](/data-signals/ticks/data-credits) for the raw tick stream. Pick per use case; the shapes stay consistent.
## Why this is hard to replicate
| Property | Why it matters | What it takes to build |
| - | - | - |
| **Mempool visibility** | See flow before it confirms | A co-located, self-run Polygon node and pending-tx decoding |
| **Complete tick capture** | Replay the book at any moment | Recorders that never miss an update — a gap is permanent |
| **Chain-accurate analytics** | P\&L you can trust | Full-history indexing with reorg handling since 2020 |
| **Cross-venue normalization** | One integration instead of N | Per-venue decoders maintained as protocols evolve |
## Data freshness & finalization
* **WebSocket** events arrive sub-second; pending-trade events arrive before on-chain confirmation.
* **REST** reads reflect indexed state within seconds of confirmation.
* **Tick history** datasets are finalized daily through the previous calendar day — finalized means gap-checked and immutable.
## Next
Every endpoint, organized by what it's for.
How the raw tick datasets are priced and delivered.
# Prediction Markets 101
Source: https://docs.predexon.com/concepts/prediction-markets
Understand how prediction markets work and the key concepts behind the Predexon API
**Concepts learning path · 1 of 3.** Start here for the mental model: how prediction markets work, what a market/outcome/token is, and how venues differ. Once this clicks, the rest of the docs read naturally.
## What Are Prediction Markets?
Prediction markets are platforms where you trade contracts on the outcomes of real-world events. Each contract resolves to either **\$1** (if the outcome happens) or **\$0** (if it doesn't). The current trading price reflects the market's implied probability of that outcome.
For example, if a contract "Will X happen?" trades at **\$0.65**, the market implies a **65% probability** that X will occur.
## Key Concepts
### Markets and Conditions
A **market** is a single tradeable question (e.g., "Will Team A win?"). Markets are identified by a `condition_id`, the unique identifier for the underlying question.
Each market has two sides:
* **Yes** - you profit if the event happens
* **No** - you profit if the event doesn't happen
Yes and No prices always sum to approximately \$1.
### Token IDs
Each side of a market has its own **token ID**, the specific tradeable asset. A single `condition_id` maps to two token IDs (one for Yes, one for No). When placing trades or fetching prices, you'll often work with token IDs.
### Events
An **event** groups related markets together. For example, "2026 Election" might be an event containing markets for each candidate.
### Orderbooks
Prediction markets use **orderbooks**, a list of open buy and sell orders at various prices. The orderbook determines the current best price and available liquidity.
Key orderbook terms:
* **Bid** - highest price a buyer is willing to pay
* **Ask** - lowest price a seller is willing to accept
* **Spread** - the gap between the best bid and best ask
### Positions
When you buy shares in a market, you hold a **position**. Positions track:
* Which outcome you hold (Yes or No)
* How many shares
* Your average entry price
### Resolution
When the real-world event concludes, the market **resolves**. Winning shares pay out \$1 each. Losing shares pay \$0. After resolution, winning positions become **redeemable** and can be exchanged for your collateral (pUSD on Polymarket, USDT on Predict.fun).
## Platforms Predexon Supports
Predexon provides a unified API across multiple prediction market platforms:
| Platform | Blockchain | Collateral | Data | Trading |
| - | - | - | - | - |
| **Polymarket** | Polygon | pUSD | Full coverage | Supported |
| **Predict.fun** | BSC | USDT (BEP-20) | Markets, orderbooks | Supported |
| **Opinion** | BSC | USDT (BEP-20) | Markets, orderbooks | Supported |
| **Limitless** | Base | USDC | Markets, orderbooks | Supported |
| **Hyperliquid** | HyperCore | USDH (USDC-pegged) | - | Supported |
| **Kalshi** | - (centralized) | - | Markets, trades, orderbooks | - |
| **Binance** | - | - | Crypto tick & candle data | - |
## What You Can Do with Predexon
* **Read market data** - prices, candlesticks, volume, open interest, orderbooks across all platforms
* **Trade programmatically** - place and manage orders on Polymarket, Predict.fun, Opinion, Limitless, and Hyperliquid via the Trading API
* **Analyze wallets** - track positions, calculate P\&L, profile trading behavior
* **Find cross-platform opportunities** - match equivalent markets between Polymarket and Kalshi
* **Follow smart money** - see where high-performing traders are active
* **Stream real-time data** - subscribe to trades, activity, and market lifecycle events via WebSocket
***
## Continue learning
How wallets get classified as smart money — and how to use the signal.
If you're ready to build, head to the right persona guide on the landing page.
# Smart Money & Analytics
Source: https://docs.predexon.com/concepts/smart-money
Understand how Predexon identifies and tracks high-performing traders
**Concepts learning path · 2 of 3.** Smart money is one of Predexon's most-used signals. Understand how wallets get classified so you can use the leaderboard, smart-activity, and smart-money endpoints with intent.
## What Is Smart Money?
Smart money refers to **high-performing wallets** identified by their trading metrics. Predexon analyzes all trading activity on Polymarket and classifies wallets based on realized P\&L, ROI, win rate, volume, and trade count.
By tracking what these profitable traders are doing, you can discover markets with informed interest and understand how skilled traders are positioning themselves.
## Wallet Classification
Every wallet with sufficient trading history receives a set of **boolean labels** and a computed **primary trading style**.
### Trading Styles
| Style | Description |
| - | - |
| `WHALE` | Large position sizes and high volume |
| `MARKET_MAKER` | Provides liquidity on both sides |
| `ACTIVE_TRADER` | High trade frequency |
| `BUY_AND_HOLD` | Takes positions and holds through resolution |
| `DEGEN` | High-risk, high-frequency trading |
| `HIGH_CONVICTION` | Concentrated positions in few markets |
| `CONTRARIAN` | Trades against prevailing sentiment |
| `VALUE_HUNTER` | Targets perceived mispricings |
Each wallet can have multiple boolean labels (e.g., `is_whale`, `is_market_maker`, `is_contrarian`) in addition to a single `primary_trading_style`. This lets you build custom filter combinations.
## Smart Money Endpoints
### Smart Activity Markets
`GET /v2/polymarket/markets/smart-activity` - Discover which markets high-performing wallets are actively trading. You define "smart" using filter criteria, and the API ranks markets by smart wallet activity.
### Smart Money Positioning
`GET /v2/polymarket/market/{condition_id}/smart-money` - For a specific market, see how profitable traders are positioned. Returns:
* Number of smart wallets as net buyers vs sellers
* Average entry prices
* Total volume from smart wallets
* Total P\&L from smart wallets
## Analytics Endpoints
### Leaderboard
`GET /v2/polymarket/leaderboard` - Ranked list of wallets by trading performance. Filter by trading style, P\&L range, volume, and win rate.
### Cohort Stats
`GET /v2/polymarket/cohorts/stats` - Compare aggregate performance across trading style cohorts. See which styles perform best overall.
### Filter Wallets
`GET /v2/polymarket/wallets/filter` - Find wallets that traded specific markets using AND/OR logic, filtered by combined performance metrics.
### Top Holders
`GET /v2/polymarket/market/{condition_id}/top-holders` - Get the largest position holders in any market, ranked by position size. Filter by Yes or No side.
### Wallet Profile
`GET /v2/polymarket/wallet/{wallet}` - Full profile for a single wallet including all metrics, style classifications, and boolean labels.
### Similar Wallets
`GET /v2/polymarket/wallet/{wallet}/similar` - Find wallets with overlapping market activity. Useful for detecting copy-trading patterns and discovering traders with similar strategies.
## Minimum Filter Requirements
Smart money endpoints require at least **one strong filter** to ensure meaningful results:
| Filter | Minimum Threshold |
| - | - |
| `min_realized_pnl` | ≥ 5,000 (smart activity) or ≥ 1,000 (market positioning) |
| `min_total_pnl` | ≥ 5,000 (smart activity) or ≥ 1,000 (market positioning) |
| `min_roi` | ≥ 0.15 (15%) |
| `min_trades` | ≥ 100 |
| `min_volume` | ≥ 10,000 |
`min_win_rate` and `min_profit_factor` are only valid when combined with `min_trades ≥ 50`.
## Requirements
All smart money and analytics endpoints require a **Dev plan or higher**. See [Rate Limits & Plans](/rate-limits) for details.
***
## Continue learning
The mental model — markets, outcomes, tokens, venues.
Switch lanes: how the Trading API turns data into executed orders.
# Candle and trade reconciliation
Source: https://docs.predexon.com/data-signals/backtesting/candle-trade-reconciliation
Backtest faster with candles for top-level pattern detection, then reconcile against the trade tape to confirm fills were really available.
Orderbook replay is the gold standard for backtesting, but it's also the heaviest to iterate on. For first-pass strategy ideas, candles are much cheaper to crunch — and you can reconcile against the trade tape afterward to confirm that what your candle-based strategy "did" was actually achievable.
This page covers the candle-first workflow and how to validate it against ground truth.
***
## Two complementary endpoints
| Endpoint | Granularity | Use for |
| - | - | - |
| [`/v2/polymarket/candlesticks/{condition_id}`](/api-reference/markets/candlesticks) | 1m, 5m, 15m, 1h, 4h, 1d (auto-fit) | Fast pattern search, indicator-based strategies |
| [`/v2/polymarket/trades`](/api-reference/trading/trades) | Per-fill | Reconciliation, exact-fill simulation, VWAP checks |
Both are **free and unlimited**.
***
## Pass 1 — fast candle-driven backtest
Pull condition-level candles and run your strategy in pandas. This is fast enough to grid-search parameters.
```python theme={null}
import requests, pandas as pd
API_KEY = "YOUR_API_KEY"
BASE = "https://api.predexon.com"
HEADERS = {"x-api-key": API_KEY}
CONDITION_ID = "0x4c57..."
candles = requests.get(
f"{BASE}/v2/polymarket/candlesticks/{CONDITION_ID}",
headers=HEADERS,
params={"interval": 60, "start_time": 1736294400, "end_time": 1736380800},
).json()
df = pd.DataFrame(candles)
df["sma_20"] = df["close"].rolling(20).mean()
# trivial example: long the YES side when close > 20-period SMA
df["signal"] = (df["close"] > df["sma_20"]).astype(int)
df["ret"] = df["close"].pct_change().shift(-1)
df["pnl"] = df["signal"] * df["ret"]
print(f"hypothetical PnL: {df['pnl'].sum():.4f}")
```
Candles use **seconds** for `start_time` / `end_time`. The orderbook and trades endpoints sometimes use **milliseconds** — check each endpoint's reference page.
This gets you a quick read on whether the signal has any edge at all. But the close price is a mid — it doesn't tell you whether you'd have actually been filled there.
***
## Pass 2 — reconcile against the trade tape
For each candle bar where your strategy triggered, ask the trades endpoint *what actually traded in that bar*. Replace candle close with VWAP of executed trades, or check that there was enough volume to support your hypothetical size.
```python theme={null}
def trades_in_bar(token_id, bar_start_sec, bar_end_sec):
return requests.get(
f"{BASE}/v2/polymarket/trades",
headers=HEADERS,
params={
"token_id": token_id,
"start_time": bar_start_sec,
"end_time": bar_end_sec,
"limit": 500,
},
).json()
def realized_vwap(trades):
if not trades:
return None
notional = sum(float(t["price"]) * float(t["size"]) for t in trades)
volume = sum(float(t["size"]) for t in trades)
return notional / volume if volume else None
```
Three checks worth running:
1. **Was there volume?** If `sum(sizes) < your_target_size`, your strategy couldn't have moved that much money without walking the book.
2. **Was the candle close achievable?** Compare candle `close` to the realized VWAP. Large divergences mean the close was a low-volume tick — not a price you could trade at.
3. **Did your direction get hit?** Filter trades by side. If your signal said "buy" but every executed trade in the bar was a sell, the book was offered down — you'd be paying through the spread.
***
## Pass 3 — fall back to orderbook replay for the edge cases
Once you've reconciled against trades, you'll find a handful of bars where:
* Trades volume was thin
* Realized VWAP diverged from candle close
* Your size was a big chunk of the bar's volume
For those bars, drop to [orderbook replay](/data-signals/backtesting/orderbook-replay) and simulate the fill against the actual book. This gives you a much more realistic PnL than candle-only backtesting, without paying the orderbook-replay cost across your entire dataset.
***
## A note on tokens vs conditions
* Candles by **`condition_id`** give you the market-level OHLCV (usually the Yes side by convention).
* Candles by **`token_id`** ([`/candlesticks/token/{token_id}`](/api-reference/markets/candlesticks-by-token)) let you backtest either outcome independently. Use this if your strategy trades both sides.
* Trades and orderbook are always per-token. Make sure all three are aligned to the same outcome when reconciling.
# Orderbook replay
Source: https://docs.predexon.com/data-signals/backtesting/orderbook-replay
Replay historical Polymarket orderbook state to backtest strategies against the real depth that existed at the time.
The most accurate way to backtest a prediction-market strategy is to replay the **orderbook snapshots that actually existed** at decision time — not a candle, not a mid price, the real bids and asks. Predexon stores per-token orderbook snapshots starting **January 1st, 2026** and exposes them as a free, unlimited endpoint.
**Free & unlimited.** [`GET /v2/polymarket/orderbooks`](/api-reference/markets/orderbooks) doesn't count against your monthly quota on any plan.
***
## What you get per snapshot
Each snapshot is a full L2 book for one token at one point in time:
```json theme={null}
{
"timestamp": 1736294400000,
"token_id": "82855...",
"condition_id": "0x...",
"bids": [{ "price": "0.62", "size": "5000" }, ...],
"asks": [{ "price": "0.63", "size": "3000" }, ...]
}
```
Snapshots are produced at the cadence the market produced them — busy markets give you more snapshots per second, illiquid markets fewer. Timestamps are **milliseconds**.
***
## Walk an interval
The simplest replay loop: pull snapshots between two timestamps, then iterate.
```python theme={null}
import requests, time
API_KEY = "YOUR_API_KEY"
BASE = "https://api.predexon.com"
HEADERS = {"x-api-key": API_KEY}
TOKEN_ID = "82855..." # the outcome you're backtesting
START_MS = 1736294400000 # 2026-01-08 00:00 UTC
END_MS = 1736380800000 # 2026-01-09 00:00 UTC
cursor = START_MS
while cursor < END_MS:
snaps = requests.get(
f"{BASE}/v2/polymarket/orderbooks",
headers=HEADERS,
params={
"token_id": TOKEN_ID,
"start_time": cursor,
"end_time": END_MS,
"limit": 200,
},
).json()
if not snaps:
break
for s in snaps:
# your strategy sees the book exactly as it was at s["timestamp"]
best_bid = float(s["bids"][0]["price"]) if s["bids"] else 0
best_ask = float(s["asks"][0]["price"]) if s["asks"] else 1
spread = best_ask - best_bid
# decide(s, best_bid, best_ask, spread)
cursor = snaps[-1]["timestamp"] + 1
```
`limit` caps at **200** snapshots per call. The walk pattern above advances the cursor to the last snapshot's timestamp plus one millisecond.
***
## Simulating fills
The honest way to simulate a fill against a historical book:
| Order type | Simulated fill |
| - | - |
| **Market buy of size S** | Walk the asks ascending. Take size from each level until you've filled S or exhausted the book. Average price = your fill price. |
| **Market sell of size S** | Same, walking the bids descending. |
| **Limit buy at P, size S** | Fill only if `best_ask ≤ P` at the snapshot. Take size from asks `≤ P` only. |
| **Resting limit order** | At each subsequent snapshot, check if a trade would have crossed your price. The [trades endpoint](/api-reference/trading/trades) is the ground truth for what actually traded — match against it. |
For resting limit orders, pair orderbook snapshots with the [trades endpoint](/api-reference/trading/trades) over the same window. The trades tape tells you what actually crossed; the orderbook tells you whether your resting order would have been the resting side.
***
## Slippage and depth checks
You can answer "could I have actually traded \$X in this market at the time?" directly from a snapshot:
```python theme={null}
def simulate_market_buy(snapshot, usd_to_spend):
spent, filled = 0.0, 0.0
for level in snapshot["asks"]:
price = float(level["price"])
size = float(level["size"])
level_cost = price * size
if spent + level_cost <= usd_to_spend:
spent += level_cost
filled += size
else:
remaining_usd = usd_to_spend - spent
partial_size = remaining_usd / price
spent += remaining_usd
filled += partial_size
break
avg_price = spent / filled if filled else None
return {"filled_shares": filled, "avg_price": avg_price, "usd_spent": spent}
```
Run this across every snapshot in your window to get a slippage curve for the size you actually want to trade — a much better sanity check than blindly trusting candle data.
***
## Backtesting tips
* **Use both sides of the market.** Subscribe (or replay) Yes *and* No outcomes. Spreads between them often imply arbitrage opportunities or stale liquidity.
* **Snapshots aren't tick-level.** They're produced when the book changes. Between two snapshots, assume the book held — don't interpolate.
* **Watch for empty books.** Newly created markets, low-liquidity outcomes, and weekends all produce sparse snapshots. Handle `bids: []` and `asks: []` gracefully.
* **Pair with the trades endpoint.** [`GET /v2/polymarket/trades`](/api-reference/trading/trades) gives you the actual fills that happened — useful for validating your simulated execution.
***
## Going live with the same logic
Once a strategy backtests well, the live version uses the same shapes via the [orderbook WebSocket channel](/websocket/orderbook) instead of REST. The `book_snapshot` event has the same `bids` / `asks` shape, and `price_change` events let you maintain the book incrementally — so your decision function rarely needs to change between backtest and prod.
# Backtesting Predexon signals
Source: https://docs.predexon.com/data-signals/backtesting/signal-backtest
Validate smart-money, top-holders, and pending-trade signals against historical trades and orderbook state before going live.
Predexon ships several derived signals out of the box — smart-money flow, top-holders, pending-trade detection. The honest question before you build a strategy around any of them is: **did this signal actually predict anything historically?** This page walks the workflow for answering that.
The pattern is the same for every signal:
1. Pull the signal's historical state at decision time `t`.
2. Pull the price/orderbook state at `t + horizon` from market data.
3. Compute realized PnL if you had acted on the signal at `t`.
***
## Signal 1 — smart-money flow
[`GET /v2/polymarket/market/{condition_id}/smart-money`](/api-reference/smart-money/smart-money-market) tells you whether profitable wallets are net buyers or sellers in a market over a window. The natural backtest: **does smart-money net-buying predict price up over the next N hours?**
The smart-money endpoint reflects current state, not historical state. To backtest, you reconstruct the signal from raw trade data:
```python theme={null}
import requests
from datetime import datetime, timezone
BASE = "https://api.predexon.com"
HEADERS = {"x-api-key": "YOUR_API_KEY"}
def smart_wallets():
# Top-100 wallets by realized profit
return requests.get(
f"{BASE}/v2/polymarket/leaderboard",
headers=HEADERS,
params={"sort_by": "realized_profit", "limit": 100},
).json()
def smart_net_flow(token_id, start_sec, end_sec, wallet_set):
trades = requests.get(
f"{BASE}/v2/polymarket/trades",
headers=HEADERS,
params={
"token_id": token_id,
"start_time": start_sec,
"end_time": end_sec,
"limit": 1000,
},
).json()
net = 0.0
for t in trades:
if t["maker"] not in wallet_set and t["taker"] not in wallet_set:
continue
size = float(t["size"])
net += size if t["side"] == "BUY" else -size
return net
```
Then for each decision time:
```python theme={null}
import pandas as pd
smart = {w["wallet"] for w in smart_wallets()["wallets"]}
results = []
for t0 in decision_times: # e.g. every hour
pre = smart_net_flow(TOKEN, t0 - 3600, t0, smart) # last hour
p_t0 = price_at(TOKEN, t0)
p_t1 = price_at(TOKEN, t0 + 14400) # 4h horizon
results.append({"t": t0, "flow": pre, "ret": p_t1 - p_t0})
df = pd.DataFrame(results)
print(df.groupby(pd.qcut(df["flow"], 5))["ret"].mean())
```
A monotonic relationship between flow quintile and forward return is the signal you're hoping to see. Flat means the signal isn't predictive at that horizon.
***
## Signal 2 — top-holders concentration
[`GET /v2/polymarket/markets/{condition_id}/top-holders`](/api-reference/analytics/top-holders) shows position concentration. The hypothesis: **when one side is held by a few large wallets, it's vulnerable to a flush.**
Reconstruct from snapshots of positions over time:
* For each `condition_id` in your universe, sample top-holders periodically (e.g. daily).
* Compute a concentration metric (HHI, top-5 share, etc.).
* Pair with the same-day [orderbook](/data-signals/backtesting/orderbook-replay) snapshot to know what depth was sitting against that concentration.
* Look at forward price moves over a 1d / 1w horizon, bucketed by concentration.
This is a slow signal — daily decisions, weekly horizons — so candle data plus periodic snapshots are usually enough. No need for tick-by-tick orderbook replay.
***
## Signal 3 — pending-trade leading indicator
The [pending-trades WebSocket](/websocket/pending-trades) detects fills from the Polygon mempool 3–5 seconds before confirmation. The backtest question: **does the pending side predict the next confirmed-trade direction tight enough to act on?**
Pending events are real-time only — there's no historical replay endpoint for them. To backtest, you have two options:
1. **Run a recorder for a week.** Connect to the pending-trades channel and dump every event to disk. Then pair against the confirmed [trades endpoint](/api-reference/trading/trades) for the same window. Compute hit rate, average lead time, and PnL under realistic latency assumptions.
2. **Proxy with confirmed trades + book delta.** For each confirmed trade, look at the orderbook snapshot \~3 seconds before. Did the best bid/ask shift in the direction of the trade? Use that as a proxy for what pending-trade detection would have told you.
The recorder approach is more honest. Plan on a week of paper-collection before you trust the signal enough to size into it.
***
## What to actually measure
Three numbers matter more than total PnL:
| Metric | Why |
| - | - |
| **Hit rate by horizon** | Does the signal predict at 5min, 1h, 4h, 1d? Often the edge only exists in one band. |
| **Slippage realism** | After paying spread + walking the book for your size (see [orderbook replay](/data-signals/backtesting/orderbook-replay)), does the PnL survive? |
| **Capacity** | What's the max position size before the signal's edge equals your market impact? Reconcile against [trade-tape](/data-signals/backtesting/candle-trade-reconciliation) volume to answer this honestly. |
A signal with a 55% hit rate that survives realistic slippage at \$10k/trade is more valuable than a 70% hit rate that collapses at \$1k.
***
## Going live with the same code
If the backtest holds up, the live version connects to:
* [WebSocket trades](/websocket/trades) and [activity](/websocket/activity) for real-time flow
* [WebSocket pending-trades](/websocket/pending-trades) for the mempool edge
* Your venue-native execution client (e.g. the Polymarket CLOB API) when your signal fires
The data shapes are the same in REST and WebSocket, so your decision function should rarely need to change.
# Data & Signals
Source: https://docs.predexon.com/data-signals/overview
Normalized prediction-market data, wallet analytics, smart-money signals, and real-time WebSocket feeds across every major venue.
Everything in this tab is built for one job: **giving you the data and signals you need to find edges in prediction markets.** Markets, prices, orderbook history, tick-level replay, trades, positions, wallet P\&L, smart-money flow, and live streaming — all behind one API key, normalized across Polymarket, Kalshi, Limitless, Opinion, Predict.fun, and Binance.
Three products anchor everything here:
1. **The fastest WebSocket** — mempool-aware streams, up to 5 seconds ahead of Polymarket's RTDS.
2. **Orderbook tick history** — every book update at every granularity, as bulk Parquet.
3. **Blockchain-indexed analytics** — wallet P\&L, smart money, and leaderboards, accurate to the block.
**Most of what's here is free and unlimited.** All list-market and orderbook-history endpoints don't count toward your monthly quota on any plan. [See the full free-endpoint list →](/rate-limits)
***
## Your first call
```bash cURL theme={null}
curl -H "x-api-key: YOUR_API_KEY" \
"https://api.predexon.com/v2/polymarket/markets?status=open&sort=volume&limit=5"
```
```python Python theme={null}
import requests
r = requests.get(
"https://api.predexon.com/v2/polymarket/markets",
headers={"x-api-key": "YOUR_API_KEY"},
params={"status": "open", "sort": "volume", "limit": 5},
)
for m in r.json()["markets"]:
print(m["title"])
```
```javascript Node.js theme={null}
const res = await fetch(
"https://api.predexon.com/v2/polymarket/markets?status=open&sort=volume&limit=5",
{ headers: { "x-api-key": "YOUR_API_KEY" } }
);
const { markets } = await res.json();
markets.forEach(m => console.log(m.question));
```
New here? Walk through the full setup in [Quickstart →](/quickstart)
***
## What's in this tab
**New, and the most-requested dataset by quant teams.** Raw, tick-level orderbook deltas as bulk Parquet — every update at full resolution across Polymarket, Kalshi, and Opinion. Replay the book at any moment; aggregate to any granularity.
List markets and events across all venues, plus live lifecycle events (creation, resolution) over WebSocket.
Spot price, OHLCV candlesticks (1m–1d), volume curves, open interest, Binance reference candles, and Chainlink crypto streams.
Historical orderbook snapshots across venues plus the live L2 WebSocket stream for Polymarket.
Full historical trade tape (Polymarket, Kalshi) plus live trades and mempool pending-trade signals over WebSocket.
On-chain activity (splits, merges, redemptions, NegRisk conversions), position snapshots, and the live activity WebSocket channel.
Wallet profiles, positions, realized + unrealized P\&L, similar-wallet discovery, and connected-wallet clusters on Polymarket.
Profitable-wallet positioning, top holders, smart-activity-per-market, global + per-market leaderboards, cohort stats.
UMA resolution markets and live oracle + collateral events for tracking how questions resolve.
Replay historical orderbook state, reconcile candles with the trade tape, and backtest signals against the pending-trade leading indicator.
***
## Common recipes
`GET /v2/polymarket/markets?sort=volume`
`GET /v2/polymarket/search?q=trump`
`GET /v2/polymarket/candlesticks/{condition_id}`
`GET /v2/polymarket/orderbooks`
`GET /v2/polymarket/wallet/pnl/{wallet}`
`GET /v2/polymarket/market/{condition_id}/smart-money`
`GET /v2/data/ticks/quote` → `GET /v2/data/ticks`
Subscribe with `filters.status: "pending"` on the trades channel.
***
## Authentication & limits
All endpoints in this tab live on `https://api.predexon.com` and authenticate via the `x-api-key` header. See [Authentication](/authentication) and [Rate limits](/rate-limits) in **Start Here** for plan details, free-endpoint lists, and best-practice patterns (caching, retries, connection pooling).
# Data Credits & Pricing
Source: https://docs.predexon.com/data-signals/ticks/data-credits
How bulk tick downloads are priced: prepaid credits, quotes, and download charges
Bulk tick downloads are priced by **download size**, not per request. One call can authorize gigabytes of Parquet, so charges come out of a **prepaid data-credit balance** on your organization, kept completely separate from your monthly API request quota.
**Get started in two minutes.** [Sign up](https://dashboard.predexon.com), open **[Data Credits](https://dashboard.predexon.com/data-credits)**, and **add a payment method to unlock \$50 in free credits**. That is enough to pull a handful of markets and inspect the data in your own tools. There is no approval step and no charge to add a card. Top up any amount from \$5 to \$10,000 when you are ready for more.
## Coverage
Every tick dataset covers complete order-book history from launch through yesterday, finalized daily and growing one day at a time.
| Dataset | Available from | Through |
| - | - | - |
| Polymarket ticks | **2026-03-02** | yesterday (T-1) |
| Kalshi ticks | **2026-03-05** (lossless sub-cent from **2026-04-20**) | yesterday (T-1) |
| Kalshi book snapshots | **2026-09-18** | yesterday (T-1) |
| Opinion ticks | **2026-03-10** | yesterday (T-1) |
Data is **finalized T-1**: each UTC day is compacted the following morning (around 06:00 UTC). Requests for today, or any day not yet compacted, return `404 not_final`. A single request spans up to **31 days**; pull a longer range across multiple requests.
## Pricing
Priced per **GiB of compressed Parquet** (what you actually download). zstd compression makes this roughly 8 to 15 times smaller than the raw data it represents.
| Dataset | Price | Typical full day |
| - | - | - |
| Polymarket ticks | **\$40.00 / GiB** | \~13 GiB, about \$520 |
| Kalshi ticks | **\$80.00 / GiB** | \~3 to 5 GiB, about \$250 to \$400 (Aug 2026) |
| Kalshi book snapshots | **\$80.00 / GiB**, no minimum | small, tens of MiB; a single market is usually a few cents |
| Opinion ticks | **\$80.00 / GiB** | small, the \$5 minimum |
Every download has a **\$5.00 minimum charge** (except Kalshi book snapshots, which have no minimum so seeding a single-market replay costs cents on top of the deltas), and `market_id`-filtered downloads are priced on the pruned files only, so single-market pulls are usually at or near the minimum. That makes filtering the cheapest way to validate the data before a larger pull.
Your balance, credits, and charge history live in the dashboard at **[dashboard.predexon.com/data-credits](https://dashboard.predexon.com/data-credits)**.
### Credits and top-ups
* **\$50 free to start**, added when you put a payment method on file (one welcome credit per organization).
* **Top up any amount** from \$5 to \$10,000. Spend **\$500 or more in a single top-up and get 10% extra** credits.
* **Set a daily spend cap** on the dashboard to bound how much a single day's downloads can spend.
### A quote is required for large charges
Downloads that would cost **more than \$100** must include a `quote_id`, so you always see the price before a large charge lands. Smaller downloads charge directly. To confirm a large download, call `/quote` first, then pass the returned `quote_id` to the download (see below).
## Filter to a single market
By default a request returns the whole venue-day (all markets). Pass **`market_id`** to prune the download to just the file(s) covering one market. Because the Parquet is sorted by market, this drops the price from a full day to near the **\$5 minimum**.
`market_id` is venue-specific:
| Venue | Pass as `market_id` | Example |
| - | - | - |
| Polymarket | The market's **condition ID** (`0x...` hash) | `0xc5d31fcde97152616e441aaa9b5a2394bd6bff3c5f623b7260f90987f3fe96e4` |
| Kalshi (`kalshi`, `kalshi_snapshots`) | The market **ticker**, or a series root to match every ticker under it | `KXBTCD-26JUN1917-T63499.99`, `KXBTC15M` |
| Opinion | The **numeric market id** | `1274` |
The Polymarket example above is a short-duration "Bitcoin Up or Down" market that traded on **2026-03-05** (Polymarket runs these down to 5-minute windows, each a separate market):
```bash theme={null}
# Full Polymarket day: ~$514, ~12.8 GiB
curl -s -H "x-api-key: $PREDEXON_API_KEY" \
"https://api.predexon.com/v2/data/ticks/quote?venue=polymarket&date=2026-03-05"
# Just that one Bitcoin market: ~$5 (near the minimum)
curl -s -H "x-api-key: $PREDEXON_API_KEY" \
"https://api.predexon.com/v2/data/ticks/quote?venue=polymarket&date=2026-03-05&market_id=0xc5d31fcde97152616e441aaa9b5a2394bd6bff3c5f623b7260f90987f3fe96e4"
```
Three things to know:
* **Match the date to when the market traded.** A `market_id` only has data on the days that market was actually open. Pair it with the right date (the Bitcoin market above trades on `2026-03-05`). If the venue/date/market combination has no data, you get `404 not_found` and are not charged. Use `GET /v2/polymarket/search?q=...` to look up a market's condition ID and its `start_time` / `end_time`.
* **Filtering is file-level.** `market_id` returns the 1–2 Parquet files that contain your market, and those files still include neighboring markets. After downloading, filter to the exact market on the `market_id` column in DuckDB, Polars, or pandas.
* **One market per request.** For several markets, request each one, or pull the covering file(s) for the day and filter client-side. Polymarket creates many short-lived markets (a new "Up or Down" market every few minutes), each with its own `market_id`, so a "series" is not a single id.
## Get a quote first
`GET /v2/data/ticks/quote` prices a download without charging anything. It takes the same parameters as the download endpoint.
```bash theme={null}
curl -s -H "x-api-key: $PREDEXON_API_KEY" \
"https://api.predexon.com/v2/data/ticks/quote?venue=kalshi&date=2026-06-25"
```
```json theme={null}
{
"dataset": "ticks",
"venue": "kalshi",
"date": "2026-06-25",
"total_bytes": 1269674310,
"total_rows": 191500047,
"price_cents": 9460,
"currency": "usd",
"balance_cents": 5000,
"quote_id": "ba661ff9-9c05-45cd-bf9e-a45a6fa29f95",
"expires_at": "2026-06-25T21:03:18.475Z"
}
```
For downloads **under \$100**, a quote is optional. Call the download directly and pay the current price, or pass a `quote_id` to lock the quoted price until `expires_at` (about 30 minutes). For downloads **over \$100**, a `quote_id` is **required** (see [above](#a-quote-is-required-for-large-charges)):
```bash theme={null}
# 1. Quote the download, then note the price and quote_id
curl -s -H "x-api-key: $PREDEXON_API_KEY" \
"https://api.predexon.com/v2/data/ticks/quote?venue=polymarket&start_date=2026-06-20&end_date=2026-06-25"
# 2. Confirm by passing the quote_id to the download
curl -s -H "x-api-key: $PREDEXON_API_KEY" \
"https://api.predexon.com/v2/data/ticks?venue=polymarket&start_date=2026-06-20&end_date=2026-06-25"e_id=..."
```
## When you're charged
You are charged **when signed URLs are issued** (the moment `GET /v2/data/ticks` returns), not when you fetch the files. Two properties make this safe to script against:
* **Retries never double-charge.** Repeating the same request (same venue, dates, market filter) within **24 hours** returns fresh signed URLs against the original charge, for free. If your download script dies or the signed URLs expire, just re-run the same request.
* **Failures are refunded automatically.** If the data service fails after your balance was debited, the charge is refunded before you see the error.
Charge details ride on the download response headers:
| Header | Meaning |
| - | - |
| `x-predexon-cf-data-price-cents` | Price of this download |
| `x-predexon-cf-data-charged-cents` | Amount actually debited (`0` on a re-issue) |
| `x-predexon-cf-data-reused` | `true` when re-issued against a prior charge |
| `x-predexon-cf-data-balance-cents` | Balance after this request |
| `x-predexon-cf-data-authorization-id` | Charge reference (quote this in support requests) |
## Errors
| Status | Code | Meaning |
| - | - | - |
| `401` | `missing_api_key` / `invalid_api_key` | No or bad `x-api-key` |
| `402` | `quote_required` | Download exceeds \$100. Call `/quote` first and pass the `quote_id` to confirm the price |
| `402` | `insufficient_data_credits` | Balance below the price (`price_cents` and `balance_cents` are included) |
| `403` | `dataset_not_enabled` | Dataset not enabled for your organization. Datasets are enabled automatically at signup, so this is rare; [contact us](mailto:team@predexon.com) if you hit it |
| `403` | `spend_cap_exceeded` | Your daily spend cap would be exceeded |
| `409` | `quote_expired` / `quote_mismatch` / `quote_consumed` | The `quote_id` is stale, for different parameters, or already used. Re-quote or omit it |
| `404` | `not_final` | Requested day is not compacted yet (data is finalized T-1 at around 06:00 UTC) |
| `404` | `no_data` | No tick data exists for the requested window. Nothing is charged |
Nothing is ever charged on an error: quotes never debit, and validation (including `not_final` and `no_data`) runs before any charge.
## Datasets
Every top-of-book CLOB price change
Full orderbook-delta firehose
Complete orderbook deltas
# Tick Data
Source: https://docs.predexon.com/data-signals/ticks/kalshi
GET /v2/data/ticks
Download raw, tick-level Kalshi orderbook deltas and book snapshots as bulk columnar Parquet
Download the **raw orderbook-delta firehose** behind Kalshi. Every price-level change at full resolution, as sorted, `zstd`-compressed Parquet, plus the **book snapshots** you need to seed a replay. Call with `venue=kalshi` for the deltas and `venue=kalshi_snapshots` for the snapshots, then feed the signed URLs into your own DuckDB, Polars, or Spark. Seed a book from a snapshot and replay the deltas to reconstruct it at any instant (see [Replaying the book](#replaying-the-book)).
**Live, self-serve.** [Sign up](https://dashboard.predexon.com), add a payment method for **\$50 in free credits**, and download. Priced at **\$80.00/GiB** of compressed Parquet: a full day of deltas is typically **3 to 5 GiB, about \$250 to \$400** (August 2026), and a single 15-minute market filtered by `market_id` is near the \$5 minimum. Delta history is available from **2026-03-05** through yesterday (T-1); prices and sizes are **lossless sub-cent from 2026-04-20** (earlier days were captured in whole cents and carry `price_dollars` on a whole cent). See **[Data Credits & Pricing](/data-signals/ticks/data-credits)**.
**Finalized data only (T-1).** Ticks are compacted daily at **06:00 UTC**; today and future days return `404 not_final`. For ready-made, throttled (one per 0.5 s) book snapshots via the REST API use [Kalshi Orderbook History](/api-reference/kalshi/orderbooks-subcent).
| Constraint | Value |
| - | - |
| Date range | Up to **31 days** per request |
| Signed URL TTL | About **1 hour** (`expires_at`) |
| Day selection | exactly one of `date`, `start_date`+`end_date`, or `start_time`+`end_time` |
Timestamps are epoch **milliseconds, UTC**. To pull a single market, pass the **Kalshi ticker** as `market_id` to prune a day down to 1 or 2 files (Parquet is sorted by `ticker`). A series root such as `KXBTC15M` matches every `KXBTC15M-*` ticker. See [filtering to one market](/data-signals/ticks/data-credits#filter-to-a-single-market).
## Schema: orderbook deltas (`venue=kalshi`)
Orderbook deltas (signed change in resting size at a price level), sorted by `(ticker, timestamp, sequence)`.
| Column | Type | Notes |
| - | - | - |
| `ticker` | string | Kalshi market ticker |
| `timestamp` | int64 | Event time (epoch ms) |
| `price` | int32 | Price level in whole cents, rounded (0 to 100). Convenience column |
| `delta` | double | Signed change in resting quantity |
| `price_dollars` | string | Authoritative price in dollars, sub-cent (for example `"0.1690"`) |
| `delta_fp` | string | Authoritative signed change, full precision |
| `side` | string | `yes` / `no` |
| `sequence` | int64 | Exchange sequence number of the frame on the capture connection that delivered it |
Kalshi quotes sub-cent, down to a tenth of a cent. `price_dollars` and `delta_fp` carry the exact values; the integer `price` and `delta` columns are rounded convenience copies. For sub-cent-accurate work, read `price_dollars`. The `no` side is quoted in NO prices (a NO bid at `0.9600` is the same order as a YES ask at `0.0400`).
## Schema: book snapshots (`venue=kalshi_snapshots`)
Full-book snapshots, one row per resting level, in the **same `(side, price_dollars)` terms as the deltas**. Kalshi sends a snapshot when a market is subscribed (every market open, every reconnect) and our capture re-requests one for every market about once an hour, so a busy market has a snapshot at its open and re-anchors through the day. Rows come from our primary capture stream (`source` = `B`); other capture streams may be added later as additional `source` values. Sorted by `(ticker, timestamp, sequence, side, price_dollars)`.
| Column | Type | Notes |
| - | - | - |
| `ticker` | string | Kalshi market ticker |
| `timestamp` | int64 | Event-time anchor (epoch ms): the book is the state **after** every delta with `timestamp <= snapshot.timestamp` and **before** every delta after it |
| `received_at` | int64 | When our capture received the frame (epoch ms). Informational |
| `sequence` | int64 | Frame sequence number on the capture connection; orders rows within one `source` only |
| `source` | string | Capture process that saw the frame (`B` today; more may be added). Frames from different sources are different events, and all are kept |
| `snapshot_id` | string | Opaque id shared by all rows of one snapshot (unique together with `ticker`) |
| `side` | string | `yes` / `no`. **`NULL` means an empty book** (one row, no levels) |
| `price` | int32 | Whole cents, rounded. Convenience column |
| `price_dollars` | string | Authoritative price in dollars, sub-cent |
| `quantity` | double | Resting size. Convenience column |
| `quantity_fp` | string | Authoritative resting size, full precision |
Snapshots are available from **2026-09-18** through yesterday. Earlier dates are not available yet; deltas alone go back to 2026-03-05.
## Replaying the book
1. Pick the latest snapshot for the ticker with `timestamp <= t` and load all rows sharing its `snapshot_id` into a map keyed by `(side, price_dollars)`. A `NULL` `side` row is an empty book.
2. Apply every delta with `snapshot.timestamp < timestamp <= t`, in `(timestamp, sequence)` order: `book[(side, price_dollars)] += delta_fp`, dropping levels that reach zero.
3. Re-anchor whenever you pass another snapshot: if a delta-only chain drifts (a level goes negative, or a snapshot disagrees with your replayed book), the capture missed frames in between; the next snapshot is authoritative and restarts the chain.
```sql theme={null}
-- DuckDB: book for one market at instant :t
WITH snap AS (
SELECT * FROM read_parquet('snapshots/*.parquet')
WHERE ticker = :ticker AND timestamp <= :t
AND snapshot_id = (SELECT snapshot_id FROM read_parquet('snapshots/*.parquet')
WHERE ticker = :ticker AND timestamp <= :t
ORDER BY timestamp DESC, sequence DESC LIMIT 1)
), levels AS (
SELECT side, price_dollars, CAST(quantity_fp AS DOUBLE) AS qty FROM snap WHERE side IS NOT NULL
UNION ALL
SELECT side, price_dollars, CAST(delta_fp AS DOUBLE)
FROM read_parquet('deltas/*.parquet')
WHERE ticker = :ticker AND timestamp > (SELECT max(timestamp) FROM snap) AND timestamp <= :t
)
SELECT side, price_dollars, SUM(qty) AS quantity FROM levels
GROUP BY 1, 2 HAVING SUM(qty) > 0 ORDER BY side, price_dollars DESC;
```
**Capture gaps.** The delta stream is the union of several redundant WebSocket captures, but a market can still be blind for seconds to minutes when every capture was disconnected or backlogged (for example 2026-08-18, when 29 of 97 `KXBTC15M` markets had silences over 20 s). Deltas alone cannot bridge such a hole; the next snapshot after it does. A replay that only ever seeds once and never re-anchors will drift after a gap.
Get a price before downloading with `GET /v2/data/ticks/quote` using the same parameters. See [Data Credits & Pricing](/data-signals/ticks/data-credits). Other venues: [Polymarket Ticks](/data-signals/ticks/polymarket), [Opinion Ticks](/data-signals/ticks/opinion).
# Tick Data
Source: https://docs.predexon.com/data-signals/ticks/opinion
GET /v2/data/ticks
Download raw, tick-level Opinion orderbook deltas as bulk columnar Parquet
Download the **raw orderbook-delta firehose** behind Opinion. Every price-level change at full resolution, as sorted, `zstd`-compressed Parquet. Call with `venue=opinion`, then feed the signed URLs into your own DuckDB, Polars, or Spark. Replay the deltas to reconstruct the book at any instant.
**Live, self-serve.** [Sign up](https://dashboard.predexon.com), add a payment method for **\$50 in free credits**, and download. Priced at **\$80.00/GiB** of compressed Parquet (a full day is typically the **\$5 minimum**), charged when signed URLs are issued. History is available from **2026-03-10** through yesterday (T-1). See **[Data Credits & Pricing](/data-signals/ticks/data-credits)**.
**Finalized data only (T-1).** Ticks are compacted daily at **06:00 UTC**; today and future days return `404 not_final`. For ready-made snapshots use [Opinion Orderbook History](/api-reference/opinion/orderbooks).
| Constraint | Value |
| - | - |
| Date range | Up to **31 days** per request |
| Signed URL TTL | About **1 hour** (`expires_at`) |
| Day selection | exactly one of `date`, `start_date`+`end_date`, or `start_time`+`end_time` |
Timestamps are epoch **milliseconds, UTC**. To pull a single market, pass its **numeric Opinion market id** (the `market_id` column) as `market_id` to prune a day down to 1 or 2 files (Parquet is sorted by `market_id`). See [filtering to one market](/data-signals/ticks/data-credits#filter-to-a-single-market).
## Schema
Orderbook deltas, sorted by `(market_id, token_id, ts)`.
| Column | Type | Notes |
| - | - | - |
| `market_id` | int64 | Opinion market identifier |
| `token_id` | string | Outcome token ID |
| `side` | string | Order side (`buy` / `sell`) |
| `price` | string | Price as a decimal string, 0 to 1 |
| `size` | string | Size at the level |
| `ts` | int64 | Event time (epoch ms) |
Get a price before downloading with `GET /v2/data/ticks/quote` using the same parameters. See [Data Credits & Pricing](/data-signals/ticks/data-credits). Other venues: [Kalshi Ticks](/data-signals/ticks/kalshi), [Polymarket Ticks](/data-signals/ticks/polymarket).
# Tick Data
Source: https://docs.predexon.com/data-signals/ticks/polymarket
GET /v2/data/ticks
Download raw, tick-level Polymarket CLOB price changes as bulk columnar Parquet
Download the **raw price-change firehose** behind Polymarket. Every top-of-book update at full resolution, as sorted, `zstd`-compressed Parquet. Call with `venue=polymarket`, then feed the signed URLs into your own DuckDB, Polars, or Spark.
**Live, self-serve.** [Sign up](https://dashboard.predexon.com), add a payment method for **\$50 in free credits**, and download. Priced at **\$40.00/GiB** of compressed Parquet (a full day is typically about **\$520**), charged when signed URLs are issued. History is available from **2026-03-02** through yesterday (T-1). See **[Data Credits & Pricing](/data-signals/ticks/data-credits)**.
**Finalized data only (T-1).** Ticks are compacted daily at **06:00 UTC**; today and future days return `404 not_final`. For real-time updates use the [WebSocket Orderbook channel](/websocket/orderbook).
| Constraint | Value |
| - | - |
| Date range | Up to **31 days** per request |
| Signed URL TTL | About **1 hour** (`expires_at`) |
| Day selection | exactly one of `date`, `start_date`+`end_date`, or `start_time`+`end_time` |
Timestamps are epoch **milliseconds, UTC**. To pull a single market, pass its **Polymarket condition ID** (the `0x...` hash, the `market_id` column) as `market_id` to prune a day down to 1 or 2 files (Parquet is sorted by `market_id`). See [filtering to one market](/data-signals/ticks/data-credits#filter-to-a-single-market).
## Schema
Top-of-book price changes, sorted by `(market_id, asset_id, ts_ms)`.
| Column | Type | Notes |
| - | - | - |
| `market_id` | string | Condition / market identifier |
| `asset_id` | string | Outcome token ID |
| `ts_ms` | int64 | Event time (epoch ms) |
| `received_at` | int64 | Ingest time (epoch ms) |
| `side` | string | `BUY` / `SELL` |
| `price` | string | Price as a decimal string, 0 to 1 |
| `size` | string | Size at the level |
| `best_bid` | string | Best bid at the time |
| `best_ask` | string | Best ask at the time |
Get a price before downloading with `GET /v2/data/ticks/quote` using the same parameters. See [Data Credits & Pricing](/data-signals/ticks/data-credits). Other venues: [Kalshi Ticks](/data-signals/ticks/kalshi), [Opinion Ticks](/data-signals/ticks/opinion).
# Welcome to Predexon
Source: https://docs.predexon.com/index
The data layer for prediction markets — the fastest WebSocket, tick-level orderbook history, and blockchain-indexed analytics behind a single API key.
Predexon is the data layer for prediction markets — built for quants, builders, researchers, and AI agents. One API key gives you everything below.
## How it fits together
```mermaid theme={null}
flowchart TB
KEY([🔑 One Predexon API key])
KEY --> DATA[Data API
api.predexon.com]
KEY --> WS[WebSocket
wss.predexon.com]
KEY --> TICKS[Tick History
bulk Parquet downloads]
KEY --> MCP[MCP Server
for AI agents]
DATA --> D1[Markets · Prices · Candles
Orderbook History · Trades]
DATA --> D2[Wallets · P&L · Smart Money
Leaderboards · Cohorts]
WS --> W1[Live trades · Pending trades
Orderbook · Activity]
WS --> W2[Lifecycle · Oracle
Crypto price streams]
TICKS --> T1[Every orderbook tick
at every granularity]
TICKS --> T2[Polymarket · Kalshi · Opinion
finalized daily]
MCP --> M1[Tools across
every surface above]
classDef key fill:#7C3AED,stroke:#7C3AED,color:#fff
classDef surface fill:#1f1f1f,stroke:#7C3AED,color:#fff
classDef detail fill:#f5f3ff,stroke:#7C3AED,color:#1f1f1f
class KEY key
class DATA,TICKS,WS,MCP surface
class D1,D2,T1,T2,W1,W2,M1 detail
```
**Read the diagram top-down.** Pick the surface you need. Mix freely — most real integrations use REST + WebSocket together, with tick downloads for backtesting and MCP for agent-driven flows.
***
## The three data products
The fastest feed in prediction markets. Mempool access streams pending transactions before they confirm — up to **5 seconds faster than Polymarket's RTDS**.
Every placement, update, cancel, and fill — the raw tick stream at every granularity, downloadable as Parquet. **New.**
Wallet P\&L, smart-money flow, leaderboards, and positions — indexed straight from the chain, accurate to the block.
***
## Pick your path
You want candlesticks, orderbook history, wallet P\&L, smart-money signals, and live WebSocket feeds to build models, dashboards, or backtests.
**Start with** [Quant Quickstart →](/start-here/quant-guide)
You're building a product on prediction-market data — dashboards, trackers, discovery, analytics UX. You want one API and one set of keys across every venue.
**Start with** [Builder Architecture Guide →](/start-here/builder-guide)
You trade on the venues directly and want the data edge: the fastest feeds, smart-money signals, and tick history to backtest against.
**Start with** [The data-driven trading loop →](/start-here/active-trader-guide)
You're shipping an agent that reads prediction-market state. Drop the MCP server into Claude, Cursor, Codex, or your own stack.
**Start with** [Agent Cookbook →](/start-here/agent-cookbook)
**First time here?** Run [Get Started in 5 minutes](/start-here/get-started) — sign up, get a key, make your first call. Then come back and pick your path.
***
## Coverage at a glance
| Venue | Market data | Real-time | Tick history |
| - | :-: | :-: | :-: |
| **Polymarket** | ✓ | ✓ | ✓ |
| **Kalshi** | ✓ | ✗ | ✓ |
| **Opinion** | ✓ | ✗ | ✓ |
| **Limitless** | ✓ | ✗ | ✗ |
| **Predict.fun** | ✓ | ✗ | ✗ |
| **Binance** (crypto reference) | ✓ | ✗ | ✗ |
| **Chainlink Data Streams** | ✗ | ✓ | ✗ |
***
## Plans
**Most market data is free and unlimited** on every plan. Tick-history downloads are billed separately via [data credits](/data-signals/ticks/data-credits) — \$50 free when you add a payment method.
| | Free | Dev | Pro | Enterprise |
| - | - | - | - | - |
| **Price** | \$0/mo | \$49/mo | \$249/mo | \$499+/mo |
| **Rate limit** | 1 req/s | 20 req/s | 100 req/s | Custom |
| **Monthly requests** | 1,000 | 1M | 5M | Unlimited |
| **WebSocket** | ✗ | 10 subs × 10 items | 100 subs × 500 items, wildcards | Custom |
| **Smart Money** | ✗ | ✓ | ✓ | ✓ |
[Authentication & rate limits →](/authentication) · [Full pricing →](https://predexon.com/pricing)
***
## Need help?
Active community, direct line to the team.
Enterprise, partnerships, anything else: `team@predexon.com`.
# MCP Server
Source: https://docs.predexon.com/mcp-server
Use Predexon API with AI assistants via Model Context Protocol
Drop one config block into Claude Desktop, Cursor, or Codex and your assistant can reason about prediction markets directly — no boilerplate HTTP calls, no SDK plumbing. Try this in Claude after install:
> *"What's the smart-money flow on the top 5 Polymarket markets right now, and which wallets are driving it?"*
Claude composes the tools below — `list_polymarket_markets`, `get_polymarket_smart_money`, `get_polymarket_wallet_pnl` — to answer in one round-trip. Same key gates the same data you'd get from REST; same plan limits apply.
**Building an agent on top of this?** [Agent Cookbook](/start-here/agent-cookbook) walks five concrete patterns — research assistant, position monitor, smart-money alerter, copy-trade watcher, wallet analyst — with system prompts you can paste straight in.
## What is MCP?
[Model Context Protocol (MCP)](https://modelcontextprotocol.io) is the open standard for letting AI assistants call external tools. Predexon ships an MCP server that exposes every relevant Data API endpoint as a typed tool — so models that support MCP (Claude, Cursor, Codex, Cline, VS Code Copilot) can pull live market data, wallet analytics, and smart-money signals without you writing a single fetch call. Example questions that work out of the box:
* "What are the top prediction markets by volume?"
* "Show me the price history for this market"
* "Which profitable wallets are buying this market right now?"
* "What positions does this wallet hold?"
* "What Limitless markets are open right now?"
## Installation
Install the Predexon MCP server from npm:
```bash theme={null}
npm install -g predexon-mcp
```
Or run directly with npx (no installation required):
```bash theme={null}
npx predexon-mcp
```
## Configuration
Edit your Claude Desktop configuration file:
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
```json theme={null}
{
"mcpServers": {
"predexon": {
"command": "npx",
"args": ["-y", "predexon-mcp"],
"env": {
"PREDEXON_API_KEY": "your-key"
}
}
}
}
```
Restart Claude Desktop after saving.
Add the MCP server via CLI:
```bash theme={null}
claude mcp add predexon npx predexon-mcp
```
Or manually edit `~/.claude.json`:
```json theme={null}
{
"mcpServers": {
"predexon": {
"command": "npx",
"args": ["-y", "predexon-mcp"],
"env": {
"PREDEXON_API_KEY": "your-key"
}
}
}
}
```
Cursor supports both global and project-specific MCP configuration.
**Global** (all projects): `~/.cursor/mcp.json`
**Project-specific**: `.cursor/mcp.json` in your project root
```json theme={null}
{
"mcpServers": {
"predexon": {
"command": "npx",
"args": ["-y", "predexon-mcp"],
"env": {
"PREDEXON_API_KEY": "your-key"
}
}
}
}
```
Verify the connection in **Settings > Cursor Settings > MCP**. A green dot indicates the server is connected.
[Cursor MCP Docs](https://cursor.com/docs/context/mcp)
VS Code supports MCP servers through GitHub Copilot.
**Global**: Open your user `mcp.json` via Command Palette
**Project-specific**: `.vscode/mcp.json` in your workspace
```json theme={null}
{
"servers": {
"predexon": {
"command": "npx",
"args": ["-y", "predexon-mcp"],
"env": {
"PREDEXON_API_KEY": "your-key"
}
}
}
}
```
Or add via CLI:
```bash theme={null}
code --add-mcp '{"name":"predexon","command":"npx","args":["-y","predexon-mcp"],"env":{"PREDEXON_API_KEY":"your-key"}}'
```
[VS Code MCP Docs](https://code.visualstudio.com/docs/copilot/customization/mcp-servers)
In Cline, click the **MCP Servers** icon in the sidebar, then click **Edit MCP Settings** to open the configuration file. Add:
```json theme={null}
{
"mcpServers": {
"predexon": {
"command": "npx",
"args": ["-y", "predexon-mcp"],
"env": {
"PREDEXON_API_KEY": "your-key"
}
}
}
}
```
[Cline MCP Docs](https://docs.cline.bot/mcp/configuring-mcp-servers)
OpenAI Codex CLI uses TOML configuration. Edit `~/.codex/config.toml`:
```toml theme={null}
[mcp_servers.predexon]
command = "npx"
args = ["-y", "predexon-mcp"]
[mcp_servers.predexon.env]
PREDEXON_API_KEY = "your-key"
```
Or add via CLI:
```bash theme={null}
codex mcp add predexon -- npx -y predexon-mcp
```
[Codex MCP Docs](https://developers.openai.com/codex/mcp/)
## Available Tools
The MCP server exposes tools covering market data, smart-wallet analytics, and trading insights across six platforms.
### Polymarket - Market Data
| Tool | Description |
| - | - |
| `list_polymarket_markets` | List markets with filtering and sorting |
| `get_polymarket_events` | List events (top-level groupings of markets) |
| `get_polymarket_crypto_updown` | List crypto up/down prediction markets (BTC, ETH, SOL, XRP, HYPE, DOGE, BNB) |
| `get_polymarket_price` | Get current or historical price for a token |
| `get_polymarket_candlesticks` | Get OHLCV candlestick data |
| `get_polymarket_volume` | Get cumulative volume time series |
| `get_polymarket_volume_chart` | Get per-period volume with YES/NO side split |
| `get_polymarket_open_interest` | Get historical open interest |
| `get_polymarket_orderbooks` | Get historical orderbook snapshots |
### Polymarket - Trading
| Tool | Description |
| - | - |
| `get_polymarket_trades` | Query historical trade data |
| `get_polymarket_activity` | Get on-chain activity (split/merge/redeem events) |
| `get_polymarket_positions` | Get positions for a wallet |
| `get_polymarket_all_positions` | Fetch positions across all wallets (analytics/streaming) |
| `get_polymarket_pnl` | Get realized P\&L history for a wallet |
### Polymarket - Smart Wallet Analytics
Smart wallet tools require a **Dev plan** API key or higher.
| Tool | Description |
| - | - |
| `get_polymarket_wallet_profile` | Get complete wallet profile with metrics and style classifications |
| `get_polymarket_wallet_profiles` | Batch wallet profiles - fetch up to 20 profiles in a single request |
| `get_polymarket_leaderboard` | Global leaderboard of smart wallets |
| `get_polymarket_market_leaderboard` | Wallet rankings for a specific market |
| `get_polymarket_top_holders` | Top token holders for a market by position size |
| `get_polymarket_wallet_markets` | Per-market performance breakdown for a wallet |
| `get_polymarket_wallet_volume_chart` | Per-period volume with BUY/SELL breakdown for a wallet |
| `get_polymarket_wallets_filter` | Filter wallets by market trades with AND/OR logic |
| `get_polymarket_smart_activity` | Discover markets where high-performing wallets are active |
| `get_polymarket_smart_money` | Net buyers vs sellers among smart wallets for a market |
| `get_polymarket_similar_wallets` | Find wallets with similar portfolios (copy-trade detection) |
| `get_polymarket_cohort_stats` | Compare performance across trading style cohorts |
### Kalshi
| Tool | Description |
| - | - |
| `list_kalshi_markets` | List markets with filtering and sorting |
| `get_kalshi_trades` | Fetch historical trade data |
| `get_kalshi_orderbooks` | Fetch historical orderbook snapshots |
### Binance
Binance tools require a **Dev plan** API key or higher. Supported symbols: BTCUSDT, ETHUSDT, SOLUSDT, XRPUSDT.
| Tool | Description |
| - | - |
| `get_binance_ticks` | Get raw book ticker data at microsecond granularity |
| `get_binance_candles` | Get OHLCV candlestick data from book ticker mid-prices |
### Limitless
| Tool | Description |
| - | - |
| `list_limitless_markets` | List markets with filtering and sorting |
| `get_limitless_orderbooks` | Fetch historical orderbook snapshots |
### Opinion
| Tool | Description |
| - | - |
| `list_opinion_markets` | List markets with filtering and sorting |
| `get_opinion_orderbooks` | Fetch historical orderbook snapshots |
### Predict.Fun
| Tool | Description |
| - | - |
| `list_predictfun_markets` | List markets with filtering and sorting |
| `get_predictfun_orderbooks` | Fetch historical orderbook snapshots |
## Example Usage
Once configured, you can ask your AI assistant questions naturally:
```text Query Markets theme={null}
"What are the top 5 Polymarket markets by volume right now?"
```
```text Price History theme={null}
"Show me the price history for the Trump 2024 election market over the last week"
```
```text Cross-Platform theme={null}
"Find the Kalshi equivalent of this Polymarket market: will-bitcoin-hit-100k-in-2024"
```
```text Wallet Analysis theme={null}
"What positions does wallet 0x1234... hold on Polymarket?"
```
```text Limitless theme={null}
"Show me the highest volume Limitless markets"
```
```text Predict.Fun theme={null}
"What Predict.Fun sports markets are open?"
```
## Authentication
Set your API key via the `PREDEXON_API_KEY` environment variable in your MCP configuration (see the config examples above). The server reads this automatically on startup.
As a fallback, you can also pass `api_key` as a parameter on individual tool calls, but the env var approach is recommended - it keeps your key out of conversation text and works across all tools automatically.
Get your free API key at [dashboard.predexon.com](https://dashboard.predexon.com)
## Source Code
The MCP server is open source:
* **npm**: [npmjs.com/package/predexon-mcp](https://www.npmjs.com/package/predexon-mcp)
## Next Steps
Explore every endpoint with interactive playground
Sign up for free access
# Quickstart
Source: https://docs.predexon.com/quickstart
Get started with the Predexon Data API in under 5 minutes
**Which quickstart should I use?** New here? Start with [Get Started in 5 minutes](/start-here/get-started) for the unified onboarding. This page is the Data-API-focused quickstart.
## 1. Get Your API Key
Sign up for free at [dashboard.predexon.com](https://dashboard.predexon.com) - no credit card required.
## 2. Make Your First Request
All API requests go to `https://api.predexon.com` with your API key in the `x-api-key` header.
```bash cURL theme={null}
export PREDEXON_API_KEY="your_api_key"
```
```python Python theme={null}
import requests
BASE_URL = "https://api.predexon.com"
API_KEY = "your_api_key"
HEADERS = {"x-api-key": API_KEY}
```
```typescript TypeScript theme={null}
const BASE_URL = "https://api.predexon.com";
const HEADERS = { "x-api-key": "your_api_key" };
```
Get the highest-volume open markets on Polymarket:
```bash cURL theme={null}
curl -H "x-api-key: $PREDEXON_API_KEY" \
"https://api.predexon.com/v2/polymarket/markets?status=open&sort=volume&limit=5"
```
```python Python theme={null}
response = requests.get(
f"{BASE_URL}/v2/polymarket/markets",
headers=HEADERS,
params={"status": "open", "sort": "volume", "limit": 5}
)
for market in response.json()["markets"]:
print(f"{market['title']}: ${market['total_volume_usd']:,.0f} volume")
```
```typescript TypeScript theme={null}
const response = await fetch(
`${BASE_URL}/v2/polymarket/markets?status=open&sort=volume&limit=5`,
{ headers: HEADERS }
);
const { markets } = await response.json();
markets.forEach(m =>
console.log(`${m.title}: $${m.total_volume_usd.toLocaleString()} volume`)
);
```
```json theme={null}
{
"markets": [
{
"condition_id": "0x1234...",
"market_slug": "will-donald-trump-win-the-2024-us-presidential-election",
"title": "Will Trump win the 2024 election?",
"status": "open",
"outcomes": [
{"label": "Yes", "token_id": "123...", "price": 0.62},
{"label": "No", "token_id": "456...", "price": 0.38}
],
"total_volume_usd": 1500000000,
"liquidity_usd": 25000000
}
],
"pagination": { "limit": 5, "offset": 0, "total": 1234, "has_more": true }
}
```
***
## 3. Try More Endpoints
Fetch OHLCV candlestick data for charting:
```python theme={null}
response = requests.get(
f"{BASE_URL}/v2/polymarket/candlesticks/{condition_id}",
headers=HEADERS,
params={"interval": 60, "start_time": 1704067200, "end_time": 1704153600}
)
```
Get all open positions for a wallet:
```python theme={null}
response = requests.get(
f"{BASE_URL}/v2/polymarket/wallet/positions/{wallet_address}",
headers=HEADERS,
params={"sort_by": "value", "limit": 50}
)
```
See how profitable wallets are positioned in a market:
```python theme={null}
response = requests.get(
f"{BASE_URL}/v2/polymarket/market/{condition_id}/smart-money",
headers=HEADERS,
)
```
Stream live trades in real time:
```javascript theme={null}
const ws = new WebSocket('wss://wss.predexon.com/v1/YOUR_API_KEY');
ws.onopen = () => ws.send(JSON.stringify({
action: 'subscribe',
platform: 'polymarket',
version: 1,
type: 'orders',
filters: { users: ['0x1234...'] }
}));
ws.onmessage = (e) => console.log(JSON.parse(e.data));
```
***
## Plans & Rate Limits
**Most data is free.** Core market data, trades, orderbooks, and pricing endpoints are free and unlimited on all plans.
| Tier | Rate Limit | Monthly Requests | Price |
| - | - | - | - |
| Free | 1 req/sec | 1,000 | \$0/month |
| Dev | 20 req/sec | 1,000,000 | \$49/month |
| Pro | 100 req/sec | 5,000,000 | \$249/month |
| Enterprise | Custom | Unlimited | \$499+/month |
Free tier excludes smart wallet endpoints and WebSocket. See [predexon.com/pricing](https://predexon.com/pricing) for details.
## Error Handling
| Code | Description |
| - | - |
| `200` | Success |
| `400` | Bad request - check your parameters |
| `403` | Invalid or missing API key |
| `429` | Rate limit exceeded - implement exponential backoff |
| `500` | Internal server error |
```json theme={null}
{
"error": "Invalid filter combination",
"message": "Only one of market_slug, token_id, or condition_id can be provided"
}
```
## Next Steps
Explore every endpoint with interactive playground.
Stream real-time events.
Download the raw tick stream as Parquet.
# Rate Limits & Plans
Source: https://docs.predexon.com/rate-limits
Understand Predexon's pricing tiers, rate limits, and feature access
## Plans
| | Free | Dev | Pro | Enterprise |
| - | - | - | - | - |
| **Price** | \$0/month | \$49/month | \$249/month | \$499+/month |
| **Rate Limit** | 1 req/sec | 20 req/sec | 100 req/sec | Custom |
| **Monthly Requests** | 1,000 | 1,000,000 | 5,000,000 | Unlimited |
| **Overage** | - | \$5 / 100K | \$2.50 / 100K | - |
| **WebSocket** | No access | 10 subs × 10 items | 100 subs × 500 items | Custom |
| **Wildcards** | - | - | 2 per channel | Custom |
| **Priority Routing** | No | No | No | Yes (dedicated node) |
Sign up and get your API key at [dashboard.predexon.com](https://dashboard.predexon.com).
***
## Free Endpoints
**These endpoints are free and unlimited on all plans.** They do not count toward your monthly request quota.
**Universally free:**
* **All list-market endpoints** across every venue (`/v2/{venue}/markets`)
* **All orderbook history endpoints** across every venue (`/v2/{venue}/orderbooks`)
| Endpoint | Description |
| - | - |
| `GET /v2/polymarket/markets` | List markets |
| `GET /v2/polymarket/markets/keyset` | List markets (keyset pagination) |
| `GET /v2/polymarket/events` | List events |
| `GET /v2/polymarket/events/keyset` | List events (keyset pagination) |
| `GET /v2/polymarket/crypto-updown` | Crypto up/down markets |
| `GET /v2/polymarket/market-price/{token_id}` | Market price |
| `GET /v2/polymarket/candlesticks/{condition_id}` | Candlesticks (condition-level) |
| `GET /v2/polymarket/candlesticks/token/{token_id}` | Candlesticks (per-token) |
| `GET /v2/polymarket/volume-chart/{condition_id}` | Volume chart |
| `GET /v2/polymarket/orderbooks` | Orderbook history |
| `GET /v2/polymarket/trades` | Trades history |
| `GET /v2/polymarket/activity` | Wallet activity |
| `GET /v2/polymarket/markets/{token_id}/volume` | Volume time series |
| `GET /v2/polymarket/markets/{condition_id}/open_interest` | Open interest |
| `GET /v2/polymarket/positions` | All positions |
| `GET /v2/polymarket/wallet/pnl/{wallet}` | Wallet P\&L (summary + time series) |
| `GET /v2/polymarket/wallet/{address}/cluster` | Connected wallets cluster |
| `GET /v2/polymarket/uma/markets` | UMA markets list |
| `GET /v2/polymarket/uma/market/{condition_id}` | UMA market detail |
| `GET /v2/polymarket/builders/leaderboard` | Builder leaderboard |
| `GET /v2/polymarket/builders/{builder_code}` | Builder stats |
| `GET /v2/polymarket/builders/{builder_code}/markets` | Builder top markets |
| `GET /v2/polymarket/builders/{builder_code}/timeseries` | Builder timeseries |
| Endpoint | Description |
| - | - |
| `GET /v2/kalshi/markets` | List markets |
| `GET /v2/kalshi/trades` | Trades history |
| `GET /v2/kalshi/orderbooks` | Orderbook history |
| Endpoint | Description |
| - | - |
| `GET /v2/limitless/markets` | List markets |
| `GET /v2/limitless/orderbooks` | Orderbook history |
| Endpoint | Description |
| - | - |
| `GET /v2/opinion/markets` | List markets |
| `GET /v2/opinion/orderbooks` | Orderbook history |
| Endpoint | Description |
| - | - |
| `GET /v2/predictfun/markets` | List markets |
| `GET /v2/predictfun/orderbooks` | Orderbook history |
***
## Gated Features
These features require a **Dev plan or higher**. Free tier users receive a `403` error.
| Feature | Endpoints |
| - | - |
| **Smart Money & Analytics** | Leaderboards, cohort stats, wallet profiles, smart activity |
| **Binance Data** | `/v2/binance/*` |
| **WebSocket** | `wss://wss.predexon.com/v1/{API_KEY}` |
***
## WebSocket Limits
| Metric | Dev | Pro | Enterprise |
| - | - | - | - |
| Subscriptions per connection | 10 | 100 | Custom |
| Items per subscription | 10 | 500 | Custom |
| Total items | 100 | 50,000 | Custom |
| Wildcard subscriptions | Not allowed | 2 per channel | Custom |
* Server sends a ping every **30 seconds**; pong required within 60 seconds
* Idle connections (zero subscriptions) closed after **2 minutes**
* Buffer limits: events dropped at **1 MB**, connection terminated at **4 MB**
***
## Rate Limit Errors
Exceeding your rate limit returns HTTP `429 Too Many Requests`. Implement exponential backoff in your application.
```bash theme={null}
curl https://api.predexon.com/health
# {"status": "healthy"}
```
# Active trader & fund guide
Source: https://docs.predexon.com/start-here/active-trader-guide
The data-driven trading loop — find edges with the fastest feeds, smart-money signals, and tick-level history, then act on them wherever you execute.
You trade on the venues directly. Predexon gives you the data edge: faster feeds than the venues themselves publish, signals nobody else computes, and the tick-level history to prove a strategy before you risk capital.
The loop:
```
Discover → Signal → Backtest → Size → Monitor
(Data API) (Data API + WSS) (Ticks) (you) (WebSocket + Data API)
```
Execution stays wherever you already trade — the venue's own API or UI. Everything before and after the order is what this guide covers.
***
## 1. Discover
Find the markets you care about. Common entry points:
`GET /v2/polymarket/markets?sort=volume`
Trending markets, ranked.
`GET /v2/polymarket/search?q=...`
Cross-surface search over markets, events, and tags.
`GET /v2/polymarket/categories`
Browse by category, tag, and series.
***
## 2. Signal
Find a reason to trade. The signals Predexon gives you out of the box:
| Signal | Endpoint | What it tells you |
| - | - | - |
| **Smart-money flow** | [`/v2/polymarket/market/{condition_id}/smart-money`](/api-reference/smart-money/smart-money-market) | Are profitable wallets net buyers or sellers right now? |
| **Smart activity** | [`/v2/polymarket/smart-activity`](/api-reference/smart-money/smart-activity) | Which markets are smart wallets piling into? |
| **Top holders** | [`/v2/polymarket/markets/{condition_id}/top-holders`](/api-reference/analytics/top-holders) | Position concentration on each side. |
| **Wallet P\&L** | [`/v2/polymarket/wallet/pnl/{wallet}`](/api-reference/wallet/pnl) | Track-record check before following a wallet. |
| **Pending trades** | [WebSocket pending-trades](/websocket/pending-trades) | Up to 5s edge on confirmed fills, from the Polygon mempool. |
For a live edge, subscribe to the WebSocket and react in code — the pending-trades channel shows flow **before it lands on-chain**, up to 5 seconds ahead of Polymarket's RTDS.
***
## 3. Backtest
Before sizing anything, validate the edge against history:
* **Orderbook replay**: reconstruct the book at any timestamp from [tick history](/data-signals/ticks/data-credits) — what depth would your order actually have hit? See [Orderbook replay](/data-signals/backtesting/orderbook-replay).
* **Signal validation**: pull [candles](/api-reference/markets/candlesticks) and the [trade tape](/api-reference/trading/trades), then test entries against them — see [Signal backtesting](/data-signals/backtesting/signal-backtest).
* **Queue position & fill probability**: the raw tick stream captures every placement, update, and cancel — the only dataset that answers "would I have been filled?"
Tick downloads are priced per GiB with a free quote first — [how data credits work →](/data-signals/ticks/data-credits)
***
## 4. Size
Sizing decisions are yours. The data you'll usually want before pulling the trigger:
* **Liquidity check**: current [orderbook snapshot](/websocket/orderbook) — what depth is there at your target price?
* **Recent volume**: [volume chart](/api-reference/markets/volume-chart) — is this a thin market or a deep one?
* **Open interest**: [`/v2/polymarket/markets/{condition_id}/open_interest`](/api-reference/markets/open-interest) — total dollars at risk.
Then place the order on your venue of choice, through the venue's own API or UI.
***
## 5. Monitor
Once you have positions, you watch two things: **fills** and **mark-to-market**.
| What | Where |
| - | - |
| Live fills on your wallet | [WebSocket trades](/websocket/trades) with `filters.users: [""]` |
| Live splits / merges / redemptions | [WebSocket activity](/websocket/activity) |
| Live orderbook + best bid/ask | [WebSocket orderbook](/websocket/orderbook) |
| Current positions | [Wallet positions](/api-reference/wallet/positions) on the Data API |
| Realized + unrealized P\&L | [Wallet P\&L](/api-reference/wallet/pnl) on the Data API |
Your venue wallet address plugs straight into any Data API wallet endpoint — the P\&L you see is indexed from the chain, accurate to the block.
***
## Putting it together
Strategies people run on this loop:
* **Smart-money following** — smart-money endpoint or top-holders for entry signals, WebSocket trades to monitor the wallets you're shadowing.
* **Mempool-signal trading** — pending-trades WebSocket as a leading indicator; you see large fills up to 5 seconds before the rest of the market.
* **Market-making research** — tick history to model queue dynamics and spread capture, orderbook WebSocket for live depth.
* **Event-driven strategies** — lifecycle and oracle WebSocket channels to catch creations and resolutions the moment they happen.
***
## Next
Full map of every endpoint, organized by utility.
The most granular dataset in prediction markets — quote it, download it, replay it.
# AI agent developer
Source: https://docs.predexon.com/start-here/agent-cookbook
Drop the Predexon MCP server into Claude, Cursor, or Codex. Five recipes for agents that read prediction-market data.
You're building agents that need prediction-market context — research assistants, signal bots, position monitors, alert systems. The Predexon MCP server gives any MCP-aware model access to tools spanning every data surface, behind one drop-in package.
**You'll be ready to:**
* Install the MCP server in any major agent runtime
* Compose Predexon tools with your own
* Build the five most common agent patterns (research, monitor, alert, copy-trade, wallet analysis)
***
## 60-second install
Pick your runtime, paste the config, restart.
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
```json theme={null}
{
"mcpServers": {
"predexon": {
"command": "npx",
"args": ["-y", "predexon-mcp"],
"env": { "PREDEXON_API_KEY": "your-key" }
}
}
}
```
Restart Claude Desktop. Ask: *"List the top 5 Polymarket markets by volume."*
```bash theme={null}
claude mcp add predexon npx predexon-mcp
```
Edit `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per-project):
```json theme={null}
{
"mcpServers": {
"predexon": {
"command": "npx",
"args": ["-y", "predexon-mcp"],
"env": { "PREDEXON_API_KEY": "your-key" }
}
}
}
```
Edit `~/.codex/config.toml`:
```toml theme={null}
[mcp_servers.predexon]
command = "npx"
args = ["-y", "predexon-mcp"]
[mcp_servers.predexon.env]
PREDEXON_API_KEY = "your-key"
```
Full config matrix (VS Code, Cline, etc.) on the [MCP Server page](/mcp-server).
***
## The tools at a glance
| Category | Count | What it covers |
| - | - | - |
| Polymarket markets | 9 | Lists, events, prices, candles, volume, OI, orderbooks |
| Polymarket trading | 5 | Trades, activity, positions, P\&L |
| Polymarket smart-money | 12 | Profiles, leaderboards, top holders, smart-activity, cohort stats |
| Kalshi | 3 | Markets, trades, orderbooks |
| Binance | 2 | Crypto reference ticks + candles |
| Limitless / Opinion / Predict.Fun | 6 | Markets + orderbooks per venue |
Full tool list with descriptions on the [MCP Server page](/mcp-server).
***
## Recipe 1 — Market research assistant
**Use case**: "What's happening in election markets right now?" Agent surveys, summarizes, cites.
**System prompt sketch**:
```
You are a prediction-market research assistant. When asked about a topic:
1. Use `list_polymarket_markets` to find relevant markets (filter by status="open").
2. For markets with $1M+ volume, fetch `get_polymarket_candlesticks` for the last 7 days
to identify price moves.
3. Use `get_polymarket_smart_money` on any market where price moved >5% to check if
profitable wallets are buying or selling.
4. Summarize: which markets matter, what's the price action, what is smart money doing,
what events are upcoming. Always cite the market slug and source endpoint.
```
The agent will compose tools without further prompting. Add `list_kalshi_markets` if you want it to cite Kalshi prices side-by-side.
***
## Recipe 2 — Position monitor
**Use case**: Daily digest of how a wallet's portfolio moved overnight.
**System prompt sketch**:
```
Run daily for wallet 0x.... :
1. `get_polymarket_positions` to enumerate current positions.
2. For each position, `get_polymarket_price` to compute mark-to-market vs cost basis.
3. `get_polymarket_pnl` for the period since last run.
4. Output a digest: wins, losers, biggest mover, total P&L delta, anything that
resolved overnight.
```
Pair with a cron tool (most agent runtimes have one) for fully autonomous reporting.
***
## Recipe 3 — Smart-money alerter
**Use case**: Real-time alert when profitable wallets enter a market you care about.
This one streams instead of polling. Agent runtimes that support background MCP tool use (Claude Code, custom builds) can call:
```
Every 5 minutes:
1. `get_polymarket_smart_activity` to see which markets smart money entered.
2. Filter to markets matching my watchlist (keywords: "election", "fed", "btc").
3. For each new entry, fetch `get_polymarket_smart_money` to see net positioning.
4. If net buy >$50k in the last hour, alert me with market name, smart-money flow,
current price.
```
Cheaper agents can run this on a 15-min cadence with `get_polymarket_smart_activity` alone.
***
## Recipe 4 — Copy-trade watcher
**Use case**: Track a high-performing wallet's fills and report them the moment they land.
```
Watching wallet 0xSmartTrader on Polymarket:
1. `get_polymarket_wallet_profile` once at startup to verify they're still profitable.
2. Every minute: `get_polymarket_trades` filtered by user=0xSmartTrader since last check.
3. For each new trade: report market, side, size, and price, plus the wallet's
current position in that market.
4. If I ask, compute what a mirrored position scaled to my bankroll would look like.
```
Execution is up to you and your own venue integration — see [Copy-trade cookbook recipe](/start-here/cookbook/copy-trade-wallet) for the production signal pipeline.
***
## Recipe 5 — Wallet deep-dive analyst
**Use case**: Full due-diligence report on any wallet, on request.
```
On request for wallet 0xABC:
1. `get_polymarket_wallet_profile` — headline stats, win rate, volume.
2. `get_polymarket_wallet_pnl` — realized + unrealized P&L over time.
3. `get_polymarket_wallet_positions` — what they hold right now, by market.
4. Summarize: are they profitable, in what categories, over what period,
and what are they betting on today?
```
Batch multiple wallets with `get_polymarket_wallet_profiles` (up to 20 per call) when ranking a leaderboard.
***
## Tips for building good agents on Predexon
`get_polymarket_smart_money` and `get_polymarket_smart_activity` already filter to profitable wallets. Using them keeps your context window small and signal-to-noise high vs. paginating the raw trades endpoint.
`get_polymarket_wallet_profiles` (batch) accepts up to 20 wallets per call. Have the agent batch lookups when summarizing a leaderboard.
Markets that resolved 2+ weeks ago may not appear in default queries. Add `archived=true` if your agent needs to reason about historical resolutions.
The MCP server uses REST. For wildcard firehose streaming, connect to the WebSocket directly — it's not a fit for chat-loop agent runtimes.
***
## What you should read next
Complete tool list with descriptions, all runtime configurations.
Production-grade recipes you can crib from.
The REST endpoints behind every MCP tool.
If your agent places trades — account model, custody, fee monetization.
# Best practices
Source: https://docs.predexon.com/start-here/best-practices
Patterns we've seen work in production: pagination, retries, rate-limit handling, WebSocket reconnect, idempotency, testing strategy.
Engineering patterns we recommend when shipping a Predexon integration. None of this is required to make your first call work — it's what separates a working prototype from a production system.
***
## Authentication
```bash theme={null}
export PREDEXON_API_KEY="pk_..."
```
Then in code:
```python theme={null}
headers = {"x-api-key": os.environ["PREDEXON_API_KEY"]}
```
Rotate via the [dashboard](https://dashboard.predexon.com) — keys are scoped per workspace, easy to revoke and re-issue.
Free tier covers dev easily. Use a separate Dev or Pro key for staging so you can verify rate-limit behavior matches prod.
If you're building a product with many end users, you don't need per-user Predexon keys. One platform key serves all your users' reads from your backend — end users never see the Predexon key.
***
## Pagination
Most list endpoints support one of two patterns. Use the right one for your scale.
| Pattern | Endpoints | When to use |
| - | - | - |
| **Offset-based** | Default on `markets`, `trades`, `wallet`, most analytics | One-off queries, small/medium result sets, when you need a known `total` |
| **Cursor-based (keyset)** | `markets/keyset`, `events/keyset` and a few others | Large backfills, real-time tailing, when you need consistent ordering across pages |
```python theme={null}
# Offset — fine for top-100 lists
def all_markets(limit=100):
out, offset = [], 0
while True:
page = requests.get(
f"{BASE}/v2/polymarket/markets",
headers=H,
params={"limit": limit, "offset": offset, "status": "open"},
).json()
out.extend(page["markets"])
if not page["pagination"]["has_more"]:
return out
offset += limit
# Keyset — required for large backfills (no offset ceiling)
def all_markets_keyset(limit=200):
out, cursor = [], None
while True:
params = {"limit": limit}
if cursor:
params["cursor"] = cursor
page = requests.get(
f"{BASE}/v2/polymarket/markets/keyset",
headers=H,
params=params,
).json()
out.extend(page["markets"])
if not page["pagination"]["has_more"]:
return out
cursor = page["pagination"]["next_cursor"]
```
Don't use `offset` past 10,000 — performance degrades and result quality drops. Switch to keyset for anything bigger.
***
## Retries and exponential backoff
Retry **5xx** errors with exponential backoff. **Never** retry 4xx — those are your bug, not ours.
```python theme={null}
import time, requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def make_session():
s = requests.Session()
s.headers.update({"x-api-key": os.environ["PREDEXON_API_KEY"]})
retry = Retry(
total=5,
backoff_factor=0.5, # 0.5s, 1s, 2s, 4s, 8s
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE"],
respect_retry_after_header=True,
)
s.mount("https://", HTTPAdapter(max_retries=retry))
return s
session = make_session()
```
| Status | Retry? | Notes |
| - | - | - |
| `429` | Yes — with backoff | We send `Retry-After`; the example above respects it via `respect_retry_after_header=True` |
| `500/502/503/504` | Yes — with backoff | Transient. Usually self-heals in `<30s` |
| `409` | Maybe — once | Concurrent modification (fee policy). Single retry, then surface to user |
| `400/401/403/404` | No | Fix your request |
***
## Rate limit handling
You'll hit `429` long before any other failure mode. Cheap defenses, in order of effort:
Most market data doesn't change every second. Cache list-markets and similar slow-moving endpoints for 60s minimum.
```python theme={null}
from functools import lru_cache
from cachetools import TTLCache, cached
cache = TTLCache(maxsize=1000, ttl=60)
@cached(cache)
def market(condition_id):
return session.get(f"{BASE}/v2/polymarket/markets", params={"condition_id": condition_id}).json()
```
`/v2/polymarket/wallets/profiles` accepts up to 20 wallets per call. `/v2/polymarket/wallets/filter` lets you filter server-side instead of pulling everything and filtering locally. Use them.
If you find yourself polling `/v2/polymarket/trades` every second, switch to the [WebSocket trades channel](/websocket/trades). Streaming consumes WebSocket subs (cheap) instead of REST quota (expensive).
Free is 1 req/s. Dev is 20 req/s. Pro is 100 req/s. Enterprise is custom. If you have a sustained workload above Pro, [email us](mailto:team@predexon.com) — we'll discuss a custom rate before throttling you in production.
***
## WebSocket: reconnect and state rebuild
WebSockets disconnect. Your client must handle it cleanly without losing state.
```python theme={null}
import json, time
from websockets.sync.client import connect
WSS = "wss://wss.predexon.com/v1"
KEY = os.environ["PREDEXON_API_KEY"]
SUBSCRIPTIONS = [
{"action": "subscribe", "platform": "polymarket", "version": 1,
"type": "orders", "filters": {"users": ["0x1234..."]}},
# ... add more
]
def run_forever():
backoff = 1.0
while True:
try:
with connect(f"{WSS}/{KEY}") as ws:
for s in SUBSCRIPTIONS:
ws.send(json.dumps(s))
# On every reconnect, re-pull state from REST to bridge the gap
rebuild_state_from_rest()
backoff = 1.0 # reset on successful connect
for raw in ws:
msg = json.loads(raw)
if msg.get("type") == "event":
handle(msg)
elif msg.get("data", {}).get("event_type") == "resync":
rebuild_state_from_rest()
except Exception as e:
print(f"WS disconnect: {e}; backoff {backoff}s")
time.sleep(backoff)
backoff = min(backoff * 2, 60)
```
**Three rules**:
1. **Re-pull REST state on every reconnect** — bridges any events you missed during the disconnect.
2. **Handle `resync` events from the server** — it tells you "rebuild state, more snapshots coming." Same recovery as a hard reconnect.
3. **Exponential backoff on reconnect attempts**, capped at 60s. Don't hammer the server during an outage.
See [WebSocket Subscriptions](/websocket/subscriptions) for connection lifecycle details and [WebSocket Overview](/websocket/overview) for keepalive timing.
***
## Testing strategy
Mock your business logic that *calls* Predexon. The endpoints themselves should be hit in integration tests against real markets — that's the only way to catch shape changes early.
Record real events to JSON files, replay them through your handler in tests. Lets you test without keeping a connection open.
Capture a real response, snapshot-test against it. When we ship a non-breaking field addition, your test still passes; when a breaking change ships, your test catches it.
***
## Observability
Things you'll want to instrument from day one:
| Metric | Why |
| - | - |
| Request latency p50/p95/p99 per endpoint | Catches degradation before users do |
| Error rate per endpoint | Pinpoints which endpoint broke, not "the API broke" |
| `429` count per minute | Tells you when to upgrade tier |
| WebSocket disconnect frequency + reconnect duration | If reconnect takes >5s, that's user-visible |
| WebSocket event → handler-done latency | Your real edge is only as fast as your slowest handler |
We don't have a public status page yet, but `GET /health` on every base URL returns `{"status": "healthy"}` — use it as a synthetic check.
***
## Common gotchas
Candles use **seconds**. Orderbook snapshots use **milliseconds**. WebSocket events use seconds except the orderbook channel (milliseconds). Always check the page reference.
Polymarket prices are 0–1 decimals. Kalshi prices are 0–100 cents. Normalize on read.
A market has one `condition_id` and N `token_id`s (one per outcome). Candles by condition give you market-level OHLCV; candles by token give you per-outcome.
WebSocket requires Dev plan or higher. You won't see this until you try to connect and get `403`. Test early.
***
## What to read next
The detailed limit and free-endpoint matrix.
Auth header, x402 pay-per-call, CORS.
Full subscription lifecycle, ack/resync flow.
Moving from another provider? Common translations.
# App builder & platform
Source: https://docs.predexon.com/start-here/builder-guide
Architecture patterns for products built on prediction-market data — dashboards, trackers, discovery, and analytics UX on one API.
You're building a product on prediction-market data. Could be a market-discovery app, a wallet tracker, a copy-trading signal product, an analytics terminal, or a media property with live odds. This guide is about the architecture decisions — Predexon handles the indexing, normalization, and streaming so you don't have to.
**You'll be ready to:**
* Split reads correctly between REST and WebSocket
* Serve market pages, wallet pages, and leaderboards from the right endpoints
* Keep your quota bill near zero using free historical endpoints
* Handle errors, caching, and latency expectations realistically
***
## The core architecture
Almost every data product ends up with the same shape:
```mermaid theme={null}
flowchart LR
WSS[Predexon WebSocket
live trades · books · lifecycle] --> ING[Your ingester]
REST[Predexon Data API
markets · wallets · analytics] --> APP[Your backend]
ING --> CACHE[(Your cache / DB)]
CACHE --> APP
APP --> UI[Your UI]
```
* **REST for state, WebSocket for change.** Load a market page from REST once, then keep it live with the orderbook and trades channels. Don't poll.
* **Cache reads you'll repeat.** Market metadata changes rarely — cache it hours. Prices and books change constantly — stream them instead of re-fetching.
* **Historical endpoints are free and unlimited** on every plan (trades, orderbooks, wallet activity). Backfill as much as you want; it never touches your quota.
***
## Serving the classic pages
| Page in your product | Endpoints behind it |
| - | - |
| **Market list / discovery** | [List markets](/api-reference/markets/list-markets) sorted by volume, [search](/api-reference/discovery/search), [categories & tags](/api-reference/discovery/categories) |
| **Market detail** | [Market price](/api-reference/markets/market-price), [candlesticks](/api-reference/markets/candlesticks), [orderbooks](/api-reference/markets/orderbooks), [top holders](/api-reference/analytics/top-holders) |
| **Live tape** | [WebSocket trades](/websocket/trades) — plus [pending trades](/websocket/pending-trades) if you want your tape up to 5s ahead of everyone else's |
| **Wallet page** | [Wallet profile](/api-reference/wallet/wallet-profile), [positions](/api-reference/wallet/positions), [P\&L](/api-reference/wallet/pnl) |
| **Leaderboard** | [Global leaderboard](/api-reference/analytics/leaderboard), [per-market leaderboard](/api-reference/analytics/market-leaderboard) |
| **Smart-money feed** | [Smart activity](/api-reference/smart-money/smart-activity), [smart money per market](/api-reference/smart-money/smart-money-market) |
Code skeleton for a live market page:
```python theme={null}
import os, requests
HEADERS = {"x-api-key": os.environ["PREDEXON_API_KEY"]}
BASE = "https://api.predexon.com"
# Initial state — one REST call each
market = requests.get(
f"{BASE}/v2/polymarket/markets", headers=HEADERS,
params={"condition_ids": condition_id},
).json()
candles = requests.get(
f"{BASE}/v2/polymarket/candlesticks/{condition_id}",
headers=HEADERS, params={"interval": "1h"},
).json()
# Then keep it live over WebSocket (trades + orderbook channels)
# wss.predexon.com — see /websocket/overview
```
***
## Differentiate with the hard datasets
The pages above are table stakes. The datasets that make a product stand out:
* **Pending trades (mempool)** — show fills before they confirm. Your users see flow up to 5 seconds before apps built on Polymarket's RTDS. [WebSocket pending-trades →](/websocket/pending-trades)
* **Tick-level orderbook history** — power "book at any moment" scrubbers, spread-history charts, and execution-quality analytics from the raw tick stream. [Tick downloads →](/data-signals/ticks/data-credits)
* **Smart-money analytics** — profitable-wallet flow per market, computed from full chain history. Nobody can fork your UI and get this from the venue API. [Smart money →](/api-reference/smart-money/smart-money-market)
***
## Latency, errors, and operational realism
What to expect in production:
| Surface | Typical latency | Notes |
| - | - | - |
| Data API (read) | 50–200ms | Cacheable reads; use conditional fetching where possible. |
| WebSocket events | `<1s` end-to-end | Pending-trades lead confirmed trades — and beat RTDS by up to 5s. |
| Tick downloads | Seconds–minutes | Signed URLs; size depends on the slice you quote. |
**Error patterns you'll see most**:
* `429` — you outran your plan's rate limit. Back off exponentially; consider caching or a higher tier.
* `401 / 403` — wrong or missing `x-api-key`.
* `503` rarely — surface as "temporarily unavailable" and let users retry; don't hot-loop yourself.
See [Best Practices](/start-here/best-practices) for retry/backoff patterns, caching, and connection pooling.
***
## Common builder recipes
Stream a wallet's fills in real time and surface them to your users.
Positions + P\&L + live updates for any wallet. Same patterns power most prediction-market UIs.
Alert when profitable wallets pile into a market.
The 5-seconds-early tape, productized.
***
## What you should read next
Full map of every endpoint, organized by utility.
Channels, subscriptions, and plan limits for live data.
Retries, caching, rate limits, error handling.
What's free and unlimited vs. what counts toward quota.
# Copy-trade a wallet
Source: https://docs.predexon.com/start-here/cookbook/copy-trade-wallet
Subscribe to a target wallet's trades over WebSocket, then mirror each fill through your own venue-native execution.
The most-requested integration on Predexon. You pick a profitable wallet, watch every fill it does in real time, and mirror those fills through your own execution at a fraction of the size.
**You'll build**:
1. A WebSocket listener that fires on every trade by the target wallet
2. A handler that translates target-wallet trades into orders for your own execution layer
3. A scale-and-execute path that mirrors on the same venue
**Endpoints used**: 1 WebSocket channel + 1 REST endpoint. Runs on Dev plan or higher (WebSocket requires Dev+).
Predexon supplies the **detection** side: the real-time fill stream and the wallet analytics to vet your target. Execution happens through the venue's own API (e.g. the Polymarket CLOB client) — shown below as a stub you wire to your client of choice.
***
## Architecture
```mermaid theme={null}
flowchart LR
TARGET[Target wallet
0xSmartTrader]
WS[WebSocket
trades channel
filters.users]
HANDLER[Your handler
scale + validate]
EXEC[Your execution
venue-native client]
ACCOUNT[Your account
positions mirror target]
TARGET -.fills.-> WS
WS --event--> HANDLER
HANDLER --order--> EXEC
EXEC --> ACCOUNT
```
***
## Step 1 — Verify the target
Before mirroring, check they're actually profitable. Pull the wallet profile:
```python theme={null}
import os, requests, time, json
from websockets.sync.client import connect
DATA = "https://api.predexon.com"
WSS = "wss://wss.predexon.com/v1"
KEY = os.environ["PREDEXON_API_KEY"]
H = {"x-api-key": KEY}
TARGET = "0xSmartTraderAddressHere"
profile = requests.get(f"{DATA}/v2/polymarket/wallet/{TARGET}", headers=H).json()
print(f"Profit: ${profile['realized_profit_usd']:,.0f} ROI: {profile['roi']:.1%}")
assert profile["realized_profit_usd"] > 50_000, "Target isn't profitable enough"
```
Re-run this check daily — wallets degrade.
***
## Step 2 — Subscribe to their trades
The trades channel takes a `users` filter. You'll receive every fill they take in real time, on both V1 and V2 Polymarket contracts.
```python theme={null}
def subscribe():
return json.dumps({
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "orders",
"filters": {"users": [TARGET]},
})
with connect(f"{WSS}/{KEY}") as ws:
ws.send(subscribe())
for raw in ws:
msg = json.loads(raw)
if msg.get("type") != "event":
continue
handle_trade(msg["data"])
```
***
## Step 3 — Scale and mirror
The hardest part of copy-trading is **sizing**. Mirror their trade as a fraction of their original size, capped by your venue balance. Execution goes through your own venue client:
```python theme={null}
SIZE_RATIO = 0.10 # mirror 10% of their size
MAX_PER_TRADE_USD = 500
def execute_on_venue(token_id, side, size, order_type="market", price=None):
# your venue-native execution — e.g. Polymarket CLOB client
# from py_clob_client.client import ClobClient ...
raise NotImplementedError
def handle_trade(trade):
# only mirror trades initiated by the target wallet (not against them)
if trade["user"].lower() != TARGET.lower():
return
# only follow buys; let me close on my own logic
if trade["side"] != "BUY":
return
target_shares = float(trade["shares_normalized"])
target_price = float(trade["price"])
notional = target_shares * target_price
# scale
mirror_shares = target_shares * SIZE_RATIO
mirror_notional = mirror_shares * target_price
if mirror_notional > MAX_PER_TRADE_USD:
mirror_shares = MAX_PER_TRADE_USD / target_price
# the target trades on Polymarket — mirror directly on the same token_id
resp = execute_on_venue(
token_id=trade["token_id"],
side="buy",
size=round(mirror_shares, 2),
order_type="market", # or "limit" at target_price * 1.005
)
print(f"Mirrored: {trade['token_id']} shares={mirror_shares:.2f} → {resp}")
```
The example above mirrors on the same venue (Polymarket → Polymarket), which is the most reliable copy-trading path — the `token_id` in the event is exactly the token your venue client trades.
***
## Step 4 — Run it under supervision
Things that will go wrong in production:
| Failure | Mitigation |
| - | - |
| WebSocket disconnect | Wrap the loop with reconnect + resume. See [Subscriptions](/websocket/subscriptions). |
| Mirror order fails (insufficient balance) | Catch the venue error, alert yourself, top up the venue, don't auto-retry. |
| Target sells before you fill | Use market orders + smaller size; or accept the slippage as part of the cost. |
| Target wallet changes strategy | Re-pull the wallet profile daily; pause mirroring if `realized_profit` drops. |
| You mirror a trade and they immediately reverse | Build a "follow exits too" version: also handle `side: "SELL"` to close mirrored positions. |
For the simplest possible production version, also subscribe to the [activity channel](/websocket/activity) to mirror splits, merges, and redemptions.
***
## Variations
* **Multi-wallet copy-trading**: subscribe with `filters: {"users": [w1, w2, w3, ...]}` and route each trade to the right end-user account in your DB.
* **Filtered copy-trading**: only mirror trades above a notional threshold, or in specific market categories.
* **Agent-driven copy-trading**: replace the WebSocket loop with the [Agent Cookbook](/start-here/agent-cookbook) recipe 4, which polls and reasons about whether to mirror.
***
## Reference
* [WebSocket trades channel](/websocket/trades) — filter shape, event payload
* [Wallet P\&L](/api-reference/wallet/pnl) — track how your mirrored book performs vs. the target
* [Wallet Profile](/api-reference/wallet/wallet-profile) — for the daily target check
* [Best Practices](/start-here/best-practices) — WebSocket reconnect pattern
# Front-run from pending trades
Source: https://docs.predexon.com/start-here/cookbook/pending-trade-frontrun
Detect Polymarket fills 3–5 seconds before they confirm by listening to the Polygon mempool, then act on the leading indicator.
Polymarket trades have to wait for the next Polygon block to confirm. Predexon decodes the mempool so you see those trades **3–5 seconds before** they settle on-chain — up to 5 seconds before Polymarket's own real-time feed shows them. That window is enough to lift the book or take the other side, depending on your strategy.
**You'll build**:
1. A WebSocket subscription that fires on pending trades
2. A latency-honest decision function
3. (Optional) An execution path — via your own venue client — that acts inside the 3–5s window
**Endpoints used**: 1 WebSocket channel + 1 REST call. Requires Dev+ for WebSocket.
This is a latency-sensitive strategy. If your handler takes >1 second to decide or your round-trip to your execution venue is high, you'll miss the window. Co-locate near your execution venue's infrastructure and benchmark before sizing.
***
## How pending trades work
```mermaid theme={null}
sequenceDiagram
participant U as End user
participant POLY as Polymarket CLOB
participant POLYGON as Polygon mempool
participant PX as Predexon decoder
participant YOU as You
U->>POLY: Place order
POLY->>POLYGON: Submit fill tx
POLYGON->>PX: tx enters mempool
PX->>YOU: pending-trade event
(3–5s lead)
Note over YOU: ← Your window to act
POLYGON->>POLY: Block confirms
POLY->>YOU: trade event (confirmed)
```
You're seeing the fill before the rest of the world does — including most other Polymarket traders. The strategies that work in this window are typically:
* **Lift the book**: a large pending buy means the next mid-tick will be higher. Buy ahead of it.
* **Pre-emptive cancel**: if a pending trade is about to take your resting order, cancel and re-quote.
* **Quote tightening**: maker strategies tightening spread when the book is about to move.
***
## Step 1 — Subscribe to pending trades
Same `orders` channel as confirmed trades — just add `filters.status: "pending"`.
```python theme={null}
import os, json, time
from websockets.sync.client import connect
KEY = os.environ["PREDEXON_API_KEY"]
WSS = "wss://wss.predexon.com/v1"
def sub_msg(market_slugs):
return json.dumps({
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "orders",
"filters": {
"market_slugs": market_slugs,
"status": "pending", # the magic flag
},
})
MARKETS = ["bitcoin-100k-by-end-2026", "us-recession-2026"] # your watch list
with connect(f"{WSS}/{KEY}") as ws:
ws.send(sub_msg(MARKETS))
for raw in ws:
msg = json.loads(raw)
if msg.get("type") == "event":
handle_pending(msg["data"])
```
For maximum coverage, replace `market_slugs` with `["*"]` (firehose — Pro plan only).
***
## Step 2 — Decide fast
The handler runs on every pending event. It has 3–5 seconds to make a decision and ship an order. Anything longer and the window closes. Predexon gives you the signal and the current book state; the order itself goes out through your own venue client.
```python theme={null}
import requests
DATA = "https://api.predexon.com"
H = {"x-api-key": KEY}
MIN_NOTIONAL = 5_000 # ignore noise — only act on $5k+ pending trades
LIFT_SIZE = 100 # shares to buy
def execute_on_venue(token_id, side, size, price):
# your venue-native execution — e.g. Polymarket CLOB client
# keep this pre-warmed: authenticated session, cached allowances
raise NotImplementedError
def handle_pending(t):
notional = float(t["shares_normalized"]) * float(t["price"])
if notional < MIN_NOTIONAL:
return
if t["side"] != "BUY":
return # only lift on buy pressure for this example
# current best ask — we'll target ask + tick to actually fill
book = requests.get(
f"{DATA}/v2/polymarket/market-price/{t['token_id']}",
headers=H,
).json()
target_price = round(min(book["best_ask"] + 0.001, 0.999), 3)
# lift the book before the pending trade settles
# use a limit not a market order — slippage protection
execute_on_venue(
token_id=t["token_id"],
side="buy",
size=LIFT_SIZE,
price=target_price,
)
```
Latency budget for this handler: `<500ms` ideally. If you're processing more than a few events per second, queue them — your decision must happen before the next block confirms.
***
## Step 3 — Measure your hit rate
You don't actually know if your strategy is working until you compare pending events to confirmed events. Log both, reconcile after the fact.
```python theme={null}
# in your handler, log the pending event with timestamp_pending
# in a separate trades subscription (no status filter), log the same trade_id with timestamp_confirmed
# offline analysis:
# - lead_time = timestamp_confirmed - timestamp_pending (target: 3–5s)
# - hit_rate = fraction of your lift orders that filled at or below target_price
# - net_pnl = (avg_fill_price - mid_after_confirm) * shares for each filled lift
```
If lead time is `<1s` consistently, the strategy doesn't have edge — you're not getting enough lookahead. If hit rate is `<30%`, your sizing is too aggressive or your target price too tight.
***
## Avoiding self-front-running
If you're running this **on the same wallet** that places the parent order, your handler will see your own pending trade and try to act on it. Two fixes:
1. **Track your own outgoing orders**: keep a set of token\_ids and timestamps for orders you've placed in the last 10s; skip pending events that match.
2. **Use a separate wallet for the strategy**: your front-running wallet never places anything you'd want to detect.
```python theme={null}
MY_RECENT_ORDERS = {} # token_id → timestamp_placed
def handle_pending(t):
last = MY_RECENT_ORDERS.get(t["token_id"], 0)
if time.time() - last < 10:
return # probably my own order
...
```
***
## Reality check
| What you might assume | What's actually true |
| - | - |
| "3–5s is plenty to do anything" | Network round-trip alone is often 200–500ms. Real budget: 2–4s of decision time. |
| "All pending trades confirm" | Some get reverted. Build for 5–10% revert rate. |
| "Wildcard subscription = full coverage" | Wildcard delivers conflated batches every \~250ms. You lose per-event timing precision. Use specific market subscriptions for latency-sensitive work. |
| "I can run this from anywhere" | Polygon RPC is fastest from `us-east-1` and Frankfurt. Far-region latency will eat your edge. |
| "Free plan should be fine for testing" | WebSocket is Dev+. Even for testing you need a Dev key. |
For honest backtesting before going live, see [Signal Backtesting → pending-trade approach](/data-signals/backtesting/signal-backtest#signal-3--pending-trade-leading-indicator). Plan on collecting a week of real pending+confirmed data before sizing in.
***
## Reference
* [WebSocket pending trades](/websocket/pending-trades) — the `status: "pending"` filter
* [WebSocket trades (confirmed)](/websocket/trades) — for measuring lead time and reconciling
* [Market Price](/api-reference/markets/market-price) — for current best bid/ask before sizing
* [Signal backtesting](/data-signals/backtesting/signal-backtest)
# Portfolio monitor
Source: https://docs.predexon.com/start-here/cookbook/portfolio-monitor
Live dashboard of any wallet's positions, P&L, and fills — the pattern behind most prediction-market portfolio UIs.
If you're building any kind of portfolio UI or risk dashboard, this is the loop you need. Point it at **any wallet address** — your own, a fund's, a trader you track — pull its positions and P\&L from the Data API, then layer live updates via WebSocket.
**You'll build**:
1. Initial state load (positions + P\&L) from REST
2. Live deltas (fills, splits, redemptions) from WebSocket
3. A reconciliation step that runs periodically
**Endpoints used**: 2 REST + 2 WebSocket channels. Dev plan and up (WebSocket requires Dev+).
***
## Architecture
```mermaid theme={null}
flowchart LR
BOOT[Boot:
load positions + P&L]
STATE[(In-memory
portfolio state)]
WS_TRADES[WS trades
filters.users=wallet]
WS_ACTIVITY[WS activity
filters.users=wallet]
APPLY[Apply delta
to state]
RECONCILE[Every 60s:
re-pull positions]
BOOT --> STATE
WS_TRADES --> APPLY
WS_ACTIVITY --> APPLY
APPLY --> STATE
RECONCILE --> STATE
STATE -.-> UI[Your UI
or alerts]
```
***
## Step 1 — Boot the state
Everything is keyed on the wallet address — no account setup needed, and it works for any address on Polymarket, not just your own.
```python theme={null}
import os, requests, json, time
from websockets.sync.client import connect
DATA = "https://api.predexon.com"
WSS = "wss://wss.predexon.com/v1"
KEY = os.environ["PREDEXON_API_KEY"]
H = {"x-api-key": KEY}
WALLET = "0xWalletToMonitor"
def boot_state():
# 1. Open positions with cost basis, current value, and P&L
positions = requests.get(
f"{DATA}/v2/polymarket/wallet/positions/{WALLET}",
headers=H,
params={"limit": 200}, # open positions only by default
).json()["positions"]
# 2. P&L — summary (realized, unrealized, fees) + realized time series
pnl = requests.get(
f"{DATA}/v2/polymarket/wallet/pnl/{WALLET}",
headers=H,
).json()
return {
"wallet": WALLET,
"positions": positions,
"pnl": pnl,
"loaded_at": time.time(),
}
state = boot_state()
print(f"Loaded {len(state['positions'])} open positions, "
f"total P&L ${state['pnl']['total_pnl']:,.2f}")
```
Pass `include_closed=true` on the positions call if your dashboard also shows resolved/closed positions. Default is open (non-zero-balance) positions only.
***
## Step 2 — Subscribe to live deltas
Two channels matter for portfolio updates: `trades` (fills) and `activity` (splits / merges / redemptions). Filter both to the wallet.
```python theme={null}
def subscribe_msgs(wallet):
return [
json.dumps({
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "orders",
"filters": {"users": [wallet]},
}),
json.dumps({
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "activity",
"filters": {"users": [wallet]},
}),
]
```
A single Polymarket match emits both per-maker and taker-aggregate `order_filled` rows. Apply only events where `data["user"]` equals your wallet to avoid double-counting — see [Maker vs Taker emissions](/websocket/trades#maker-vs-taker-emissions).
***
## Step 3 — Apply each delta to state
A fill changes a position's size and cost basis. An activity event (split/merge/redeem) restructures positions. Worth modeling both — but at minimum, the trades handler is the must-have.
```python theme={null}
def apply_trade(state, t):
"""Update a position from a fill event."""
if t["user"].lower() != state["wallet"].lower():
return # counterparty emission — not this wallet's fill
token_id = t["token_id"]
size = float(t["shares_normalized"]) * (1 if t["side"] == "BUY" else -1)
price = float(t["price"])
pos = next((p for p in state["positions"] if p.get("token_id") == token_id), None)
if pos is None:
# new position
state["positions"].append({
"token_id": token_id,
"condition_id": t["condition_id"],
"size": size,
"avg_price": price,
})
else:
# weighted-average cost basis
new_size = pos["size"] + size
if new_size == 0:
state["positions"].remove(pos)
else:
pos["avg_price"] = (
(pos["avg_price"] * pos["size"] + price * size) / new_size
)
pos["size"] = new_size
def apply_activity(state, a):
"""Activity events restructure positions (splits, merges, redemptions)."""
if a["event_type"] == "redemption":
# winning side pays $1/share, losing side pays $0; remove resolved positions
state["positions"] = [
p for p in state["positions"] if p["condition_id"] != a["condition_id"]
]
# Splits / merges / NegRisk conversions: re-pull positions in step 4
```
***
## Step 4 — The main loop
```python theme={null}
def run():
state = boot_state()
last_reconcile = time.time()
with connect(f"{WSS}/{KEY}") as ws:
for msg in subscribe_msgs(WALLET):
ws.send(msg)
while True:
try:
ws.settimeout(60)
raw = ws.recv()
msg = json.loads(raw)
if msg.get("type") != "event":
continue
if msg["subscription_id"].endswith("trades"):
apply_trade(state, msg["data"])
elif msg["subscription_id"].endswith("activity"):
apply_activity(state, msg["data"])
publish(state) # send to UI / dashboard
except TimeoutError:
pass
# Reconcile every 60s — defends against missed events
if time.time() - last_reconcile > 60:
state = boot_state()
last_reconcile = time.time()
publish(state)
def publish(state):
# your UI delivery — websocket to frontend, Redis pub/sub, etc.
print(f"[{time.strftime('%H:%M:%S')}] {len(state['positions'])} positions, "
f"total P&L ${state['pnl']['total_pnl']:.2f}")
```
***
## What the UI does with this state
| Display | Source |
| - | - |
| Open positions table | `state["positions"]` |
| Mark-to-market | `position.size * latest_price` — fetch latest\_price from [Market Price](/api-reference/markets/market-price) |
| Unrealized P\&L | `state["pnl"]["unrealized_pnl"]`, or `(latest_price - avg_price) * size` per position |
| Realized P\&L over time | `state["pnl"]["pnl_over_time"]` — cumulative realized P\&L series |
| Fees | `state["pnl"]["fees_paid"]` / `state["pnl"]["fees_refunded"]` |
| Recent fills | Append the trades event stream to a feed |
For periodic mark-to-market, the cheapest pattern is batch the latest prices for all unique `token_id`s in the positions list and refresh every few seconds.
***
## Variations
* **Multi-wallet dashboard**: subscribe with `filters: {"users": [w1, w2, ...]}` on both channels and keep one state object per wallet; boot each from the same two REST calls.
* **Alerting**: add thresholds — alert on unrealized loss > X, on a new position above \$Y, on a redemption event.
* **Per-market P\&L drill-down**: pass `condition_id` to [Wallet P\&L](/api-reference/wallet/pnl) to break out a single market (correctly handles splits/merges).
* **Historical equity curve**: pair with [Wallet Volume Chart](/api-reference/wallet/volume-chart) for cumulative-volume overlays.
***
## Reference
* [Wallet Positions](/api-reference/wallet/positions) — open positions with cost basis, `include_closed` for history
* [Wallet P\&L](/api-reference/wallet/pnl) — summary + realized P\&L time series
* [WebSocket trades](/websocket/trades), [activity](/websocket/activity)
* [Best Practices — WebSocket reconnect](/start-here/best-practices)
# Detect smart-money entry into a market
Source: https://docs.predexon.com/start-here/cookbook/smart-money-entry
Watch which markets profitable wallets are piling into, alert when net smart-money flow crosses a threshold, optionally auto-size in via your own execution.
Smart-money flow is one of the most consistent prediction-market signals. Build an alerter that fires when net buying by profitable wallets accelerates into a market — then act on it.
**You'll build**:
1. A poller that ranks markets by smart-money inflow
2. An alert path (Discord / Slack / email)
3. (Optional) A sizing rule that opens a position — via your own venue client — when the signal fires
**Endpoints used**: 2 REST calls per cycle. Free + Dev plan; runs on Dev because Smart Money is a gated feature.
***
## Architecture
```mermaid theme={null}
flowchart LR
POLL[Cron / scheduler
every 5–15 min]
SCAN[smart_activity
top markets by inflow]
CHECK[smart_money_market
net positioning]
GATE{|flow|
> threshold?}
ALERT[Alert
Discord / Slack]
EXEC[Optional:
your venue execution]
POLL --> SCAN
SCAN --markets ranked--> CHECK
CHECK --> GATE
GATE -- yes --> ALERT
GATE -- yes --> EXEC
GATE -- no --> POLL
```
***
## Step 1 — Find markets smart money is entering
The `smart-activity` endpoint ranks markets by recent activity from top-performing wallets. Pull the leaderboard once per cycle.
```python theme={null}
import os, requests, time
BASE = "https://api.predexon.com"
H = {"x-api-key": os.environ["PREDEXON_API_KEY"]}
def top_smart_markets(window_hours=4, limit=20):
return requests.get(
f"{BASE}/v2/polymarket/markets/smart-activity",
headers=H,
params={"window_hours": window_hours, "limit": limit},
).json()["markets"]
```
This already filters to wallets with positive realized P\&L. You get a ranked list of markets they're trading in the last 4 hours.
***
## Step 2 — Read the net positioning
For each candidate, ask: are they net buying or net selling, and by how much?
```python theme={null}
def smart_money(condition_id):
return requests.get(
f"{BASE}/v2/polymarket/market/{condition_id}/smart-money",
headers=H,
).json()
def evaluate(candidates):
signals = []
for m in candidates:
sm = smart_money(m["condition_id"])
pos = sm["positioning"] # SmartMoneyPositioning
net_usd = pos["total_smart_buy_volume"] - pos["total_smart_sell_volume"]
wallet_count = pos["smart_wallet_count"]
if abs(net_usd) > 25_000 and wallet_count >= 5:
signals.append({
"market": m["title"],
"condition_id": m["condition_id"],
"market_slug": m["market_slug"],
"side": "yes" if net_usd > 0 else "no",
"flow_usd": net_usd,
"wallets": wallet_count,
"net_buyers_pct": pos["net_buyers_pct"],
"avg_buy_price": pos["avg_smart_buy_price"],
})
return signals
```
The thresholds (`\$25k`, `5 wallets`) are starting points. Tune to your appetite.
***
## Step 3 — Alert
Plug into whatever channel you live in.
```python theme={null}
import json
import urllib.request
DISCORD_WEBHOOK = os.environ["DISCORD_WEBHOOK"]
def alert(signal):
msg = (
f"**Smart money signal: {signal['side'].upper()}**\n"
f"{signal['market']}\n"
f"Net flow: ${signal['flow_usd']:+,.0f} from {signal['wallets']} smart wallets "
f"({signal['net_buyers_pct']*100:.0f}% net buyers)\n"
f"Avg smart-money buy price: ${signal['avg_buy_price']:.2f}\n"
f"https://polymarket.com/market/{signal['market_slug']}"
)
req = urllib.request.Request(
DISCORD_WEBHOOK,
data=json.dumps({"content": msg}).encode(),
headers={"Content-Type": "application/json"},
)
urllib.request.urlopen(req)
```
***
## Step 4 — Optional: auto-size into the signal
If you trust the signal enough to commit capital, open the position through your own execution layer. Since smart-money runs on Polymarket data, the simplest path is a venue-native Polymarket order — resolve the right token\_id by `condition_id` from the Data API, then hand off to your client.
```python theme={null}
SIZE_USD = 100
def execute_on_venue(token_id, side, size):
# your venue-native execution — e.g. Polymarket CLOB client
raise NotImplementedError
def maybe_trade(signal):
# look up the YES/NO token_ids for this market
market = requests.get(
f"{BASE}/v2/polymarket/markets",
headers=H,
params={"condition_id": signal["condition_id"], "limit": 1},
).json()["markets"][0]
token_id = next(
o["token_id"] for o in market["outcomes"]
if o["label"].lower() == signal["side"]
)
current_price = next(
o["price"] for o in market["outcomes"]
if o["label"].lower() == signal["side"]
)
shares = SIZE_USD / current_price
return execute_on_venue(
token_id=token_id,
side="buy",
size=round(shares, 2),
)
```
***
## The full loop
```python theme={null}
SEEN = set() # don't re-alert on the same signal
while True:
for s in evaluate(top_smart_markets()):
key = (s["condition_id"], s["side"])
if key in SEEN:
continue
SEEN.add(key)
alert(s)
# maybe_trade(s) # uncomment to auto-execute via your venue client
time.sleep(600) # 10 min cadence
```
Reset `SEEN` periodically (every few hours) if you want re-alerts for sustained signals.
***
## Tuning
| Knob | Tighter signal | Looser signal |
| - | - | - |
| `window_hours` | 1–2 hrs (recent activity only) | 24 hrs (broader) |
| `net_usd` threshold | \$100k+ | \$5k+ |
| `wallet_count` floor | 10+ unique wallets | 2+ |
| Cycle cadence | every 1 min | every 30 min |
| `realized_profit_min` for the smart-money pool | \$500k+ | \$10k+ |
Backtest before going live — see [Signal Backtesting](/data-signals/backtesting/signal-backtest) for how to honestly measure whether the signal predicts forward returns at your horizon.
***
## Reference
* [Smart Activity](/api-reference/smart-money/smart-activity) — ranked markets by smart-wallet inflow
* [Smart Money per market](/api-reference/smart-money/smart-money-market) — net positioning
* [Signal backtesting walkthrough](/data-signals/backtesting/signal-backtest)
# Get started in 5 minutes
Source: https://docs.predexon.com/start-here/get-started
Sign up, get a key, make your first call. Then pick the path that fits your use case.
Five minutes from now you'll have a working API call against live prediction-market data. Then we'll send you to the guide for your use case.
***
## Step 1 — Get an API key
No credit card required. The free plan gives you 1,000 requests/month against the entire Data API — enough to evaluate everything.
[Open dashboard →](https://dashboard.predexon.com)
One key covers everything. It goes in the `x-api-key` header of every request.
| Key | Used against | Surfaces |
| - | - | - |
| **Data API key** | `api.predexon.com`, `wss.predexon.com` | Data API, WebSocket, tick downloads, MCP server |
```bash theme={null}
export PREDEXON_DATA_API_KEY="pk_data_..."
```
***
## Step 2 — Make your first call
This call lists the top 5 trending markets on Polymarket. It works on the **free plan** with no setup.
```bash cURL theme={null}
curl -H "x-api-key: $PREDEXON_DATA_API_KEY" \
"https://api.predexon.com/v2/polymarket/markets?status=open&sort=volume&limit=5"
```
```python Python theme={null}
import os, requests
r = requests.get(
"https://api.predexon.com/v2/polymarket/markets",
headers={"x-api-key": os.environ["PREDEXON_DATA_API_KEY"]},
params={"status": "open", "sort": "volume", "limit": 5},
)
for m in r.json()["markets"]:
print(f"${m['total_volume_usd']:>15,.0f} {m['title']}")
```
```javascript Node.js theme={null}
const res = await fetch(
"https://api.predexon.com/v2/polymarket/markets?status=open&sort=volume&limit=5",
{ headers: { "x-api-key": process.env.PREDEXON_DATA_API_KEY } }
);
const { markets } = await res.json();
markets.forEach(m =>
console.log(`$${m.total_volume_usd.toLocaleString().padStart(15)} ${m.question}`)
);
```
```go Go theme={null}
req, _ := http.NewRequest("GET",
"https://api.predexon.com/v2/polymarket/markets?status=open&sort=volume&limit=5", nil)
req.Header.Set("x-api-key", os.Getenv("PREDEXON_DATA_API_KEY"))
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
```
You should see five live markets ranked by volume. **If you got an error, see [Troubleshooting](#troubleshooting) below.**
***
## Step 3 — Pick your path
Now go to the guide for what you're building.
Backtest with orderbook history, build smart-money models, stream live signals. Most of what you need is on the free tier.
Architecture patterns for products built on prediction-market data — one API across every venue.
The data-driven trading loop. Discover, signal, backtest, size, monitor.
Drop the MCP server into Claude, Cursor, or Codex. Five recipes for common agent tasks.
Not sure which? Skim the [Cookbook](/start-here/cookbook/copy-trade-wallet) — five concrete end-to-end recipes for common tasks.
***
## What's on each surface
| Surface | Base URL | Key | When to reach for it |
| - | - | - | - |
| **Data API** | `api.predexon.com` | Data API key | Markets, prices, candles, trades, wallets, smart money. Free on most endpoints. |
| **Tick history** | `api.predexon.com/v2/data/ticks` | Data API key | Bulk Parquet downloads of the raw orderbook tick stream. Billed via [data credits](/data-signals/ticks/data-credits). |
| **WebSocket** | `wss.predexon.com` | Data API key | Live trades, orderbook, pending-trade signals. Dev plan and up. |
| **MCP server** | `npx predexon-mcp` | Data API key | AI agents — same data through one drop-in package. |
***
## Troubleshooting
* Verify your key is in the `x-api-key` header (lowercase, hyphenated).
* Free plan can't hit Smart Money, Binance, or WebSocket — those need [Dev or higher](/rate-limits).
* WebSocket connection rejected? Same gating — see [Rate Limits](/rate-limits#gated-features).
You've hit your rate limit. Free is 1 req/s, Dev is 20 req/s, Pro is 100 req/s. Implement exponential backoff. See [Best Practices](/start-here/best-practices) for the pattern we recommend.
Most list endpoints accept `status`, `sort`, `limit` parameters — try removing filters one at a time. Markets that were resolved more than a few weeks ago may be archived and need `?archived=true`.
Every endpoint is documented under [Data & Signals](/data-signals/overview). Each page has an interactive playground.
***
## Need help?
The fastest way to get unblocked. Engineering reads every message.
`team@predexon.com` for anything that doesn't fit Discord.
# Migrating from another provider
Source: https://docs.predexon.com/start-here/migrating
Side-by-side translations from Polymarket's native API, Goldsky, Dune, and self-built indexers to Predexon endpoints.
If you've already built against another data source or run your own indexer, this page maps your existing patterns to Predexon. Most migrations are a one-line URL change.
***
## From Polymarket's native API (CLOB / Gamma)
Polymarket publishes a CLOB API for orderbook data and a Gamma API for market metadata. Both are useful; both have rough edges (rate limits, missing fields, inconsistent shapes across venues if you also use Kalshi etc.).
| Polymarket native | Predexon equivalent | Notes |
| - | - | - |
| `GET https://gamma-api.polymarket.com/markets` | [`GET /v2/polymarket/markets`](/api-reference/markets/list-markets) | Same filters, richer response, free + unlimited |
| `GET https://clob.polymarket.com/markets/{condition_id}` | [`GET /v2/polymarket/markets?condition_id={...}`](/api-reference/markets/list-markets) | Same data, normalized shape |
| `GET https://clob.polymarket.com/book?token_id={...}` | [`GET /v2/polymarket/orderbooks?token_id={...}`](/api-reference/markets/orderbooks) | Predexon also gives you **history** — Polymarket native is current-only |
| `GET https://clob.polymarket.com/trades?market={...}` | [`GET /v2/polymarket/trades?token_id={...}`](/api-reference/trading/trades) | Predexon supports cursor pagination + multi-market filters |
| `GET https://gamma-api.polymarket.com/events` | [`GET /v2/polymarket/events`](/api-reference/markets/events) | Same |
| WebSocket: `wss://ws-subscriptions-clob.polymarket.com/...` | [`wss://wss.predexon.com/v1/{key}`](/websocket/overview) | Same channels, normalized payloads, plus pending-trade detection |
| (No native equivalent) | [`GET /v2/polymarket/wallet/pnl/{wallet}`](/api-reference/wallet/pnl), smart-money, leaderboards | Net-new capability |
**Migration shape**: usually one search-replace on base URLs. Response shapes are a strict superset — your existing parsers keep working. Add the `x-api-key` header and you're done.
```diff theme={null}
- url = "https://gamma-api.polymarket.com/markets"
+ url = "https://api.predexon.com/v2/polymarket/markets"
- headers = {}
+ headers = {"x-api-key": os.environ["PREDEXON_API_KEY"]}
```
***
## From Dune
Dune is great for ad-hoc SQL; Predexon is built for application-time queries with consistent latency.
| Dune query type | Predexon equivalent | Why switch |
| - | - | - |
| `polymarket.trades` table queries | [`/v2/polymarket/trades`](/api-reference/trading/trades) | `<200ms` vs minutes for Dune query execution |
| `polymarket.positions` aggregations | [`/v2/polymarket/wallet/positions/{wallet}`](/api-reference/wallet/positions) | Pre-computed, ready-to-render |
| Wallet leaderboards | [`/v2/polymarket/leaderboard`](/api-reference/analytics/leaderboard) | Same query, cached and live |
| Orderbook reconstruction | [Tick history](/data-signals/ticks/data-credits) | The raw tick stream as Parquet — no on-chain table has book depth |
Dune still wins for **exploratory** work and one-off custom aggregations. Predexon wins for anything in your hot application path.
Often the right pattern is: prototype the analysis in Dune, then port to Predexon for production.
***
## From Goldsky / The Graph / a self-built subgraph
If you're running a Polymarket subgraph or other indexed source, the question isn't whether yours works — it's whether the maintenance burden is invisible until it breaks. Self-indexers fail quietly. You discover the gap when a reorg drops events, when a contract upgrade silently changes a field, when your node falls behind and your dashboard is showing stale prices.
| Self-indexed pain point | What Predexon gives you |
| - | - |
| Reorg handling, missed events | We handle on-chain reorgs and replay |
| Multi-venue indexing | One API across 7 venues; you maintain 0 indexers |
| Smart-money / leaderboards | Pre-computed analytics layers |
| Pending-trade detection | Native — requires a co-located Polygon node otherwise |
| On-call burden | Zero — we run the infra |
| Cost | Free tier covers most workloads; Pro is \$249/mo |
**The engineering ROI math**: a single engineer-hour at most companies costs \$100–200 loaded. If your team is putting 5h/month into indexer maintenance and on-call (and most do — it's just that the cost is hidden in your engineers' headers), that's \$6k–24k/year. Pro tier (\$249/mo = \$3k/year) pays for itself even if the only thing it eliminates is your on-call rotation. And if a subgraph outage costs you a degraded trading day, the cost gap is much bigger than that.
If you genuinely enjoy maintaining the subgraph, keep it. If "the subgraph works fine" actually means "nobody's looked at it in 3 months and I hope nothing has drifted," migrate.
***
## From a custom Polygon indexer
You probably built this because you needed:
* Sub-second confirmed-trade latency → [WebSocket trades channel](/websocket/trades) is the same data, no node to run
* Mempool / pending-trade detection → [WebSocket pending-trades](/websocket/pending-trades) gives you exactly this
* Cost basis / P\&L reconstruction → [Wallet P\&L](/api-reference/wallet/pnl) is pre-computed
* Full-depth book history → [Tick history](/data-signals/ticks/data-credits) captures every book update at every granularity
The hardest indexer pattern to replicate is **wildcard firehose for niche tokens**. Predexon offers wildcard subscriptions on Pro (2 per channel) which covers most use cases. If you need every Polygon log for custom analysis, you'll still want your own node — but use Predexon for everything else and save the node for the 10% it actually adds.
***
## From Kalshi's native API
| Kalshi native | Predexon equivalent | Notes |
| - | - | - |
| `GET /trade-api/v2/markets` | [`GET /v2/kalshi/markets`](/api-reference/kalshi/list-markets) | Same data, no Kalshi auth required (we proxy) |
| `GET /trade-api/v2/markets/{ticker}/trades` | [`GET /v2/kalshi/trades?ticker={...}`](/api-reference/kalshi/trades) | Historical trade tape |
| `GET /trade-api/v2/markets/{ticker}/orderbook` | [`GET /v2/kalshi/orderbooks`](/api-reference/kalshi/orderbooks) | Historical snapshots |
Predexon is data-only — execution stays on Kalshi's native API; use Predexon for everything you read.
***
## Migration checklist
The [Quickstart](/start-here/get-started) gets you running in 5 min. Diff one of your real queries against the Predexon equivalent to confirm fields match.
Don't big-bang the migration. Replace one endpoint, ship, verify metrics. Repeat.
See [Best Practices](/start-here/best-practices). Caching is usually a 10x rate-limit relief on day one.
If you were polling Polymarket's CLOB every second, the equivalent WebSocket subscription is cheaper and faster.
The point is to delete code, not accumulate it.
***
## Need a translation that's not here?
We've helped teams migrate from a dozen different stacks. If you're stuck on a specific query or shape, [drop into Discord](https://discord.com/invite/dxwzMcs8hX) — fastest answers there.
# Quant & data trader
Source: https://docs.predexon.com/start-here/quant-guide
From zero to a working backtest in 10 minutes. The data, signals, and tools quants reach for first.
You're here to find edges in prediction-market data. This guide gets you from API key to a runnable backtest, then points you at the deeper material.
**The most-requested quant dataset: [orderbook tick history](/data-signals/ticks/data-credits).** Every placement, update, cancel, and fill as bulk Parquet — the raw stream behind queue-position modeling, fill-probability estimation, and full book replay. Quote any slice for free; \$50 in credits on signup.
**You'll be ready to:**
* Pull historical orderbook + trade + candle data for any market
* Reconstruct any wallet's full position history and P\&L
* Build smart-money signals from the leaderboard endpoint
* Stream live signals (trades, pending trades, orderbook) over WebSocket
***
## 10 minutes to a backtest
Goal: pull 30 days of orderbook snapshots for the highest-volume open market, plus the trade tape, plus the candle series — enough to run a real backtest.
```python theme={null}
import os, requests, pandas as pd, time
API_KEY = os.environ["PREDEXON_API_KEY"]
BASE = "https://api.predexon.com"
HEADERS = {"x-api-key": API_KEY}
# 1. Find the highest-volume open market
top = requests.get(
f"{BASE}/v2/polymarket/markets",
headers=HEADERS,
params={"status": "open", "sort": "volume", "limit": 1},
).json()["markets"][0]
condition_id = top["condition_id"]
token_id = top["outcomes"][0]["token_id"] # "Yes" side
print(f"Backtesting: {top['title']}")
# 2. Pull 30 days of hourly candles (seconds for candles)
end_s = int(time.time())
start_s = end_s - 30 * 24 * 3600
candles = requests.get(
f"{BASE}/v2/polymarket/candlesticks/{condition_id}",
headers=HEADERS,
params={"interval": 60, "start_time": start_s, "end_time": end_s},
).json()
candles_df = pd.DataFrame(candles)
print(f"Candles: {len(candles_df)} bars")
# 3. Pull the trade tape (seconds for trades)
trades = requests.get(
f"{BASE}/v2/polymarket/trades",
headers=HEADERS,
params={"token_id": token_id, "start_time": start_s, "end_time": end_s, "limit": 1000},
).json()
print(f"Trades: {len(trades)}")
# 4. Walk historical orderbook snapshots (milliseconds for orderbook)
end_ms = end_s * 1000
start_ms = start_s * 1000
snapshots, cursor = [], start_ms
while cursor < end_ms:
page = requests.get(
f"{BASE}/v2/polymarket/orderbooks",
headers=HEADERS,
params={"token_id": token_id, "start_time": cursor, "end_time": end_ms, "limit": 200},
).json()
if not page: break
snapshots.extend(page)
cursor = page[-1]["timestamp"] + 1
print(f"Orderbook snapshots: {len(snapshots)}")
```
That's three free endpoints, no rate-limit risk, \~30 lines of code, ready for pandas/numpy/whatever your strategy framework is.
Every endpoint used here is **free and unlimited**. Run as many backtests as you want.
***
## The data tools quants reach for first
Per-token L2 snapshots from Jan 1 2026. Simulate fills against the real book — see [Orderbook Replay](/data-signals/backtesting/orderbook-replay) for the pattern.
OHLCV at 1m / 5m / 15m / 1h / 4h / 1d. By condition (market-level) or per-token (each outcome). Pair with trades for VWAP reconciliation.
Every fill on Polymarket and Kalshi. Filter by token, wallet, time range. Ground truth for execution simulation.
Realized + unrealized P\&L time series for any wallet. The basis for smart-money cohort backtests.
Net positioning of profitable wallets in any market right now. Great for confirmation signals.
Top wallets globally or per-market, sortable by realized profit, ROI, volume, or win rate.
The raw tick stream at every granularity as bulk Parquet — queue position, fill probability, and book replay.
Mempool detection of fills before they confirm — up to 5 seconds ahead of Polymarket's RTDS. Used by latency-sensitive strategies.
***
## Backtesting walkthroughs
Three production-grade patterns we've written up:
Simulate fills against historical L2 state. The gold-standard for capacity-sensitive strategies.
Fast candle-driven backtests with trade-tape validation. Pass 1 is cheap; pass 2 keeps you honest.
How to honestly test smart-money, top-holders, and pending-trade signals before sizing into them.
***
## Going live
Once a strategy backtests well, swap REST polling for WebSocket streams using the same data shapes.
| What you backtested with | What you stream live with |
| - | - |
| `GET /v2/polymarket/trades` | [WS trades channel](/websocket/trades) |
| `GET /v2/polymarket/orderbooks` (snapshots) | [WS orderbook channel](/websocket/orderbook) (snapshots + deltas) |
| `GET /v2/polymarket/activity` | [WS activity channel](/websocket/activity) |
| Pending-trade replay (not available historically) | [WS pending-trades](/websocket/pending-trades) |
Read [WebSocket Overview](/websocket/overview) for connection patterns, subscription limits, and reconnect handling.
***
## Common quant recipes
Wallet leaderboard + smart-money endpoint + alert path.
Mempool WS → signal → your venue-native execution.
Wallet positions + P\&L + live fills for any address.
Subscribe wallet → mirror each fill. End-to-end.
***
## What you should read next
Full map of every data endpoint, organized by utility.
Pagination, retries, rate-limit handling, WebSocket reconnect logic.
# Activity Events
Source: https://docs.predexon.com/websocket/activity
Real-time split, merge, and redemption events
The activity channel (`"activity"`) delivers four event types: `split`, `merge`, `redeem`, and `conversion`. These represent position management operations on Polymarket's Conditional Token Framework.
**pUSD deposit/withdrawal events are on a separate [collateral channel](/websocket/collateral)** - not mixed into activity. Subscribe to both if you need full wallet flow.
`condition_id` is `null` only for the `conversion` event type (NegRisk conversions span multiple conditions). Clients with strict typed generators (`condition_id: string`) should widen to `string | null` if they want to consume conversion events. If you filter by `event_type` in `{"split", "merge", "redeem"}`, no change is needed.
## split
A user deposits collateral (USDC) to mint outcome tokens for a market.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_2f4b15b33798",
"data": {
"event_type": "split",
"user": "0x6d38b759c90c3158fa7b4fa45b0145eefd58d910",
"condition_id": "0x7fc83e5029c8e7b752f2fda61158f6cbc185c0a7b60d34d56231fcc09c62ce44",
"collateral_token": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
"amount": 100000000,
"amount_normalized": 100,
"payout": null,
"payout_normalized": null,
"tx_hash": "0x0708391d4276731737d14a388491afb8a9c2f5b133f597189993b3ebf9154c37",
"log_index": "0x355",
"block_number": "0x4ebcd4d",
"timestamp": 1770244769,
"title": "Bitcoin Up or Down - February 4, 5:45PM-6:00PM ET",
"market_slug": "btc-updown-15m-1770245100",
"market_id": "1329555",
"image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/BTC+fullsize.png",
"tokens": [
{ "token_id": "3461486922...", "label": "Up" },
{ "token_id": "9291673453...", "label": "Down" }
]
}
}
```
## merge
A user returns a full set of outcome tokens to redeem collateral (USDC) before market resolution.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_2f4b15b33798",
"data": {
"event_type": "merge",
"user": "0xe8dd7741ccb12350957ec71e9ee332e0d1e6ec86",
"condition_id": "0x4dab1a074a9dbfc122f49b1b4e683d0b9960738107623d2dfd974eb98865a020",
"collateral_token": null,
"amount": 40980000,
"amount_normalized": 40.98,
"payout": null,
"payout_normalized": null,
"tx_hash": "0xd198bdd0226e5225a103aaaa01cf38aac4bc34891dd8ba31832f454cb7d65da1",
"log_index": "0x54f",
"block_number": "0x4ebcd51",
"timestamp": 1770244777,
"title": "Will \"NUEVAYOL\" be played first at the Super Bowl halftime show?",
"market_slug": "will-nuevayol-be-played-first-at-the-super-bowl-halftime-show",
"market_id": "811834",
"image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/...",
"tokens": [
{ "token_id": "8405376573...", "label": "Yes" },
{ "token_id": "3675765791...", "label": "No" }
]
}
}
```
## redeem
A user redeems winning outcome tokens for collateral (USDC) after a market has resolved.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_2f4b15b33798",
"data": {
"event_type": "redeem",
"user": "0x1f4c2a421a36e5da4e013c557dc29fdec7cb2e01",
"condition_id": "0x6b6de56b0057be1b236ef4e52745ca1160497419789ffe3c6f0f95b12e195762",
"collateral_token": null,
"amount": 0,
"amount_normalized": 0,
"payout": 20000000,
"payout_normalized": 20,
"tx_hash": "0x8fbd25676f0b4d0a2af786716f406100f24492b1246bb16b580cb7a5aefd0931",
"log_index": "0x26d",
"block_number": "0x4ebcd4a",
"timestamp": 1770244763,
"title": "Will Sunderland AFC win on 2026-02-02?",
"market_slug": "epl-sun-bur-2026-02-02-sun",
"market_id": "1225150",
"image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/...",
"tokens": [
{ "token_id": "3331904137...", "label": "Yes" },
{ "token_id": "9751235886...", "label": "No" }
]
}
}
```
## conversion
A user converts "No" positions in a NegRisk event back to collateral. NegRisk conversions span multiple conditions, so `condition_id` is `null` - use `neg_risk_event_id` + `index_set` instead.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_2f4b15b33798",
"data": {
"event_type": "conversion",
"user": "0xbc5403011df159368c4bc716e07bcf858dfe2eec",
"condition_id": null,
"amount": 13074500,
"amount_normalized": 13.0745,
"tx_hash": "0xabc...",
"log_index": "0x548",
"block_number": "0x51e0002",
"timestamp": 1776824366,
"version": 2,
"neg_risk_event_id": "0x6375f96fe3910d68...",
"index_set": "320"
}
}
```
* `neg_risk_event_id`: the NegRiskAdapter's `marketId` identifying a cluster of related binary questions (distinct from Polymarket's numeric `market_id`).
* `index_set`: bitmap of which question indices within that event were converted. Decimal string of a `uint256`. Interpret by bit position: `indexSet & (1 << i)` → question `i` was included. Example `"320"` = bits 6 & 8 set.
* Routes on `activity.user.*` and `activity.wildcard` only (no `activity.condition.*` route since there's no single condition).
## Field Reference
| Field | Type | Description |
| - | - | - |
| `event_type` | `"split"` \| `"merge"` \| `"redeem"` \| `"conversion"` | |
| `user` | string | User address |
| `condition_id` | string \| null | Market condition ID. **`null` only for `conversion` events.** |
| `neg_risk_event_id` | string \| null | NegRiskAdapter marketId (present on `conversion` only) |
| `index_set` | string \| null | uint256 bitmap as decimal string (present on `conversion` only) |
| `collateral_token` | string \| null | Collateral token contract address (USDC) |
| `amount` | number | Collateral amount (raw, 6 decimal places) |
| `amount_normalized` | number | Human-readable collateral amount |
| `payout` | number \| null | Payout amount in raw units (redeem only) |
| `payout_normalized` | number \| null | Human-readable payout (redeem only) |
| `tokens` | array \| null | Outcome tokens for this market |
| `title` | string \| null | Market title |
| `market_slug` | string \| null | Market URL slug |
| `market_id` | string \| null | Internal market ID |
| `image` | string \| null | Market image URL |
| `tx_hash` | string | Transaction hash |
| `log_index` | string | Log index (hex) |
| `block_number` | string | Block number (hex) |
| `timestamp` | number | Unix timestamp in seconds |
| `version` | number \| null | `1` for V1 contracts, `2` for V2 contracts (currently always `1`) |
## Example: Tracking Redemptions
```javascript theme={null}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'event') {
const data = msg.data;
if (data.event_type === 'redeem') {
console.log(`Redemption: $${data.payout_normalized}`);
console.log(`Market: ${data.title}`);
console.log(`Wallet: ${data.user}`);
}
}
};
```
# Collateral Events
Source: https://docs.predexon.com/websocket/collateral
Real-time pUSD deposit and withdrawal events (V2)
The collateral channel (`"collateral"`) streams pUSD wrap/unwrap events - deposits and withdrawals of the V2 trading collateral. This channel is opt-in and separate from the activity channel.
Collateral events are **V2-only**. Users depositing via USDC.e on V1 won't appear here. Subscribe to the [activity channel](/websocket/activity) for V1 collateral flows via split/merge/redeem.
## Subscribe
```json theme={null}
{
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "collateral",
"filters": { "users": ["0x3b27d0fb..."] }
}
```
### Filter Types
| Filter | Description | Example |
| - | - | - |
| `users` | Wallet addresses | `["0x123...", "0xabc..."]` |
| Wildcard `["*"]` | All collateral events | `{"users": ["*"]}` |
`condition_ids`, `market_slugs`, and `token_ids` are **not supported** - collateral flow isn't tied to a specific market.
***
## Event Types
### polyusd\_wrapped
A user deposited underlying collateral (e.g. USDC.e) and received pUSD.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_xxx",
"data": {
"event_type": "polyusd_wrapped",
"user": "0x3b27d0fb...",
"asset": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174",
"amount": 45000000,
"amount_normalized": 45,
"tx_hash": "0x...",
"log_index": "0x9ed",
"block_number": "0x51c9418",
"timestamp": 1776638042,
"version": 2
}
}
```
### polyusd\_unwrapped
A user burned pUSD and received underlying collateral back.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_xxx",
"data": {
"event_type": "polyusd_unwrapped",
"user": "0x3b27d0fb...",
"asset": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174",
"amount": 10000000,
"amount_normalized": 10,
"tx_hash": "0x...",
"log_index": "0x3a2",
"block_number": "0x51c9500",
"timestamp": 1776638200,
"version": 2
}
}
```
***
## Field Reference
| Field | Type | Description |
| - | - | - |
| `event_type` | `"polyusd_wrapped"` \| `"polyusd_unwrapped"` | Wrap = deposit, Unwrap = withdrawal |
| `user` | string | End-recipient of the operation |
| `asset` | string | Underlying ERC-20 address (typically USDC.e `0x2791bca1...`) |
| `amount` | number | Raw amount (6 decimals, same as USDC) |
| `amount_normalized` | number | Human-readable amount |
| `tx_hash` | string | Transaction hash |
| `log_index` | string | Log index within the transaction (hex) |
| `block_number` | string | Block number (hex) |
| `timestamp` | number | Unix timestamp in seconds |
| `version` | number | Always `2` |
***
## Example: Tracking Wallet Deposits
```javascript theme={null}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'event' && msg.data.event_type === 'polyusd_wrapped') {
const data = msg.data;
console.log(`Deposit: $${data.amount_normalized} from ${data.user}`);
console.log(`Asset: ${data.asset}`);
console.log(`Tx: ${data.tx_hash}`);
}
if (msg.type === 'event' && msg.data.event_type === 'polyusd_unwrapped') {
const data = msg.data;
console.log(`Withdrawal: $${data.amount_normalized} to ${data.user}`);
}
};
```
# Crypto Prices
Source: https://docs.predexon.com/websocket/crypto-prices
Real-time crypto price ticks relayed from Chainlink Data Streams
The crypto-prices channel streams real-time price ticks from [Chainlink Data Streams](https://docs.chain.link/data-streams) (Mercury). One tick per upstream observation - no batching, no aggregation. Use this for charting, signals, or as a price reference alongside the on-chain Polymarket channels.
This is the only channel that uses `platform: "chainlink"`. All other channels use `platform: "polymarket"`.
## Subscribe
```json theme={null}
{
"action": "subscribe",
"platform": "chainlink",
"version": 1,
"type": "crypto",
"filters": { "feeds": ["BTC/USD", "ETH/USD"] }
}
```
### Filter Types
| Filter | Description | Example |
| - | - | - |
| `feeds` | One or more feed symbols | `["BTC/USD", "SOL/USD"]` |
| Wildcard `["*"]` | All entitled feeds | `{"feeds": ["*"]}` |
`users`, `token_ids`, `condition_ids`, and `market_slugs` are **not supported** - crypto prices aren't tied to wallets or markets. Subscribing with any of those returns `INVALID_FILTERS`.
### Supported Feeds
| Symbol | Asset |
| - | - |
| `BTC/USD` | Bitcoin |
| `ETH/USD` | Ethereum |
| `SOL/USD` | Solana |
| `XRP/USD` | XRP |
| `BNB/USD` | BNB |
| `DOGE/USD` | Dogecoin |
| `HYPE/USD` | Hyperliquid |
***
## Event Type
### price\_feed
A single price tick from one of the subscribed feeds.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_xxx",
"data": {
"event_type": "price_feed",
"feed": "BTC/USD",
"price": 66225.49,
"bid": 66221.69,
"ask": 66229.46,
"observed_at": 1774672588,
"timestamp": 1774672588
}
}
```
***
## Field Reference
| Field | Type | Description |
| - | - | - |
| `event_type` | `"price_feed"` | Always `price_feed` |
| `feed` | string | Feed symbol (e.g. `"BTC/USD"`) |
| `price` | number | Mid / benchmark price reported by Chainlink |
| `bid` | number | Bid price |
| `ask` | number | Ask price |
| `observed_at` | number | Upstream observation timestamp (Unix seconds) |
| `timestamp` | number | Same as `observed_at` |
Chainlink reports prices as 1e18 fixed-point integers; Predexon converts to floating-point with 8 decimal places of precision before publishing.
***
## Example: Track Multiple Feeds
```javascript theme={null}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'event' && msg.data.event_type === 'price_feed') {
const { feed, price, bid, ask, observed_at } = msg.data;
const spread = ask - bid;
console.log(`[${feed}] $${price.toFixed(2)} bid=${bid.toFixed(2)} ask=${ask.toFixed(2)} spread=${spread.toFixed(4)}`);
}
};
ws.send(JSON.stringify({
action: 'subscribe',
platform: 'chainlink',
version: 1,
type: 'crypto',
filters: { feeds: ['BTC/USD', 'ETH/USD', 'SOL/USD'] }
}));
```
# Lifecycle Events
Source: https://docs.predexon.com/websocket/lifecycle
Real-time token registrations and market resolutions
The lifecycle channel (`"lifecycle"`) delivers three event types: `condition_prepared`, `token_registered`, and `condition_resolution`.
**V1 vs V2:** `condition_prepared` fires for both V1 and V2 markets (new market signal from `ConditionalTokens`). `token_registered` is **V1-only** - V2 exchanges no longer emit it. Use `condition_prepared` as your "new market" signal to cover both. See the [V2 Migration Guide](/websocket/v2-migration).
## condition\_prepared
Emitted when a new condition is created on the shared `ConditionalTokens` contract. Fires for both V1 and V2 markets.
For **binary markets** (`outcome_slot_count === 2`), token IDs are derived deterministically from `(condition_id, is_neg_risk)` using the CTF Gnosis formula and included in the `tokens` array. For non-binary markets, `tokens` is `null` - query Gamma or CLOB by `condition_id` to resolve them.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_2f4b15b33798",
"data": {
"event_type": "condition_prepared",
"condition_id": "0xa7ff1749...",
"oracle": "0x65070be91477460d8a7aeeb94ef92fe056c2f2a7",
"question_id": "0xb5bd0abc...",
"tx_hash": "0xabc...",
"log_index": "0xe5",
"block_number": "0x51bfb94",
"timestamp": 1776559955,
"title": null,
"market_slug": null,
"market_id": null,
"image": null,
"tokens": [
{ "token_id": "1234567890...", "label": "Yes" },
{ "token_id": "9876543210...", "label": "No" }
],
"is_neg_risk": false,
"outcome_slot_count": 2
}
}
```
* Metadata fields (`title`, `market_slug`, `market_id`, `image`) are always `null` at prepare time.
* `tokens` is populated for binary markets (`outcome_slot_count === 2`) with derived token IDs. `null` otherwise.
* **Token labels** (`"Yes"` / `"No"`) are **placeholders** - clients should resolve canonical labels via Gamma/CLOB once market metadata is indexed.
* `is_neg_risk` is derived from the oracle address (`true` if oracle = `NegRiskAdapter`).
* `outcome_slot_count` is the number of outcomes for the condition (typically `2`).
## token\_registered
**V1 only.** V2 exchanges no longer emit `TokenRegistered`. Use `condition_prepared` above for new market signals that cover both V1 and V2.
Emitted when a new V1 market's outcome tokens are registered on-chain. Metadata fields (`title`, `market_slug`, etc.) may be null for brand-new markets since off-chain metadata may not yet be available.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_2f4b15b33798",
"data": {
"event_type": "token_registered",
"condition_id": "0x197047c17a5111889b1090b979105a8822774a8f173085f0703f311eeff91804",
"oracle": null,
"question_id": null,
"tx_hash": "0xbdb667d47544cd25dfe8a03e53a287cb6cc073ae502887efbd07ac15120aa9f6",
"log_index": "0x1b7",
"block_number": "0x4ebcd5f",
"timestamp": 1770244805,
"title": null,
"market_slug": null,
"market_id": null,
"image": null,
"tokens": [
{ "token_id": "2505367458...", "label": "Unknown" },
{ "token_id": "3237569069...", "label": "Unknown" }
],
"is_neg_risk": false
}
}
```
## condition\_resolution
Emitted when a market resolves. The `tokens` array includes a `won` field indicating which outcome won. For voided or non-standard resolutions, the `is_invalid` field is set to `true` and all token `won` fields are `false`.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_2f4b15b33798",
"data": {
"event_type": "condition_resolution",
"condition_id": "0x6b6de56b0057be1b236ef4e52745ca1160497419789ffe3c6f0f95b12e195762",
"oracle": "0x6a9d222616c90fcd177a525e9fa661a564a67b14",
"question_id": "0x1234...",
"tx_hash": "0x...",
"log_index": "0x...",
"block_number": "0x...",
"timestamp": 1770244800,
"title": "Will Sunderland AFC win on 2026-02-02?",
"market_slug": "epl-sun-bur-2026-02-02-sun",
"market_id": "1225150",
"image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/...",
"is_neg_risk": false,
"is_invalid": false,
"tokens": [
{ "token_id": "3331904137...", "label": "Yes", "won": true },
{ "token_id": "9751235886...", "label": "No", "won": false }
]
}
}
```
### `is_invalid` behavior
When Polymarket resolves a market with a non-standard payout vector (anything other than `[0,1]` or `[1,0]`), the event includes `is_invalid: true` and all token `won` fields are set to `false`. This covers voided markets, split resolutions (e.g. 50/50 refunds), and other non-standard outcomes.
| Payout vector | `is_invalid` | Token `won` values | Meaning |
| - | - | - | - |
| `[1, 0]` | `false` | `[true, false]` | Normal resolution - first token wins |
| `[0, 1]` | `false` | `[false, true]` | Normal resolution - second token wins |
| `[1, 1]` | `true` | `[false, false]` | Voided/split - 50/50 refund |
| `[0, 0]` | `true` | `[false, false]` | Invalid resolution |
| Any other | `true` | `[false, false]` | Non-standard payout |
**Breaking semantic change:** Previously, voided markets showed `won: true` on both tokens. Now they show `won: false` on both tokens with `is_invalid: true`. Clients that relied on the old behavior should update to check `is_invalid`.
## Field Reference
| Field | Type | Description |
| - | - | - |
| `event_type` | `"condition_prepared"` \| `"token_registered"` \| `"condition_resolution"` | |
| `condition_id` | string | Market condition ID |
| `oracle` | string \| null | Oracle address (resolution only) |
| `question_id` | string \| null | Question ID (resolution only) |
| `is_neg_risk` | boolean | Whether this is a neg-risk market |
| `is_invalid` | boolean | `true` when the market resolved with a non-standard payout (resolution only) |
| `tokens` | array \| null | Outcome tokens; includes `won` boolean on resolution events |
| `title` | string \| null | Market title |
| `market_slug` | string \| null | Market URL slug |
| `market_id` | string \| null | Internal market ID |
| `image` | string \| null | Market image URL |
| `tx_hash` | string | Transaction hash |
| `log_index` | string | Log index (hex) |
| `block_number` | string | Block number (hex) |
| `timestamp` | number | Unix timestamp in seconds |
| `outcome_slot_count` | number \| null | Number of outcomes (present on `condition_prepared` only) |
| `version` | number \| null | `1` for V1 contracts, `2` for V2 contracts |
## Example: Monitoring Resolutions
```javascript theme={null}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'event' && msg.data.event_type === 'condition_resolution') {
const data = msg.data;
if (data.is_invalid) {
console.log(`Market voided/invalid: ${data.title}`);
console.log(`Condition: ${data.condition_id}`);
return;
}
const winner = data.tokens.find(t => t.won);
console.log(`Market resolved: ${data.title}`);
console.log(`Winner: ${winner?.label}`);
console.log(`Condition: ${data.condition_id}`);
}
};
```
# Oracle Events
Source: https://docs.predexon.com/websocket/oracle
Real-time UMA oracle events: proposals, disputes, settlements, and resets for Polymarket markets
The oracle channel (`"oracle"`) streams real-time UMA oracle resolution events for Polymarket markets. Track when outcomes are proposed, disputed, settled, or reset.
Polymarket uses the [UMA Optimistic Oracle](https://docs.polymarket.com/concepts/resolution) to resolve markets. The process follows a propose-challenge-settle cycle: a proposer submits an outcome with a bond, a 2-hour challenge window opens, and if undisputed the outcome is accepted. Disputes escalate to UMA's voting mechanism.
## Subscribe
```json theme={null}
{
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "oracle",
"filters": {
"condition_ids": ["0x4c57..."]
}
}
```
### Filter Types
| Filter | Description | Example |
| - | - | - |
| `condition_ids` | Market condition IDs | `["0x4c57..."]` |
| `market_slugs` | Market URL slugs | `["will-x-happen-by-april-7"]` |
Use `["*"]` for the full firehose of all oracle events (in either filter).
`users` and `token_ids` filters are not supported on the oracle channel.
***
## Event Types
### proposal
Outcome proposed. A 2-hour liveness window starts, during which the proposal can be disputed.
Frequency: \~6,800/day
```json theme={null}
{
"type": "event",
"subscription_id": "sub_xxx",
"data": {
"event_type": "proposal",
"condition_id": "0x4c57...",
"market_id": "123456",
"title": "Will X happen by April 7?",
"market_slug": "will-x-happen-by-april-7",
"image": "https://...",
"outcomes": ["Yes", "No"],
"tokens": [{"token_id": "82855...", "label": "Yes"}, {"token_id": "55194...", "label": "No"}],
"is_neg_risk": false,
"proposer": "0xabc...",
"proposed_price": "1",
"expiration_timestamp": 1775204004,
"request_timestamp": 1775196804,
"timestamp": 1775196804
}
}
```
***
### settled
Oracle accepted the proposed answer. The market is resolving.
Frequency: \~6,600/day
```json theme={null}
{
"type": "event",
"subscription_id": "sub_xxx",
"data": {
"event_type": "settled",
"condition_id": "0x4c57...",
"market_id": "123456",
"title": "Will X happen by April 7?",
"market_slug": "will-x-happen-by-april-7",
"image": "https://...",
"outcomes": ["Yes", "No"],
"tokens": [{"token_id": "82855...", "label": "Yes"}, {"token_id": "55194...", "label": "No"}],
"is_neg_risk": false,
"proposer": "0xabc...",
"disputer": null,
"settled_price": "1",
"payout": "752000000",
"request_timestamp": 1775196804,
"timestamp": 1775204005
}
}
```
***
### dispute
Proposal challenged. Rare but high signal.
Frequency: \~15/day
```json theme={null}
{
"type": "event",
"subscription_id": "sub_xxx",
"data": {
"event_type": "dispute",
"condition_id": "0x4c57...",
"market_id": "123456",
"title": "Will X happen by April 7?",
"market_slug": "will-x-happen-by-april-7",
"image": "https://...",
"outcomes": ["Yes", "No"],
"tokens": [{"token_id": "82855...", "label": "Yes"}, {"token_id": "55194...", "label": "No"}],
"is_neg_risk": false,
"proposer": "0xabc...",
"disputer": "0xdef...",
"proposed_price": "1",
"request_timestamp": 1775196804,
"timestamp": 1775200405
}
}
```
***
### reset
Dispute succeeded. The market re-enters the proposal phase.
Frequency: \~15/day
```json theme={null}
{
"type": "event",
"subscription_id": "sub_xxx",
"data": {
"event_type": "reset",
"condition_id": "0x4c57...",
"question_id": "0x9f8e...",
"title": "Will X happen by April 7?",
"market_slug": "will-x-happen-by-april-7",
"image": "https://...",
"outcomes": ["Yes", "No"],
"tokens": [{"token_id": "82855...", "label": "Yes"}, {"token_id": "55194...", "label": "No"}],
"is_neg_risk": false,
"request_timestamp": 1775196804,
"timestamp": 1775200406
}
}
```
***
## Field Reference
### Common Fields (all event types)
| Field | Type | Description |
| - | - | - |
| `event_type` | string | `"proposal"`, `"settled"`, `"dispute"`, or `"reset"` |
| `condition_id` | string | Market condition ID (hex) |
| `title` | string | Market question text |
| `market_slug` | string | Market URL slug |
| `image` | string | Market image URL |
| `outcomes` | string\[] | Outcome labels (e.g. `["Yes", "No"]`) |
| `tokens` | object\[] | Token IDs with labels (`token_id`, `label`) |
| `is_neg_risk` | boolean | Whether the market uses neg-risk framework |
| `timestamp` | number | When Predexon processed the event (Unix s) |
| `request_timestamp` | number | When the original price request was made (Unix s) |
### Event-Specific Fields
| Field | Type | Events | Description |
| - | - | - | - |
| `market_id` | string | proposal, dispute, settled | Polymarket market ID |
| `question_id` | string | reset | UMA question ID |
| `proposer` | string | proposal, dispute, settled | Address that proposed the outcome |
| `disputer` | string\|null | dispute, settled | Address that disputed. Present but `null` in settled when undisputed. |
| `proposed_price` | string | proposal, dispute | Proposed outcome: `"1"` = Yes, `"0"` = No |
| `settled_price` | string | settled | Final resolved outcome: `"1"` = Yes, `"0"` = No |
| `expiration_timestamp` | number | proposal | 2-hour liveness window end (Unix s) |
| `payout` | string | settled | Bond payout amount (uint256) |
***
## Unsubscribe
To stop receiving oracle events, send an unsubscribe with the subscription ID from the original `ack`:
```json theme={null}
{
"action": "unsubscribe",
"subscription_id": "sub_xxx"
}
```
See [Subscriptions](/websocket/subscriptions) for full details on subscription management, including updating filters on an existing subscription.
# Orderbook Stream
Source: https://docs.predexon.com/websocket/orderbook
Real-time L2 orderbook data: price changes, trades, and book snapshots for Polymarket markets
The orderbook channel (`"orderbook"`) streams real-time off-chain orderbook data from Polymarket's CLOB. Subscribe by `token_ids`, `condition_ids`, or `market_slugs`, or use wildcard (`["*"]`) for the full firehose of all 100k+ tokens.
* **Targeted subscriptions** deliver individual events as they happen. Latency is essentially identical to Polymarket's native feeds.
* **Wildcard subscriptions** deliver 250ms batched events with price-level conflation.
## Subscribe
```json theme={null}
{
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "orderbook",
"filters": {
"market_slugs": ["us-x-iran-ceasefire-by-april-7-278"]
}
}
```
### Filter Types
Exactly one filter type per subscribe message.
| Filter | Description | Example |
| - | - | - |
| `token_ids` | CLOB token IDs | `["82855...", "55194..."]` |
| `condition_ids` | Market condition IDs | `["0x4c57..."]` |
| `market_slugs` | Market URL slugs | `["bitcoin-100k-2026"]` |
Use `["*"]` with any filter type for the full firehose.
When subscribing by `market_slugs` or `condition_ids`, both sides of the market (Yes + No) are resolved and subscribed automatically. You'll receive a `book_snapshot` for each outcome. When subscribing by `token_ids`, you receive only the specific outcome(s) you subscribed to.
`users` filter is not supported on the orderbook channel. Use the [trades channel](/websocket/trades) for wallet-level filtering.
***
## Connection Lifecycle
### Targeted Subscriptions
```
→ subscribe
← ack
← book_snapshot (Yes side)
← book_snapshot (No side)
← snapshots_done
← price_change events (individual, as they happen)
← last_trade events
← tick_size_change events
```
### Wildcard / Firehose
```
→ subscribe
← ack
← event_batch (every ~250ms, conflated)
← event_batch
← ...
```
***
## Event Types
### book\_snapshot
Sent on subscribe. Includes full orderbook state and market metadata for enrichment.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_xxx",
"data": {
"event_type": "book_snapshot",
"token_id": "82855...",
"condition_id": "0x4c57...",
"slug": "us-x-iran-ceasefire-by-april-7-278",
"question": "US x Iran ceasefire by April 7?",
"event_title": "US x Iran ceasefire by...?",
"outcome": "Yes",
"timestamp": 1775196804071,
"bids": [{"price": "0.02", "size": "5000"}, ...],
"asks": [{"price": "0.98", "size": "3000"}, ...],
"hash": "abc123..."
}
}
```
`question` and `event_title` are included only in `book_snapshot` events. Live events (`price_change`, `last_trade`, `tick_size_change`) omit these for bandwidth efficiency. Use the snapshot for display metadata.
***
### price\_change
Live delta for a single price level. Size `"0"` means the level was removed; otherwise upsert (add or update).
```json theme={null}
{
"type": "event",
"subscription_id": "sub_xxx",
"data": {
"event_type": "price_change",
"token_id": "82855...",
"condition_id": "0x4c57...",
"slug": "us-x-iran-ceasefire-by-april-7-278",
"outcome": "Yes",
"timestamp": 1775196805000,
"side": "BUY",
"price": "0.03",
"size": "71891.78",
"best_bid": "0.03",
"best_ask": "0.97"
}
}
```
#### Field Reference
| Field | Type | Description |
| - | - | - |
| `event_type` | string | `"price_change"` |
| `token_id` | string | CLOB token ID |
| `condition_id` | string | Market condition ID (hex) |
| `slug` | string | Market URL slug |
| `outcome` | string | `"Yes"` or `"No"` |
| `timestamp` | number | Polymarket server timestamp (Unix ms) |
| `side` | string | `"BUY"` (bid) or `"SELL"` (ask) |
| `price` | string | Price level (0 to 1) |
| `size` | string | Size at this level (`"0"` = removed) |
| `best_bid` | string | Current best bid price for this token |
| `best_ask` | string | Current best ask price for this token |
***
### last\_trade
Trade execution event.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_xxx",
"data": {
"event_type": "last_trade",
"token_id": "82855...",
"condition_id": "0x4c57...",
"slug": "us-x-iran-ceasefire-by-april-7-278",
"outcome": "Yes",
"timestamp": 1775196806000,
"side": "BUY",
"price": "0.46",
"size": "219.22",
"fee_rate_bps": "0"
}
}
```
***
### tick\_size\_change
Emitted when the market's tick size changes.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_xxx",
"data": {
"event_type": "tick_size_change",
"token_id": "82855...",
"condition_id": "0x4c57...",
"slug": "us-x-iran-ceasefire",
"outcome": "Yes",
"timestamp": 1775196807000,
"tick_size": "0.001"
}
}
```
***
### snapshots\_done
Marker sent after all initial snapshots have been delivered. `count` is the number of `book_snapshot` events that preceded it.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_xxx",
"data": {
"event_type": "snapshots_done",
"count": 2
}
}
```
***
### event\_batch
Firehose only. Batched every \~250ms with price-level conflation. Each element in the `events` array has the same shape as the `data` field in individual events (`price_change`, `last_trade`, `tick_size_change`, etc.).
```json theme={null}
{
"type": "event_batch",
"subscription_id": "sub_xxx",
"count": 350,
"events": [{...}, {...}, ...],
"gap": false
}
```
If `gap: true`, events were missed during a reconnect. Client should rebuild state from the next snapshot.
***
### resync
Sent after an internal reconnect. Signals the client to discard local state and rebuild.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_xxx",
"data": {
"event_type": "resync"
}
}
```
After `resync`, fresh `book_snapshot` events and a `snapshots_done` follow automatically.
***
## Unsubscribe
To stop receiving orderbook events, send an unsubscribe with the subscription ID from the original `ack`:
```json theme={null}
{
"action": "unsubscribe",
"subscription_id": "sub_xxx"
}
```
Updating orderbook subscriptions is not supported. To change filters, unsubscribe and resubscribe.
See [Subscriptions](/websocket/subscriptions) for full details on subscription management.
***
## New Market Timing
Newly created markets are available within a few seconds of creation on Polymarket. Book snapshots may be empty initially. Live deltas will populate as the market becomes active.
# Overview
Source: https://docs.predexon.com/websocket/overview
Real-time Polymarket trade, activity, and lifecycle events via WebSocket
Real-time streaming of Polymarket on-chain events including trades, position activity, market lifecycle, orderbook updates, and oracle events.
WebSocket requires a **Dev plan or higher** (\$49/mo). [Get your API key →](https://dashboard.predexon.com)
***
## Use Cases
Subscribe to top traders' wallets and mirror their positions in real time.
Track specific markets for price movements and trading activity.
Monitor wallets for trade confirmations, splits, merges, and redemptions.
Stream all events with wildcard subscriptions to build datasets.
Subscribe to real-time depth and price level changes via the orderbook channel.
Detect trades 3–5 seconds early with pending trade events from the Polygon mempool.
***
## Quick Start
```javascript theme={null}
const ws = new WebSocket('wss://wss.predexon.com/v1/your_api_key'); // [!code highlight]
ws.onopen = () => {
ws.send(JSON.stringify({
action: 'subscribe',
platform: 'polymarket',
version: 1,
type: 'orders', // [!code highlight]
filters: { users: ['0x1234...'] }
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'event') {
console.log('Trade:', msg.data);
}
};
```
***
## Connection
**Endpoint:**
```
wss://wss.predexon.com/v1/YOUR_API_KEY
```
On success:
```json theme={null}
{ "type": "connected", "message": "Connected to Predexon WebSocket" }
```
| HTTP Status | Reason |
| - | - |
| 401 | Missing or invalid API key |
| 403 | WebSocket requires Dev plan or higher |
| 429 | Connection limit exceeded |
| 503 | Server at capacity |
***
## Channels
| Channel | Platform | `type` value | Description |
| - | - | - | - |
| **Trades** | `polymarket` | `"orders"` | Order fills and fee refunds (V1 + V2). Supports [pending events](/websocket/pending-trades). |
| **Activity** | `polymarket` | `"activity"` | Position splits, merges, redemptions, and NegRisk conversions |
| **Lifecycle** | `polymarket` | `"lifecycle"` | New market creation (`condition_prepared`), token registration (V1), and resolution |
| **Orderbook** | `polymarket` | `"orderbook"` | Real-time L2: price changes, trades, book snapshots |
| **Oracle** | `polymarket` | `"oracle"` | UMA events: proposals, disputes, settlements, resets |
| **Collateral** | `polymarket` | `"collateral"` | pUSD wrap/unwrap events (V2 deposits and withdrawals) |
| **Crypto Prices** | `chainlink` | `"crypto"` | [Chainlink Data Streams](/websocket/crypto-prices) crypto price ticks (BTC, ETH, SOL, XRP, BNB, DOGE, HYPE) |
### Filter Availability
| Filter | Trades | Activity | Lifecycle | Orderbook | Oracle | Collateral | Crypto Prices |
| - | :-: | :-: | :-: | :-: | :-: | :-: | :-: |
| `users` | Yes | Yes | - | - | - | Yes | - |
| `token_ids` | - | - | - | Yes | - | - | - |
| `condition_ids` | Yes | Yes | Yes | Yes | Yes | - | - |
| `market_slugs` | Yes | - | - | Yes | Yes | - | - |
| `feeds` | - | - | - | - | - | - | Yes |
| Wildcard `["*"]` | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
### Timestamp Units
| Channel | Unit |
| - | - |
| Orderbook | **milliseconds** (Polymarket server) |
| All others | **seconds** (Predexon normalizer) |
***
## Plan Limits
| Limit | Dev | Pro | Enterprise |
| - | - | - | - |
| Subscriptions / connection | 10 | 100 | Custom |
| Items / subscription | 10 | 500 | Custom |
| Total items | 100 | 50,000 | Custom |
| Wildcard subscriptions | - | 2 per channel | Custom |
| Priority routing | - | - | Yes |
Limits are global across all channels. Wildcard connections are tracked per-channel.
A wildcard connection cannot mix wildcard and regular subscriptions on the same channel.
***
## Keepalive & Connection Management
* Server sends a **ping every 30 seconds**; pong required within 60 seconds
* Idle connections (zero subscriptions) closed after **2 minutes** (close code `4000`)
* Buffer limits: events dropped at **1 MB**, connection terminated at **4 MB**
***
## Error Handling
```json theme={null}
{ "type": "error", "code": "ERROR_CODE", "message": "Human-readable message" }
```
| Code | Description |
| - | - |
| `AUTH_REQUIRED` | No API key provided |
| `AUTH_FAILED` | API key not found or invalid |
| `CONNECTION_LIMIT` | Server-wide cap reached |
| `WILDCARD_CONNECTION_LIMIT` | Wildcard limit exceeded |
| `SUBSCRIPTION_LIMIT` | Max subscriptions reached |
| `ITEMS_PER_SUB_LIMIT` | Too many items in one subscription |
| `ITEMS_LIMIT` | Max total items reached |
| `WILDCARD_NOT_ALLOWED` | Plan doesn't support wildcards |
| `INVALID_FILTERS` | Missing/invalid filter for this channel |
| `SUBSCRIPTION_NOT_FOUND` | Subscription ID doesn't exist |
| `PARSE_ERROR` | Invalid JSON |
| `UNKNOWN_ACTION` | Unrecognized action |
| `PLAN_REQUIRED` | Feature requires a higher plan |
| `RATE_LIMIT` | Sending messages too fast |
# Pending Trades
Source: https://docs.predexon.com/websocket/pending-trades
Get trade events 3–5 seconds early from the Polygon mempool
Pending trade events are available on Dev, Pro, and Enterprise plans.
Pending events are detected from the Polygon mempool **before** the transaction is mined. They arrive on average **\~3 seconds** before the corresponding confirmed event, typically **3–5 seconds** early.
This gives you an early signal for copytrading, alerting, or analytics - before the trade is finalized on-chain.
## How It Works
1. A trade transaction enters the Polygon mempool
2. Predexon detects it and emits a **pending** event
3. \~3–5 seconds later, the transaction is mined and a **confirmed** event is emitted
4. Match the two events by `tx_hash` - the confirmed event supersedes the pending one
## Enabling Pending Events
Add a `status` filter to your orders subscription. There are three modes:
| `status` Value | What You Receive | Plan Required |
| - | - | - |
| `"confirmed"` (default) | Only confirmed (mined) events | Any plan |
| `"all"` | Both pending + confirmed events | **Dev+** |
| `"pending"` | Only pending (mempool) events | **Dev+** |
Omitting `status` defaults to `"confirmed"` - works on all tiers.
Free-tier keys cannot request pending events. Subscribing with `status: "all"` or `status: "pending"` on Free returns `PLAN_REQUIRED`. Free tier continues to receive confirmed events on the default (`"confirmed"`) as before.
### Subscribe to both pending and confirmed
```json theme={null}
{
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "orders",
"filters": {
"users": ["*"],
"status": "all"
}
}
```
### Subscribe to pending only
```json theme={null}
{
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "orders",
"filters": {
"users": ["*"],
"status": "pending"
}
}
```
### Update an existing subscription
```json theme={null}
{
"action": "update",
"subscription_id": "sub_abc123",
"filters": {
"users": ["*"],
"status": "all"
}
}
```
***
## What Differs Between Pending and Confirmed
Every `order_filled` event includes a `status` field (`"pending"` or `"confirmed"`). Most fields are identical, but a few differ:
| Field | Pending | Confirmed | Why |
| - | - | - | - |
| `status` | `"pending"` | `"confirmed"` | |
| `price` | May differ slightly | Exact | Can differ by a fraction of a cent if partial fills change the effective price |
| `shares` / `shares_normalized` | May differ slightly | Exact | If the tx partially fills, final on-chain amounts may differ |
| `fee` | Estimated | Exact | Actual fee may vary slightly after on-chain settlement |
| `log_index` | `null` | Integer | No log exists yet for pending |
| `order_hash` | String | String | Now included in both pending and confirmed events |
| `timestamp` | Time seen in mempool | Block timestamp | |
| `tx_hash` | Exact | Exact | Same hash - use this to match pending → confirmed |
All other fields - `user`, `taker`, `token_id`, `side`, and all metadata (market slug, title, outcome, etc.) - are **identical** between pending and confirmed.
***
## Important Caveats
* **Not guaranteed to confirm** - some pending transactions may be dropped, reverted, or replaced. Treat `status: "pending"` as a strong signal, not a guarantee.
* **Only `order_filled` events** - pending detection applies to trade fills only. Fee refunds, activity, and lifecycle events remain confirmed-only.
* **Match by `tx_hash`** - when the confirmed event arrives, it supersedes the pending one. Use `tx_hash` to correlate them.
***
## Example: Handling Both Pending and Confirmed
```javascript theme={null}
const pendingTrades = new Map(); // tx_hash → pending data
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'event' && msg.data.event_type === 'order_filled') {
const data = msg.data;
if (data.status === 'pending') {
// Early signal from mempool - act on it, but expect confirmation
pendingTrades.set(data.tx_hash, data);
console.log(`[PENDING] ${data.side} ${data.outcome} ${data.shares_normalized} @ ${data.price}`);
console.log(` Market: ${data.title}`);
console.log(` Maker: ${data.user}`);
}
if (data.status === 'confirmed') {
const wasPending = pendingTrades.delete(data.tx_hash);
const label = wasPending ? '[CONFIRMED]' : '[NEW]';
const value = data.shares_normalized * data.price;
console.log(`${label} ${data.side} ${data.outcome} ${data.shares_normalized} @ ${data.price}`);
console.log(` Market: ${data.title}`);
console.log(` Value: $${value.toFixed(2)}`);
}
}
};
```
# Activity Playground
Source: https://docs.predexon.com/websocket/playground-activity
Position splits, merges, and redemptions on Polymarket.
# Collateral Playground
Source: https://docs.predexon.com/websocket/playground-collateral
pUSD wrap/unwrap events (V2). Tracks deposits and withdrawals of the V2 trading collateral.
# Crypto Prices Playground
Source: https://docs.predexon.com/websocket/playground-crypto-prices
Real-time crypto price ticks relayed from Chainlink Data Streams (Mercury). One tick per upstream observation.
# Lifecycle Playground
Source: https://docs.predexon.com/websocket/playground-lifecycle
Market creation (token_registered) and resolution (condition_resolution) events.
# Oracle Playground
Source: https://docs.predexon.com/websocket/playground-oracle
UMA oracle resolution events — proposals, disputes, settlements, and resets.
# Orderbook Playground
Source: https://docs.predexon.com/websocket/playground-orderbook
Real-time L2 orderbook from Polymarket CLOB. Exactly one filter type per subscription.
# Trades Playground
Source: https://docs.predexon.com/websocket/playground-trades
Real-time order fills and fee refunds. Set status filter to `all` or `pending` for mempool events (3-5s early).
# Subscriptions
Source: https://docs.predexon.com/websocket/subscriptions
Create and manage WebSocket subscriptions
## Subscribe
Create a subscription to receive real-time events matching your filters.
### Request
```json theme={null}
{
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "orders",
"filters": {
"users": ["0x1234...", "0x5678..."]
}
}
```
### Fields
| Field | Required | Description |
| - | - | - |
| `action` | Yes | `"subscribe"` |
| `platform` | Yes | `"polymarket"` (Polymarket channels) or `"chainlink"` (crypto-prices channel) |
| `version` | Yes | `1` |
| `type` | Yes | Channel: `"orders"`, `"activity"`, `"lifecycle"`, `"orderbook"`, `"oracle"`, `"collateral"`, or `"crypto"` |
| `filters` | Yes | Object with exactly one key: `users`, `token_ids`, `condition_ids`, `market_slugs`, or `feeds`. Optionally includes `status` for the orders channel. |
Set the filter value to `["*"]` to receive all events on that channel (wildcard subscription, Pro plan only).
### Status Filter (Orders Channel)
The `status` filter controls whether you receive pending (mempool) events, confirmed (on-chain) events, or both on the orders channel.
| Value | Behavior | Plan Required |
| - | - | - |
| `"confirmed"` (default) | Only confirmed (mined) events | Any plan |
| `"all"` | Both pending + confirmed events | **Dev+** |
| `"pending"` | Only pending (mempool) events | **Dev+** |
Omitting `status` defaults to `"confirmed"` - works on all tiers. Free tier subscribing with `"all"` or `"pending"` returns `PLAN_REQUIRED`.
### Filter Types
| Filter | Description | Example |
| - | - | - |
| `users` | Wallet addresses | `["0x123...", "0xabc..."]` |
| `token_ids` | CLOB token IDs (orderbook only) | `["82855...", "55194..."]` |
| `condition_ids` | Market condition IDs | `["0x456...", "0x789..."]` |
| `market_slugs` | Market URL slugs | `["will-donald-trump-win-the-2024-us-presidential-election"]` |
| `feeds` | Chainlink crypto feed symbols (crypto-prices only) | `["BTC/USD", "ETH/USD"]` |
Not all filters are available on all channels. See the [filter availability table](/websocket/overview#filter-availability-by-channel) for details.
The **orderbook** channel requires exactly one of `token_ids`, `condition_ids`, or `market_slugs`. The `users` filter is not supported. Update (`action: "update"`) is also not supported. Unsubscribe and resubscribe to change filters.
The **oracle** channel supports `condition_ids` and `market_slugs`. The `users` and `token_ids` filters are not supported.
### Success Response
```json theme={null}
{
"type": "ack",
"subscription_id": "sub_2f4b15b33798",
"channel": "trades"
}
```
Save the `subscription_id` to update or unsubscribe later.
***
## Examples
### Subscribe to trade alerts for wallets
Track trades from specific wallet addresses:
```json theme={null}
{
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "orders",
"filters": {
"users": [
"0x1234567890abcdef1234567890abcdef12345678",
"0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
]
}
}
```
### Subscribe to activity for a market
Track splits, merges, and redemptions on a specific market:
```json theme={null}
{
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "activity",
"filters": {
"condition_ids": ["0x1234567890abcdef1234567890abcdef12345678901234567890abcdef12345678"]
}
}
```
### Subscribe to lifecycle events
Track token registrations and market resolutions:
```json theme={null}
{
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "lifecycle",
"filters": {
"condition_ids": ["0x1234567890abcdef1234567890abcdef12345678901234567890abcdef12345678"]
}
}
```
### Subscribe to pending + confirmed trades
Get early trade signals from the mempool alongside confirmed events:
```json theme={null}
{
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "orders",
"filters": {
"users": ["*"],
"status": "all"
}
}
```
### Wildcard subscription (Pro only)
Subscribe to **all** trades across Polymarket:
```json theme={null}
{
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "orders",
"filters": {
"users": ["*"]
}
}
```
A wildcard connection cannot mix wildcard and regular subscriptions on the same channel. Up to 2 wildcard connections per channel per API key.
***
## Unsubscribe
Remove an existing subscription using its ID.
### Request
```json theme={null}
{
"action": "unsubscribe",
"subscription_id": "sub_2f4b15b33798"
}
```
### Response
```json theme={null}
{
"type": "ack",
"subscription_id": "sub_2f4b15b33798",
"channel": "trades"
}
```
***
## Update Subscription
Replace the entire filter set on an existing subscription.
### Request
```json theme={null}
{
"action": "update",
"subscription_id": "sub_2f4b15b33798",
"filters": {
"condition_ids": ["0xabcd..."]
}
}
```
You can also update the `status` filter on an existing orders subscription:
```json theme={null}
{
"action": "update",
"subscription_id": "sub_2f4b15b33798",
"filters": {
"users": ["*"],
"status": "pending"
}
}
```
### Response
```json theme={null}
{
"type": "ack",
"subscription_id": "sub_2f4b15b33798",
"channel": "trades"
}
```
Updating replaces the existing filters entirely. Include all items you want to track.
# Trade Events
Source: https://docs.predexon.com/websocket/trades
Real-time confirmed order fills and fee refunds
The trades channel (`"orders"`) streams real-time trade events from Polymarket. By default, you receive **confirmed** events - trades that have been mined on-chain. Covers both V1 and V2 Polymarket contracts.
Want even earlier signals? [Pending trade events](/websocket/pending-trades) deliver trades **3–5 seconds before** confirmation by decoding transactions from the Polygon mempool. Use `status: "all"` to receive both.
**V1 vs V2:** Every `order_filled` event has a `version` field (`1` or `2`). V2 trades add `builder` and `metadata` fields (bytes32 hex, currently zero). `fee_refund` events are **V1-only** - V2 emits net fees directly on `order_filled` (no refund flow). See the [V2 Migration Guide](/websocket/v2-migration).
## Event Types
| Event | Description |
| - | - |
| `order_filled` | An order was filled on-chain |
| `fee_refund` | A maker fee rebate was processed |
***
## order\_filled
```json theme={null}
{
"type": "event",
"subscription_id": "sub_2f4b15b33798",
"data": {
"status": "confirmed",
"event_type": "order_filled",
"token_id": "61192765571543...",
"token_label": "Up",
"side": "SELL", // [!code highlight]
"market_slug": "btc-updown-15m-1770244200",
"condition_id": "0x04f954e4f30f...",
"shares": 2000000,
"shares_normalized": 2, // [!code highlight]
"price": 0.04, // [!code highlight]
"tx_hash": "0xf30a29f249...",
"log_index": "0x21e",
"title": "Bitcoin Up or Down - February 4, 5:30PM-5:45PM ET",
"timestamp": 1770244731,
"order_hash": "0x8bf54f44e5d7...",
"user": "0xe9cbb1c9b3f7f411...", // [!code highlight]
"taker": "0x98f36c3d6300b905...",
"role": "maker", // [!code highlight]
"outcome": "Up",
"outcome_index": 0,
"complement_token_id": "9876543210...",
"complement_token_label": "Down",
"is_neg_risk": false,
"market_id": "1329542",
"image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/BTC+fullsize.png",
"fee": 0.008,
"version": 2,
"builder": "0x0000000000000000000000000000000000000000000000000000000000000000",
"metadata": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
}
```
### Field Reference
| Field | Type | Description |
| - | - | - |
| `status` | `"pending"` \| `"confirmed"` | Event source - see [Pending Trades](/websocket/pending-trades) |
| `event_type` | `"order_filled"` | |
| `user` | string | Maker address (order placer) |
| `taker` | string | Taker address (order filler) - equals an exchange contract address on `taker`-role rows |
| `role` | `"maker"` \| `"taker"` | Which OrderFilled emission this is. See [Maker vs Taker emissions](#maker-vs-taker-emissions) |
| `side` | `"BUY"` \| `"SELL"` | Maker's side of the trade |
| `price` | number | Execution price in USDC per share |
| `shares` | number | Raw share amount (6 decimal places) |
| `shares_normalized` | number | Human-readable share amount |
| `fee` | number | Fee charged in USDC |
| `version` | number | `1` for V1 contracts, `2` for V2 contracts |
| `builder` | string \| null | bytes32 hex tag (V2 only, currently zero) |
| `metadata` | string \| null | bytes32 hex tag (V2 only, currently zero) |
| Field | Type | Description |
| - | - | - |
| `token_id` | string | Outcome token ID |
| `token_label` | string \| null | Outcome label (e.g. "Yes", "Up") |
| `outcome` | string \| null | Same as `token_label` |
| `outcome_index` | number \| null | `0` = first outcome (Yes/Up), `1` = second (No/Down) |
| `complement_token_id` | string \| null | Token ID of the opposite outcome |
| `complement_token_label` | string \| null | Label of the opposite outcome |
| `is_neg_risk` | boolean \| null | `true` for neg-risk markets |
| `condition_id` | string \| null | Market condition ID |
| `market_slug` | string | Market URL slug |
| `market_id` | string \| null | Internal market ID |
| `title` | string \| null | Market title |
| `image` | string \| null | Market image URL |
| Field | Type | Description |
| - | - | - |
| `order_hash` | string \| null | Order hash |
| `tx_hash` | string | Transaction hash |
| `log_index` | string \| null | Log index within the transaction (hex) |
| `timestamp` | number | Unix timestamp in seconds |
***
## Maker vs Taker emissions
A single `matchOrders` transaction emits **multiple `order_filled` events** - one per maker matched, plus one synthetic taker-aggregate emission summarizing the taker's overall fill. The `role` field tells you which one you're looking at:
| `role` | Description | When to use |
| - | - | - |
| `"maker"` | Per-maker fill. One row per maker order matched in the tx. `taker` is the real taker user address. | Default for almost all use cases - counting fills, attributing volume to maker wallets, copytrading specific makers. |
| `"taker"` | Synthetic taker-aggregate. One row per `matchOrders` tx, summing across all makers. `taker` equals the exchange contract address. | Use when you want one row per taker action - e.g. dashboards keyed on the taker, or to avoid double-counting volume across the per-maker rows. |
Pending events set `role` directly from the maker/taker builder; confirmed events derive it from the chain log (`taker == exchange contract` ⇒ `"taker"`, else `"maker"`). Empirically the two agree 100%, so you can dedupe pending → confirmed by `tx_hash + order_hash` regardless of role.
**Don't sum volume across both roles** - the taker-aggregate row covers the same shares as the per-maker rows. Pick one set: filter on `role === "maker"` for maker-attribution, or `role === "taker"` for taker-attribution.
***
## fee\_refund
`fee_refund` events are emitted for **V1 trades only**. V2 has no refund flow - the net fee is emitted directly on `order_filled` at match time.
Emitted when a maker fee rebate is processed on-chain.
```json theme={null}
{
"type": "event",
"subscription_id": "sub_2f4b15b33798",
"data": {
"event_type": "fee_refund",
"user": "0x47a51f21d742...",
"token_id": "53031995840519...",
"condition_id": "0xdfb2f9d3ed88...",
"market_slug": "btc-updown-15m-1770242400",
"title": "Bitcoin Up or Down - February 4, 5:00PM-5:15PM ET",
"order_hash": "0xbda8ab86c90f...", // [!code highlight]
"tx_hash": "0x97b2ae331881...",
"refund": 9.9904, // [!code highlight]
"fee_charged": 0.0096 // [!code highlight]
}
}
```
| Field | Type | Description |
| - | - | - |
| `event_type` | `"fee_refund"` | |
| `user` | string | Address that received the refund |
| `token_id` | string | Outcome token ID |
| `condition_id` | string \| null | Market condition ID |
| `market_slug` | string \| null | Market URL slug |
| `title` | string \| null | Market title |
| `order_hash` | string | The order hash this refund applies to |
| `tx_hash` | string | Transaction hash |
| `refund` | number | USDC amount refunded to the maker |
| `fee_charged` | number | Final net fee after refund |
***
## Understanding Fee Refunds
Polymarket implements a maker fee rebate program. Here's how it works:
An `order_filled` event arrives with a `fee` field representing the gross fee charged.
A `fee_refund` event for the same `order_hash` arrives 2–5ms later (same block, usually same tx). Rarely up to 50ms.
The maker's actual net fee is `fee_refund.fee_charged` (or equivalently `order_filled.fee - fee_refund.refund`).
If you don't need exact fee accounting, you can ignore `fee_refund` events. The `fee` field on `order_filled` is directionally correct for most purposes. Fee refunds are only necessary for precise PnL tracking or cost basis accounting.
***
## Example: Processing Trades
```javascript theme={null}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'event') {
const data = msg.data;
if (data.event_type === 'order_filled') {
const value = data.shares_normalized * data.price; // [!code highlight]
console.log(`${data.side} ${data.outcome} ${data.shares_normalized} @ ${data.price}`);
console.log(`Market: ${data.title}`);
console.log(`Value: $${value.toFixed(2)}`);
console.log(`Maker: ${data.user}`);
console.log(`Taker: ${data.taker}`);
}
if (data.event_type === 'fee_refund') {
console.log(`Fee refund: $${data.refund} for order ${data.order_hash}`);
console.log(`Net fee: $${data.fee_charged}`); // [!code highlight]
}
}
};
```
# V2 Migration Guide
Source: https://docs.predexon.com/websocket/v2-migration
What changed in the WebSocket API for Polymarket V2 - deployed 2026-04-19
**Zero code changes required for most clients.** All existing subscriptions, filters, and event fields continue to work unchanged. V2 adds new fields and new event types alongside V1 - nothing was removed or renamed.
## TL;DR
Polymarket is migrating from V1 contracts (USDC.e collateral) to V2 contracts (pUSD collateral). The WebSocket service now handles both - V1 and V2 events flow through the same channels and subscription semantics.
Additive changes:
1. New `version` field on `order_filled`, activity, and lifecycle events (`1` or `2`)
2. New `builder` and `metadata` fields on V2 `order_filled` events
3. New `collateral` channel for pUSD deposit/withdrawal events
4. New `condition_prepared` event type on the lifecycle channel (with derived token IDs for binary markets)
5. New `conversion` event type on the activity channel (NegRisk cross-condition conversions)
Two things to be aware of - neither affects most clients:
* **`ActivityAlert.condition_id` is now nullable** - `null` only for the new `conversion` event type. Clients with typed schemas (`condition_id: string`) should widen to `string | null` if they want to consume conversion events. Clients that filter by `event_type` in `{"split", "merge", "redeem"}` are unaffected.
* **Pending-tx access is now Dev+ gated** - Free-tier clients passing `filters.status: "all"` or `"pending"` receive `PLAN_REQUIRED`. Free-tier clients on the default (`"confirmed"`) continue to work unchanged.
***
## Breaking Change: `token_registered` Only Fires for V1
V2 exchanges no longer emit `TokenRegistered`. If you use `token_registered` as your "new market" signal, you'll miss V2 markets.
**Migration:** subscribe to the new `condition_prepared` event type on the lifecycle channel. It fires for both V1 and V2 markets - it's the upstream signal from the shared `ConditionalTokens` contract.
| Event | V1 Markets | V2 Markets |
| - | - | - |
| `token_registered` | Yes | **No longer emitted** |
| `condition_prepared` | Yes (new) | Yes (new) |
| `condition_resolution` | Yes | Yes |
The `condition_prepared` event fires earlier than `token_registered`. For **binary markets** (most Polymarket markets), the service derives token IDs deterministically from `condition_id` + `is_neg_risk` and includes them in the `tokens` array - no extra API call needed. Token labels are placeholders (`"Yes"` / `"No"`); resolve canonical labels from Gamma/CLOB if needed. Non-binary markets have `tokens: null`.
***
## New Fields on Existing Events
### Trade events (`order_filled`)
V1 fills are byte-identical to pre-migration **plus** a single new `version` field:
```json theme={null}
{
"event_type": "order_filled",
"side": "BUY",
"price": 0.61,
"shares_normalized": 2.564101,
"status": "confirmed",
"version": 1
}
```
V2 fills add `builder` and `metadata` (bytes32 hex, zero until Polymarket populates them):
```json theme={null}
{
"event_type": "order_filled",
"side": "BUY",
"price": 0.45,
"shares_normalized": 1.81818,
"status": "confirmed",
"version": 2,
"builder": "0x0000...0000",
"metadata": "0x0000...0000"
}
```
**V2 fee semantics:**
* **No `fee_refund` events** on V2 - V2 has no refund flow. The net fee is emitted directly on `order_filled` at match time.
* V2 uses separate taker and per-maker fee amounts. In practice makers typically see `fee: 0` today (operators haven't populated maker fees), but the protocol supports non-zero maker fees via `makerFeeAmounts[]`.
* V1 `fee_refund` behavior is unchanged.
### Lifecycle events
Now carry a `version` field (`1` or `2`) indicating which contracts emitted them. All other fields unchanged.
### Activity events
Now carry a `version` field (`1` for split/merge/redeem, `2` for the new `conversion` event type).
### New Activity Event: `conversion`
NegRisk cross-condition conversions (user converts "No" positions back to collateral across a NegRisk event).
```json theme={null}
{
"event_type": "conversion",
"user": "0xbc5403011df159368c4bc716e07bcf858dfe2eec",
"condition_id": null,
"amount": 13074500,
"amount_normalized": 13.0745,
"tx_hash": "0x...",
"timestamp": 1776824366,
"version": 2,
"neg_risk_event_id": "0x6375f96fe3910d68...",
"index_set": "320"
}
```
* `condition_id` is always `null` (conversion spans multiple conditions).
* `neg_risk_event_id`: NegRiskAdapter's marketId identifying the cluster of related binary questions.
* `index_set`: `uint256` bitmap (as decimal string) of which question indices were converted.
* See [Activity Events](/websocket/activity#conversion) for full details.
***
## New Channel: `collateral`
V2 replaces USDC.e with pUSD as the trading collateral. When users deposit/withdraw, the pUSD contract emits `Wrapped` / `Unwrapped` events. These flow through a new opt-in `collateral` channel.
**Subscribe:**
```json theme={null}
{
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "collateral",
"filters": { "users": ["0x..."] }
}
```
Accepts `users` filter or wildcard. Does **not** accept `condition_ids`, `market_slugs`, or `token_ids` - collateral flow isn't tied to a market.
**Event shape:**
```json theme={null}
{
"event_type": "polyusd_wrapped",
"user": "0x3b27d0fb...",
"asset": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174",
"amount": 45000000,
"amount_normalized": 45,
"tx_hash": "0x...",
"timestamp": 1776638042,
"version": 2
}
```
* `event_type`: `"polyusd_wrapped"` (deposit: underlying → pUSD) or `"polyusd_unwrapped"` (withdrawal: pUSD → underlying)
* `user`: end-recipient of the operation
* `asset`: underlying ERC-20 address (typically USDC.e)
* `amount` / `amount_normalized`: 6-decimal (same as USDC)
Activity events (splits, merges, redeems) are **not** mixed into the collateral channel. Subscribe to both if you need full wallet flow.
See [Collateral Events](/websocket/collateral) for the full event reference.
***
## Accepted Channel Types
| `type` | Status |
| - | - |
| `orders` | Unchanged |
| `activity` | Unchanged - pUSD events **not** mixed in |
| `lifecycle` | Unchanged filters; may deliver `condition_prepared` |
| `oracle` | Unchanged |
| `orderbook` | Unchanged |
| `collateral` | **New** |
***
## Contract Addresses
| Role | Address |
| - | - |
| V2 CTF Exchange | `0xe111180000d2663c0091e4f400237545b87b996b` |
| V2 NegRisk Exchange A | `0xe2222d279d744050d28e00520010520000310f59` |
| V2 NegRisk Exchange B | `0xe2222d002000ba0053cef3375333610f64600036` |
| PolyUSD (pUSD) | `0xc011a7e12a19f7b1f670d46f03b03f3342e82dfb` |
| ConditionalTokens (V1+V2) | `0x4D97DCd97eC945f40cF65F87097ACe5EA0476045` |
***
## Backwards-Compatibility Guarantees
* **No field removed, no field renamed, no filter semantic changed.**
* V1 clients that don't parse `version`, `builder`, or `metadata` will ignore them - same JSON shape as before plus extra keys.
* Clients with **strict JSON schemas** that reject unknown properties need a one-line schema update (allow additional properties, or add the new optional fields).
* Clients with a **closed enum** for `event_type` should add `"condition_prepared"`, `"polyusd_wrapped"`, `"polyusd_unwrapped"` if they want to surface those.
***
## Recommended Migration Steps
Read the new `version` field on trade/activity/lifecycle events if you want to branch on V1 vs V2 behavior.
If you used `token_registered` as your "new market" signal, add a handler for `condition_prepared`. Query Gamma or CLOB by `condition_id` to resolve token IDs.
If you track wallet deposit/withdrawal flow, subscribe to the new `collateral` channel with `type: "collateral"`.
If your JSON parser rejects unknown keys, add `version`, `builder`, `metadata` as optional fields - or allow additional properties.