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

# Orderbook Stream

> Real-time orderbook data for every Polymarket market, delivered through a single WebSocket connection.

PolyNode's Orderbook Stream delivers live orderbook snapshots, depth updates, and price changes for every active Polymarket market. Subscribe to the markets you care about and get enriched, human-readable data pushed to your connection in real time.

<CardGroup cols={2}>
  <Card title="108,000+ markets" icon="globe">
    Every active Polymarket market is tracked. Subscribe to specific markets or get everything at once.
  </Card>

  <Card title="Enriched data" icon="tags">
    Snapshots include the event title, market question, outcome label, slug, and condition ID. Streaming updates include the slug and outcome for lightweight routing.
  </Card>

  <Card title="250ms batching" icon="layer-group">
    Updates are batched into 250ms windows for efficient delivery. One message per tick with all changes for your subscribed markets.
  </Card>

  <Card title="Compression included" icon="compress">
    Add `&compress=zlib` to your connection URL for \~50% bandwidth savings. Recommended for production.
  </Card>
</CardGroup>

## Quick start

<Steps>
  <Step title="Get an API key">
    ```bash theme={null}
    curl -s -X POST https://api.polynode.dev/v1/keys \
      -H "Content-Type: application/json" \
      -d '{"name": "my-app"}'
    ```

    Save the `pn_live_...` key from the response.
  </Step>

  <Step title="Connect and subscribe">
    Subscribe using a **condition ID** or **token ID** from the REST API. Both sides of the market (Yes + No) are included automatically.

    <CodeGroup>
      ```javascript Node.js theme={null}
      const ws = new WebSocket("wss://ob.polynode.dev/ws?key=pn_live_YOUR_KEY");

      ws.onopen = () => {
        // Subscribe by condition ID — resolves to both Yes + No tokens
        ws.send(JSON.stringify({
          action: "subscribe",
          markets: ["0x7cb525e831729325d651017f81cbcb6f1adde5011c7b2283babea00b4ae93ae7"]
        }));
      };

      ws.onmessage = (event) => {
        const msg = JSON.parse(event.data);
        if (msg.type === "snapshot_batch") {
          for (const snap of msg.snapshots) {
            console.log(`${snap.question} [${snap.outcome}]: ${snap.bids.length} bids, ${snap.asks.length} asks`);
          }
        } else if (msg.type === "batch") {
          for (const update of msg.updates) {
            if (update.type === "price_change") {
              for (const asset of update.assets) {
                // size="0" means the level was removed; otherwise it's the new absolute size at that level.
                console.log(`${update.slug} [${asset.outcome}] ${asset.side} @ ${asset.price} -> size ${asset.size}  (bbo: ${asset.best_bid}/${asset.best_ask})`);
              }
            }
          }
        }
      };
      ```

      ```python Python theme={null}
      import asyncio, json, websockets

      async def stream_orderbook():
          async with websockets.connect("wss://ob.polynode.dev/ws?key=pn_live_YOUR_KEY") as ws:
              await ws.send(json.dumps({
                  "action": "subscribe",
                  "markets": ["0x7cb525e831729325d651017f81cbcb6f1adde5011c7b2283babea00b4ae93ae7"]
              }))
              async for message in ws:
                  data = json.loads(message)
                  if data["type"] == "snapshot_batch":
                      for snap in data["snapshots"]:
                          print(f"{snap['question']} [{snap['outcome']}]: {len(snap['bids'])} bids, {len(snap['asks'])} asks")
                  elif data["type"] == "batch":
                      for update in data["updates"]:
                          if update["type"] == "price_change":
                              for asset in update["assets"]:
                                  # size="0" removes the level; otherwise it's the new absolute size.
                                  print(f"{update['slug']} [{asset['outcome']}] {asset['side']} @ {asset['price']} -> size {asset['size']}  (bbo: {asset['best_bid']}/{asset['best_ask']})")

      asyncio.run(stream_orderbook())
      ```

      ```bash CLI theme={null}
      npx wscat -c "wss://ob.polynode.dev/ws?key=pn_live_YOUR_KEY"
      # once connected, send:
      {"action":"subscribe","markets":["0x7cb525e831729325d651017f81cbcb6f1adde5011c7b2283babea00b4ae93ae7"]}
      ```
    </CodeGroup>

    <Tip>
      Use the REST API to find condition IDs: `curl -s -H "x-api-key: pn_live_YOUR_KEY" "https://api.polynode.dev/v1/search?q=bitcoin"` — each result includes the `condition_id` you can pass directly to the orderbook subscribe.
    </Tip>
  </Step>

  <Step title="Receive data">
    You'll immediately receive an orderbook snapshot for each subscribed token, then live batched updates:

    ```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" }
      ]
    }
    ```
  </Step>
</Steps>

## Ways to subscribe

You can identify markets using any of these formats:

<Tabs>
  <Tab title="Firehose (all markets)">
    Subscribe to every active market with a single message. Pass `"*"` as the market identifier to get the full firehose (full data stream) of all 100,000+ tokens.

    ```json theme={null}
    {
      "action": "subscribe",
      "markets": ["*"]
    }
    ```

    The server responds with `"firehose": true` and the total number of active tokens. Live updates start flowing immediately while snapshots stream in the background.
  </Tab>

  <Tab title="Slug (recommended)">
    The simplest way to subscribe to specific markets. Use the slug from the Polymarket event URL. The server resolves it to all token IDs for that event (typically 2 per market, one for each outcome).

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

  <Tab title="Condition ID">
    Use the hex condition ID for a specific market within an event. Resolves to the Yes + No token IDs for that market.

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

  <Tab title="Token ID">
    Use the raw CLOB token ID if you already have it. This matches Polymarket's market identifier.

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

