# Stream depth and the trade tape

`/ws/market` is the public half of the platform: no bearer, no handshake token, no tenant
header. Open it, send two subscribe frames, and you have the live book and every print for
a symbol on one connection.

1. ### Open the socket

   Nothing precedes the first subscribe. The TypeScript SDK ships no market-data client, so
   drive this channel with any WebSocket implementation. Append `?encoding=msgpack` to switch
   server frames to MessagePack — on a busy book it is the cheapest win available.

   ```js
   const ws = new WebSocket('wss://api.troncharts.xyz/ws/market')
   ws.addEventListener('message', (e) => handle(JSON.parse(e.data)))
   ws.addEventListener('open', () => { /* subscribe here */ })
   ```

   :::note[No credential, and no `x-tenant-slug`]
   The `/ws/market` upgrade short-circuits before the tenant middleware, so the header every
   `/api/v1` call needs is neither read nor required here. An `Authenticate` frame is accepted
   but only attaches a scope that is echoed back — subscribe frames are never gated on it.
   :::

2. ### Subscribe to the book

   `venue` and `symbol` are separate fields; there is no `venue:symbol` string on the wire.
   You get `Result { ok: true }`, and a `Depth-Update` snapshot immediately after it when the
   server's cache is already warm. Depth serves `hyperliquid`, `aster`, `lighter`,
   `polymarket`, `kalshi`, `massive` and `b3`.

   ```json
   { "type": "Subscribe-Depth", "venue": "hyperliquid", "symbol": "BTC.HL" }
   ```

   Every level is `{ px, sz }` as decimal strings, plus `ct` — the resting-order count — on
   the venues that expose one.

   ```json
   { "type": "Depth-Update", "venue": "hyperliquid", "symbol": "BTC.HL",
     "bids": [{ "px": "64210.0", "sz": "1.842" }],
     "asks": [{ "px": "64211.5", "sz": "0.311" }],
     "source": "be_canonical", "ts": "2026-08-04T12:00:00.000Z" }
   ```

3. ### Subscribe to the tape

   The tape's venue set is narrower than depth's — `hyperliquid`, `aster`, `massive`, `b3`,
   the four with a trade-print producer. Asking for any other venue is rejected as
   `invalid_frame` rather than accepted and left silently blank.

   ```json
   { "type": "Subscribe-Tape", "venue": "hyperliquid", "symbol": "BTC.HL" }
   ```

   Handle both wire shapes. Prints are batched into a `trades` array — one frame per flush
   window, not per print, because B3 alone was measured at 612 prints per second — while the
   legacy flattened single-print frame is still part of the contract.

   ```js
   function onTape(f) {
     const prints = 'trades' in f ? f.trades : [f]
     for (const p of prints) console.log(p.side, p.px, p.sz, p.ts)
   }
   ```

4. ### Add the channels you need

   The same socket multiplexes `Subscribe-Depth-L3` for the per-order book (`lighter` and
   `b3` only) → `Order-Book-Update`, `Subscribe-BBO` → `BBO-Update`, `Subscribe-VAP` →
   `VAP-Update`, and `Subscribe-Candles` with an `interval` written as a count plus `s`, `m`,
   `h`, `d`, `w` or `M` (`1m`, `4h`, `1d`) → `Candle-Update`. Each has a matching
   `Unsubscribe-*`, `Unsubscribe-All` drops everything, and `Ping` answers `Pong`. Past the
   per-socket subscription cap you get `Result { ok: false, error: "subscription_limit" }`.

**Next:** [Market channel](https://docs.troncharts.xyz/docs/realtime/market/) · [Market data](https://docs.troncharts.xyz/docs/trading/market-data/) · [Stream account and position updates](https://docs.troncharts.xyz/docs/recipes/stream-account-updates/)