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

# Streaming

> Live OCX data over Server-Sent Events — order books, the options board, your fills and positions, and portfolio balances

OCX pushes real-time data over **Server-Sent Events (SSE)**, not raw WebSockets.
Connect with any SSE or `EventSource` client, apply the initial `snapshot`, then
fold in `delta`/`update` events. All streams diff server-side and send a comment
heartbeat (`:heartbeat`) roughly every 15s so proxies keep the connection open.

<Info>
  **Reconnect discipline.** On any disconnect, reconnect and re-apply the fresh
  `snapshot` — do not assume you can resume mid-stream. Messages carry a
  monotonic sequence (`id:` or `eventSeq`); if you detect a gap, reconnect and
  re-snapshot rather than trusting a partial book.
</Info>

## The five streams

<CardGroup cols={2}>
  <Card title="Perp order book" icon="chart-column">
    `GET /perps/book-stream` — public depth for one perp/future market.
  </Card>

  <Card title="Perp user stream" icon="user-lock">
    `GET /perps/stream/events` — book + your fills + your positions.
  </Card>

  <Card title="Options board" icon="table-cells">
    `GET /markets/board/stream` — public marks, IV, Greeks per underlying.
  </Card>

  <Card title="Options CLOB stream" icon="bolt">
    `GET /options/stream/events` — tradable book + tape for one instrument.
  </Card>

  <Card title="Portfolio balances" icon="wallet">
    `GET /portfolio/balances/stream` — your cross-bucket balance state.
  </Card>
</CardGroup>

Public streams (`book-stream`, `board/stream`) need no credentials. Private
streams require a session cookie or API key; their fills, positions, and
balances are scoped to the authenticated account.

## Envelope & event names

Named-event streams emit `event: snapshot` on connect then `event: delta` (or
`event: update`) on change, with a canonical envelope:

```
{ "eventId", "eventType", "eventSeq", "engineTs", "sourceTs", "payload" }
```

In an order-book delta, a level with `size: "0"` means that price level was
**removed**. An `event: error` carries `{ code, message, retryable }`.

## Perp order book — public

<ParamField query="marketId" type="string" required />

<ParamField query="depth" type="number">Levels. Default `25`, max `100`.</ParamField>
<ParamField query="intervalMs" type="number">Emit cadence. Default `100`, range `50`–`2000`.</ParamField>

`GET /perps/book-stream` — pure market data, no user fields. First `snapshot` is
the full book; then `update` diffs. Each level is `{ price, size, total }`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -N "https://{API_BASE}/perps/book-stream?marketId=BTC-PERP&depth=25&intervalMs=100"
  ```

  ```js JavaScript theme={null}
  const es = new EventSource(`https://${API_BASE}/perps/book-stream?marketId=BTC-PERP&depth=25`);
  es.addEventListener("snapshot", e => applyBook(JSON.parse(e.data)));
  es.addEventListener("update",   e => applyDiff(JSON.parse(e.data))); // size:"0" = level removed
  ```
</CodeGroup>

```json data theme={null}
{ "marketId": "BTC-PERP",
  "bids": [{ "price": "68200", "size": "1.25", "total": "1.25" }],
  "asks": [{ "price": "68220", "size": "0.80", "total": "0.80" }] }