You can mix formats in a single subscribe:

```json theme={null}
{
  "action": "subscribe",
  "markets": [
    "what-price-will-bitcoin-hit-in-march-2026",
    "0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96",
    "73624432805780182150964443951045800666977811185963019133914618974858599458273"
  ]
}
```

## Finding markets

Use the PolyNode REST API to search for markets and find their slugs, condition IDs, or token IDs:

```bash theme={null}
# Search by keyword
curl -s "https://api.polynode.dev/v1/search?q=bitcoin" | python3 -m json.tool

# Look up by slug
curl -s "https://api.polynode.dev/v1/markets/slug/what-price-will-bitcoin-hit-in-march-2026"

# Look up by condition ID
curl -s "https://api.polynode.dev/v1/markets/condition/0x561ffbf7de21ef3781c441f30536b026d2b301d7a4a0145a8f526f98db049ba2"
```

Or just grab the slug from any Polymarket event URL: `polymarket.com/event/{slug}`.

## Connection URL

```
wss://ob.polynode.dev/ws?key=pn_live_YOUR_KEY
```

With compression (\~50% bandwidth savings):

```
wss://ob.polynode.dev/ws?key=pn_live_YOUR_KEY&compress=zlib
```

## Subscription limits

Use `markets: ["*"]` for the full all-market firehose. Explicit market lists are capped at **15,000 resolved token IDs per connection** to protect low-latency fan-out; split large explicit watchlists across connections or use the firehose path when you truly need every market.

| Tier                      | Connections | Explicit list cap          | Firehose | Session limit |
| ------------------------- | ----------- | -------------------------- | -------- | ------------- |
| **Free**                  | 1           | 15,000 tokens / connection | Included | 1 hour/day    |
| **Starter** (\$50/mo)     | 10          | 15,000 tokens / connection | Included | Unlimited     |
| **Growth** (\$200/mo)     | 50          | 15,000 tokens / connection | Included | Unlimited     |
| **Enterprise** (\$750/mo) | Unlimited   | 15,000 tokens / connection | Included | Unlimited     |

Each Polymarket market has two tokens (one per outcome). Subscribing to both tokens of a market gives you the full two-sided orderbook.

Plan upgrades increase the number of concurrent Orderbook Stream connections. The explicit-list token cap is a per-connection safety limit and is not removed by upgrading.

<Tip>
  **Free tier includes the full firehose.** Subscribe to every market and see the full data flow. The only limit is a 1-hour daily session so you can properly evaluate the product before upgrading.
</Tip>

<Note>
  **Orderbook connections are separate from event WebSocket connections.** Your plan's Event WebSocket limit applies to `ws.polynode.dev`; the limits above apply to the Orderbook Stream at `ob.polynode.dev`. Session time resets at midnight UTC.
</Note>

## Connection lifecycle

<Tabs>
  <Tab title="Firehose">
    ```
    Connect   -> wss://ob.polynode.dev/ws?key=pn_live_...
              <-  (connection established)

    Send      -> {"action": "subscribe", "markets": ["*"]}
              <-  {"type": "subscribed", "firehose": true, "markets": 104972}
              <-  {"type": "batch", "ts": ..., "updates": [...]}         (live data starts immediately)
              <-  {"type": "snapshot_batch", "count": 50, "snapshots": [...]}  (snapshots stream in parallel)
              <-  {"type": "batch", ...}                                  (every 100ms)
              <-  {"type": "snapshot_batch", ...}                         (every 200ms)
              <-  {"type": "snapshots_done", "total": 67}
              <-  Ping frame                                              (every 30s)
              ->  Pong frame                                              (automatic)
    ```

    Live updates start flowing immediately. You don't have to wait for snapshots to finish. Snapshots are delivered in batches of 50 every 200ms for tokens that have active book data.
  </Tab>

  <Tab title="Specific markets">
    ```
    Connect   -> wss://ob.polynode.dev/ws?key=pn_live_...
              <-  (connection established)

    Send      -> {"action": "subscribe", "markets": ["bitcoin-slug"]}
              <-  {"type": "subscribed", "markets": 4, "resolved_from": {"token_ids": 0, "slugs": 1, "condition_ids": 0}}
              <-  {"type": "snapshot_batch", "count": 2, "snapshots": [...]}
              <-  {"type": "snapshots_done", "total": 2}

              <-  {"type": "batch", "ts": ..., "updates": [...]}
              <-  {"type": "batch", ...}    (every 250ms when there are changes)
              <-  Ping frame                (every 30s)
              ->  Pong frame                (automatic)

    Send      -> {"action": "unsubscribe"}
              <-  {"type": "unsubscribed"}
    ```
  </Tab>
</Tabs>

## Separate from the main WebSocket

The Orderbook Stream runs on a **separate endpoint** (`ob.polynode.dev`) from the main PolyNode WebSocket (`ws.polynode.dev`). They are independent services:

|              | Main WebSocket                                | Orderbook Stream                              |
| ------------ | --------------------------------------------- | --------------------------------------------- |
| **Endpoint** | `wss://ws.polynode.dev/ws`                    | `wss://ob.polynode.dev/ws`                    |
| **Data**     | On-chain events (settlements, trades, blocks) | Off-chain orderbook (bids, asks, prices)      |
| **Timing**   | Pre-confirmation (pending + confirmed)        | Real-time (live order placement/cancellation) |
| **Use case** | Trade detection, copy trading, alerts         | Market making, pricing, depth analysis        |

You can connect to both simultaneously with the same API key.
