# Cross-venue arbitrage
Source: https://docs.oddpool.com/arbitrage/current
GET https://api.oddpool.com/arbitrage/current
Find risk-free cross-venue arbitrage opportunities, live from real-time order books.
Find markets where buying YES on one venue and NO on another costs less than 1 dollar combined. The gap is risk-free profit. Prices and order books are **live** — recomputed continuously from real-time venue WebSocket feeds (Kalshi, Polymarket, Opinion), not a periodic snapshot.
**Example:** "Will the Fed cut rates?" -- YES is 32c on Kalshi, NO is 60c on Polymarket. Buy both for 92c, locking in an 8c gross spread. After \~3c in venue fees (Kalshi taker + Polymarket taker), you pocket about 5c per contract guaranteed.
## Parameters
Minimum net profit after fees, in cents.
When `true`, attaches each leg's live ask ladder (top 25 levels) under `orderbook`, so you can size an order without a second call.
Deprecated and ignored. The legacy endpoint used it as a staleness window; the live cache is always current, so it has no effect. Accepted only for backwards compatibility.
## Example
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/arbitrage/current?min_net_cents=0.5&orderbook=true"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/arbitrage/current",
headers={"X-API-Key": "your_api_key"},
params={"min_net_cents": 0.5, "orderbook": True},
)
opportunities = response.json()
```
## Response
A JSON array of opportunities, sorted by `net_cents` (highest first). An empty array means there are no current opportunities at or above your threshold. `net_cents` is your per-contract profit after all fees.
The opportunity tells you the trade directly: **buy YES on `buy_yes_market`, buy NO on `buy_no_market`.** `yes_leg` / `no_leg` give the exact `(market, identifier, book_side)` to hit for each side.
`gross_cents` / `fee_cents` / `net_cents` are the **top-of-book** economics (per contract): `net_cents = round((1 − (best_yes_ask + best_no_ask)) × 100) − fees`. `executable_size` and `max_profit_dollars` are the **depth-aware** answer to "how much can I actually take, and what's it worth?" — they walk *both* legs' ask ladders together: at each price level they fill the smaller of the two available sizes, add `(level_net_after_fees / 100) × size` to the profit, and advance until a level pair is no longer profitable after fees (or the ladders run out). So `executable_size` is the total fillable contracts and `max_profit_dollars` the summed profit across all profitable levels.
**`max_profit_dollars` is a conservative floor, not a guarantee.** It only considers the **top 25 levels**, so true fillable depth beyond that isn't counted. Fees are applied **per level** using the same per-venue rates as `net_cents` (Kalshi sector taker, Polymarket per-market `feeSchedule.rate`, Opinion topic rate + floor). It does not model slippage from latency, partial fills, or order-placement minimums — size against `executable_size` and the attached `orderbook`, not blindly.
Each venue block includes execution-ready identifiers you can pass directly to the venue's trading API:
* **Kalshi** — `market_ticker` is the ticker accepted by Kalshi's trade API.
* **Polymarket** — `condition_id` is the on-chain condition; `yes_token_id` / `no_token_id` are the CLOB token IDs for placing orders.
* **Opinion** — `child_market_id` is the categorical child market ID; `yes_token_id` / `no_token_id` are the trading-side token IDs.
Prices and order books are real-time; **volume and liquidity refresh roughly hourly**. Opinion does not expose 24h volume or liquidity, so `volume_24h` and `liquidity` are `null` for Opinion legs. `timestamp` is when the opportunity was last recomputed.
```json theme={null}
[
{
"event_id": "stanley-cup-2026",
"event_title": "NHL Stanley Cup Champion 2026",
"outcome_key": "colorado_avalanche",
"label": "Colorado Avalanche",
"timestamp": "2026-05-26T02:21:54.190359",
"market_type": null,
"resolution_time": "2026-06-29T19:00:00",
"kalshi_event_ticker": "KXNHL-26",
"polymarket_event_slug": "2026-nhl-stanley-cup-champion",
"opinion_market_id": 345,
"kalshi": {
"market_ticker": "KXNHL-26-COL",
"yes_ask": 0.07, "no_ask": 0.95,
"volume": 6352005, "volume_24h": 320871,
"open_interest": 3146772
},
"polymarket": {
"condition_id": "0xf8f63bb47b2a7c2e0c1be3cedf4075079b11c07476d76a9469065b0c4791961a",
"yes_token_id": "101738487887518832481587379955535423775326921556438741919099866785354159699479",
"no_token_id": "87978082071653935678874296685430503892266481242311708420787197372467948088235",
"yes_ask": 0.055, "no_ask": 0.948,
"volume": 15219511.07, "volume_24h": 215850.28,
"liquidity": 50818.82
},
"opinion": {
"child_market_id": 5566,
"yes_token_id": "109494079162951671873858278805080808339824558282001815297323400464995130701684",
"no_token_id": "74166100071861466695478367383768425561540521028754831523233794115776851950059",
"yes_ask": 0.21, "no_ask": 0.782,
"volume": 38765.84, "volume_24h": null, "liquidity": null
},
"buy_yes_market": "polymarket",
"buy_no_market": "opinion",
"gross_cents": 16.3,
"fee_cents": 2,
"net_cents": 14.3,
"executable_size": 97.87,
"max_profit_dollars": 13.94,
"yes_leg": { "market": "polymarket", "identifier": "101738487887518832481587379955535423775326921556438741919099866785354159699479", "book_side": "ask" },
"no_leg": { "market": "opinion", "identifier": "74166100071861466695478367383768425561540521028754831523233794115776851950059", "book_side": "ask" },
"orderbook": {
"as_of": "2026-05-26T02:21:47.062741",
"yes": {
"market": "polymarket",
"asks": [
{ "price": 0.055, "size": 42.97 },
{ "price": 0.056, "size": 14989.0 }
]
},
"no": {
"market": "opinion",
"asks": [
{ "price": 0.782, "size": 97.87 }
]
}
}
}
]
```
`orderbook` is only present when you pass `orderbook=true`. It carries the top 25 ask levels per leg — ask side only (the price ladder to *buy* that outcome).
# Price spreads
Source: https://docs.oddpool.com/arbitrage/differences
GET https://api.oddpool.com/arbitrage/current/difference
Find where venues disagree on the same outcome's price.
Find where venues disagree on the same outcome's price. A large spread signals potential mispricing.
**Example:** "Will the Fed cut rates?" -- YES is 42c on Kalshi but 39c on Polymarket. If you think the true price is closer to 42c, Polymarket is offering a discount.
Unlike arbitrage, price spreads are **not risk-free**. They highlight venue disagreement, which can signal a trading edge.
## Parameters
Lookback window in minutes.
## Example
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/arbitrage/current/difference?minutes=10"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/arbitrage/current/difference",
headers={"X-API-Key": "your_api_key"},
params={"minutes": 10}
)
diffs = response.json()
```
## Response
Each row also includes full `kalshi`, `polymarket`, and `opinion` venue blocks (with prices, volume, and execution-ready identifiers like `market_ticker`, `condition_id`, and `yes_token_id` / `no_token_id`) — see the [Cross-venue arbitrage](/arbitrage/current) reference for the full venue-block shape.
```json theme={null}
[
{
"event_id": 42,
"event_title": "Fed rate decision March",
"kalshi_event_ticker": "KXFEDDECISION-26MAR",
"polymarket_event_slug": "fed-rate-march",
"opinion_market_id": null,
"market_type": "binary",
"outcome_key": "yes",
"label": "25bps cut",
"timestamp": "2026-03-11T14:30:00",
"resolution_time": "2026-03-19T18:00:00",
"kalshi": { "market_ticker": "KXFEDDECISION-26MAR-C25", "yes_ask": 0.42, "no_ask": 0.60, "...": "..." },
"polymarket": { "condition_id": "0xde04...", "yes_token_id": "307678...", "no_token_id": "403029...", "yes_ask": 0.39, "no_ask": 0.63, "...": "..." },
"opinion": { "child_market_id": null, "yes_token_id": null, "no_token_id": null, "...": "..." },
"side": "YES",
"side1": "Kalshi",
"side2": "Polymarket",
"side1_price": 0.42,
"side2_price": 0.39,
"diff": 0.03
}
]
```
# Arbitrage
Source: https://docs.oddpool.com/arbitrage/overview
Cross-venue arbitrage opportunities and price spreads.
Requires **Premium** plan (100/mo).
Programmatic access to the [Arbitrage Dashboard](https://oddpool.com/arb-dashboard) and [Price Spreads](https://oddpool.com/market-differences). Two types of cross-venue signals, updated in real time.
## What you can build
* **Arb bot:** poll for risk-free cross-venue opportunities and execute automatically
* **Spread alerts:** get notified when venue disagreement exceeds your threshold
* **Price dashboard:** track how the same outcome is priced across Kalshi, Polymarket, and Opinion
# Authentication
Source: https://docs.oddpool.com/authentication
Authenticate API requests with your API key.
Authenticate requests by including your API key in the `X-API-Key` header. Generate API keys from your [account settings](https://oddpool.com/account).
```bash theme={null}
curl -H "X-API-Key: oddpool_your_api_key" \
"https://api.oddpool.com/whales/user/events"
```
## Endpoint access by plan
| Endpoint group | Required plan | Price |
| --------------- | ------------- | ---------------------------------------------- |
| Search | Free | Free |
| Historical data | Free | Free |
| WebSocket feeds | Free+ | Free (dist only), Pro+ for book/trade/snapshot |
| Whale tracking | Pro | 30/mo |
| Arbitrage | Premium | 100/mo |
| Price spreads | Premium | 100/mo |
Keep your API key confidential. Do not expose it in client-side code or public repositories.
Requests with invalid or expired credentials receive a `401` (invalid key) or `403` (insufficient tier) response.
# Error handling
Source: https://docs.oddpool.com/errors
HTTP status codes and error response format.
The API uses standard HTTP response codes to indicate success or failure.
| Code | Description |
| ----- | -------------------------------------------------------- |
| `200` | Success |
| `400` | Bad request -- invalid parameters |
| `401` | Unauthorized -- invalid API key |
| `403` | Forbidden -- no active subscription or insufficient tier |
| `404` | Not found -- resource does not exist |
| `429` | Rate limit exceeded |
| `500` | Server error |
## Error response format
All errors return a JSON body with a `detail` field:
```json theme={null}
{"detail": "Error message"}
```
When you receive a `429`, check the `Retry-After` header for the number of seconds to wait before retrying.
# Event OHLCV
Source: https://docs.oddpool.com/events/ohlcv
GET https://api.oddpool.com/events/{event_id}/ohlcv
OHLCV bars for every outcome under an event in one request.
OHLCV bars for every outcome under an event in a single request. Works on Kalshi and Polymarket events. Same bar shape as [Market OHLCV](/markets/ohlcv) with one bar series per outcome.
Bars go back to **2026-03-21** for both Kalshi and Polymarket. Events whose markets all resolved before that date won't have history.
For one specific market or arbitrary ids across multiple events, use [Market OHLCV](/markets/ohlcv) — pass the ids you want.
## Parameters
Kalshi event ticker (e.g. `KXFEDDECISION-26JUN`) or Polymarket event slug.
Window start (ISO 8601), inclusive. Defaults to 30 days ago. Mutually exclusive with `last`.
Window end (ISO 8601), exclusive. Defaults to now. Mutually exclusive with `last`.
Window shorthand, e.g. `14d`, `12h`, `4w`. See [Market OHLCV](/markets/ohlcv) for full syntax. Mutually exclusive with `from`/`to`.
Bar size: `6h | 1d | 1w | 1m`. Sub-6h granularity is not available.
`bars[]` and `stats` shape and semantics are identical to [Market OHLCV](/markets/ohlcv) — including nullable bar fields, units of `change_pct` vs `change_1d/7d/30d`, and bar-count behavior of `last=N`.
## Example
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/events/KXFEDDECISION-26JUN/ohlcv?last=14d&interval=1d"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/events/KXFEDDECISION-26JUN/ohlcv",
headers={"X-API-Key": "your_api_key"},
params={"last": "14d", "interval": "1d"},
)
event = response.json()
for outcome in event["outcomes"]:
print(outcome["market_id"], outcome["outcome_label"], outcome["stats"]["change_pct"])
```
## Response
```json theme={null}
{
"event_id": "KXFEDDECISION-26JUN",
"exchange": "kalshi",
"event_title": "Fed decision in Jun 2026?",
"category": "Economics",
"scheduled_close_at": "2026-06-18T18:00:00+00:00",
"interval": "1d",
"snapshot_cadence": "6h",
"window_start": "2026-04-21T03:08:44+00:00",
"window_end": "2026-05-05T03:08:44+00:00",
"outcomes": [
{
"market_id": "KXFEDDECISION-26JUN-H0",
"outcome_label": "Will the Federal Reserve hold rates at the June 2026 meeting?",
"status": "active",
"result": null,
"stats": { "window_close": 0.95, "change_pct": 6.7416, "change_1d": 0.012, "...": "..." },
"bars": [
{"ts": "2026-04-21T00:00:00+00:00", "open": 0.89, "high": 0.91, "low": 0.87, "close": 0.90, "volume": 51230}
]
},
{
"market_id": "KXFEDDECISION-26JUN-C25",
"outcome_label": "Will the Federal Reserve cut rates by 25bps at the June 2026 meeting?",
"status": "active",
"result": null,
"stats": { "window_close": 0.04, "change_pct": -50.0, "change_1d": -0.01, "...": "..." },
"bars": [
{"ts": "2026-04-21T00:00:00+00:00", "open": 0.08, "high": 0.09, "low": 0.07, "close": 0.08, "volume": 28104}
]
}
]
}
```
Each outcome's `close` is its own market's last YES price, not a normalized event probability. Across an event's outcomes, closes typically sum slightly above 1 (\~1.02–1.08) due to per-market bid/ask spreads — a few percent over is normal and not a data issue.
## Errors
| Code | Reason |
| ----- | ------------------------------------------------------------------------------------- |
| `400` | Invalid window or interval. |
| `404` | `event_id` not in our pipeline, or the event has no markets in our snapshot pipeline. |
# Oddpool API
Source: https://docs.oddpool.com/index
Prediction market data across Kalshi and Polymarket.
The Oddpool API provides programmatic access to prediction market data across Kalshi and Polymarket -- including whale trades, cross-venue arbitrage, price spreads, real-time WebSocket feeds, and full-text search.
All API requests should be made to:
```text theme={null}
https://api.oddpool.com
```
## Quick start
```bash theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/arbitrage/current?min_net_cents=0.5"
```
A machine-readable version of these docs is available at [`/llms.txt`](https://api.oddpool.com/llms.txt) -- fetch it to get the full API reference as structured markdown.
## What you can build
Stream real-time orderbooks, trades, and probability distributions from Kalshi and Polymarket over a single connection.
Monitor large trades across prediction markets. Track events and get alerts when whales move.
Find cross-venue arbitrage opportunities and price spreads between Kalshi and Polymarket.
Full-text search across all prediction markets with filtering by exchange, category, volume, and liquidity.
Orderbook snapshots, top-of-book timeseries, and trade tapes for Kalshi markets.
Orderbook snapshots, top-of-book timeseries, and trade tapes for Polymarket markets.
# Get an entity
Source: https://docs.oddpool.com/institutions/reference-data/entities/get
GET https://api.oddpool.com/reference/v2/entities/{ope}
Fetch an entity by its Oddpool ID.
Fetch one entity by its Oddpool ID. Returns the same **entity** object as [Look up an entity](/institutions/reference-data/entities/lookup).
## Path parameters
The entity id, e.g. `OPE:TEAM:LOS-ANGELES-LAKERS`. Colons are URL-safe; `OPE%3ATEAM%3ALOS-ANGELES-LAKERS` works too.
## Request
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/reference/v2/entities/OPE:TEAM:LOS-ANGELES-LAKERS"
```
```python Python theme={null}
import requests
r = requests.get(
"https://api.oddpool.com/reference/v2/entities/OPE:TEAM:LOS-ANGELES-LAKERS",
headers={"X-API-Key": "your_api_key"},
)
entity = r.json()
```
## Response
A canonical **entity**. Every field is defined in [Response fields](/institutions/reference-data/response-fields#entity).
```json theme={null}
{
"oddpool_id": "OPE:TEAM:LOS-ANGELES-LAKERS",
"kind": "team",
"canonical_name": "Los Angeles Lakers",
"aliases": ["Lakers", "LA Lakers", "LAL"],
"wikidata_qid": "Q121783",
"metadata": { "league": "NBA", "sport": "basketball", "city": "Los Angeles" },
"provenance": "wikidata",
"source_synced_at": "2026-05-27T01:16:04Z"
}
```
## Errors
Returns `404` when no entity has this id:
```json theme={null}
{ "detail": { "error_code": "ENTITY_NOT_FOUND", "oddpool_id": "OPE:PERSON:NOT-A-REAL-PERSON" } }
```
# Search entities
Source: https://docs.oddpool.com/institutions/reference-data/entities/list
GET https://api.oddpool.com/reference/v2/entities
Find entities by substring and kind.
Search and page entities. `q` is a case-insensitive substring match on the canonical name. Each item is a full **entity**, the same object as [Look up an entity](/institutions/reference-data/entities/lookup).
## Query parameters
Substring match on the canonical name, e.g. `lakers`.
One of `person`, `team`, `political_party`, `cultural_work`, `geographic_region`, `organization`, `event_occurrence`.
Filter to a specific Wikidata Q-ID.
`wikidata` for Wikidata-backed records, `llm_auto` for auto-minted ones.
Items per page (1–2000).
The `next_cursor` from the previous response.
## Request
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/reference/v2/entities?q=lakers&kind=team"
```
```python Python theme={null}
import requests
r = requests.get(
"https://api.oddpool.com/reference/v2/entities",
headers={"X-API-Key": "your_api_key"},
params={"q": "lakers", "kind": "team"},
)
page = r.json()
```
## Response
A page of entities. Each is the full **entity** object. Every field is defined in [Response fields](/institutions/reference-data/response-fields#entity).
Pass as `cursor` for the next page. `null` on the last page.
```json theme={null}
{
"items": [
{
"oddpool_id": "OPE:TEAM:LOS-ANGELES-LAKERS",
"kind": "team",
"canonical_name": "Los Angeles Lakers",
"aliases": ["Lakers", "LA Lakers", "LAL"],
"wikidata_qid": "Q121783",
"metadata": { "league": "NBA", "sport": "basketball", "city": "Los Angeles" },
"provenance": "wikidata",
"source_synced_at": "2026-05-27T01:16:04Z"
}
],
"next_cursor": null
}
```
# Look up an entity
Source: https://docs.oddpool.com/institutions/reference-data/entities/lookup
GET https://api.oddpool.com/reference/v2/entities/lookup
Look up a team, person, or party by Wikidata Q-ID, Oddpool ID, or name.
Look up a single entity: a team, person, party, company, work, region, or occurrence. Pass exactly one of: `oddpool_id`, `wikidata_qid`, or `kind` + `name`.
## Query parameters
Wikidata Q-ID, e.g. `Q22686`.
Oddpool entity id, e.g. `OPE:PERSON:DONALD-TRUMP`.
One of `person`, `team`, `political_party`, `cultural_work`, `geographic_region`, `organization`, `event_occurrence`. Use with `name`.
Exact canonical name (case-insensitive). Use with `kind`.
## Request
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/reference/v2/entities/lookup?wikidata_qid=Q22686"
```
```python Python theme={null}
import requests
r = requests.get(
"https://api.oddpool.com/reference/v2/entities/lookup",
headers={"X-API-Key": "your_api_key"},
params={"wikidata_qid": "Q22686"},
)
entity = r.json()
```
## Response
A canonical **entity**. Every field is defined in [Response fields](/institutions/reference-data/response-fields#entity).
```json theme={null}
{
"oddpool_id": "OPE:PERSON:DONALD-TRUMP",
"kind": "person",
"canonical_name": "Donald Trump",
"aliases": ["Trump", "Donald J. Trump", "President Trump", "Donald John Trump"],
"wikidata_qid": "Q22686",
"metadata": { "country": "Q30", "party_qid": "Q29468", "person_kind": "politician" },
"provenance": "wikidata",
"source_synced_at": "2026-05-27T01:16:04Z"
}
```
## Errors
Returns `404` when no entity matches:
```json theme={null}
{ "detail": { "error_code": "ENTITY_NOT_FOUND", "oddpool_id": "OPE:PERSON:NOT-A-REAL-PERSON" } }
```
# Get a matched event
Source: https://docs.oddpool.com/institutions/reference-data/events/get
GET https://api.oddpool.com/reference/v2/events/{opi}
Fetch a matched event by its Oddpool ID.
Fetch one event by its Oddpool ID. Returns the same **event** object as [Get Oddpool ID by ticker](/institutions/reference-data/events/lookup).
## Path parameters
The event id, e.g. `OPI-OO4GBEZJ`.
## Request
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/reference/v2/events/OPI-OO4GBEZJ"
```
```python Python theme={null}
import requests
r = requests.get(
"https://api.oddpool.com/reference/v2/events/OPI-OO4GBEZJ",
headers={"X-API-Key": "your_api_key"},
)
event = r.json()
```
## Response
A matched **event**. Every field is defined in [Response fields](/institutions/reference-data/response-fields#event).
```json theme={null}
{
"oddpool_id": "OPI-OO4GBEZJ",
"title": "Gremio vs Corinthians",
"subject_domain": "Sports",
"classified_at": "2026-05-25T18:42:11Z",
"venue_listings": [
{
"oddpool_id": "OPL:KALSHI:KXBRASILEIROGAME-26MAY30GPACOR",
"venue": "kalshi",
"venue_event_id": "KXBRASILEIROGAME-26MAY30GPACOR",
"venue_question": "Who will win the Brasileirão match Grêmio vs Corinthians on May 30?"
},
{
"oddpool_id": "OPL:POLYMARKET:445404",
"venue": "polymarket",
"venue_event_id": "445404",
"venue_question": "Grêmio vs Corinthians: Match Result"
}
],
"venue_pairs": [
{
"venue_a": "kalshi",
"venue_a_event_id": "KXBRASILEIROGAME-26MAY30GPACOR",
"venue_b": "polymarket",
"venue_b_event_id": "445404",
"basis_class": "identical",
"outcome_universe": "identical"
}
],
"outcomes": [
{
"oddpool_id": "OPO:OO4GBEZJ:GREMIO-FBPA-JITQ",
"label": "Grêmio FBPA",
"outcome_kind": "multi_class_named",
"outcome_params": {},
"entity": {
"oddpool_id": "OPE:TEAM:GREMIO-FBPA",
"kind": "team",
"canonical_name": "Grêmio FBPA",
"aliases": ["Grêmio", "Gremio", "Tricolor Gaúcho"],
"wikidata_qid": "Q190301",
"metadata": {
"league": "Campeonato Brasileiro Série A",
"sport": "association football",
"city": "Porto Alegre"
}
},
"venue_listings": [
{
"oddpool_id": "OPL:KALSHI:KXBRASILEIROGAME-26MAY30GPACOR-GPA",
"venue": "kalshi",
"venue_market_id": "KXBRASILEIROGAME-26MAY30GPACOR-GPA",
"venue_event_id": "KXBRASILEIROGAME-26MAY30GPACOR",
"venue_yes_token": null,
"venue_no_token": null,
"venue_question": "Gremio"
},
{
"oddpool_id": "OPL:POLYMARKET:0X830BC4CBE4FC945B6CEE65EB1B0A5FEBACB16004C4C7B702A20376E6E12B1429",
"venue": "polymarket",
"venue_market_id": "0x830bc4cbe4fc945b6cee65eb1b0a5febacb16004c4c7b702a20376e6e12b1429",
"venue_event_id": "445404",
"venue_yes_token": "30372052209701849468461148034092137114942310389987253992056832502897412952481",
"venue_no_token": "74064390857285423190264600895526377480606423020566358098133374778358135580812",
"venue_question": "Grêmio FBPA"
}
],
"venue_pairs": [
{
"venue_a": "kalshi",
"venue_b": "polymarket",
"fungible": false,
"divergence_axes": ["condition_scope"],
"divergence_reason": "Both resolve Yes if Grêmio wins in 90 minutes plus stoppage time, but Kalshi sends cancellation/reschedule over two weeks to fair price while Polymarket keeps postponed games open and canceled-without-makeup resolves No."
}
]
}
]
}
```
## Errors
Returns `404` when no event has this id:
```json theme={null}
{
"detail": {
"error_code": "EVENT_NOT_FOUND",
"error": "no_canonical_match",
"oddpool_id": "OPI-NONEXIST"
}
}
```
# List matched events
Source: https://docs.oddpool.com/institutions/reference-data/events/list
GET https://api.oddpool.com/reference/v2/events
Page live or historical matched events, filtered by venue pair and fungibility.
Page through matched events. Each item is a full **event**, the same object as [Get Oddpool ID by ticker](/institutions/reference-data/events/lookup).
Pass `active=true` for events with markets still trading today. Without it you get the full corpus, which is mostly long-settled events.
## Query parameters
`true` returns only events with at least one venue market still trading. Recommended for live trading. Omit for the full historical corpus.
`true` returns events that are fully fungible on at least one venue pair: both venues present, same settlement rules, outcome sets correspond, and every outcome's two sides settle identically. `false` returns the negation. Narrow to a specific pair with `venue_pair`; permit specific edge-case differences with `allow`.
Requires `fungible`. Comma-separated divergence axes (`timing`, `source`, `condition_scope`) that should not disqualify a pair from being fungible. For example, `allow=condition_scope` counts cancellation/postponement-only differences as fungible. See [Fungibility and cancellation risk](#fungibility-and-cancellation-risk).
Match events where some venue pair carries this settlement class: `identical`, `settlement_window`, or `settlement_source`. Narrow to one pair with `venue_pair`.
Match events where some venue pair's outcome lists line up this way: `identical`, `partial`, or `disjoint`. Narrow to one pair with `venue_pair`.
Optional. Narrows `fungible`, `basis_class`, and `outcome_universe` to one venue pair, e.g. `kalshi,polymarket_us` (order-insensitive). Omit to match on any venue pair the event has. Tags: `kalshi`, `polymarket`, `polymarket_us`. See [Supported venues](/institutions/reference-data/venues).
Category filter, sourced from Kalshi (e.g. `Sports`). Free text.
ISO 8601. Returns only events re-evaluated on or after this time. Pass your last sync time to fetch just what changed.
Items per page (1–2000).
The `next_cursor` from the previous response.
`fungible`, `basis_class`, and `divergence_axes` describe how two venues settle relative to each other. They're defined in [Response fields](/institutions/reference-data/response-fields).
## Fungibility and cancellation risk
`fungible=true` is strict: a single outcome that settles differently on the two venues disqualifies the whole event, so it returns fewer events than you might expect.
The usual reason is `condition_scope`, the most common divergence axis, and usually just the venues' standard cancellation or postponement policy. An outcome flagged with only `condition_scope` is the **same** outcome on both venues: it settles identically in the normal case and differs only on rare edge cases (a game is cancelled or postponed past a window, a tie), where one venue pays the last-traded price and the other resolves No or 50/50. It does not mean the two markets are different.
Which filter you want depends on whether you hold positions through settlement:
* **You close before the event resolves.** Cancellation risk never reaches you. Include these pairs and trade them: `?fungible=true&allow=condition_scope`.
* **You hold to settlement.** Either keep the strict `?fungible=true` for pairs with no edge-case risk at all, or add `allow=condition_scope` to include the cancellation-policy pairs and read each one's `divergence_reason` to size the risk before trading.
## Request
```bash cURL theme={null}
# fungible on any venue pair, counting cancellation-policy-only differences as fungible
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/reference/v2/events?fungible=true&allow=condition_scope&active=true&limit=100"
```
```python Python theme={null}
import requests
r = requests.get(
"https://api.oddpool.com/reference/v2/events",
headers={"X-API-Key": "your_api_key"},
params={"fungible": "true", "allow": "condition_scope", "active": "true", "limit": 100},
)
page = r.json()
```
To narrow to one venue pair, add `venue_pair=kalshi,polymarket_us`.
## Response
A page of events. Each is the full **event** object. Every field is defined in [Response fields](/institutions/reference-data/response-fields#event).
Pass as `cursor` to get the next page. `null` on the last page.
```json theme={null}
{
"items": [
{
"oddpool_id": "OPI-OO4GBEZJ",
"title": "Gremio vs Corinthians",
"subject_domain": "Sports",
"classified_at": "2026-05-25T18:42:11Z",
"venue_listings": [
{
"oddpool_id": "OPL:KALSHI:KXBRASILEIROGAME-26MAY30GPACOR",
"venue": "kalshi",
"venue_event_id": "KXBRASILEIROGAME-26MAY30GPACOR",
"venue_question": "Who will win the Brasileirão match Grêmio vs Corinthians on May 30?"
},
{
"oddpool_id": "OPL:POLYMARKET:445404",
"venue": "polymarket",
"venue_event_id": "445404",
"venue_question": "Grêmio vs Corinthians: Match Result"
}
],
"venue_pairs": [
{
"venue_a": "kalshi",
"venue_a_event_id": "KXBRASILEIROGAME-26MAY30GPACOR",
"venue_b": "polymarket",
"venue_b_event_id": "445404",
"basis_class": "identical",
"outcome_universe": "identical"
}
],
"outcomes": [
{
"oddpool_id": "OPO:OO4GBEZJ:GREMIO-FBPA-JITQ",
"label": "Grêmio FBPA",
"outcome_kind": "multi_class_named",
"outcome_params": {},
"entity": {
"oddpool_id": "OPE:TEAM:GREMIO-FBPA",
"kind": "team",
"canonical_name": "Grêmio FBPA",
"aliases": ["Grêmio", "Gremio", "Tricolor Gaúcho"],
"wikidata_qid": "Q190301",
"metadata": {
"league": "Campeonato Brasileiro Série A",
"sport": "association football",
"city": "Porto Alegre"
}
},
"venue_listings": [
{
"oddpool_id": "OPL:KALSHI:KXBRASILEIROGAME-26MAY30GPACOR-GPA",
"venue": "kalshi",
"venue_market_id": "KXBRASILEIROGAME-26MAY30GPACOR-GPA",
"venue_event_id": "KXBRASILEIROGAME-26MAY30GPACOR",
"venue_yes_token": null,
"venue_no_token": null,
"venue_question": "Gremio"
},
{
"oddpool_id": "OPL:POLYMARKET:0X830BC4CBE4FC945B6CEE65EB1B0A5FEBACB16004C4C7B702A20376E6E12B1429",
"venue": "polymarket",
"venue_market_id": "0x830bc4cbe4fc945b6cee65eb1b0a5febacb16004c4c7b702a20376e6e12b1429",
"venue_event_id": "445404",
"venue_yes_token": "30372052209701849468461148034092137114942310389987253992056832502897412952481",
"venue_no_token": "74064390857285423190264600895526377480606423020566358098133374778358135580812",
"venue_question": "Grêmio FBPA"
}
],
"venue_pairs": [
{
"venue_a": "kalshi",
"venue_b": "polymarket",
"fungible": false,
"divergence_axes": ["condition_scope"],
"divergence_reason": "Both resolve Yes if Grêmio wins in 90 minutes plus stoppage time, but Kalshi sends cancellation/reschedule over two weeks to fair price while Polymarket keeps postponed games open and canceled-without-makeup resolves No."
}
]
}
]
}
],
"next_cursor": "eyJpZCI6MTI4NDQ0fQ"
}
```
## Paging
Follow `next_cursor` until it's `null`:
```bash theme={null}
curl -H "X-API-Key: $KEY" "https://api.oddpool.com/reference/v2/events?active=true&limit=100"
curl -H "X-API-Key: $KEY" "https://api.oddpool.com/reference/v2/events?active=true&limit=100&cursor="
```
For incremental sync, store the largest `classified_at` you've seen and pass it as `classified_since` next time.
# Get Oddpool ID by ticker
Source: https://docs.oddpool.com/institutions/reference-data/events/lookup
GET https://api.oddpool.com/reference/v2/events/lookup
Resolve a Kalshi, Polymarket, or Polymarket US ticker to its Oddpool event, outcomes, and venue sides.
Give this endpoint a Kalshi, Polymarket, or Polymarket US id and it returns the matched event: its Oddpool ID, every venue that lists it, every outcome, the entity each outcome refers to, and the venue ticker you trade on each side.
Kalshi input can be an event ticker or a market ticker (a market ticker resolves to its parent event). Polymarket input can be a conditionId, event slug, or numeric event id. Polymarket US input is the market slug.
## Query parameters
Pass either `oddpool_id`, or `venue` + `id`.
`kalshi`, `polymarket`, or `polymarket_us` (see [Supported venues](/institutions/reference-data/venues)). Required when you pass `id`.
The venue's id. Kalshi: event or market ticker. Polymarket: conditionId (`0x…`), event slug, or numeric event id. Polymarket US: market slug.
An Oddpool event id (`OPI-XXXXXXXX`) instead of a venue id.
## Request
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/reference/v2/events/lookup?venue=kalshi&id=KXBRASILEIROGAME-26MAY30GPACOR-GPA"
```
```python Python theme={null}
import requests
r = requests.get(
"https://api.oddpool.com/reference/v2/events/lookup",
headers={"X-API-Key": "your_api_key"},
params={"venue": "kalshi", "id": "KXBRASILEIROGAME-26MAY30GPACOR-GPA"},
)
event = r.json()
```
## Response
A matched **event**. Every field is defined in [Response fields](/institutions/reference-data/response-fields#event).
```json theme={null}
{
"oddpool_id": "OPI-OO4GBEZJ",
"title": "Gremio vs Corinthians",
"subject_domain": "Sports",
"classified_at": "2026-05-25T18:42:11Z",
"venue_listings": [
{
"oddpool_id": "OPL:KALSHI:KXBRASILEIROGAME-26MAY30GPACOR",
"venue": "kalshi",
"venue_event_id": "KXBRASILEIROGAME-26MAY30GPACOR",
"venue_question": "Who will win the Brasileirão match Grêmio vs Corinthians on May 30?"
},
{
"oddpool_id": "OPL:POLYMARKET:445404",
"venue": "polymarket",
"venue_event_id": "445404",
"venue_question": "Grêmio vs Corinthians: Match Result"
}
],
"venue_pairs": [
{
"venue_a": "kalshi",
"venue_a_event_id": "KXBRASILEIROGAME-26MAY30GPACOR",
"venue_b": "polymarket",
"venue_b_event_id": "445404",
"basis_class": "identical",
"outcome_universe": "identical"
}
],
"outcomes": [
{
"oddpool_id": "OPO:OO4GBEZJ:GREMIO-FBPA-JITQ",
"label": "Grêmio FBPA",
"outcome_kind": "multi_class_named",
"outcome_params": {},
"entity": {
"oddpool_id": "OPE:TEAM:GREMIO-FBPA",
"kind": "team",
"canonical_name": "Grêmio FBPA",
"aliases": ["Grêmio", "Gremio", "Tricolor Gaúcho"],
"wikidata_qid": "Q190301",
"metadata": {
"league": "Campeonato Brasileiro Série A",
"sport": "association football",
"city": "Porto Alegre"
}
},
"venue_listings": [
{
"oddpool_id": "OPL:KALSHI:KXBRASILEIROGAME-26MAY30GPACOR-GPA",
"venue": "kalshi",
"venue_market_id": "KXBRASILEIROGAME-26MAY30GPACOR-GPA",
"venue_event_id": "KXBRASILEIROGAME-26MAY30GPACOR",
"venue_yes_token": null,
"venue_no_token": null,
"venue_question": "Gremio"
},
{
"oddpool_id": "OPL:POLYMARKET:0X830BC4CBE4FC945B6CEE65EB1B0A5FEBACB16004C4C7B702A20376E6E12B1429",
"venue": "polymarket",
"venue_market_id": "0x830bc4cbe4fc945b6cee65eb1b0a5febacb16004c4c7b702a20376e6e12b1429",
"venue_event_id": "445404",
"venue_yes_token": "30372052209701849468461148034092137114942310389987253992056832502897412952481",
"venue_no_token": "74064390857285423190264600895526377480606423020566358098133374778358135580812",
"venue_question": "Grêmio FBPA"
}
],
"venue_pairs": [
{
"venue_a": "kalshi",
"venue_b": "polymarket",
"fungible": false,
"divergence_axes": ["condition_scope"],
"divergence_reason": "Both resolve Yes if Grêmio wins in 90 minutes plus stoppage time, but Kalshi sends cancellation/reschedule over two weeks to fair price while Polymarket keeps postponed games open and canceled-without-makeup resolves No."
}
]
}
]
}
```
## Errors
Returns `404` when no match exists for the id:
```json theme={null}
{
"detail": {
"error_code": "EVENT_NOT_FOUND",
"error": "no_canonical_match",
"venue": "kalshi",
"id": "KXNONEXIST-99"
}
}
```
# Oddpool IDs
Source: https://docs.oddpool.com/institutions/reference-data/oddpool-ids
The four identifier families (OPI, OPO, OPL, OPE) and how they nest.
Every layer of a response carries its own Oddpool ID. The four families name the four layers:
| Family | Layer | What it identifies | Format | Example |
| ------ | ------- | ------------------------------------------------------------ | --------------------------------------------- | --------------------------------- |
| `OPI` | Event | One real-world resolution: a game, election, or Fed decision | `OPI-{8-char base32}` | `OPI-OO4GBEZJ` |
| `OPO` | Outcome | One named answer on an event | `OPO:{opi-suffix}:{label-slug}-{4-char hash}` | `OPO:OO4GBEZJ:GREMIO-FBPA-JITQ` |
| `OPL` | Listing | One venue's tradeable side of an outcome | `OPL:{VENUE}:{slug(venue_market_id)}` | `OPL:KALSHI:KXPRESPARTY-28-TRUMP` |
| `OPE` | Entity | A team, person, party, or other real-world entity | `OPE:{KIND}:{slug}` | `OPE:PERSON:DONALD-TRUMP` |
## How they nest
An event lookup returns the whole tree in one payload:
* **Event** (`OPI`): one real-world resolution, like a game, election, or Fed decision
* **Outcome** (`OPO`): one named answer on the event
* **Venue listing** (`OPL`): one venue's tradeable side of that outcome, the ticker you trade
* **Entity** (`OPE`): the team, person, or party the outcome refers to
## Stability
Oddpool IDs are deterministic and stable for the lifetime of the underlying row. Once you resolve a venue ticker to an ID, the mapping doesn't move. All four families are safe to cache indefinitely.
Treat IDs as opaque strings. The formats above are documented so they're recognizable in logs; resolve and store them rather than constructing or parsing them yourself.
## URL encoding
`OPE` and `OPO` ids contain colons. Colons are URL-safe per RFC 3986, so they work directly in a path (`/reference/v2/entities/OPE:TEAM:LOS-ANGELES-LAKERS`). Clients that URL-encode them may use `OPE%3ATEAM%3ALOS-ANGELES-LAKERS` equivalently.
# Reference Data API
Source: https://docs.oddpool.com/institutions/reference-data/overview
Match the same event across venues to one ID, with the venue tickers to trade each side.
The Reference Data API is an enterprise product for institutional customers. It is separate from the retail Oddpool API and is not available on consumer plans. Access requires an institutional agreement and a reference-data-enabled API key. Contact [avi@oddpool.com](mailto:avi@oddpool.com) for access.
Kalshi, Polymarket, and other venues each give the same event a different ticker, ID, and question text. The Reference Data API matches them and gives you:
* **one ID per event**: the same game, election, or Fed decision across every venue that lists it
* **one ID per outcome**: each named answer on that event
* **one ID per entity**: the team, person, or party an outcome refers to, with a Wikidata Q-ID
* **the venue tickers** you need to subscribe to live prices on each side
For each matched event, it also tells you where the two venues agree and where they settle differently, so you can size a cross-venue position knowing exactly how fungible the two sides are. That classification is documented on [List matched events](/institutions/reference-data/events/list) and returned on every event.
## Base URL and auth
```text theme={null}
https://api.oddpool.com
```
Every request needs your API key in the `X-API-Key` header and returns JSON:
```bash theme={null}
curl -H "X-API-Key: oddpool_" \
"https://api.oddpool.com/reference/v2/events?active=true&limit=100"
```
A `401` means the key is missing or unknown; a `403` means the key is valid but doesn't include reference-data access (or its trial has expired).
## Quickstart
```bash Resolve a venue ticker theme={null}
# a Kalshi, Polymarket, or Polymarket US id -> the matched event
curl -H "X-API-Key: $KEY" \
"https://api.oddpool.com/reference/v2/events/lookup?venue=kalshi&id=KXSB-27-ARI"
```
```bash Get an event by ID theme={null}
curl -H "X-API-Key: $KEY" \
"https://api.oddpool.com/reference/v2/events/OPI-U3PW57WP"
```
```bash List live events theme={null}
# events with markets still trading today
curl -H "X-API-Key: $KEY" \
"https://api.oddpool.com/reference/v2/events?active=true&limit=100"
```
```bash Look up an entity theme={null}
curl -H "X-API-Key: $KEY" \
"https://api.oddpool.com/reference/v2/entities/lookup?wikidata_qid=Q22686"
```
## Endpoints
Turn a Kalshi, Polymarket, or Polymarket US id into the matched event, with every outcome and venue side.
Page live or historical events, filtered by venue pair and fungibility.
Look up a team, person, or party by Wikidata Q-ID, Oddpool ID, or name.
Find entities by substring and kind.
## How a response is shaped
Every event response nests four layers, each with its own Oddpool ID:
* **Event** (`OPI`): one real-world resolution, like a game, election, or Fed decision
* **Outcome** (`OPO`): one named answer on the event, like a team, "Yes", or a price bucket
* **Venue listing** (`OPL`): one venue's tradeable side of that outcome, the ticker you trade
* **Entity** (`OPE`): the team, person, or party the outcome refers to
The venue tags you can pass to `venue` and `venue_pair`, and how they combine.
What OPI, OPO, OPL, and OPE identify, how they nest, and why they're safe to cache.
Definitions for every field in the event and entity responses, including basis\_class, outcome kinds, and divergence.
# Response fields
Source: https://docs.oddpool.com/institutions/reference-data/response-fields
Every field in the event and entity responses, defined.
The full field dictionary for the [event](/institutions/reference-data/events/lookup) and [entity](/institutions/reference-data/entities/lookup) responses. Each endpoint returns these objects; this page defines every field once.
## Event
The object returned by [Get Oddpool ID by ticker](/institutions/reference-data/events/lookup), [Get a matched event](/institutions/reference-data/events/get), and each item in [List matched events](/institutions/reference-data/events/list).
The event id (`OPI-XXXXXXXX`).
Event display title.
Free-text category, sourced from Kalshi (e.g. `Sports`). Not a closed enum; may be `null` for uncategorized events.
ISO 8601 timestamp of the last time the event's canonical state was refreshed. Advances on new outcomes, settlement reclassification, and divergence re-validation, not on price or volume. Use it as the high-water mark for incremental sync via `classified_since`.
The event as each venue lists it, one entry per venue. See [Event venue listing](#event-venue-listing).
Per-venue-pair settlement classification, one entry per pair of venues that both list the event. See [Event venue pair](#event-venue-pair).
Each named answer on the event. See [Outcome](#outcome).
## Event venue listing
An entry in the event's `venue_listings`. Names the parent event on one venue.
Listing id (`OPL:…`).
`kalshi`, `polymarket`, or `polymarket_us`.
The venue's identifier for the parent event.
Raw question text from the venue at the time of mapping.
## Event venue pair
An entry in the event's `venue_pairs`. Fungibility is a property of a venue pair, not a scalar on the event, so an event on N venues has one entry per pair. Venues are precedence-ordered (`kalshi` \< `polymarket` \< `polymarket_us`).
First venue in the pair.
Venue A's event id. Names the exact venue event the pair relates (needed when a canonical event spans several events on one venue; redundant in the common 1:1 case).
Second venue in the pair.
Venue B's event id.
The settlement-rule axis: whether the two venues agree on when, by what authority, and under what timing window the event resolves.
| Value | Meaning |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `identical` | Same resolution anchor, same authority, gap ≤ 24 hours. |
| `settlement_window` | Settlement timestamps diverge by more than 24 hours, or the venues anchor on different stages of the resolution chain (e.g. election day vs. certification). |
| `settlement_source` | Both venues settle on the same nominal value but read it from two different named authorities (e.g. National Weather Service vs. Weather Underground). |
The outcome-list correspondence axis: how much the two venues' outcome sets line up.
| Value | Meaning |
| ----------- | ------------------------------------------------ |
| `identical` | Every outcome paired 1:1 across the two venues. |
| `partial` | Some outcomes paired, at least one is one-sided. |
| `disjoint` | No outcomes are paired across the two venues. |
## Outcome
An entry in the event's `outcomes`.
Outcome id (`OPO:…`).
Display label: an entity name, bucket descriptor, `Yes`, etc.
The structural type of the outcome.
| Value | Shape |
| --------------------- | ----------------------------------------------------- |
| `binary` | Single YES/NO proposition. |
| `multi_class_named` | N named entity outcomes; one wins. |
| `range_bin` | Continuous variable bucketed into discrete intervals. |
| `threshold_above` | Cumulative ladder: above N / ≥ N. |
| `threshold_below` | Cumulative ladder: below N / ≤ N. |
| `point_mass` | Exact-value outcome. |
| `spread` | Handicap on a line. |
| `will_happen_by_date` | Time-bound binary with a deadline. |
Type-specific structured parameters. Numeric kinds carry `outcome_params.bucket`; `will_happen_by_date` carries `outcome_params.deadline`; `binary` and `multi_class_named` are `{}`. See [outcome\_params shapes](#outcome-params-shapes). Additional non-load-bearing keys may appear alongside `bucket` / `deadline` (`unmatched_in_counterparty`, `residual`, audit keys). Rely on `bucket` / `deadline` and ignore unknown siblings.
The canonical entity this outcome refers to, or `null` for non-entity outcomes such as `Tie` or `Other`. See [Entity](#entity).
The tradeable side of this outcome on each venue, the ids you subscribe to. See [Outcome venue listing](#outcome-venue-listing).
Per-venue-pair fungibility for this outcome, one entry per pair of venues that both list it. See [Outcome venue pair](#outcome-venue-pair).
### outcome\_params shapes
```jsonc theme={null}
// range_bin: half-open numeric interval
"outcome_params": { "bucket": { "shape": "discrete_range",
"lo": 0.0, "hi": 50000.0, "lo_inclusive": true, "hi_inclusive": false, "units": "count" } }
// threshold_above: cumulative ladder, open upper bound
"outcome_params": { "bucket": { "shape": "cumulative_threshold",
"lo": 25.0, "hi": null, "lo_inclusive": false, "hi_inclusive": null, "units": "bps" } }
// threshold_below: cumulative ladder, open lower bound
"outcome_params": { "bucket": { "shape": "cumulative_below",
"lo": null, "hi": 0.0, "lo_inclusive": null, "hi_inclusive": false, "units": "count" } }
// point_mass: exact value
"outcome_params": { "bucket": { "shape": "point_value",
"lo": -25, "hi": -25, "lo_inclusive": true, "hi_inclusive": true, "units": "bps" } }
// spread: handicap on a line; line and side are spread-only
"outcome_params": { "bucket": { "shape": "spread", "line": 1.5, "side": "favorite",
"lo": 1.5, "hi": null, "lo_inclusive": false, "hi_inclusive": null, "units": "pts" } }
// will_happen_by_date
"outcome_params": { "deadline": { "iso8601": "2027-01-01", "inclusive": false } }
// binary, multi_class_named: no structural params
"outcome_params": { }
```
## Outcome venue listing
An entry in an outcome's `venue_listings`, one venue's tradeable side of the outcome.
Listing id (`OPL:…`).
`kalshi`, `polymarket`, or `polymarket_us`.
The venue's native identifier for this tradeable side, the value you trade on.
* Kalshi: the market ticker.
* Polymarket binary YES/NO: the `conditionId`. Trade via the CLOB using `conditionId` plus the side's token below.
* Polymarket atomic-binary multi-side: `conditionId#`. Split on `#` to recover the native `conditionId`; the side's CLOB token is below.
* Polymarket US: `#`. Split on `#` to recover the order-API `marketSlug` and the side. Polymarket US has no CLOB tokens; orders reference the `marketSlug` and side directly.
The venue's identifier for the parent event. Kalshi: event ticker. Polymarket and Polymarket US: the event slug (the human-readable id, not the numeric event id).
Polymarket CLOB token id for the YES side. `null` on Kalshi and Polymarket US.
Polymarket CLOB token id for the NO side. `null` on Kalshi and Polymarket US.
Raw question text from the venue at the time of mapping.
## Outcome venue pair
An entry in an outcome's `venue_pairs`, showing whether the outcome's two venue sides are one tradeable instrument.
First venue in the pair.
Second venue in the pair.
`true` iff the event leg's `basis_class == identical` and `divergence_axes == []`. When true, the two listings are one tradeable instrument (same payoff, same nominal value, same authority, same timing) and can be treated as one for quoting, hedging, and risk. The one-glance answer.
Subset of `{timing, source, condition_scope}`: how the two listings settle differently when they are the same nominal outcome. Empty means no axis-level divergence on this leg.
| Axis | Meaning |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `timing` | Same outcome, fixed at different moments: one on the event itself, the other on a later administrative step (certification, swearing-in). The eventual answer matches, but early-resolution and payout timing can differ. |
| `source` | Same value, read from two different named authorities (e.g. National Weather Service vs. Weather Underground). Agree in the modal case, can diverge on edge measurements. |
| `condition_scope` | The most common axis, and usually just the venues' standard cancellation/postponement policy. Still the same outcome: it settles identically in the normal case and differs only on rare edge cases (game cancelled or postponed past a window, a tie), where one venue pays the last-traded price and the other resolves No or 50-50. Common on sports and entertainment. |
For how `condition_scope` affects the `fungible` filter, and when to treat it as tradeable with `allow`, see [Fungibility and cancellation risk](/institutions/reference-data/events/list#fungibility-and-cancellation-risk).
One-sentence explanation of the specific case, populated when `divergence_axes` is non-empty.
## Entity
The object returned by the [entity endpoints](/institutions/reference-data/entities/lookup), and nested under each `outcome.entity`.
Entity id (`OPE:{KIND}:{slug}`).
One of `person`, `team`, `political_party`, `cultural_work`, `geographic_region`, `organization`, `event_occurrence`.
The canonical display name.
Alternate names observed on venues and curated alternates.
Wikidata Q-ID, a stable cross-reference to FEC, bioguide, LEI, sports-reference, MusicBrainz, and other identifier systems. `null` for auto-minted entities.
Kind-specific structured fields. Shape varies by `kind`. For example, Person: `country`, `party_qid`, `person_kind`; Team: `league`, `sport`, `city`, `parent_qid`; Organization: `country`, `tickers`. Values that reference other entities are Wikidata Q-IDs (`country: "Q30"`). Treat as open JSON: use the keys you recognize, ignore the rest.
`wikidata` when the record is sourced from Wikidata; `llm_auto` when auto-minted from venue text because no Wikidata match was available.
ISO 8601 timestamp of the most recent Wikidata sync, where applicable.
# Using Wikidata IDs
Source: https://docs.oddpool.com/institutions/reference-data/using-wikidata-ids
Join canonical entities to campaign finance, tickers, rosters, and the rest of the open-data graph through their Wikidata Q-ID.
Every canonical entity carries a `wikidata_qid`. Wikidata is a free, structured database that stores hundreds of external identifiers for each entity, so the Q-ID is a ready-made join key: from one Oddpool entity you can reach a politician's campaign-finance record, a company's stock ticker and LEI, a team's league and roster, and more, without maintaining your own entity-matching layer.
The pattern is always the same: read `wikidata_qid` off the entity, then query Wikidata with it.
```python theme={null}
import requests
# Resolve an Oddpool entity to its Wikidata Q-ID
entity = requests.get(
"https://api.oddpool.com/reference/v2/entities/lookup",
headers={"X-API-Key": "your_api_key"},
params={"kind": "person", "name": "Donald Trump"},
).json()
qid = entity["wikidata_qid"] # "Q22686"
```
Wikidata asks every caller to send a descriptive `User-Agent` (an app name and contact), and it rate-limits anonymous traffic. The examples below set one. For heavy or repeated use, cache what you pull.
## Turn a Q-ID into a name and profile
The simplest call: fetch an entity's labels, description, and claims as JSON, no query language needed.
```python theme={null}
import requests
UA = {"User-Agent": "my-app/1.0 (you@example.com)"}
def wikidata_entity(qid):
url = f"https://www.wikidata.org/wiki/Special:EntityData/{qid}.json"
return requests.get(url, headers=UA).json()["entities"][qid]
e = wikidata_entity("Q22686")
# Some items store their label under the "mul" (multilingual) code instead of "en".
labels = e["labels"]
name = (labels.get("en") or labels.get("mul"))["value"]
description = e["descriptions"]["en"]["value"]
print(name) # Donald Trump
print(description) # American businessman and politician (born 1946), President of the United States ...
```
## Cross-walk an election market's candidates to campaign finance
For an election market, take each candidate outcome's `entity.wikidata_qid` and resolve them, in one request, to their campaign-finance identifiers. `wdt:P2686` is the OpenSecrets people ID and `wdt:P2390` is the Ballotpedia ID; from either you can pull filings, totals, and donor data.
```python theme={null}
import requests
UA = {"User-Agent": "my-app/1.0 (you@example.com)"}
# wikidata_qid values read off the candidate outcomes of an election event
qids = ["Q22686", "Q10853588"]
values = " ".join(f"wd:{q}" for q in qids)
query = f"""
SELECT ?person ?personLabel ?opensecrets ?ballotpedia WHERE {{
VALUES ?person {{ {values} }}
OPTIONAL {{ ?person wdt:P2686 ?opensecrets. }} # OpenSecrets people ID
OPTIONAL {{ ?person wdt:P2390 ?ballotpedia. }} # Ballotpedia ID
SERVICE wikibase:label {{ bd:serviceParam wikibase:language "en,mul". }}
}}"""
r = requests.get(
"https://query.wikidata.org/sparql",
params={"query": query, "format": "json"},
headers=UA,
)
for row in r.json()["results"]["bindings"]:
print(row["personLabel"]["value"],
row.get("opensecrets", {}).get("value"),
row.get("ballotpedia", {}).get("value"))
# Donald Trump N00023864 Donald_Trump
# Kamala Harris N00036915 Kamala_Harris
```
To see every identifier system an entity is part of, list its external-ID properties. This is how you discover what a given entity can join to before hardcoding a property:
```sparql theme={null}
SELECT ?propLabel ?value WHERE {
wd:Q22686 ?p ?value .
?prop wikibase:directClaim ?p ; wikibase:propertyType wikibase:ExternalId .
SERVICE wikibase:label { bd:serviceParam wikibase:language "en,mul". }
}
```
For Q22686 this returns Ballotpedia, OpenSecrets, C-SPAN, Library of Congress, VIAF, and more.
## Get a company's ticker, exchange, and LEI
For a market on a public company, resolve the entity to its listings and legal identifiers. `p:P414` is the stock-exchange statement, with the ticker as a `pq:P249` qualifier, and `wdt:P1278` is the Legal Entity Identifier.
```python theme={null}
import requests
UA = {"User-Agent": "my-app/1.0 (you@example.com)"}
query = """
SELECT ?exchangeLabel ?ticker ?lei WHERE {
wd:Q312 p:P414 ?s .
?s ps:P414 ?exchange ; pq:P249 ?ticker .
OPTIONAL { wd:Q312 wdt:P1278 ?lei. }
SERVICE wikibase:label { bd:serviceParam wikibase:language "en,mul". }
}"""
r = requests.get(
"https://query.wikidata.org/sparql",
params={"query": query, "format": "json"},
headers=UA,
)
for row in r.json()["results"]["bindings"]:
print(row["exchangeLabel"]["value"], row["ticker"]["value"], row.get("lei", {}).get("value"))
# Nasdaq AAPL HWUPKR0MPOU8FGXBT394
# Tokyo Stock Exchange 6689 HWUPKR0MPOU8FGXBT394
```
## Common cross-walks
A few properties useful for prediction-market data. Use the external-ID discovery query above to find the rest.
| System | Wikidata property | Unlocks |
| ----------------------------- | ----------------- | --------------------------------------- |
| OpenSecrets people ID | `P2686` | US federal campaign finance |
| Ballotpedia ID | `P2390` | US politics and election profiles |
| Stock exchange + ticker | `P414` + `P249` | Public equities |
| Legal Entity Identifier (LEI) | `P1278` | Corporate filings and counterparty data |
| Sports league | `P118` | A team's competition |
| VIAF ID | `P214` | Library and authority records |
Wikidata publishes the [full list of external-identifier properties](https://www.wikidata.org/wiki/Wikidata:Database_reports/List_of_properties/all) and a public [SPARQL endpoint](https://query.wikidata.org).
# Supported venues
Source: https://docs.oddpool.com/institutions/reference-data/venues
The venues you can pass to venue and venue_pair, and how they combine into pairs.
The registry currently covers these venues. Pass the **tag** wherever an endpoint takes a `venue`, and combine two tags for a `venue_pair`.
| Tag | Venue |
| --------------- | ------------- |
| `kalshi` | Kalshi |
| `polymarket` | Polymarket |
| `polymarket_us` | Polymarket US |
More venues are added over time; new tags appear here.
## Trading a side
Each venue encodes its tradeable side differently in `venue_market_id`. Split on `#` to recover the base id and the side.
| Venue | `venue_market_id` | CLOB tokens |
| ------------- | ----------------------------------------------------------------- | ------------------------------------ |
| Kalshi | market ticker | none |
| Polymarket | `conditionId`, or `conditionId#` for atomic-binary markets | `venue_yes_token` / `venue_no_token` |
| Polymarket US | `#YES` or `#NO` | none |
On Polymarket US, orders reference the `marketSlug` and side directly, so there are no CLOB tokens (`venue_yes_token` and `venue_no_token` are `null`). `venue_event_id` is the event slug on both Polymarket and Polymarket US. See [Response fields](/institutions/reference-data/response-fields#outcome-venue-listing) for the full listing shape.
## Where you pass them
* **`venue`** on [Get Oddpool ID by ticker](/institutions/reference-data/events/lookup) takes a single tag alongside the venue's `id`.
* **`venue_pair`** on [List matched events](/institutions/reference-data/events/list) takes two tags to scope the fungibility filters to one pair.
* In responses, listings carry a `venue` tag, and `venue_pairs` entries carry `venue_a` / `venue_b`.
## Venue pairs
A `venue_pair` is two distinct tags, comma-separated and order-insensitive. With the current venues:
* `kalshi,polymarket`
* `kalshi,polymarket_us`
* `polymarket,polymarket_us`
Within a pair, venues are precedence-ordered `kalshi` \< `polymarket` \< `polymarket_us`, which fixes which side is `venue_a` and which is `venue_b` in responses.
# Historical orderbook
Source: https://docs.oddpool.com/kalshi/orderbook
GET https://api.oddpool.com/historical/kalshi/orderbook
Full orderbook snapshots with YES bids, NO bids, and derived best bid/ask.
Full orderbook snapshots with YES bids, NO bids, and derived best bid/ask from the YES perspective. Without `start_time`/`end_time`, returns the most recent snapshots.
## Parameters
Kalshi market ticker (e.g., `KXFEDDECISION-26APR-H0`).
Start time in Unix ms. Omit with `end_time` to get latest data.
End time in Unix ms. Omit with `start_time` to get latest data.
Snapshot interval: `1m` or `5m`.
Max rows per page (1-200).
Cursor from previous response for next page.
## Example
```bash theme={null}
curl -H "X-API-Key: oddpool_..." \
"https://api.oddpool.com/historical/kalshi/orderbook?market_id=KXFEDDECISION-26APR-H0&start_time=1774015200000&end_time=1774018800000&limit=5"
```
## Response
```json theme={null}
{
"snapshots": [
{
"market_id": "KXFEDDECISION-26APR-H0",
"timestamp": 1774015256106,
"yes_bids": [
{"price": "0.9400", "size": 12500.0},
{"price": "0.9300", "size": 8000.0}
],
"no_bids": [
{"price": "0.0600", "size": 21.0},
{"price": "0.0500", "size": 366630.0}
],
"best_yes_bid": 0.94,
"best_yes_ask": 0.94,
"mid": 0.94,
"spread": 0.0
}
],
"pagination": {
"limit": 5,
"count": 5,
"has_more": true,
"pagination_key": "eyJo..."
}
}
```
# Kalshi historical data
Source: https://docs.oddpool.com/kalshi/overview
Historical orderbook snapshots, top-of-book timeseries, and trade data for Kalshi markets.
Available on all plans (Free tier included).
Historical orderbook snapshots, top-of-book timeseries, and trade data for Kalshi markets. Data available from **March 19, 2026** onward. Returns native Kalshi data with YES and NO sides. Data becomes available approximately one hour after it occurs.
## What you can build
* **Backtesting:** replay historical orderbooks and trades to validate trading strategies before deploying capital
* **Price charts:** build custom candlestick or mid-price charts from top-of-book snapshots
* **Liquidity analysis:** measure how bid/ask depth and spreads change around major events like FOMC decisions or economic releases
* **Execution quality:** compare your historical fills against the orderbook at time of execution to measure slippage
* **Volatility models:** derive implied volatility from orderbook snapshots and track how it evolves over time
* **Trade flow analysis:** identify patterns in trade size, frequency, and taker side leading into event resolution
## Pagination
All historical endpoints use cursor-based pagination. Pass `pagination_key` from the previous response to get the next page.
```json theme={null}
{
"pagination": {
"limit": 100,
"count": 100,
"has_more": true,
"pagination_key": "eyJo..."
}
}
```
# Historical top of book
Source: https://docs.oddpool.com/kalshi/top-of-book
GET https://api.oddpool.com/historical/kalshi/top-of-book
Lightweight timeseries of best bid, best ask, mid, and spread.
Lightweight timeseries of best bid, best ask, mid, and spread. Same data as the orderbook endpoint but without the full level arrays. Ideal for charting price history.
## Parameters
Kalshi market ticker (e.g., `KXFEDDECISION-26APR-H0`).
Start time in Unix ms. Omit with `end_time` to get latest data.
End time in Unix ms. Omit with `start_time` to get latest data.
Snapshot interval: `1m` or `5m`.
Max rows per page (1-200).
Cursor from previous response for next page.
## Example
```bash theme={null}
curl -H "X-API-Key: oddpool_..." \
"https://api.oddpool.com/historical/kalshi/top-of-book?market_id=KXFEDDECISION-26APR-H0&start_time=1774015200000&end_time=1774018800000&granularity=5m"
```
## Response
```json theme={null}
{
"snapshots": [
{
"market_id": "KXFEDDECISION-26APR-H0",
"timestamp": 1774015256106,
"best_yes_bid": 0.94,
"best_yes_ask": 0.94,
"mid": 0.94,
"spread": 0.0
}
],
"pagination": { ... }
}
```
`best_yes_bid` and `best_yes_ask` are `null` when the book had no resting orders at sample time. Filter client-side if you only want quoted snapshots.
# Historical trades
Source: https://docs.oddpool.com/kalshi/trades
GET https://api.oddpool.com/historical/kalshi/trades
Every trade execution on a Kalshi market.
Every trade execution on a Kalshi market. Kalshi only surfaces the taker's buy side in public data.
## Parameters
Kalshi market ticker (e.g., `KXFEDDECISION-26APR-H0`).
Start time in Unix ms.
End time in Unix ms.
Max rows per page (1-200).
Cursor from previous response for next page.
## Example
```bash theme={null}
curl -H "X-API-Key: oddpool_..." \
"https://api.oddpool.com/historical/kalshi/trades?market_id=KXFEDDECISION-26APR-H0&start_time=1774015200000&end_time=1774018800000"
```
## Response
```json theme={null}
{
"trades": [
{
"market_id": "KXFEDDECISION-26APR-H0",
"timestamp": 1774015436710,
"taker_side": "yes",
"price": "0.9500",
"size": 105.0,
"trade_id": "4bd8859f-eeaf-685e-e4b5-e9f17516b89c"
}
],
"pagination": { ... }
}
```
# Market OHLCV
Source: https://docs.oddpool.com/markets/ohlcv
GET https://api.oddpool.com/markets/ohlcv
Open/high/low/close/volume bars for one or many markets.
OHLCV time series for markets on Kalshi or Polymarket. Pass 1 to 50 `market_ids` per request; the response is always an array of wrapped objects (metadata + bars + window stats). Markets not in our snapshot pipeline are silently omitted — compare submitted vs returned `market_ids` to detect.
Probability values are 0–1 (e.g. `0.62` = 62%). Volume is contracts traded during the bar period. Underlying snapshot cadence is 6 hours; `1d` / `1w` / `1m` intervals are aggregated server-side.
Bars go back to **2026-03-21** for both Kalshi and Polymarket. Markets that resolved before that date won't have history; coverage continues to grow forward.
For one bar series per outcome under an event (FOMC dashboards, championship odds, etc.), see [Event OHLCV](/events/ohlcv).
Don't have a `market_id`? Use [Search markets](/search/search-markets) or [Search series](/search/series).
## Parameters
Comma-separated `market_id` list, 1 to 50 entries. Kalshi tickers (e.g. `KXFEDDECISION-26JUN-H0`) or Polymarket condition IDs (`0x…`).
Window start (ISO 8601), inclusive. Defaults to 30 days ago. Mutually exclusive with `last`.
Window end (ISO 8601), exclusive. Defaults to now. Mutually exclusive with `last`.
Window shorthand. `` where unit is `h | d | w | m`. Examples: `30d`, `12h`, `4w`, `6m`. Mutually exclusive with `from`/`to`.
Bar size. One of `6h` (native cadence), `1d`, `1w`, `1m`. Sub-6h granularity is not available — snapshot cadence is 6 hours.
Maximum window is 365 days per request. `last=N` includes the in-progress current bucket — `last=14d&interval=1d` returns \~15 bars (14 completed days + today's partial). Trim the last bar if you only want completed periods.
## Examples
### One market, last 30 days
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/markets/ohlcv?market_ids=KXFEDDECISION-26JUN-H0&last=30d&interval=1d"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/markets/ohlcv",
headers={"X-API-Key": "your_api_key"},
params={"market_ids": "KXFEDDECISION-26JUN-H0", "last": "30d", "interval": "1d"},
)
data = response.json()
```
### Multiple markets in one call
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/markets/ohlcv?market_ids=KXFEDDECISION-26JUN-H0,KXFEDDECISION-26JUL-H0&last=14d&interval=1d"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/markets/ohlcv",
headers={"X-API-Key": "your_api_key"},
params={
"market_ids": "KXFEDDECISION-26JUN-H0,KXFEDDECISION-26JUL-H0",
"last": "14d",
"interval": "1d",
},
)
markets = response.json()
```
### Realized volatility over the last 30 days
Standard deviation of daily intraday range, in probability points.
```python Python theme={null}
import requests
import statistics
response = requests.get(
"https://api.oddpool.com/markets/ohlcv",
headers={"X-API-Key": "your_api_key"},
params={"market_ids": "KXFEDDECISION-26JUN-H0", "last": "30d", "interval": "1d"},
)
bars = [b for b in response.json()[0]["bars"] if b["high"] is not None]
ranges = [b["high"] - b["low"] for b in bars]
print("mean range:", statistics.mean(ranges))
print("stdev: ", statistics.stdev(ranges))
```
## Response
```json theme={null}
[
{
"market_id": "KXFEDDECISION-26JUN-H0",
"exchange": "kalshi",
"question": "Will the Federal Reserve hold rates at the June 2026 meeting?",
"category": "Economics",
"event_id": "KXFEDDECISION-26JUN",
"event_title": "Fed decision in Jun 2026?",
"status": "active",
"result": null,
"scheduled_close_at": "2026-06-18T18:00:00+00:00",
"interval": "1d",
"snapshot_cadence": "6h",
"window_start": "2026-04-21T03:08:44+00:00",
"window_end": "2026-05-05T03:08:44+00:00",
"stats": {
"window_open": 0.89,
"window_close": 0.95,
"window_high": 0.95,
"window_low": 0.87,
"window_volume": 788507,
"change_pct": 6.7416,
"change_1d": 0.012,
"change_7d": 0.041,
"change_30d": 0.075
},
"bars": [
{"ts": "2026-04-21T00:00:00+00:00", "open": 0.89, "high": 0.91, "low": 0.87, "close": 0.90, "volume": 51230},
{"ts": "2026-04-22T00:00:00+00:00", "open": 0.90, "high": 0.92, "low": 0.89, "close": 0.91, "volume": 48910}
]
}
]
```
### Field reference
| Field | Meaning |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bars[].ts` | Bar-start timestamp. For native `6h`, the actual snapshot timestamp; for aggregated intervals, the bucket start (`2026-05-04T00:00:00Z` for `1d`, etc.). |
| `bars[].open / high / low / close` | Probabilities in `[0, 1]`. **Can be `null`** for a bucket with no underlying snapshots (very thin markets or snapshot gaps); skip or forward-fill in chart code. |
| `bars[].volume` | Contracts traded during the bar. Aggregated intervals (`1d`, `1w`, `1m`) sum across the underlying 6h snapshots. **For per-period flow analysis, prefer `interval=6h`** — aggregated volume can look uneven across consecutive bars. |
| `stats.window_*` | Aggregates over the requested window, derived from the bars. |
| `stats.change_pct` | `(window_close - window_open) / window_open × 100`. **Percent** (e.g. `5.56` = +5.56%). |
| `stats.change_1d / 7d / 30d` | **Probability-point deltas** in `[-1, +1]` (e.g. `0.05` = +5pp). Anchored to the latest snapshot — they answer "where is this vs N days ago" regardless of `from`/`to`/`last`. They will not match `change_pct` even when your window length matches. |
## Errors
| Code | Reason |
| ----- | ----------------------------------------------------------------------------------------------------------- |
| `400` | `market_ids` empty, more than 50 ids, both `last` and `from`/`to` set, invalid interval, or invalid window. |
# Historical orderbook
Source: https://docs.oddpool.com/polymarket/orderbook
GET https://api.oddpool.com/historical/polymarket/orderbook
Full orderbook snapshots with bids, asks, and derived best bid/ask per token.
Full orderbook snapshots with bids, asks, and derived best bid/ask per token. Without `start_time`/`end_time`, returns the most recent snapshots.
Polymarket markets have two tokens (YES and NO) per condition. Each snapshot row is for a single token. Use the `asset_id` parameter to filter to a specific token, or omit it to get both.
## Parameters
Polymarket condition ID (`0x...`).
Filter by specific token ID. Omit to get both YES and NO tokens.
Start time in Unix ms. Omit with `end_time` to get latest data.
End time in Unix ms. Omit with `start_time` to get latest data.
Snapshot interval: `1m` or `5m`.
Max rows per page (1-200).
Cursor from previous response for next page.
## Example
```bash theme={null}
curl -H "X-API-Key: oddpool_..." \
"https://api.oddpool.com/historical/polymarket/orderbook?market_id=0x36e8ca24...&start_time=1774015200000&end_time=1774018800000&limit=5"
```
## Response
```json theme={null}
{
"snapshots": [
{
"asset_id": "56914066788195652124...",
"market_id": "0x00000977017fa72fb6b1...",
"timestamp": 1774026026396,
"bids": [
{"price": "0.987", "size": 5000.0},
{"price": "0.985", "size": 3200.0}
],
"asks": [
{"price": "0.990", "size": 2100.0},
{"price": "0.995", "size": 4500.0}
],
"best_bid": 0.987,
"best_ask": 0.99,
"mid": 0.9885,
"spread": 0.003
}
],
"pagination": {
"limit": 5,
"count": 5,
"has_more": true,
"pagination_key": "eyJo..."
}
}
```
# Polymarket historical data
Source: https://docs.oddpool.com/polymarket/overview
Historical orderbook snapshots, top-of-book timeseries, and trade data for Polymarket markets.
Available on all plans (Free tier included).
Historical orderbook snapshots, top-of-book timeseries, and trade data for Polymarket markets. Data available from **March 20, 2026** onward. Returns native Polymarket data with asset IDs, bids/asks, and condition IDs. Data becomes available approximately one hour after it occurs.
Polymarket markets have two tokens (YES and NO) per condition. Each snapshot row is for a single token. Use the `asset_id` parameter to filter to a specific token, or omit it to get both.
## What you can build
* **Backtesting:** replay historical orderbooks and trades to validate trading strategies before deploying capital
* **Price charts:** build custom candlestick or mid-price charts from top-of-book snapshots
* **Liquidity analysis:** measure how bid/ask depth and spreads change around major events like elections or economic releases
* **Execution quality:** compare your historical fills against the orderbook at time of execution to measure slippage
* **On-chain trade analysis:** correlate trade flow with on-chain activity using `transaction_hash` and `fee_rate_bps` fields
* **Market making research:** study spread dynamics and depth profiles to calibrate quoting strategies
## Pagination
All historical endpoints use cursor-based pagination. Pass `pagination_key` from the previous response to get the next page.
```json theme={null}
{
"pagination": {
"limit": 100,
"count": 100,
"has_more": true,
"pagination_key": "eyJo..."
}
}
```
# Historical top of book
Source: https://docs.oddpool.com/polymarket/top-of-book
GET https://api.oddpool.com/historical/polymarket/top-of-book
Lightweight timeseries of best bid, best ask, mid, and spread per token.
Lightweight timeseries of best bid, best ask, mid, and spread per token. Same data as the orderbook endpoint but without the full level arrays.
Polymarket markets have two tokens (YES and NO) per condition. Use `asset_id` to filter to a specific token.
## Parameters
Polymarket condition ID (`0x...`).
Filter by specific token ID. Omit to get both YES and NO tokens.
Start time in Unix ms. Omit with `end_time` to get latest data.
End time in Unix ms. Omit with `start_time` to get latest data.
Snapshot interval: `1m` or `5m`.
Max rows per page (1-200).
Cursor from previous response for next page.
## Example
```bash theme={null}
curl -H "X-API-Key: oddpool_..." \
"https://api.oddpool.com/historical/polymarket/top-of-book?market_id=0x36e8ca24...&asset_id=56914066...&start_time=1774015200000&end_time=1774018800000&granularity=5m"
```
## Response
```json theme={null}
{
"snapshots": [
{
"asset_id": "56914066788195652124...",
"market_id": "0x00000977017fa72fb6b1...",
"timestamp": 1774026026396,
"best_bid": 0.987,
"best_ask": 0.99,
"mid": 0.9885,
"spread": 0.003
}
],
"pagination": { ... }
}
```
`best_bid` and `best_ask` are `null` when the book had no resting orders at sample time. Filter client-side if you only want quoted snapshots.
# Historical trades
Source: https://docs.oddpool.com/polymarket/trades
GET https://api.oddpool.com/historical/polymarket/trades
Trade executions on a Polymarket market.
Trade executions on a Polymarket market. Polymarket trades can be BUY or SELL. Use `asset_id` to filter to a specific token.
## Parameters
Polymarket condition ID (`0x...`).
Filter by specific token ID.
Start time in Unix ms.
End time in Unix ms.
Max rows per page (1-200).
Cursor from previous response for next page.
## Example
```bash theme={null}
curl -H "X-API-Key: oddpool_..." \
"https://api.oddpool.com/historical/polymarket/trades?market_id=0x36e8ca24...&start_time=1774015200000&end_time=1774018800000"
```
## Response
```json theme={null}
{
"trades": [
{
"asset_id": "56914066788195652124...",
"market_id": "0x00000977017fa72fb6b1...",
"timestamp": 1774020951044,
"side": "BUY",
"price": "0.987",
"size": 1.317,
"transaction_hash": "0x554c0b25544baa5f...",
"fee_rate_bps": "0"
}
],
"pagination": { ... }
}
```
# Rate limits
Source: https://docs.oddpool.com/rate-limits
Per-tier rate limits, monthly quotas, and API key allowances.
API requests are rate limited per tier. Exceeding your limit returns a `429` status with a `Retry-After` header indicating how long to wait.
| Tier | Rate limit | Monthly quota | API keys |
| ---------- | ----------- | -------------- | --------- |
| Free | 1 req/sec | 1,000 requests | 1 |
| Pro | 10 req/sec | 1M requests | Unlimited |
| Premium | 25 req/sec | 5M requests | Unlimited |
| Enterprise | 500 req/sec | Unlimited | Unlimited |
Free tier has a hard cap at the monthly quota. Paid tiers have higher limits. [View pricing](https://oddpool.com/pricing) for details.
# Event markets
Source: https://docs.oddpool.com/search/event-markets
GET https://api.oddpool.com/search/events/{event_id}/markets
List all markets under one event.
List every outcome market under a known event. Useful once you have an `event_id` from [Search events](/search/search-events) and want all the tradable outcomes — for example, every strike on a Kalshi BTC event or every candidate on a Polymarket election.
## Parameters
Event identifier (e.g. `KXFEDDECISION-26MAR`).
## Example
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/search/events/KXFEDDECISION-26MAR/markets"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/search/events/KXFEDDECISION-26MAR/markets",
headers={"X-API-Key": "your_api_key"}
)
markets = response.json()
```
## Response
Returns the same shape as [`/search/markets`](/search/search-markets), one row per outcome market under the event.
# Search
Source: https://docs.oddpool.com/search/overview
Discover and filter prediction markets across Kalshi and Polymarket.
Available on all plans (Free tier included).
Search covers three things: finding a *series* (a recurring or related group of events), finding *markets* and *events* inside one, and tailing recent listings. Most workflows that feed into the historical endpoints start with [Search series](/search/series).
| Endpoint | Use for |
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| [Search series](/search/series) | Find a `series_id` like `KXBTC15M` or `btc-up-or-down-15m`. Start here if you don't already have one. |
| [Search markets](/search/search-markets) | List markets by text query, by `series_id`, or both. |
| [Search events](/search/search-events) | List events by text query, by `series_id`, or both. |
| [Recent markets](/search/recent-markets) | Newest market listings, no query required. |
| [Recent events](/search/recent-events) | Newest event listings, no query required. |
| [Event markets](/search/event-markets) | List the outcome markets under one event. |
## Common workflows
Find the series you want, then list its closed markets in a time window. Feed the resulting `market_id` values into the [Kalshi](/kalshi/overview) or [Polymarket](/polymarket/overview) historical endpoints.
Scope to a series (e.g. one NBA market type or one Kalshi BTC resolution), pull active events, then poll their orderbooks.
Don't know a `series_id` yet? Search markets or events directly with `q=`. Useful for one-offs and cross-series queries.
Tail the most recent listings on either exchange, or watermark a search by `discovered_after` to poll for new entries within a series.
Once you have an `event_id`, list every outcome market under it.
# Recent events
Source: https://docs.oddpool.com/search/recent-events
GET https://api.oddpool.com/search/recent/events
Latest event listings without a search query.
The most recently discovered events across both venues. For watermarked polling within a known series, use [Search events](/search/search-events) with `discovered_after` instead.
## Parameters
`kalshi` or `polymarket`.
1-100.
## Example
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/search/recent/events?exchange=polymarket"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/search/recent/events",
headers={"X-API-Key": "your_api_key"},
params={"exchange": "polymarket"}
)
events = response.json()
```
# Recent markets
Source: https://docs.oddpool.com/search/recent-markets
GET https://api.oddpool.com/search/recent/markets
Latest market listings without a search query.
The most recently discovered markets across both venues — useful for new-listing alerts when you don't have a search query yet. For watermarked polling within a known series, use [Search markets](/search/search-markets) with `discovered_after` instead.
## Parameters
`kalshi` or `polymarket`.
1-100.
## Example
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/search/recent/markets?limit=10"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/search/recent/markets",
headers={"X-API-Key": "your_api_key"},
params={"limit": 10}
)
markets = response.json()
```
## Response
```json theme={null}
[
{
"market_id": "KXBTC15M-26MAY040415-15",
"exchange": "kalshi",
"series_id": "KXBTC15M",
"question": "BTC price up in next 15 mins?",
"category": null,
"status": "active",
"volume": 0,
"liquidity": 0,
"last_yes_price": "0.0000",
"last_no_price": "0.0000",
"event_id": "KXBTC15M-26MAY040415",
"event_title": "BTC Up or Down - 15 minutes",
"slug": null,
"discovered_at": "2026-05-04T00:20:30Z",
"settled_at": null
}
]
```
# Search events
Source: https://docs.oddpool.com/search/search-events
GET https://api.oddpool.com/search/events
Find events by text query, by series, or both.
An event groups all the outcome markets for one question. "Fed Rate Decision March 2026" is one event with markets for each rate-cut size; a single 15-minute Bitcoin event has up/down markets for that window.
You must pass **either `q` or `series_id`** (or both). Requests with neither return `400`. To find a `series_id`, use [Search series](/search/series).
## Parameters
Full-text search on `title`. Required when `series_id` is not provided.
Filter to one series. Examples: `KXBTC15M`, `btc-up-or-down-15m`, `KXFEDDECISION`. Required when `q` is not provided.
`kalshi` or `polymarket`.
`active` or `closed`.
Exact category match.
Minimum aggregate volume across child markets.
ISO timestamp filter on `discovered_at`.
`relevance`, `newest`, `markets`, `volume`, or `liquidity`. `relevance` requires `q`; without `q` the default is `newest`.
1-100.
Pagination offset. The response is a bare JSON array — increment `offset` by `limit` until you receive an empty array.
## Examples
### List active events in a series
Use `series_id` to scope results — typical for "show me what's tradable in this series right now" workflows.
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/search/events?series_id=KXBTC15M&status=active&limit=10"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/search/events",
headers={"X-API-Key": "your_api_key"},
params={"series_id": "KXBTC15M", "status": "active", "limit": 10},
)
events = response.json()
```
### Free-text search
Useful when you don't know the `series_id` or want to span series.
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/search/events?q=election&sort_by=volume"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/search/events",
headers={"X-API-Key": "your_api_key"},
params={"q": "election", "sort_by": "volume"},
)
events = response.json()
```
## Response
```json theme={null}
[
{
"event_id": "KXBTC15M-26MAY040400",
"exchange": "kalshi",
"series_id": "KXBTC15M",
"title": "BTC Up or Down - 15 minutes",
"category": "Crypto",
"status": "active",
"image_url": "https://d1lvyva3zy5u58.cloudfront.net/series-images-webp/KXBTC15M.webp?size=sm",
"discovered_at": "2026-05-04T00:05:52Z",
"market_count": 1,
"total_volume": 0,
"total_liquidity": 0,
"market_questions": ["BTC price up in next 15 mins?"]
}
]
```
## Errors
| Code | Reason |
| ----- | ------------------------------------- |
| `400` | Neither `q` nor `series_id` provided. |
# Search markets
Source: https://docs.oddpool.com/search/search-markets
GET https://api.oddpool.com/search/markets
Find markets by text query, by series, or both.
You must pass **either `q` or `series_id`** (or both). Requests with neither return `400`.
Use `q` for free-text search and `series_id` to scope results to one series. If you don't have a `series_id` yet, find one via [Search series](/search/series). To list every outcome under a single known event, use [Event markets](/search/event-markets) instead.
## Parameters
Full-text search on `question`. Required when `series_id` is not provided.
Filter to one series. Examples: `KXBTC15M`, `btc-up-or-down-15m`, `KXFEDDECISION`. Required when `q` is not provided.
`kalshi` or `polymarket`.
`active` (still trading) or `closed` (settled with a result).
Exact category match.
Minimum volume.
Minimum liquidity.
ISO timestamp. Returns markets with `settled_at >= settled_after`. Pair with `status=closed` for backtest workflows.
ISO timestamp. Returns markets with `settled_at <= settled_before`.
ISO timestamp filter on `discovered_at`. For polling new listings.
`relevance`, `newest`, `volume`, or `liquidity`. `relevance` requires `q`; without `q` the default is `newest`.
1-100.
Pagination offset. The response is a bare JSON array — increment `offset` by `limit` until you receive an empty array.
## Examples
### Settled-window discovery for a backtest
List the closed markets that resolved in a window for a known series. The returned `market_id` values feed directly into the [Kalshi historical](/kalshi/overview) endpoints.
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/search/markets?series_id=KXBTC15M&status=closed&settled_after=2026-04-25T00:00:00Z&limit=100"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/search/markets",
headers={"X-API-Key": "your_api_key"},
params={
"series_id": "KXBTC15M",
"status": "closed",
"settled_after": "2026-04-25T00:00:00Z",
"limit": 100,
},
)
markets = response.json()
```
### Free-text search
Filter markets across series by keyword. Sort by volume to surface the most active.
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/search/markets?q=fed+rate&exchange=kalshi&sort_by=volume"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/search/markets",
headers={"X-API-Key": "your_api_key"},
params={"q": "fed rate", "exchange": "kalshi", "sort_by": "volume"},
)
markets = response.json()
```
## Response
```json theme={null}
[
{
"market_id": "KXBTC15M-26MAY020000-00",
"exchange": "kalshi",
"series_id": "KXBTC15M",
"question": "BTC price up in next 15 mins?",
"category": null,
"status": "closed",
"volume": 375089,
"liquidity": 119393,
"last_yes_price": "0.9990",
"last_no_price": "0.0000",
"event_id": "KXBTC15M-26MAY020000",
"event_title": "BTC 15 min · $78,396.72 target",
"slug": null,
"discovered_at": "2026-05-01T03:50:00Z",
"settled_at": "2026-05-02T04:00:26Z"
}
]
```
## Errors
| Code | Reason |
| ----- | ------------------------------------- |
| `400` | Neither `q` nor `series_id` provided. |
# Search series
Source: https://docs.oddpool.com/search/series
GET https://api.oddpool.com/search/series
Discover Kalshi and Polymarket series.
A *series* groups recurring or related events under one identifier. `KXBTC15M` is the Kalshi 15-minute Bitcoin up/down series — one event opens every 15 minutes and a new market is created for it. `btc-up-or-down-15m` is the Polymarket equivalent. `KXFEDDECISION` is the series for Kalshi Fed rate-decision events.
Use this endpoint to find a `series_id`, then pass it to [Search markets](/search/search-markets) or [Search events](/search/search-events) to drill in.
## Parameters
Matches the series title (full-text) **or** the `series_id` (case-insensitive substring). `q=KXBTC` returns every Kalshi BTC series; `q=15m` returns every 15-minute series across both venues; `q=bitcoin` returns series whose title contains "Bitcoin".
`kalshi` or `polymarket`.
Exact match on `category`. Common values: `Crypto`, `Sports`, `Politics`, `Up or Down`.
`active` or `closed`.
`title` (alphabetical) or `newest` (by `discovered_at`).
1-100.
Pagination offset.
## Examples
### Find all 15-minute crypto series across both venues
A single call surfaces the matching Kalshi and Polymarket series side by side.
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/search/series?q=15m"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/search/series",
headers={"X-API-Key": "your_api_key"},
params={"q": "15m"},
)
series = response.json()
```
### Browse Polymarket sports series
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/search/series?exchange=polymarket&category=Sports&limit=50"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/search/series",
headers={"X-API-Key": "your_api_key"},
params={"exchange": "polymarket", "category": "Sports", "limit": 50},
)
series = response.json()
```
## Response
```json theme={null}
[
{
"series_id": "KXBTC15M",
"exchange": "kalshi",
"title": "Bitcoin price up down",
"category": "Crypto",
"status": "active",
"n_events_total": 4285,
"n_events_active": 88,
"earliest_event_at": "2026-03-13T19:00:00Z",
"latest_event_at": "2026-05-04T00:05:52Z"
},
{
"series_id": "btc-up-or-down-15m",
"exchange": "polymarket",
"title": "BTC Up or Down 15 Min",
"category": "Up or Down",
"status": "active",
"n_events_total": 4310,
"n_events_active": 96,
"earliest_event_at": "2026-03-15T07:00:00Z",
"latest_event_at": "2026-05-04T01:23:19Z"
}
]
```
## Field reference
| Field | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `series_id` | Pass this to `?series_id=` on [Search markets](/search/search-markets) and [Search events](/search/search-events). |
| `n_events_total` | Total events in this series. |
| `n_events_active` | Events currently open for trading. |
| `earliest_event_at` | Earliest event in this series. |
| `latest_event_at` | Most recent event in this series. |
Not every event belongs to a series — about 15% are one-off markets (news markets, single-instance political markets) that aren't part of any recurring group. Find those via [Search events](/search/search-events) text search instead.
# Feed catalog
Source: https://docs.oddpool.com/websocket/catalog
GET https://api.oddpool.com/feeds/catalog
Discover available events and the exact channel names to subscribe to.
List all available feed events, grouped by feed vertical. Returns event keys, venues, outcome counts, and pre-built channel names.
## Parameters
Filter by feed vertical: `macro`, `crypto`, or `weather`.
Filter by event status: `active`, `upcoming`, or `resolved`. Useful for crypto feeds where hundreds of short-lived events accumulate — use `status=active` to get only live events.
Opt-in expansions, comma-separated. Supported values:
* `channels` — adds a ready-to-paste `subscribe_all` array to each event listing every channel you'd subscribe to.
* `outcomes` — replaces the integer `outcomes` count with the full outcome list (same shape as the single-event endpoint).
* `*` — all expansions.
The default response stays compact for browsing; agents subscribing to data should use `expand=channels` to skip the second API call.
## Example
```bash theme={null}
# All active crypto events
curl -H "X-API-Key: oddpool_..." \
"https://api.oddpool.com/feeds/catalog?feed=crypto&status=active"
# Macro events with ready-to-paste subscribe arrays
curl -H "X-API-Key: oddpool_..." \
"https://api.oddpool.com/feeds/catalog?feed=macro&expand=channels"
```
## Response (default)
```json theme={null}
{
"feeds": {
"macro": {
"description": "Cross-venue macro economic prediction markets",
"events": [
{
"event_key": "fomc-2026-06-17",
"title": "FOMC June 2026",
"type": "fomc",
"event_date": "2026-06-17",
"release_at": "2026-06-17T17:59:00+00:00",
"status": "active",
"venues": ["kalshi", "polymarket"],
"outcomes": 5,
"channels": {
"dist": "dist:fomc-2026-06-17",
"snapshot": "snapshot:fomc-2026-06-17",
"book": "book:fomc-2026-06-17:{outcome_key}",
"trade": "trade:fomc-2026-06-17:{outcome_key}"
}
}
]
}
}
}
```
| Field | Description |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event_key` | Unique identifier for the event. Use this in channel subscriptions. |
| `event_date` | Calendar date of the release (YYYY-MM-DD). |
| `release_at` | Full ISO 8601 timestamp with timezone for the actual release moment. CPI is 8:30 ET (12:30 UTC), FOMC is 14:00 ET, etc. Use this for scheduling and timing logic. |
| `venues` | Which prediction market venues have outcomes configured for this event. **Caveat**: this reflects "configured" not "currently quoting". An event listing both venues may have one quiet venue with no live book. |
| `outcomes` | Number of outcomes (default) OR full outcome array (with `expand=outcomes`). |
| `enrichment` | External data source attached to every message. Only present for enriched feeds (`"binance"` for crypto). Macro events do not have this field. |
| `channels.dist` | Event-level — subscribe to this single channel to get the joint cross-venue distribution for all outcomes. Free tier. |
| `channels.snapshot` | Event-level — full state reset every 60s. Pro tier. |
| `channels.book` / `channels.trade` | **Templates with `{outcome_key}` placeholder**. Per-outcome channels — replace the placeholder with a value from `expand=outcomes` or the single-event endpoint. Pro tier. |
## Response with `?expand=channels`
Adds `subscribe_all` to each event — a ready-to-paste array of every channel for that event:
```json theme={null}
{
"event_key": "fomc-2026-06-17",
"channels": {
"dist": "dist:fomc-2026-06-17",
"snapshot": "snapshot:fomc-2026-06-17",
"book": "book:fomc-2026-06-17:{outcome_key}",
"trade": "trade:fomc-2026-06-17:{outcome_key}",
"subscribe_all": [
"dist:fomc-2026-06-17",
"snapshot:fomc-2026-06-17",
"book:fomc-2026-06-17:cut_50",
"book:fomc-2026-06-17:cut_25",
"book:fomc-2026-06-17:hold",
"book:fomc-2026-06-17:hike_25",
"book:fomc-2026-06-17:hike_50",
"trade:fomc-2026-06-17:cut_50",
"trade:fomc-2026-06-17:cut_25",
"trade:fomc-2026-06-17:hold",
"trade:fomc-2026-06-17:hike_25",
"trade:fomc-2026-06-17:hike_50"
]
}
}
```
Use `subscribe_all` directly as the `channels` field of your WebSocket subscribe action. No more N+1 lookup for outcome keys.
## Response with `?expand=outcomes`
Replaces the integer `outcomes` count with the full outcome array (same shape as the single-event endpoint):
```json theme={null}
{
"event_key": "fomc-2026-06-17",
"outcome_count": 5,
"outcomes": [
{
"outcome_key": "hold",
"label": "Fed maintains rate",
"venues": ["kalshi", "polymarket"],
"channels": {
"book": "book:fomc-2026-06-17:hold",
"trade": "trade:fomc-2026-06-17:hold"
}
},
"..."
]
}
```
When `expand=outcomes` is set, both `outcomes` (array) and `outcome_count` (number) are returned. Without expand, only `outcomes` (number) is returned.
***
## Get single event
```http theme={null}
GET https://api.oddpool.com/feeds/catalog/{event_key}
```
Get a single event with its full outcome list, ready-to-paste subscribe array, and per-outcome channel names.
### Example
```bash theme={null}
curl -H "X-API-Key: oddpool_..." \
"https://api.oddpool.com/feeds/catalog/fomc-2026-06-17"
```
### Response
```json theme={null}
{
"event_key": "fomc-2026-06-17",
"title": "FOMC June 2026",
"type": "fomc",
"feed": "macro",
"event_date": "2026-06-17",
"release_at": "2026-06-17T17:59:00+00:00",
"status": "active",
"venues": ["kalshi", "polymarket"],
"outcome_count": 5,
"outcomes": [
{
"outcome_key": "hold",
"label": "Fed maintains rate",
"venues": ["kalshi", "polymarket"],
"channels": {
"book": "book:fomc-2026-06-17:hold",
"trade": "trade:fomc-2026-06-17:hold"
}
},
{
"outcome_key": "cut_25",
"label": "Cut 25bps",
"venues": ["kalshi", "polymarket"],
"channels": {
"book": "book:fomc-2026-06-17:cut_25",
"trade": "trade:fomc-2026-06-17:cut_25"
}
}
],
"channels": {
"dist": "dist:fomc-2026-06-17",
"snapshot": "snapshot:fomc-2026-06-17",
"book": "book:fomc-2026-06-17:{outcome_key}",
"trade": "trade:fomc-2026-06-17:{outcome_key}",
"subscribe_all": [
"dist:fomc-2026-06-17",
"snapshot:fomc-2026-06-17",
"book:fomc-2026-06-17:hold",
"...": "..."
]
}
}
```
***
## Decode event types
```http theme={null}
GET https://api.oddpool.com/feeds/event-types
```
Returns a stable dictionary mapping each `event_type` token (e.g., `cpi`, `fomc`, `nfp`) to human-readable label, description, publishing agency, typical release time + timezone, source URL, and category. Use this to interpret event\_type values without prior macro-economic knowledge.
### Example
```bash theme={null}
curl -H "X-API-Key: oddpool_..." "https://api.oddpool.com/feeds/event-types"
```
### Response
```json theme={null}
{
"event_types": {
"cpi": {
"label": "Consumer Price Index",
"description": "US headline inflation. Measures the month-over-month or year-over-year change in a basket of consumer goods and services.",
"agency": "Bureau of Labor Statistics",
"release_local_time": "08:30",
"timezone": "America/New_York",
"release_url": "https://www.bls.gov/cpi/",
"category": "inflation",
"vertical": "macro"
},
"fomc": {
"label": "FOMC Rate Decision",
"description": "Federal Open Market Committee policy decision on the federal funds rate target range. Eight scheduled meetings per year.",
"agency": "Federal Reserve",
"release_local_time": "14:00",
"timezone": "America/New_York",
"release_url": "https://www.federalreserve.gov/monetarypolicy/fomccalendars.htm",
"category": "monetary_policy",
"vertical": "macro"
}
}
}
```
| Field | Description |
| -------------------- | --------------------------------------------------------------------------------------------------------------- |
| `label` | Human-readable name. Use in dashboards. |
| `description` | One-paragraph plain-English explanation. |
| `agency` | Who publishes the data. |
| `release_local_time` | Time-of-day at which releases typically drop, in the named timezone. `null` for continuous markets (crypto). |
| `timezone` | IANA timezone identifier for `release_local_time`. |
| `category` | Higher-level grouping (`inflation`, `employment`, `growth`, `monetary_policy`, `markets`, `crypto`, `weather`). |
| `vertical` | Which `feed=` filter this type belongs to. |
# Crypto feed
Source: https://docs.oddpool.com/websocket/crypto-feed
Bitcoin and Ethereum prediction markets enriched with real-time Binance spot and futures data.
Real-time BTC and ETH prediction market data from Kalshi and Polymarket, enriched with Binance spot/futures reference prices on every message. Events are auto-discovered and matched cross-venue — new timeframes appear continuously and resolve automatically.
## Assets
| Asset | Binance symbol | Kalshi series | Polymarket series |
| -------- | -------------- | -------------------- | --------------------------------------------------- |
| Bitcoin | `BTCUSDT` | `KXBTCD`, `KXBTC15M` | `btc-up-or-down-*`, `bitcoin-multi-strikes-hourly` |
| Ethereum | `ETHUSDT` | `KXETHD`, `KXETH15M` | `eth-up-or-down-*`, `ethereum-multi-strikes-hourly` |
## Event types
### Hourly above/below (cross-venue matched)
Threshold strike markets: "Will BTC be above \$68,500 at 3PM ET?" Both Kalshi and Polymarket run these, and Oddpool matches them by datetime so you get cross-venue probabilities on a single channel.
```text theme={null}
btc-hourly-2026-03-24-1500-et "BTC Hourly Mar 24 3:00PM ET"
eth-hourly-2026-03-24-1500-et "ETH Hourly Mar 24 3:00PM ET"
```
Each hourly event has many outcomes (one per strike price). Kalshi typically has \~188 strikes, Polymarket \~10. All are available as separate outcomes on the same event.
### 15-minute up/down (cross-venue matched)
Binary "will the price go up or down in this 15-minute window?" Available on both Kalshi and Polymarket, matched by start time.
```text theme={null}
btc-15m-2026-03-24-1445-et "BTC 15min Mar 24 2:45PM ET"
eth-15m-2026-03-24-1445-et "ETH 15min Mar 24 2:45PM ET"
```
### 5-minute up/down (Polymarket only)
Same as 15-minute but with 5-minute resolution. Only available on Polymarket.
```text theme={null}
btc-5m-2026-03-24-1450-et "BTC 5min Mar 24 2:50PM ET"
eth-5m-2026-03-24-1450-et "ETH 5min Mar 24 2:50PM ET"
```
### Hourly up/down (Polymarket only)
Binary up/down for the full hour. Only available on Polymarket.
```text theme={null}
btc-updown-hourly-2026-03-24-15-et "BTC Up/Down Hourly Mar 24 3PM ET"
eth-updown-hourly-2026-03-24-15-et "ETH Up/Down Hourly Mar 24 3PM ET"
```
## Binance reference data
Every crypto `dist`, `book`, and `trade` message includes a `reference` field with real-time Binance spot and futures data. This gives your trading agent an immediate reference price without needing a separate data feed.
```json theme={null}
"reference": {
"source": "binance",
"symbol": "BTCUSDT",
"spot_bid": 68641.30,
"spot_ask": 68641.31,
"spot_mid": 68641.30,
"futures_mark": 68723.51,
"funding_rate": -0.00002124,
"volume_24h": 951752117.03,
"ts": 1774246512319
}
```
| Field | Description |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `spot_bid` / `spot_ask` | Binance spot best bid and ask. Tight spread = high liquidity. |
| `spot_mid` | `(spot_bid + spot_ask) / 2`, rounded to 2 decimal places. |
| `futures_mark` | Binance USDM futures mark price. Compare with `spot_mid` to gauge basis (contango/backwardation). |
| `funding_rate` | Current funding rate. Positive = longs pay shorts (bullish consensus). Negative = bears pay longs. |
| `volume_24h` | 24-hour trading volume in USD. |
| `ts` | When this reference data was captured (Unix ms). Compare with `published_ts` to check staleness — typically under 10ms. |
The `reference` field is only present on crypto events. Macro events (FOMC, CPI, etc.) do not include it.
## Example: distribution message
```json theme={null}
{
"channel": "dist:btc-5m-2026-03-24-0830-et",
"data": {
"event_key": "btc-5m-2026-03-24-0830-et",
"seq": 2043,
"published_ts": 1774246512323,
"outcomes": [
{
"outcome": "0xf09bc369",
"label": "Bitcoin Up or Down - March 24, 8:30AM-8:35AM ET",
"kalshi_prob": null,
"poly_prob": 0.645,
"prob": 0.645,
"kalshi_depth_usd": 0,
"poly_depth_usd": 2871.65
}
],
"total_kalshi_depth_usd": 0,
"total_poly_depth_usd": 2871.65,
"reference": {
"source": "binance",
"symbol": "BTCUSDT",
"spot_bid": 68641.30,
"spot_ask": 68641.31,
"spot_mid": 68641.30,
"futures_mark": 68723.51,
"funding_rate": -0.00002124,
"volume_24h": 951752117.03,
"ts": 1774246512319
}
}
}
```
## Example: cross-venue matched hourly
Hourly above/below events that exist on both venues show cross-venue probabilities:
```json theme={null}
{
"channel": "dist:btc-hourly-2026-03-24-0900-et",
"data": {
"event_key": "btc-hourly-2026-03-24-0900-et",
"seq": 87,
"published_ts": 1774260000123,
"outcomes": [
{
"outcome": "t68499.99",
"label": "Bitcoin above $68,500 at 9AM ET?",
"kalshi_prob": 0.585,
"poly_prob": 0.59,
"prob": 0.5869,
"kalshi_depth_usd": 3241.50,
"poly_depth_usd": 1856.20
}
],
"total_kalshi_depth_usd": 45612.88,
"total_poly_depth_usd": 12340.56,
"reference": {
"source": "binance",
"symbol": "BTCUSDT",
"spot_bid": 68490.00,
"spot_ask": 68490.50,
"spot_mid": 68490.25,
"futures_mark": 68520.10,
"funding_rate": -0.00002124,
"volume_24h": 951752117.03,
"ts": 1774260000118
}
}
}
```
## Event lifecycle
Crypto events are **auto-discovered** from both venues. New events appear continuously — a new 5-minute window every 5 minutes, a new 15-minute window every 15 minutes, a new hourly window every hour.
| Phase | Duration | What happens |
| ------------ | ------------------------ | ---------------------------------------- |
| **Active** | During the event window | Data streaming, distributions updating |
| **Resolved** | 2 hours after event ends | Grace period for final settlement trades |
| **Removed** | After resolution | Event stops appearing in catalog |
Use `?feed=crypto&status=active` in the [catalog endpoint](/websocket/catalog) to get only live events.
## Use cases
**BTC 5-minute Polymarket trading bot.** The 5min BTC up/down markets on Polymarket update every 2-10ms at the distribution level. Combined with sub-10ms Binance spot price staleness, you get a fused view of BTC spot price + market-implied probability at near-real-time latency — everything a trading bot needs to place and adjust orders within each 5-minute window. Build mean-reversion or momentum signals on the probability-vs-spot relationship.
**BTC and ETH 15-minute Kalshi + Polymarket arbitrage bot.** The 15min up/down events exist on both Kalshi and Polymarket, cross-venue matched by time window. Each message includes `venue_id` with exact execution identifiers for both venues. When `kalshi_prob` and `poly_prob` diverge on the same 15-minute BTC or ETH outcome, an arbitrage bot can immediately act on the spread. The `reference` field provides the Binance spot price to validate whether the divergence is justified by price movement.
**Strike distance signals.** Compare `spot_mid` from the Binance reference with the prediction market's strike price to compute real-time distance-to-strike. A Kalshi "BTC above $68,500" market at 58% probability with spot at $68,490 means the market prices a 58% chance of a \$10 move — your model may disagree.
**Basis trading.** The `futures_mark` vs `spot_mid` spread (basis) indicates market sentiment. Widening contango during a prediction market probability spike suggests leveraged longs are driving the move. Combine with `funding_rate` to gauge crowding — useful context for any Kalshi or Polymarket crypto trading bot.
**Volatility regime detection.** Monitor `volume_24h` alongside prediction market depth (`kalshi_depth_usd`, `poly_depth_usd`). Low prediction market depth during high BTC or ETH spot volume signals uncertainty — wider spreads and more opportunity for a trading bot. High depth during low volume signals consensus.
**Multi-asset correlation.** BTC and ETH feeds run simultaneously with independent Binance enrichment. Track how ETH Polymarket probabilities respond to BTC spot moves (or vice versa) on both Kalshi and Polymarket to identify cross-asset momentum or hedging opportunities across 5min, 15min, and hourly timeframes.
## Quick start
```python theme={null}
import asyncio, websockets, json
async def stream_btc():
async with websockets.connect("wss://feeds.oddpool.com/ws") as ws:
# Authenticate
await ws.send(json.dumps({"action": "auth", "api_key": "oddpool_..."}))
print(await ws.recv())
# Get active crypto events from catalog
# GET https://api.oddpool.com/feeds/catalog?feed=crypto&status=active
# Subscribe to a BTC 5-minute distribution
await ws.send(json.dumps({
"action": "subscribe",
"channels": ["dist:btc-5m-2026-03-24-0830-et"]
}))
print(await ws.recv())
# Stream messages
async for msg in ws:
data = json.loads(msg)
if data["type"] == "data":
payload = data["data"]
ref = payload.get("reference", {})
for o in payload.get("outcomes", []):
print(f"BTC spot ${ref.get('spot_mid', '?'):,.2f} | "
f"{o['label']}: {o['prob']:.1%} "
f"(K:{o['kalshi_prob'] or '—'} P:{o['poly_prob'] or '—'})")
asyncio.run(stream_btc())
```
# Limits and errors
Source: https://docs.oddpool.com/websocket/limits
WebSocket connection limits by tier and error codes.
## Limits by tier
| Tier | Connections | Events | Channels | Snapshots |
| ---------- | ----------- | --------- | --------- | --------- |
| Free | 1 | 2 | dist only | No |
| Pro | 3 | 10 | All | Yes |
| Premium | 10 | Unlimited | All | Yes |
| Enterprise | Unlimited | Unlimited | All | Yes |
## Error codes
| Code | Description |
| --------------------- | ---------------------------------------------------------------------------------------------- |
| `CHANNEL_NOT_ALLOWED` | Your tier does not have access to this channel type. Free tier only supports `dist:` channels. |
| `EVENT_LIMIT` | Concurrent event subscription limit reached. Unsubscribe from an event to free up a slot. |
| `CONNECTION_LIMIT` | Maximum concurrent connections reached. Close an existing connection first. |
# Macro feed
Source: https://docs.oddpool.com/websocket/macro-feed
Cross-venue matched macro economic events from Kalshi and Polymarket.
Cross-venue matched macro economic events from Kalshi and Polymarket. Real-time orderbooks, trade executions, and liquidity-weighted probability distributions for FOMC, CPI, NFP, GDP, unemployment, rate expectations, equities, and commodities.
## Event categories
| Category | Examples |
| ---------------------- | ------------------------------------------------------- |
| FOMC Rate Decisions | Upcoming meeting outcomes, total rate cuts for the year |
| Inflation (CPI) | Year-over-year, month-over-month |
| Jobs | Nonfarm payrolls, unemployment rate |
| GDP | Quarterly real GDP, annual GDP growth |
| Equities & Commodities | S\&P 500, Nasdaq 100, gold, silver |
Use the [feed catalog](/websocket/catalog) endpoint to get the full list of available events and their channel names.
## Use cases
**Arbitrage.** See cross-venue price divergence in real-time. Each message includes `venue_id` with the venue-specific identifiers (Kalshi market ticker, Polymarket condition/token IDs) needed to execute immediately.
**Signal generation.** The `dist` channel computes a liquidity-weighted cross-venue probability on every book update -- a better signal than either venue alone.
**Macro monitoring.** One WebSocket covers FOMC, CPI, NFP, GDP, unemployment, and rate expectations. Subscribe to `dist:*` events and build a real-time macro probability surface.
**Trading agents.** Normalized schema means your agent code is venue-agnostic. Parse once, trade anywhere.
# Message reference
Source: https://docs.oddpool.com/websocket/messages
Schema reference for dist, book, trade, and snapshot channel messages.
Four channel types, one envelope. Every message arrives wrapped in:
```json theme={null}
{"channel": "dist:fomc-2026-04-29", "data": { ... }}
```
The wrapper has exactly two top-level fields: `channel` (string) and `data` (object). Filter your message handler on `channel`, not on a `type` field — there is no `type` field in the wire format.
## Channel types
| Channel | Subscribe to | Description | Tier |
| ------------ | ---------------------------- | ----------------------------------------------------- | ---- |
| **dist** | `dist:fomc-2026-04-29` | Cross-venue probability distribution for all outcomes | Free |
| **book** | `book:fomc-2026-04-29:hold` | Normalized orderbook updates for one outcome | Pro |
| **trade** | `trade:fomc-2026-04-29:hold` | Trade executions for one outcome | Pro |
| **snapshot** | `snapshot:fomc-2026-04-29` | Full state reset every 60s for all outcomes | Pro |
`dist` and `snapshot` are **event-level** channels — subscribe with just the `event_key`. `book` and `trade` are **per-outcome** channels — append `:{outcome_key}` (use `?expand=channels` on `/feeds/catalog` to get a ready-to-paste list).
`snapshot` payload differs from `dist`: snapshot wraps the per-outcome list under `data.distribution` and adds full orderbook state per outcome. `dist` uses `data.outcomes`.
## Distribution
Shows the market-implied probability for every outcome in an event, combining data from both venues.
```json theme={null}
{
"event_key": "fomc-2026-04-29",
"seq": 312,
"published_ts": 1773892530947,
"outcomes": [
{
"outcome": "hold",
"label": "Fed maintains rate",
"kalshi_prob": 0.945,
"poly_prob": 0.955,
"prob": 0.9479,
"kalshi_depth_usd": 4714197.04,
"poly_depth_usd": 1965879.48
}
],
"total_kalshi_depth_usd": 4784397.57,
"total_poly_depth_usd": 2104564.65,
"reference": {
"source": "binance",
"symbol": "BTCUSDT",
"spot_bid": 68641.30,
"spot_ask": 68641.31,
"spot_mid": 68641.30,
"futures_mark": 68723.51,
"funding_rate": -0.00002124,
"volume_24h": 951752117.03,
"ts": 1774246512319
}
}
```
| Field | Description |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `prob` | Combined probability, weighted by each venue's liquidity. Kalshi at 94.5% with 4.7M USD depth + Polymarket at 95.5% with 2.0M USD depth = 94.79%. |
| `kalshi_prob` / `poly_prob` | Each venue's YES mid-price (best bid + best ask) / 2. Null if that venue has no data. |
| `kalshi_depth_usd` / `poly_depth_usd` | Total USD within +/-5c of mid on each venue. Shows how much liquidity backs each probability. |
| `reference` | External reference data (e.g., Binance spot/futures for crypto events). Only present on enriched feeds. See [crypto feed](/websocket/crypto-feed) for field details. |
## Book update
Normalized orderbook updates from both venues. Prices are contract prices (0-1), sizes are absolute quantities. Derived fields like best bid/ask, mid, spread, and depth are pre-computed on every update.
```json theme={null}
{
"event_key": "fomc-2026-04-29",
"outcome": "hold",
"venue": "polymarket",
"token": "yes",
"venue_id": {
"condition_id": "0x36e8ca2...",
"token_id": "63586620628..."
},
"seq": 4217,
"exchange_ts": 1773892530351,
"received_ts": 1773892530360,
"published_ts": 1773892530363,
"update_type": "delta",
"levels": [
{"side": "bid", "price": "0.955", "size": 8300.00},
{"side": "ask", "price": "0.965", "size": 0}
],
"best_bid": "0.955",
"best_ask": "0.965",
"mid": "0.960",
"spread": "0.010",
"bid_depth_usd": 47141.97,
"ask_depth_usd": 23456.78,
"reference": { ... }
}
```
| Field | Description |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| `venue_id` | Identifiers to execute on the venue. Kalshi: market ticker. Polymarket: condition ID + token ID. |
| `update_type` | "snapshot" = full book replacement. "delta" = only changed levels. Size of 0 = level removed. |
| `levels` | Price levels with side (bid/ask), price (0-1), and size (absolute quantity after this update). |
| `best_bid` / `best_ask` / `mid` / `spread` | Pre-computed from full cross-token book state, not just the levels in this update. |
| `bid_depth_usd` / `ask_depth_usd` | Total USD within +/-5c of mid on bid and ask sides. |
| `reference` | External reference data for enriched feeds. Only present on crypto events. Omitted for macro. |
## Trade
Every trade execution from both venues, including the specific contract traded, price, quantity, and a venue-provided trade ID.
```json theme={null}
{
"event_key": "fomc-2026-04-29",
"outcome": "hold",
"venue": "kalshi",
"token": "no",
"action": "buy",
"venue_id": {"market_ticker": "KXFEDDECISION-26APR-H0"},
"seq": 89,
"exchange_ts": 1773892530000,
"received_ts": 1773892530008,
"published_ts": 1773892530010,
"price": "0.06",
"qty": 136.00,
"trade_id": "a4d77927-dafe-5bde-53ed-80f6129cfd19",
"reference": { ... }
}
```
## Timestamps
Every book and trade message carries four timestamps (Unix ms) for end-to-end latency decomposition.
| Field | Description |
| -------------- | ----------------------------------------------------------------------------- |
| `exchange_ts` | When the exchange says the event occurred. Null if not provided by the venue. |
| `received_ts` | When we read the raw message off the venue WebSocket. |
| `published_ts` | When the normalized message was published internally. |
| `gateway_ts` | When the gateway sent the message to your WebSocket. |
# WebSocket overview
Source: https://docs.oddpool.com/websocket/overview
Stream real-time prediction market data over a single WebSocket connection.
Stream real-time, normalized prediction market data over WebSocket. One connection gives you cross-venue orderbooks, trades, and probability distributions from both Kalshi and Polymarket -- no venue-specific code needed.
Data is addressed by **what**, not where. You subscribe to `dist:fomc-2026-04-29` or `dist:btc-5m-2026-03-24-0830-et`, not a venue-specific ticker. Both venues arrive on the same channel, tagged by `venue` field.
Two feeds are available:
* [**Macro feed**](/websocket/macro-feed) — FOMC, CPI, NFP, GDP, unemployment, equities, commodities
* [**Crypto feed**](/websocket/crypto-feed) — BTC and ETH prediction markets enriched with real-time Binance spot/futures data
```text theme={null}
wss://feeds.oddpool.com/ws
```
## Authentication
Send an auth message with your API key after connecting. The server responds with your tier and limits.
```json theme={null}
// Send
{"action": "auth", "api_key": "oddpool_abc123..."}
// Response
{
"type": "auth",
"status": "ok",
"user_id": 42,
"tier": "pro",
"limits": {
"max_events": 10,
"max_connections": 3,
"channels": ["dist", "book", "trade", "snapshot"]
}
}
```
`max_events: 0` means **unlimited** (Premium tier and above). Any positive number is the cap.
## Subscribe and unsubscribe
Subscribe to channels by event. Events count as concurrent subscriptions -- unsubscribing frees up slots.
### Channel format
| Format | Example | Scope |
| ------------------------------ | --------------------------- | --------------------------- |
| `{type}:{event_key}` | `dist:fomc-2026-04-29` | All outcomes for that event |
| `{type}:{event_key}:{outcome}` | `book:fomc-2026-04-29:hold` | Single outcome |
### Example
```json theme={null}
// Subscribe to multiple channels at once
{"action": "subscribe", "channels": ["dist:fomc-2026-04-29", "book:fomc-2026-04-29:hold"]}
// Response
{"type": "subscribed", "channels": ["dist:fomc-2026-04-29", "book:fomc-2026-04-29:hold"]}
// Unsubscribe
{"action": "unsubscribe", "channels": ["book:fomc-2026-04-29:hold"]}
```
### Subscribe response shape
`channels` lists the channels that were actually subscribed. If any submitted channel was dropped, the response also includes a `rejected` array — only present when there is at least one rejection. Each entry has a `channel` and a `reason`:
| `reason` | When it fires |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unknown_event_key` | The `event_key` is not currently served. Use [Catalog](/websocket/catalog) to find valid keys; venue-native IDs (Kalshi market tickers, Polymarket condition IDs) are not event keys. |
| `unknown_channel_type` | The prefix is not one of `dist`, `book`, `trade`, `snapshot`. |
| `missing_event_key` | The channel string has no `event_key` after the type prefix. |
```json theme={null}
// Mixed batch
{"action": "subscribe", "channels": ["dist:fomc-2026-04-29", "dist:not-a-real-event"]}
// Response
{
"type": "subscribed",
"channels": ["dist:fomc-2026-04-29"],
"rejected": [
{"channel": "dist:not-a-real-event", "reason": "unknown_event_key", "event_key": "not-a-real-event"}
]
}
```
`dist`, `book`, and `trade` are change-driven — messages emit only when the underlying state changes, so an idle channel is normal during quiet windows. For current state on connect, also subscribe to `snapshot:{event_key}`, which delivers a full state message every 60 seconds.
# Add event
Source: https://docs.oddpool.com/whales/add-event
POST https://api.oddpool.com/whales/user/events/by-ticker
Subscribe to an event so its whale trades appear in your feed and alerts.
Adds an event to your "My Events" watchlist. The `exchange` and `event_ticker` map directly onto the `exchange` and `event_id` fields returned by [Search events](/search/search-events), so you can pipe a search result straight into a subscription.
If we have never seen the event before, we fetch its metadata from the exchange and start tracking it. Markets under the event are discovered automatically, so whale trades begin flowing into your [feed](/whales/get-feed) shortly after.
## Body parameters
`kalshi` or `polymarket`.
The event identifier. This is the `event_id` value from a [Search events](/search/search-events) result: a Kalshi event ticker (for example `KXBTCD-26JAN01`) or a Polymarket event slug (for example `btc-up-or-down-15m-1782248400`).
Minimum trade size, in USD, for a trade on this event to count as a whale. Defaults to your account default when omitted.
## Example
```bash cURL theme={null}
curl -X POST "https://api.oddpool.com/whales/user/events/by-ticker" \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"exchange": "kalshi",
"event_ticker": "KXBTCD-26JAN01",
"whale_threshold_usd": 5000
}'
```
```python Python theme={null}
import requests
# 1. Discover an event
hit = requests.get(
"https://api.oddpool.com/search/events",
headers={"X-API-Key": "your_api_key"},
params={"q": "bitcoin", "status": "active", "limit": 1},
).json()[0]
# 2. Subscribe using the search identifiers verbatim
response = requests.post(
"https://api.oddpool.com/whales/user/events/by-ticker",
headers={"X-API-Key": "your_api_key"},
json={
"exchange": hit["exchange"],
"event_ticker": hit["event_id"],
"whale_threshold_usd": 5000,
},
)
data = response.json()
```
## Response
```json theme={null}
{
"success": true,
"event_id": 65,
"event_ticker": "KXBTCD-26JAN01",
"event_title": "Bitcoin price on Jan 1, 2026",
"platform": "kalshi",
"message": "Event added to tracking. Markets will be discovered automatically."
}
```
The numeric `event_id` is your internal tracking id. You can pass it to [Update event](/whales/update-event), or unsubscribe with either the `exchange`/`event_ticker` pair or this numeric id (see [Remove event](/whales/remove-event)).
## Adding by URL
If you have a Kalshi or Polymarket page URL instead of a search result, post it to `POST /whales/user/events/by-url` with the same optional fields. We parse the event out of the URL.
```bash cURL theme={null}
curl -X POST "https://api.oddpool.com/whales/user/events/by-url" \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://kalshi.com/markets/kxbtcd/bitcoin-price"}'
```
```python Python theme={null}
import requests
response = requests.post(
"https://api.oddpool.com/whales/user/events/by-url",
headers={"X-API-Key": "your_api_key"},
json={"url": "https://kalshi.com/markets/kxbtcd/bitcoin-price"},
)
```
## Errors
| Status | Reason |
| ------ | --------------------------------------------------------------------------------- |
| `400` | Malformed `event_ticker`, the event is already resolved, or you already track it. |
| `401` | Missing or invalid API key. |
| `403` | Your plan does not include whale tracking, or your subscription is inactive. |
| `404` | The event could not be found on the exchange. |
| `422` | `exchange` is not `kalshi` or `polymarket`, or a required field is missing. |
# Event stats
Source: https://docs.oddpool.com/whales/event-stats
GET https://api.oddpool.com/whales/user/event/{event_id}/stats
Get statistics for a specific tracked event.
## Parameters
Event ID.
Time period: `24h`, `7d`, or `all`.
## Example
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/whales/user/event/65/stats?period=all"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/whales/user/event/65/stats",
headers={"X-API-Key": "your_api_key"},
params={"period": "all"}
)
data = response.json()
```
## Response
```json theme={null}
{
"event_id": 65,
"event_ticker": "KXBUNDESLIGAGAME-26JAN14KOEBMU",
"event_title": "1. FC Cologne vs Bayern Munich",
"platform": "kalshi",
"period": "all",
"stats": {
"trade_count": 151,
"total_volume": 185195.0,
"avg_trade_size": 1226.46,
"market_count": 3,
"first_trade_at": "2026-01-14T19:51:59Z",
"last_trade_at": "2026-01-14T21:24:25Z"
},
"markets": [
{
"market_ticker": "KXBUNDESLIGAGAME-26JAN14KOEBMU-BMU",
"platform": "kalshi",
"trade_count": 124,
"total_volume": 156445.15,
"avg_trade_size": 1261.65
}
]
}
```
# Event trades
Source: https://docs.oddpool.com/whales/event-trades
GET https://api.oddpool.com/whales/user/event/{event_id}
Get whale trades for a specific tracked event.
## Parameters
Event ID.
Max results (1-500).
Pagination offset.
Minimum trade size in USD.
## Example
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/whales/user/event/65?limit=20"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/whales/user/event/65",
headers={"X-API-Key": "your_api_key"},
params={"limit": 20}
)
data = response.json()
```
# Get feed
Source: https://docs.oddpool.com/whales/get-feed
GET https://api.oddpool.com/whales/user/feed
Get whale trades for all your tracked events.
## Parameters
Max results (1-500).
Pagination offset.
Filter by start date (ISO 8601).
Filter by end date (ISO 8601).
Minimum trade size in USD.
Filter by platform: `kalshi` or `polymarket`.
Filter to specific event.
## Example
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/whales/user/feed?limit=10&platform=kalshi"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/whales/user/feed",
headers={"X-API-Key": "your_api_key"},
params={"limit": 10, "platform": "kalshi"}
)
data = response.json()
```
## Response
```json theme={null}
{
"trades": [
{
"id": 12345,
"platform": "kalshi",
"event_title": "1. FC Cologne vs Bayern Munich",
"market_title": "Winner: Bayern Munich",
"market_ticker": "KXBUNDESLIGAGAME-26JAN14KOEBMU-BMU",
"outcome": "Bayern Munich",
"timestamp": "2026-01-14T21:24:25Z",
"taker_side": "yes",
"trade_size_usd": 15000.00,
"price": 0.78,
"count": 1
}
],
"stats": {
"total_volume_24h": 185195.0,
"total_trades_24h": 151,
"avg_trade_size": 1226.46
},
"pagination": {"limit": 10, "offset": 0, "total": 151}
}
```
# Get stats
Source: https://docs.oddpool.com/whales/get-stats
GET https://api.oddpool.com/whales/user/stats
Get aggregated statistics for all tracked events.
## Parameters
Time period: `24h`, `7d`, or `all`.
## Example
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/whales/user/stats?period=7d"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/whales/user/stats",
headers={"X-API-Key": "your_api_key"},
params={"period": "7d"}
)
data = response.json()
```
## Response
```json theme={null}
{
"period": "7d",
"events": [
{
"event_id": 65,
"event_ticker": "KXBUNDESLIGAGAME-26JAN14KOEBMU",
"event_title": "1. FC Cologne vs Bayern Munich",
"platform": "kalshi",
"stats": {
"trade_count": 151,
"total_volume": 185195.0,
"avg_trade_size": 1226.46,
"market_count": 3
},
"markets": [...]
}
]
}
```
# List events
Source: https://docs.oddpool.com/whales/list-events
GET https://api.oddpool.com/whales/user/events
List all events you are currently tracking.
## Example
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
https://api.oddpool.com/whales/user/events
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/whales/user/events",
headers={"X-API-Key": "your_api_key"}
)
data = response.json()
```
## Response
```json theme={null}
{
"tracked_events": [
{
"id": 65,
"event_id": 65,
"event_ticker": "KXBUNDESLIGAGAME-26JAN14KOEBMU",
"event_title": "1. FC Cologne vs Bayern Munich",
"platform": "kalshi",
"whale_threshold_usd": 1000,
"notify_on_whale_trade": true,
"whale_count_24h": 12
}
]
}
```
# List markets
Source: https://docs.oddpool.com/whales/list-markets
GET https://api.oddpool.com/whales/user/markets
List all markets for your tracked events.
## Example
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
https://api.oddpool.com/whales/user/markets
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/whales/user/markets",
headers={"X-API-Key": "your_api_key"}
)
data = response.json()
```
## Response
```json theme={null}
{
"markets": [
{
"market_id": 370,
"market_ticker": "KXBUNDESLIGAGAME-26JAN14KOEBMU-BMU",
"market_title": "Winner: Bayern Munich",
"event_id": 65,
"event_title": "1. FC Cologne vs Bayern Munich",
"platform": "kalshi",
"whale_count_24h": 45
}
],
"total_markets": 3
}
```
# Market stats
Source: https://docs.oddpool.com/whales/market-stats
GET https://api.oddpool.com/whales/user/market/{market_id}/stats
Get statistics for a specific market.
## Parameters
Market ID.
Time period: `24h`, `7d`, or `all`.
## Example
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/whales/user/market/370/stats?period=all"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/whales/user/market/370/stats",
headers={"X-API-Key": "your_api_key"},
params={"period": "all"}
)
data = response.json()
```
## Response
```json theme={null}
{
"market_id": 370,
"market_ticker": "KXBUNDESLIGAGAME-26JAN14KOEBMU-BMU",
"market_title": "Winner: Bayern Munich",
"platform": "kalshi",
"period": "all",
"stats": {
"period": "all",
"trade_count": 124,
"total_volume": 156445.15,
"avg_trade_size": 1261.65,
"first_trade_at": "2026-01-14T19:51:59Z",
"last_trade_at": "2026-01-14T21:24:25Z"
}
}
```
# Market trades
Source: https://docs.oddpool.com/whales/market-trades
GET https://api.oddpool.com/whales/user/market/{market_id}/trades
Get whale trades for a specific market.
## Parameters
Market ID.
Max results (1-500).
Pagination offset.
Minimum trade size in USD.
## Example
```bash cURL theme={null}
curl -H "X-API-Key: your_api_key" \
"https://api.oddpool.com/whales/user/market/370/trades?limit=10"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.oddpool.com/whales/user/market/370/trades",
headers={"X-API-Key": "your_api_key"},
params={"limit": 10}
)
data = response.json()
```
# Whale tracking
Source: https://docs.oddpool.com/whales/overview
Monitor large trades across prediction markets.
Requires **Pro** plan (30/mo).
Programmatic access to [Whale Tracker](https://oddpool.com/whales). Build trading signals, power automated agents, or integrate whale alerts into your own workflows.
## What you can build
* **Trading signals:** use large trades as directional indicators -- when whales pile into one side, it often signals informed flow
* **Alert bot:** poll the feed endpoint and push notifications to Slack, Discord, or Telegram when trades exceed your threshold
* **Smart money dashboard:** aggregate whale activity across events to see where the biggest capital is moving
* **Copy trading:** track specific events and mirror whale positioning in real time
* **Market sentiment analysis:** correlate whale trade volume and direction with price movements to build sentiment models
* **Event-level risk monitoring:** watch for unusual whale activity spikes that may signal new information ahead of resolution
## Managing your tracked events
"My Events" is your personal watchlist. Whale alerts and the personalized [feed](/whales/get-feed) are scoped to the events you track, each with its own dollar threshold. You can manage this list programmatically, which is ideal for automating daily rotation (drop stale events, add fresh ones) instead of editing it by hand.
The workflow pairs directly with [Search](/search/overview):
1. Discover events with [`GET /search/events`](/search/search-events). Each result has an `exchange` and an `event_id` (a Kalshi ticker or Polymarket slug).
2. Subscribe with [`POST /whales/user/events/by-ticker`](/whales/add-event), passing that same `exchange` and `event_id`.
3. Review your watchlist with [`GET /whales/user/events`](/whales/list-events).
4. Adjust a threshold with [`PATCH /whales/user/events/{event_id}`](/whales/update-event), or unsubscribe with [`DELETE /whales/user/events/by-ticker/{exchange}/{event_ticker}`](/whales/remove-event).
All management endpoints authenticate with your `X-API-Key`, the same key used for every other endpoint. The key identifies your account, so requests only ever read or change your own watchlist. Each call counts toward your plan [rate limits](/rate-limits).
# Remove event
Source: https://docs.oddpool.com/whales/remove-event
DELETE https://api.oddpool.com/whales/user/events/by-ticker/{exchange}/{event_ticker}
Unsubscribe from an event you are tracking.
Removes an event from your "My Events" watchlist. This mirrors [Add event](/whales/add-event): you can unsubscribe with the same `exchange` and `event_ticker` you subscribed with, so rotation scripts never need to look up the internal numeric id.
## Path parameters
`kalshi` or `polymarket`.
The event identifier (the search `event_id`): a Kalshi event ticker or a Polymarket event slug.
## Example
```bash cURL theme={null}
curl -X DELETE \
"https://api.oddpool.com/whales/user/events/by-ticker/kalshi/KXBTCD-26JAN01" \
-H "X-API-Key: your_api_key"
```
```python Python theme={null}
import requests
response = requests.delete(
"https://api.oddpool.com/whales/user/events/by-ticker/kalshi/KXBTCD-26JAN01",
headers={"X-API-Key": "your_api_key"},
)
data = response.json()
```
## Response
```json theme={null}
{
"success": true,
"event_id": 65,
"event_ticker": "KXBTCD-26JAN01",
"platform": "kalshi"
}
```
## Removing by numeric id
If you already have the numeric `event_id` from [List events](/whales/list-events) or [Add event](/whales/add-event), you can delete with it directly.
```bash cURL theme={null}
curl -X DELETE "https://api.oddpool.com/whales/user/events/65" \
-H "X-API-Key: your_api_key"
```
```python Python theme={null}
import requests
response = requests.delete(
"https://api.oddpool.com/whales/user/events/65",
headers={"X-API-Key": "your_api_key"},
)
```
## Errors
| Status | Reason |
| ------ | ---------------------------------------------------------------------------- |
| `400` | Invalid `exchange` or malformed `event_ticker`. |
| `401` | Missing or invalid API key. |
| `403` | Your plan does not include whale tracking, or your subscription is inactive. |
| `404` | The event does not exist, or you are not tracking it. |
# Update event
Source: https://docs.oddpool.com/whales/update-event
PATCH https://api.oddpool.com/whales/user/events/{event_id}
Change the whale threshold for a tracked event.
Updates the per-event settings for an event you already track. Use it to raise or lower the dollar threshold that defines a whale on this event.
## Path parameters
The numeric tracking id, as returned by [List events](/whales/list-events) or [Add event](/whales/add-event).
## Body parameters
Minimum trade size, in USD, for a trade on this event to count as a whale.
## Example
```bash cURL theme={null}
curl -X PATCH "https://api.oddpool.com/whales/user/events/65" \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{"whale_threshold_usd": 10000}'
```
```python Python theme={null}
import requests
response = requests.patch(
"https://api.oddpool.com/whales/user/events/65",
headers={"X-API-Key": "your_api_key"},
json={"whale_threshold_usd": 10000},
)
data = response.json()
```
## Response
```json theme={null}
{
"success": true,
"event_id": 65,
"whale_threshold_usd": 10000,
"notify_on_whale_trade": true
}
```
## Errors
| Status | Reason |
| ------ | ---------------------------------------------------------------------------- |
| `401` | Missing or invalid API key. |
| `403` | Your plan does not include whale tracking, or your subscription is inactive. |
| `404` | You are not tracking this event. |