wss://api.backquant.com/v2/ws/options
Single connection, multiple subscriptions. The same canonical trade payload from the REST tape, pushed sub-second after the upstream venue emits it.
Authentication
Pass your API key on the WebSocket handshake - either of:- Query parameter -
wss://api.backquant.com/v2/ws/options?api_key=bq_live_...(easiest from browsers). Authorization: Bearerheader - preferred from server-to-server clients.X-API-Keyheader - accepted as a fallback for clients that can set arbitrary handshake headers.
1008 policy-violation code and a human-readable reason.
Channel inventory
{coin} is typically BTC or ETH (SOL/HYPE appear when tape exists).
{venue} is one of deribit, bybit, okx, binance, derive,
thalex, delta_india, delta, bullish, paradex (case-insensitive on
subscribe; the server normalises to lowercase). bullish and paradex are
accepted but quiet: their trades are not collected yet. {SYMBOL} is BTCUSDT, ETHUSDT,
SOLUSDT, or HYPEUSDT. See Venue coverage for
what each venue ships.
Levels note (changed in 2.6.0): gex.levels.* pushes flow-signed
levels - the same model and the same builder as REST
/v2/gex/levels, so the two surfaces always agree.
Before 2.6.0 this channel pushed a std (textbook) snapshot while REST
defaulted to flow.
When the aggressor tape is unavailable for a symbol the payload falls back
to the worker’s std snapshot and says so: positioning: "std" plus a
fallback_reason. Always read positioning before overlaying the numbers
on anything else. See Positioning.
On subscribing to gex.levels.* the server sends the current levels
immediately, flagged "snapshot": true, then pushes again only when the
levels move. Without that first frame you could not tell an idle book from a
subscription that never took.
Max 48 channels per connection.
Protocol
Server → client envelope
Every server frame is a JSON object with anevent field:
Client → server commands
Trade payload
amount and premium_usd are comparable across venues. For Delta ingest
details see Venue coverage → Delta units.
Connection lifecycle
- Connect with API key → server validates → counts connection against your per-tier cap.
- Receive
welcome→ sanity-checksubscription_tierandavailable_channels. - Send
subscribe→ server replies withsubscribedack + immediately starts streaming matching trades. - Stream - trades arrive on their channel;
status.heartbeatarrives every 15 s if subscribed. - Heartbeat liveness - the server expects at least one client frame (subscribe / unsubscribe / ping) within 60 s. Otherwise it closes with code
1001. - Slow consumer - each client has a 1000-message send buffer. If full for > 5 s the server sends a
slow_consumerevent then closes with code1013. Reduce your subscription set or process frames faster. - Disconnect → connection counter decrements; reconnect any time.
Consolidated chain
chain.{SYMBOL}.{EXPIRY} (for example chain.BTCUSDT.30OCT26) and
chain.{SYMBOL}.all stream the options chain merged across every venue
that lists each contract, the same chain /v2/options/chain
serves. Channel names are case-insensitive; an expiry that is not listed is
refused with an error carrying available_expiries.
On subscribe the server sends a snapshot; after each chain refresh (about
every 30 seconds) it sends only the contracts whose quotes, implied vol,
open interest or 24h volume changed, and the ids that left the chain:
Greeks ride along on every changed contract, but a greek moving on its own
does not count as a change (they all move with spot every refresh). Quote
sizes and per-venue rows are not in the chain we publish, so they are not
streamed.
Limits: up to 12 chain channels per connection, of which one may be
.all.
Sizes on BTC (October 2026): about 630 KB for the whole-chain snapshot and
400 KB per refresh; about 50 to 65 KB per expiry.
Per-tier connection caps
Exceeding the cap on connect returns close code
1008 with a
connection-cap message. WS usage does not draw down monthly REST
quota - see Rate limits.
Reconnect template
The server has no replay -$ cursor is used on the underlying Redis stream, so a reconnect resumes with whatever’s live, not whatever you missed. For zero-gap consumers, also poll /v2/tape?after=<last_ts> on reconnect to fetch trades that arrived during the gap.
scripts/v2_ws_demo/ws_tape_demo.py.
