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

# Message Reference

> Complete reference for all message types sent and received on the Orderbook Stream.

## 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 (full data stream) with `"*"`.

<Tabs>
  <Tab title="Firehose (all markets)">
    ```json theme={null}
    {
      "action": "subscribe",
      "markets": ["*"]
    }
    ```

    Subscribes to every active Polymarket market (100,000+ tokens). Live updates start flowing immediately while snapshots stream in the background.
  </Tab>

  <Tab title="By slug">
    ```json theme={null}
    {
      "action": "subscribe",
      "markets": ["what-price-will-bitcoin-hit-in-march-2026"]
    }
    ```
  </Tab>

  <Tab title="By condition ID">
    ```json theme={null}
    {
      "action": "subscribe",
      "markets": ["0x561ffbf7de21ef3781c441f30536b026d2b301d7a4a0145a8f526f98db049ba2"]
    }
    ```
  </Tab>

  <Tab title="By token ID">
    ```json theme={null}
    {
      "action": "subscribe",
      "markets": [
        "73624432805780182150964443951045800666977811185963019133914618974858599458273",
        "1666184515532238710431784265702709312060757077236443477960106115591255115343"
      ]
    }
    ```
  </Tab>
</Tabs>

| 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.

```json theme={null}
{
  "action": "unsubscribe"
}
```

### Ping

Client-initiated keepalive.

```json theme={null}
{
  "action": "ping"
}
```

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.

<Tabs>
  <Tab title="Firehose">
    ```json theme={null}
    {
      "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 |
  </Tab>

  <Tab title="Specific markets">
    ```json theme={null}
    {
      "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                            |
  </Tab>
</Tabs>

<Tip>
  If you subscribe with a slug and `markets` comes back as 0, the slug didn't match any known event. Use the [search API](https://api.polynode.dev/v1/search?q=bitcoin) to find the correct slug.
</Tip>

### `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.

```json theme={null}
{
  "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:

```json theme={null}
{
  "type": "snapshots_done",
  "total": 12239
}
```

<Tip>
  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.
</Tip>

### `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.

```json theme={null}
{
  "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.

<Tip>
  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.
</Tip>

### `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.

```json theme={null}
{
  "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",
          "size": "2500",
          "side": "BUY",
          "best_bid": "0.55",
          "best_ask": "0.56"
        },
        {
          "asset_id": "114694726451307654528948558967898493662917070661203465131156925998487819889437",
          "outcome": "Yes",
          "price": "0.45",
          "size": "0",
          "side": "SELL",
          "best_bid": "0.44",
          "best_ask": "0.46"
        }
      ]
    },
    {
      "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 level changed due to an order placement, cancellation, or fill. Each entry in `assets` describes a single level change. `size` is the absolute new size at that level — `"0"` means the level was removed. You can apply `price_change` events directly to your local book to maintain tick-accurate state without polling.

| Field    | Type      | Description                            |
| -------- | --------- | -------------------------------------- |
| `type`   | string    | `"price_change"`                       |
| `market` | string    | Condition ID                           |
| `slug`   | string    | Event slug                             |
| `assets` | object\[] | Array of per-level changes (see below) |

Each entry in `assets`:

| Field      | Type   | Description                                                        |
| ---------- | ------ | ------------------------------------------------------------------ |
| `asset_id` | string | Token ID                                                           |
| `outcome`  | string | Outcome label (`"Yes"`, `"No"`, etc.)                              |
| `price`    | string | The level that changed                                             |
| `size`     | string | Absolute size at that level after the change. `"0"` means removed. |
| `side`     | string | `"BUY"` (affects bids) or `"SELL"` (affects asks)                  |
| `best_bid` | string | Best bid on this asset after the change                            |
| `best_ask` | string | Best ask on this asset after the change                            |

<Tip>
  Multiple levels on the same `asset_id` may appear in one batch — each represents a distinct level change. The 250ms coalescer merges repeat hits on the same `(asset_id, price, side)` to last-write-wins, so you always get the most recent size for each level.
</Tip>

#### `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) |

<Warning>
  `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)
</Warning>

#### `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.

```json theme={null}
{
  "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                        |

<Tip>
  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.
</Tip>

<Info>
  **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](mailto:josh@quantish.live) if your strategy requires tick-by-tick data.
</Info>

### `unsubscribed`

Acknowledgment after unsubscribe.

```json theme={null}
{
  "type": "unsubscribed"
}
```

### `pong`

Response to a client ping.

```json theme={null}
{
  "type": "pong"
}
```

### Error messages

```json theme={null}
{
  "error": "input_too_large",
  "message": "Too many markets in one request. Use markets: [\"*\"] for the full firehose, or split very large explicit lists across requests."
}
```

```json theme={null}
{
  "error": "session_limit",
  "message": "Free tier 1-hour daily limit reached."
}
```

| Error                | Cause                                                                                                                                                                  |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token_limit`        | Explicit subscription resolved above 15,000 token IDs on one connection. Use `markets: ["*"]` for the full firehose, or split large explicit lists across connections. |
| `input_too_large`    | Request contains an unusually large explicit identifier list. Use `markets: ["*"]` for the full firehose, or split very large explicit lists across connections.       |
| `service_warming_up` | The Orderbook Stream accepted the connection but is still loading market metadata after a restart. Retry the subscribe shortly.                                        |
| `session_limit`      | Free tier 1-hour daily session expired (resets at midnight UTC)                                                                                                        |
