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

> ## Agent Instructions
> To integrate AGG, start with Quickstart: REST (https://docs.agg.market/quickstart/rest), then Order lifecycle & statuses (https://docs.agg.market/concepts/order-lifecycle).
> Track every trade until it reaches a terminal status. Before retrying a failed or timed-out call, read Errors, retries & idempotency (https://docs.agg.market/concepts/errors).
> The API reference is generated from https://docs.agg.market/openapi/openapi.json.

# Orderbook firehose

> Stream every non-empty book for selected venues from your backend. Requires an app API key, and stays off until Snag enables it.

The firehose sends every book that has levels for the venues you name, then the live changes. It runs on the [orderbook socket](https://docs.agg.market/recipes/orderbook-streams.md), not on the shared `wss://ws.agg.market/ws` socket. It is a server channel. It is not a per-outcome `orderbook` subscribe, and a browser cannot open it.

## Access

Snag enables the firehose for an app. You cannot turn it on. Email `support@agg.market` with your app id to request it.

The handshake requires a valid `x-app-api-key`. That is the same header as [Server connections](https://docs.agg.market/api/websocket.md#server-connections). A browser `Origin` is not enough, including when your app does not require an API key on other sockets. If the firehose is off, or the socket has no valid app API key, subscribe is refused:

```json theme={null}
{
  "type": "error",
  "code": "firehose_not_enabled"
}
```

Branch on `code`, not on `message`. If Snag turns the firehose off, or the key is revoked, open firehose sockets close with `4003` within a minute. The same happens when a venue you are streaming is removed from your enablement: reconnect and subscribe again, and the venues you still have are served. A short outage on our side does not close the socket.

Each enabled app has a connection cap. The default is 2 concurrent firehose connections. Snag sets the number when enabling the app. Stay within the cap you were given.

Each server also has a firehose capacity shared by all apps. When it is full, subscribe is refused and the socket stays open:

```json theme={null}
{
  "type": "error",
  "code": "too_many_connections",
  "message": "Orderbook firehose is at capacity on this server; reconnect with backoff"
}
```

Close the socket and reconnect with backoff. A new connection usually lands on another server.

## Subscribe

Open `wss://ob.agg.market/ws?appId=YOUR_APP_ID`, send the API key on the handshake, and wait for `connected`. Heartbeats and the other close codes are on the [WebSocket Protocol](https://docs.agg.market/api/websocket.md) page.

```javascript theme={null}
import WebSocket from "ws";

const ws = new WebSocket(`wss://ob.agg.market/ws?appId=${appId}`, {
  headers: { "x-app-api-key": process.env.AGG_API_KEY },
});
```

Then subscribe:

```json theme={null}
{
  "action": "subscribe",
  "channel": "firehose",
  "venues": ["polymarket"],
  "latency": "realtime",
  "batchMs": 10
}
```

`venues` is the list of venue slugs to stream. Slugs are on [Venues](https://docs.agg.market/venues/overview.md). You only receive venues included when the firehose was enabled for your app. An empty enablement list means every venue your app is allowed to see. This message cannot add venues beyond that list.

`latency` follows the [orderbook tiers](https://docs.agg.market/recipes/orderbook-streams.md#tiers). Omit it, or send `"conflated"`, for the default 100 ms tier. `"realtime"` opts in. `seq` is per tier here too.

`batchMs` is milliseconds and applies only to realtime. `0` sends each change as an `orderbook_delta`. Any other value groups those changes into `orderbook_delta_batch` over that many milliseconds. The conflated tier ignores `batchMs`. It sends one `orderbook_delta_batch` per 100 ms flush, covering the books that changed in that window. Set `batchMs` when you opt into realtime, including `0` when you want no grouping.

For each venue the socket then sends:

1. `subscribed`
2. One or more `orderbook_snapshot_batch` messages
3. `firehose_ready`
4. `orderbook_delta_batch`, or `orderbook_delta` when realtime `batchMs` is `0`

```json theme={null}
{ "type": "subscribed", "channel": "firehose" }
```

```json theme={null}
{ "type": "firehose_ready", "venue": "polymarket" }
```

Ignore fields on those acks that you do not use. `firehose_ready` means that venue's snapshot has been sent. Live deltas for the venue follow. A venue snapshot is every non-empty book at the same full depth as a per-outcome snapshot. Read the socket while it arrives.

A server sends a limited number of venue snapshots at once. When several sockets subscribe together, yours can get `subscribed` right away and its first `orderbook_snapshot_batch` some seconds later. Heartbeats continue meanwhile. Keep the socket open and keep reading; do not resubscribe.

## Messages

Each entry in a batch is a complete `orderbook_snapshot` or `orderbook_delta` object, the same objects as the [per-outcome stream](https://docs.agg.market/api/websocket.md#1-orderbook-stream). The batch envelopes are the only new message types.

```json theme={null}
{
  "type": "orderbook_snapshot_batch",
  "venue": "polymarket",
  "part": 12,
  "parts": 480,
  "snapshots": [
    {
      "type": "orderbook_snapshot",
      "outcomeId": "vmo_1",
      "seq": 1710000001,
      "checksum": 2918476531,
      "bids": [[0.55, 800, { "polymarket": 800 }]],
      "asks": [[0.56, 600, { "polymarket": 600 }]],
      "venueOrderbooks": {
        "polymarket": {
          "bids": [[0.55, 800]],
          "asks": [[0.56, 600]]
        }
      },
      "venues": {
        "polymarket": { "bestBid": 0.55, "bestAsk": 0.56 }
      },
      "midpoint": 0.555,
      "spread": 0.01,
      "timestamp": 1710000000000
    }
  ]
}
```

`part` and `parts` identify the slice of that venue's snapshot. Apply every slice. Do not treat the venue as live until `firehose_ready` for it. Books in different slices do not depend on each other. Replace a book on every snapshot object, whatever its `seq` is.

```json theme={null}
{
  "type": "orderbook_delta_batch",
  "deltas": [
    {
      "type": "orderbook_delta",
      "outcomeId": "vmo_1",
      "seq": 1710000002,
      "checksum": 3847261950,
      "bidChanges": [[0.55, 900, { "polymarket": 900 }]],
      "askChanges": [],
      "venueDeltaBooks": {
        "polymarket": {
          "bidChanges": [[0.55, 900]],
          "askChanges": []
        }
      },
      "venues": {
        "polymarket": { "bestBid": 0.55, "bestAsk": 0.56 }
      },
      "midpoint": 0.555,
      "spread": 0.01,
      "timestamp": 1710000001000
    }
  ]
}
```

A delta batch is not split by venue. Each delta names its `outcomeId`. With realtime `batchMs` set to `0`, the socket sends that `orderbook_delta` object on its own, with no envelope.

Empty books are omitted. A book that does not appear in the snapshot has no levels. When a book first gets levels, it arrives as an `orderbook_snapshot` by itself, not inside a batch. Apply it the same way as any other snapshot.

Drop a delta whose `seq` is less than or equal to the book's current `seq`. Deltas from before that book's snapshot can still show up, and they are stale. For example, a batch can repeat a change that a single `orderbook_snapshot` sent just before it already includes. After a resync, rebuild from the new snapshot and drop books that are no longer in it. They are empty.

## Falling behind

The per-outcome rule (about 1 MB behind for 10 seconds) does not apply to this channel. If your backlog of live deltas exceeds 64 MB, or you fall more than 30 seconds behind, the socket sends the error below. While a venue snapshot is being sent, 30 seconds behind means the socket accepted none of it for 30 seconds. Deltas that arrive during the snapshot count toward the 64 MB, so read the snapshot quickly.

```json theme={null}
{
  "type": "error",
  "code": "firehose_resync"
}
```

A fresh snapshot follows: `orderbook_snapshot_batch` slices, then `firehose_ready` for the venues you are streaming. Replace your books.

Three `firehose_resync` errors in five minutes closes the connection as a slow consumer (`4008`). Reconnect with backoff and subscribe again.

`firehose_resync` is also sent when our upstream feed is lost or replaced. The fresh snapshot follows once the server's books are complete again, which can take some seconds. Keep reading. This resync does not count toward the three.

## Related

> [card] [Orderbook streams](https://docs.agg.market/recipes/orderbook-streams.md)
>
> The orderbook socket, the 100 ms default, and the realtime opt-in.

> [card] [WebSocket Protocol](https://docs.agg.market/api/websocket.md)
>
> The snapshot and delta objects inside each batch, plus close codes.

> [card] [Real-Time Orderbook](https://docs.agg.market/recipes/websocket-orderbook.md)
>
> Per-outcome books when you do not need a whole venue.

> [card] [Venues](https://docs.agg.market/venues/overview.md)
>
> Slugs to send in `venues`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.