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

# Fill stream

> React to executions and position changes in real time over SSE instead of polling

Instead of polling for fills, subscribe to the real-time stream and react the
instant a quote executes. Each fill arrives as a **trade** plus a **position**
change, which feeds directly into your next quoting cycle.

The stream is **Server-Sent Events** (`Content-Type: text/event-stream`). Connect
with any SSE / `EventSource` client and reconnect on drop.

## Endpoints

There are two user streams. For market making, prefer the **all-markets** stream —
one connection covers your whole book.

<Tabs>
  <Tab title="All markets (recommended)">
    `GET {API_BASE}/perps/stream/user` — **Auth: session cookie or API key.**

    Your fills and position changes across **every** market on a single
    connection. No order book is included, so it stays lightweight even with many
    markets open.

    <ParamField query="openOnly" type="boolean">Restrict position data to open positions.</ParamField>
    <ParamField query="intervalMs" type="integer">Server diff/emit cadence in ms (default \~250, range 100–5000).</ParamField>
  </Tab>

  <Tab title="Single market">
    `GET {API_BASE}/perps/stream/events` — **Auth: session cookie or API key.**

    One market's fills, positions, **and** its order book. Use it when you also
    want the live book for a specific market.

    <ParamField query="marketId" type="string" required>The market to stream.</ParamField>
    <ParamField query="depth" type="integer">Order-book depth to include (default 25, max 250).</ParamField>
    <ParamField query="openOnly" type="boolean">Restrict position data to open positions.</ParamField>
    <ParamField query="intervalMs" type="integer">Server diff/emit cadence in ms (default \~500, range 100–5000).</ParamField>
  </Tab>
</Tabs>

<Note>
  Both streams are scoped to your authenticated account for the trades and positions
  they carry. Use `/perps/stream/user` to cover an N-market book with **one**
  connection; use `/perps/stream/events` when you specifically need a market's order
  book alongside your fills.
</Note>

## What it carries

The stream delivers a canonical envelope and, scoped to your authenticated
account for the fills and positions parts, three things:

<CardGroup cols={3}>
  <Card title="Orderbook" icon="book">
    The public CLOB book (single-market stream only; levels with `size:"0"` mean
    the level was removed).
  </Card>

  <Card title="Your trades" icon="receipt">
    Your fills: `side`, `price`, `quantity`, `liquidity` (maker/taker),
    `createdAt`.
  </Card>

  <Card title="Your positions" icon="chart-line">
    Your positions: `side`, `quantity`, `entryPrice`, `realizedPnl`, `status`
    (upserts + `removedIds`).
  </Card>
</CardGroup>

## Event model

Every message is an envelope:

```json theme={null}
{ "eventId": "…", "eventType": "PerpStreamDelta", "eventSeq": 10432, "engineTs": "…", "sourceTs": "…", "payload": { } }
```

| Event      | `eventType`          | Meaning                                                                                 |
| ---------- | -------------------- | --------------------------------------------------------------------------------------- |
| `snapshot` | `PerpStreamSnapshot` | Full state on connect: book + recent trades + your positions.                           |
| `delta`    | `PerpStreamDelta`    | Incremental changes: changed book levels, newly-seen trades, position upserts/removals. |
| `error`    | —                    | `{ code, message, retryable }` on upstream failure.                                     |

`eventSeq` increases monotonically (once per emitted event) so you can **detect
gaps**. Apply the initial `snapshot`, then fold in each `delta`. If you see a
sequence jump or the connection drops, reconnect — you will receive a fresh
`snapshot` to re-sync from.

## Consume it

<CodeGroup>
  ```js JavaScript theme={null}
  // One connection covers your whole book. Each trade / position carries its marketId.
  const es = new EventSource(
    `${API_BASE}/perps/stream/user?openOnly=true`,
    { withCredentials: true } // sends the session cookie; or front with an API-key proxy
  );

  let lastSeq = null;

  es.addEventListener("snapshot", (e) => {
    const { payload, eventSeq } = JSON.parse(e.data);
    lastSeq = eventSeq;
    initPositions(payload.positions);            // baseline across all markets
  });

  es.addEventListener("delta", (e) => {
    const { payload, eventSeq } = JSON.parse(e.data);
    if (lastSeq != null && eventSeq > lastSeq + 1) {
      es.close();      // gap detected — reconnect and re-snapshot
      reconnect();
      return;
    }
    lastSeq = eventSeq;

    for (const t of payload.trades ?? []) onFill(t);          // your execution, any market
    for (const p of payload.positions?.upserts ?? []) onPosition(p);
  });

  es.addEventListener("error", (e) => {
    // network drop or { code, message, retryable } — back off and reconnect
  });

  function onFill(trade) {
    // trade.liquidity === "maker" | "taker"; adjust inventory, then re-quote that market
    updateInventory(trade);
    scheduleRequote(trade.marketId);
  }
  ```

  ```bash curl theme={null}
  curl -N "{API_BASE}/perps/stream/user?openOnly=true" \
    -H "x-api-key: $OCX_API_KEY"
  ```
</CodeGroup>

<Tip>
  A fill shows up as a trade **and** a position delta. Use the position delta as the
  source of truth for inventory, then skew or shrink your next `quotes/bulk` ladder
  accordingly. See [inventory management](/market-makers/best-practices#inventory-and-risk).
</Tip>

## Reconcile with pull endpoints

The stream is push-first, but you can reconcile at any time:

* `GET {API_BASE}/perps/positions` — your open positions.
* `GET {API_BASE}/perps/orders/open` — your currently resting orders.
* `GET {API_BASE}/perps/trades` — your recent fills.
* `GET {API_BASE}/perps/balances` — your perp balance / margin view.

<Warning>
  Idle connections receive a comment heartbeat (`:heartbeat`) every few seconds so
  proxies keep the stream open. If you stop seeing heartbeats, treat the connection
  as dead and reconnect.
</Warning>