```

## Perp user stream — private

<ParamField query="marketId" type="string" required>One market per connection.</ParamField>
<ParamField query="depth" type="number">Default `25`, max `250`.</ParamField>

<ParamField query="openOnly" type="boolean" />

<ParamField query="intervalMs" type="number">Default `~500`, range `100`–`5000`.</ParamField>

`GET /perps/stream/events` — **auth required.** One market per connection,
carrying three things: the public **order book**, **your fills**, and **your
positions**. Pass `marketId`.

`GET /perps/stream/user` — **auth required.** Your fills and positions across
**all** markets on one connection (no order book). This is the stream to use for
account-wide fill tracking and market making — one connection instead of one per
market. Params: `openOnly`, `intervalMs` (default `~250`).

<ResponseField name="payload.orderbook" type="object">Book levels; deltas send changed levels (`size:"0"` = removed).</ResponseField>

<ResponseField name="payload.trades" type="array">
  Your fills: `{ side, price, quantity, liquidity, createdAt }` where `liquidity`
  is `maker` or `taker`.
</ResponseField>

<ResponseField name="payload.positions" type="array">
  Your positions: `{ side, quantity, entryPrice, realizedPnl, status }`. Deltas
  send position `upserts` and `removedIds`.
</ResponseField>

```js theme={null}
const es = new EventSource(
  `https://${API_BASE}/perps/stream/events?marketId=BTC-PERP`,
  { withCredentials: true }
);
es.addEventListener("snapshot", e => init(JSON.parse(e.data).payload));
es.addEventListener("delta",    e => apply(JSON.parse(e.data).payload));
```

<Tip>
  A fill arrives as a trade **plus** a position delta. Market makers feed this
  straight into the next `quotes/bulk` cycle — skew or shrink quotes as inventory
  builds. See the [Market makers](/market-makers/fill-stream) guide.
</Tip>

## Options board — public

<ParamField query="underlying" type="string">e.g. `BTC`. Defaults to the primary underlying.</ParamField>

`GET /markets/board/stream` — \~1 update/sec, `data`-only messages wrapping
`{ eventType: "OptionsBoardSnapshot", eventSeq, engineTs, payload }`. The payload
is the full board snapshot: `underlyingPrice`, `underlyingSymbol`, `expiries[]`,
per-expiry forwards, and `options[]` rows with `strike, expiry, bid, ask, mark,
iv, delta` plus the volatility-index block.

```bash theme={null}
curl -N "https://{API_BASE}/markets/board/stream?underlying=BTC"
```

<Note>
  The board is the reference marks surface — not a tradable book. For a hittable
  options book, use the options CLOB stream below.
</Note>

## Options CLOB stream — private

<ParamField query="marketId" type="string" required>One option instrument.</ParamField>
<ParamField query="depth" type="number">Default `25`, max `250`.</ParamField>
<ParamField query="intervalMs" type="number">Default `~100`, range `50`–`2000`.</ParamField>
<ParamField query="includeReference" type="boolean">Add the reference-mark overlay.</ParamField>

`GET /options/stream/events` — **auth required.** Same envelope as the perp user
stream: `event: snapshot` (`OptionsStreamSnapshot`) then `event: delta`
(`OptionsStreamDelta`) with monotonic `eventSeq`. Payload: the instrument's
tradable **CLOB order book**, the public **trade tape**
(`{ side, price, quantity, liquidity }`, no counterparty data), and — only when
`includeReference=true` — a reference overlay `{ markPrice, source, tradable: false }`.

<Warning>
  The reference overlay is always flagged `tradable: false` — it is a marks
  reference, never a hittable price. Route orders against the CLOB book, not the
  overlay.
</Warning>

```js theme={null}
const es = new EventSource(
  `https://${API_BASE}/options/stream/events?marketId=BTC_CALL_70000_2026-12-25&includeReference=true`,
  { withCredentials: true }
);
es.addEventListener("snapshot", e => init(JSON.parse(e.data).payload));
es.addEventListener("delta",    e => apply(JSON.parse(e.data).payload));
```

## Portfolio balances — private

`GET /portfolio/balances/stream` — **auth required.** `event: snapshot`
(`{ type: "snapshot", payload }`) on connect, then `event: update` whenever your
unified balances change across the wallet / perps / options / spot buckets.
Heartbeat every 15s; `event: error` on upstream failure.

```js theme={null}
const es = new EventSource(`https://${API_BASE}/portfolio/balances/stream`, { withCredentials: true });
es.addEventListener("snapshot", e => setBalances(JSON.parse(e.data).payload));
es.addEventListener("update",   e => setBalances(JSON.parse(e.data).payload));
```

## Consumption checklist

<Steps>
  <Step title="Connect and snapshot">
    Open the stream, apply the first `snapshot` as your full state.
  </Step>

  <Step title="Fold in deltas">
    Apply each `delta`/`update`; treat a level with `size: "0"` as removed.
  </Step>

  <Step title="Watch the sequence">
    Track `id:` / `eventSeq`. A gap means you missed data.
  </Step>

  <Step title="Reconnect on drop or gap">
    Reconnect and re-snapshot — never resume a book from a partial state.
  </Step>
</Steps>
