# Market order book

> The per-market channel that delivers one market's aggregated order book together with its full market record.

Source: https://docs.stxapp.io/websockets/order-book/

Streams the live order book for one market: two sides, bid and offer, each a
ladder of price levels carrying size, liquidity and cumulative depth, so you
can read how far the market goes without summing levels yourself. It
refreshes roughly every 200 ms, not on every individual order; see
[Server pushes](#server-pushes) below for the exact shape of a push.

The channels under [Channels](/websockets/) carry *your* orders, fills and
positions. This one carries **the market**: the full aggregated book and the
market record, one market per topic.

:::note[Watching several markets]
If you follow more than one market, use the [`orderbook`](/websockets/channels/market-data/#orderbook)
topic instead. One join covers every market you name, and it uses the same
dollar-string format as the REST API and your account channels.
:::

:::tip[See one without writing any code]
[Live markets](/explore/live-markets/) streams a real book from this channel in
your browser and shows the raw frames beside it. Useful for
checking your own rendering against, or for seeing current spreads before you
build.
:::

One channel per market. Join `market:<market_id>` for each market you are
watching or trading, and join [`market_updates`](/websockets/channels/market-updates/)
alongside it if you also want lightweight change notifications across many
markets at once.

## Joining

The topic accepts either the market UUID or its symbol:

```json
["1","1","market:2bc3d8d8-1f4e-4b8a-9c1d-3e5f7a9b1c2d","phx_join",{}]
["1","1","market:NFLSF2025","phx_join",{}]
```

Unlike the `market_updates` channel, **a successful join replies with the
current state immediately**: you do not have to ask for a snapshot first:

```json
["1","1","market:2bc3d8d8-…","phx_reply",
  {"status":"ok","response":{
    "market_id":"2bc3d8d8-…",
    "status":"open",
    …,
    "ob":{"b":[…],"o":[…]}
  }}]
```

The reply is the full market record, the same shape the
[`markets`](/websockets/channels/markets/) channel sends, with `ob` added. The two
parts use different units: the market record's prices are in cents, while `ob`
is in dollars. Read each field's units from its own page.

A join is refused with a reason you can act on:

| `reason` | Meaning |
| --- | --- |
| `market_not_found` | No market with that id or symbol |
| `market_not_joinable` | The market exists but is not in a joinable status |

Joinable statuses are `pre_open`, `open`, `closed` and `cancelled`. `scheduled`
markets are hidden and cannot be joined. `resulted` and `voided` are terminal:
the socket receives one final `market_update` and is then **dropped by the
server**, so treat an unexpected close on this channel as "the market ended",
not as a network fault.

:::tip
`pre_open` is joinable, so you can be on the book before the market opens. See
[Market and order status](/concepts/market-status/) for what each status means
and how to filter on it.
:::

## Server pushes

### `order_book_update`

The aggregated book, pushed **roughly every 200 ms**, not on every individual change. Between pushes many
updates are coalesced, so treat each push as the current state of the levels it
contains rather than as a single event.

```json
["1","1","market:2bc3d8d8-…","order_book_update",
  {"ob":{"b":[{"p":0.42,"q":150.0,"l":63.0,"tc":420.0,"tl":176.4}],
         "o":[{"p":0.43,"q":90.0,"l":38.7,"tc":310.0,"tl":133.3}]}}]
```

`b` is the bid side, `o` the offer side. Each level:

| Field | Meaning |
| --- | --- |
| `p` | Price, in dollars |
| `q` | Contracts available at this price |
| `l` | Liquidity at this price, in dollars |
| `tc` | Total contracts at this price and better |
| `tl` | Total liquidity at this price and better, in dollars |

These are JSON numbers, not the dollar strings the REST API and the
[`orderbook`](/websockets/channels/market-data/#orderbook) topic send.

`tc` and `tl` are cumulative, so you can read depth-to-price straight off a
level without summing the ones above it.

### `market_update`

The full `Market` map, pushed whenever the market changes, status
transitions, trading halts, settlement. Same shape the
[`markets`](/websockets/channels/markets/) channel delivers.

## Re-syncing after a reconnect

Send `request_snapshot` and the server immediately re-pushes both the current
`order_book_update` and `market_update`:

```json
["1","2","market:2bc3d8d8-…","request_snapshot",{}]
```

Do this after any reconnect. There is currently **no sequence number** on
`order_book_update`, so a client cannot detect a gap while it believes itself
connected. If you suspect you have missed data, call `request_snapshot` and
replace your book with what comes back rather than trying to reconcile.

## Authentication

This channel carries no account-specific data, so it accepts an unsigned
socket. The handshake still needs a `User-Agent` header, or it is refused with
`403`. Your account channels need a signed socket: if you also want your own
orders and fills, sign the handshake as described in
[WebSocket channels](/websockets/) and join both on that one connection.
