---
title: "Live SSE stream (up to 10 symbols per connection)"
method: GET
path: "/v1/stream"
tags: ["Heatmap"]
---

# Live SSE stream (up to 10 symbols per connection)

`GET /v1/stream`

## Request

- Base URL: `https://api.skylit.ai`
- URL: `https://api.skylit.ai/v1/stream`
- Auth: HTTP bearer

A **Server-Sent Events** (`text/event-stream`) feed of live per-strike
heatmaps. Two wire formats:

- **v2** (default with `symbols=`, or `format=v2`): up to 10 symbols on
  one connection, camelCase events matching `SymbolHeatmap`, resumable
  with `Last-Event-ID`.
- **v1** (default with `symbol=`): the original single-symbol format,
  unchanged.

### v2 events

Every event names its `symbol` (except connection-level events).

| Event | `id:` | Payload |
|---|---|---|
| `connected` | — | `{format:"v2", symbols, metric, creditsRemaining, creditsPerMinute, maxDurationSeconds}` |
| `resumed` | yes | Only when `Last-Event-ID` was sent. See *Resume*. |
| `snapshot` | yes | `StreamSnapshot` (= `SymbolHeatmap` + `lagged`). Once per symbol on connect, then on every board change. |
| `velocity` | yes | `StreamVelocity`: per-node velocity for the stream's `metric`, when it changes (checked every ~3s). |
| `symbol_unavailable` | — | `{symbol, reason: "no_live_board" \| "expiration_not_found", message}`; a `snapshot` follows once a board exists. |
| `credits` | — | `{remaining, charged}` after each per-minute debit. |
| `closed` | — | `{reason: "insufficient_credits" \| "account_suspended" \| "key_revoked" \| "monthly_cap_reached" \| "credit_check_failed"}`; stream ends. `key_revoked`: an API key on the account was revoked after this stream opened (streams opened on another still-valid key close too; reconnect to continue). `monthly_cap_reached`: the account's monthly spend cap was reached. |
| `reconnect` | yes | `{reason: "max_duration" \| "server_shutdown"}`; stream ends, reconnect with `Last-Event-ID`. |
| `: ping` | — | SSE comment every 15s so idle proxies (CloudFront: 60s) keep the connection. |

Snapshots differ from `/v1/heatmap` in two ways: `spot` is the board's
own spot, and strikes carry no `velocityPct` (use the `velocity`
event). Within a symbol, `asOf` never goes backwards; a board already
sent is never re-sent.

**Event ids (resume cursor).** The `id:` is the connection's position
in **every** symbol, not just the event's: a comma-separated list of
`SYMBOL:asOfUnixMs`, e.g. `SPY:1790000000123,QQQ:1790000000456`.
`asOfUnixMs` is the board's publish timestamp, identical on every
server, so a cursor resumes on any pod. Per symbol it never decreases
and strictly increases with each `snapshot` for that symbol.

**Resume.** Reconnect with header `Last-Event-ID: <last id>` (browsers'
`EventSource` does this automatically) or `?lastEventId=`. The server
keeps no event history: boards are full state, so it sends the
`resumed` event and then a fresh `snapshot` for every symbol that has a
live board, whether or not it changed. Per symbol `status` is:
`unchanged` (no board was published during the gap — nothing missed),
`advanced` (at least one board was published; intermediate boards are
**not** replayed, only the latest), `unknown` (symbol not in the
cursor), or `unavailable` (no live board right now). `gapCovered` is
true only if every symbol is `unchanged`; `stateRestored` is true when
every symbol received a current snapshot. Velocity is not replayed; the
latest reading is re-sent when this server has one.

**Backpressure.** A client that reads slower than boards are published
is never queued behind: each symbol holds at most one pending board, a
newer board replaces it, and the board that is finally sent carries
`lagged: true`. A client that stops reading for 30s is disconnected.

**Pricing (v2).** `N` = number of symbols. `N` credits on connect
(charged before the SSE upgrade, so an under-funded client gets a clean
`402`), then `N` credits per minute open — 1 credit ($0.001) per symbol
per minute, i.e. $0.06 per symbol-hour, plus $0.001 per symbol to open.
Minutes are charged in advance and a partial minute is not refunded.

**Limits.** At most 10 symbols per connection (more → `400`). At most
25 symbols per customer per server across all open streams (a v1
stream counts as 1); over it → `429` `stream_limit_reached` with
`Retry-After: 2` (a just-closed stream frees its symbols within about
a second). v1 also keeps its 5-streams cap. Each server streams a
bounded number of **distinct** symbols across all customers (default
20); a v2 stream that would add a new symbol beyond that gets `503`
`stream_capacity` with `Retry-After: 5` — retry (you may land on
another server). Symbols already being streamed are always admitted.
Streams end after 1 hour with `reconnect`.

### v1 events (single symbol, unchanged)

`connected` `{symbol, creditsRemaining}`, `initial_data`,
`snapshot_update`, `velocity_update` (internal PascalCase board shape),
`credits`, `closed`, `reconnect`, and a `: keepalive` comment every
30s. 1 credit to open + 1 per minute. v1 rejects `expirations`.

> OpenAPI cannot fully model an event stream. See
> https://www.skylit.ai/docs/api-reference/rate-limits-and-retries (Streams).

## Query parameters

- `symbols` string
- `symbol` string
- `format` 'v1' | 'v2'
- `lastEventId` string
- `metric` 'gamma' | 'vanna'
- `maxStrikes` union
  - integer
  - 'all'
- `maxExpirations` union
  - integer
  - 'all'
- `includeEmpty` boolean
- `expirations` string
- `layout` 'net' | 'matrix'

## Headers

- `Last-Event-ID` string

## Response `200`

An SSE stream of heatmap events.

## Other responses

- `400`: Request validation failed.
- `401`: Missing API key (`unauthorized`), sent by the gateway.
- `402`: Out of credits (`insufficient_credits`).
- `403`: Unknown, revoked or expired key (`forbidden`, from the gateway), or an admin-suspended account (`account_suspended`).
- `404`: Unknown symbol, no data available, or none of the requested `expirations` exist for the symbol (`code: expiration_not_found`).
- `429`: Per-minute rate limit exceeded.
- `503`: Heatmap data is temporarily unavailable.

---

[API](https://skmtc.dev/skylit/apis/skylit-public-api.md) · [All operations](https://skmtc.dev/skylit/apis/skylit-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/skylit/apis/skylit-public-api/revisions/890b0ea4e6f5?raw)
