Client messages
Subscribe
Subscribe to orderbook updates. You can subscribe to specific markets by slug, condition ID, or token ID, or subscribe to the full firehose with "*".
Firehose (all markets)
By slug
By condition ID
By token ID
{
"action": "subscribe",
"markets": ["*"]
}
Subscribes to every active Polymarket market (100,000+ tokens). Live updates start flowing immediately while snapshots stream in the background.{
"action": "subscribe",
"markets": ["what-price-will-bitcoin-hit-in-march-2026"]
}
{
"action": "subscribe",
"markets": ["0x561ffbf7de21ef3781c441f30536b026d2b301d7a4a0145a8f526f98db049ba2"]
}
{
"action": "subscribe",
"markets": [
"73624432805780182150964443951045800666977811185963019133914618974858599458273",
"1666184515532238710431784265702709312060757077236443477960106115591255115343"
]
}
| Field | Type | Description |
|---|
action | string | Must be "subscribe" |
markets | string[] | Array of market identifiers. Use ["*"] for the full firehose, or pass specific identifiers: slugs (e.g. "bitcoin-100k"), condition IDs (e.g. "0x561ff..."), or CLOB token IDs (numeric string). Slugs and condition IDs are resolved to their corresponding token IDs automatically. |
How identifiers are detected:
"*" → firehose (all active markets)
- Starts with
0x → condition ID
- All digits and longer than 10 characters → CLOB token ID
- Everything else → slug
Subscribing again replaces your current subscription. To add markets, send a new subscribe with the full list.
Unsubscribe
Remove all subscriptions on this connection.
{
"action": "unsubscribe"
}
Ping
Client-initiated keepalive.
Response: {"type": "pong"}
Server messages
subscribed
Acknowledgment after a successful subscribe. The response format differs slightly depending on whether you subscribed to specific markets or the firehose.
Firehose
Specific markets
{
"type": "subscribed",
"firehose": true,
"markets": 104972
}
| Field | Type | Description |
|---|
firehose | boolean | true when subscribed to the full firehose |
markets | number | Total number of active tokens you’re now receiving updates for |
{
"type": "subscribed",
"markets": 4,
"resolved_from": {
"token_ids": 0,
"slugs": 1,
"condition_ids": 0
}
}
| Field | Type | Description |
|---|
markets | number | Total number of token IDs now subscribed (after resolution) |
resolved_from | object | Breakdown of how many input entries were detected as each type |
resolved_from.token_ids | number | Entries passed through as raw token IDs |
resolved_from.slugs | number | Entries resolved from slugs |
resolved_from.condition_ids | number | Entries resolved from condition IDs |
If you subscribe with a slug and markets comes back as 0, the slug didn’t match any known event. Use the search API to find the correct slug.
snapshot_batch
Orderbook snapshots delivered in batches after subscribing. For small subscriptions (under 100 tokens), snapshots arrive in a single batch almost instantly. For large subscriptions (firehose), snapshots are streamed in batches of 50 every 200ms to avoid flooding your connection.
{
"type": "snapshot_batch",
"count": 2,
"total_sent": 2,
"snapshots": [
{
"type": "book_snapshot",
"asset_id": "114694726451307654528948558967898493662917070661203465131156925998487819889437",
"market": "0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96",
"condition_id": "0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96",
"event_title": "Netanyahu out by...?",
"question": "Netanyahu out by end of 2026?",
"outcome": "Yes",
"slug": "netanyahu-out-before-2027",
"bids": [
{ "price": "0.01", "size": "1130070" },
{ "price": "0.02", "size": "71015.52" }
],
"asks": [
{ "price": "0.99", "size": "17615.05" },
{ "price": "0.98", "size": "5000" }
]
}
]
}
| Field | Type | Description |
|---|
count | number | Number of snapshots in this batch |
total_sent | number | Running total of snapshots sent so far |
snapshots | object[] | Array of book_snapshot objects (see below) |
Once all snapshots have been delivered, you’ll receive a completion marker:
{
"type": "snapshots_done",
"total": 12239
}
Only tokens with active orderbook data are included in snapshots. Empty books are skipped. You’ll still receive live price_change and book_update events for all subscribed tokens via batched updates.
book_snapshot
Full orderbook state for a token. Sent inside snapshot_batch messages after subscribing, and occasionally in batch updates when the upstream source sends a full refresh.
{
"type": "book_snapshot",
"asset_id": "73624432805780182150964443951045800666977811185963019133914618974858599458273",
"market": "0x561ffbf7de21ef3781c441f30536b026d2b301d7a4a0145a8f526f98db049ba2",
"condition_id": "0x561ffbf7de21ef3781c441f30536b026d2b301d7a4a0145a8f526f98db049ba2",
"event_title": "What price will Bitcoin hit in March?",
"question": "Will Bitcoin reach $150,000 in March?",
"outcome": "Yes",
"slug": "what-price-will-bitcoin-hit-in-march-2026",
"bids": [
{ "price": "0.001", "size": "8604930.58" },
{ "price": "0.002", "size": "4851192.42" }
],
"asks": [
{ "price": "0.999", "size": "6152045.08" },
{ "price": "0.998", "size": "55007.54" },
{ "price": "0.997", "size": "5000" }
]
}
| Field | Type | Description |
|---|
asset_id | string | Polymarket CLOB token ID |
market | string | Condition ID (hex) |
condition_id | string | Condition ID (hex), same as market |
event_title | string | Parent event title (e.g. “What price will Bitcoin hit in March?”) |
question | string | Specific market question (e.g. “Will Bitcoin reach $150,000 in March?”) |
outcome | string | Which outcome this token represents (“Yes” or “No”) |
slug | string | URL-friendly slug for the event page on Polymarket |
bids | PriceLevel[] | Buy orders, sorted by price descending |
asks | PriceLevel[] | Sell orders, sorted by price ascending |
Each PriceLevel is {"price": "0.55", "size": "1000.00"} where price is the probability (0 to 1) and size is the number of shares.
If bids and asks are both empty, the market exists but has no active orders. You’ll still receive price_change updates in batches when the price moves.
batch
Batched updates delivered every 250ms. Contains all changes for your subscribed tokens since the last batch. Multiple updates to the same price level within a batch window are coalesced into a single net change, reducing bandwidth without losing accuracy.
{
"type": "batch",
"ts": 1773794605604,
"count": 4,
"updates": [
{
"type": "price_change",
"market": "0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96",
"slug": "netanyahu-out-before-2027",
"assets": [
{
"asset_id": "66255671088804707681511323064315150986307471908131081808279119719218775249892",
"outcome": "No",
"price": "0.55"
},
{
"asset_id": "114694726451307654528948558967898493662917070661203465131156925998487819889437",
"outcome": "Yes",
"price": "0.45"
}
]
},
{
"type": "book_update",
"asset_id": "73624432805780182150964443951045800666977811185963019133914618974858599458273",
"market": "0x561ffbf7de21ef3781c441f30536b026d2b301d7a4a0145a8f526f98db049ba2",
"outcome": "Yes",
"slug": "what-price-will-bitcoin-hit-in-march-2026",
"bids": [
{ "price": "0.45", "size": "12000" }
],
"asks": []
}
]
}
| Field | Type | Description |
|---|
ts | number | Batch timestamp (Unix milliseconds) |
count | number | Number of updates in this batch |
updates | object[] | Array of update objects (see below) |
Each update in the updates array is one of:
price_change
A market’s price moved due to an order placement or cancellation.
| Field | Type | Description |
|---|
type | string | "price_change" |
market | string | Condition ID |
slug | string | Event slug |
assets | object[] | Array of {asset_id, outcome, price} for each side |
book_update
Incremental change to the orderbook. Apply these to the last snapshot.
| Field | Type | Description |
|---|
type | string | "book_update" |
asset_id | string | Token ID |
market | string | Condition ID |
outcome | string | Outcome label |
slug | string | Event slug |
bids | PriceLevel[] | Changed bid levels (size "0" means remove) |
asks | PriceLevel[] | Changed ask levels (size "0" means remove) |
book_update messages are incremental deltas, not full replacements. To maintain an accurate orderbook:
- If a level’s
size is "0", remove that price level
- Otherwise, upsert the level (add or update)
book_snapshot
Occasional full orderbook replacement (sent when the upstream source resets).
Same schema as the initial book_snapshot message above.
last_trade_price
A trade executed on Polymarket. Delivered for every fill, not coalesced — each trade in a batch window is its own event.
{
"type": "last_trade_price",
"asset_id": "196889930626949471000189353840...",
"market": "0x8f3a...",
"price": "0.76",
"size": "5",
"side": "BUY",
"fee_rate_bps": "0",
"timestamp": "1773991152458",
"outcome": "Up",
"slug": "btc-updown-15m-1773990900"
}
| Field | Type | Description |
|---|
type | string | "last_trade_price" |
asset_id | string | Token ID that was traded |
market | string | Condition ID |
price | string | Execution price (0-1 probability) |
size | string | Number of shares traded |
side | string | "BUY" or "SELL" |
fee_rate_bps | string | Fee rate in basis points |
timestamp | string | Unix milliseconds of the trade |
outcome | string | Outcome label (e.g. “Yes”, “Up”) |
slug | string | Event slug |
Use last_trade_price events to build trade tapes, calculate VWAP, detect large fills, or trigger alerts on execution activity. Unlike price_change events which fire on order placement/cancellation, these fire only when an order is actually filled.
Need sub-millisecond granularity? Enterprise plans can be configured with a dedicated stream that delivers every individual orderbook event without batching, including per-event timestamps. Contact us if your strategy requires tick-by-tick data.
unsubscribed
Acknowledgment after unsubscribe.
{
"type": "unsubscribed"
}
pong
Response to a client ping.
Error messages
{
"error": "token_limit",
"message": "Your starter tier allows max 200 tokens. Requested 500. Upgrade at https://polynode.dev/pricing",
"max": 200,
"requested": 500
}
{
"error": "session_limit",
"message": "Free tier 1-hour daily limit reached."
}
| Error | Cause |
|---|
token_limit | Subscription exceeds your tier’s token limit (starter/growth only) |
session_limit | Free tier 1-hour daily session expired (resets at midnight UTC) |