GET /ws
Per-account WebSocket stream
Per-account WebSocket stream.
operationId: connectWebSocket
Not reachable through CCXT. WebSocket transport and token minting. Covered by the CCXT Pro channel map, not by a REST unified method.
Authentication: none declared, and the contract declares no document-level default — this operation carries its own signature in the request body. See the description above.
WebSocket endpoint for both public market data and per-account private channels. Requires a token from POST /ws/token:
wss://host/ws?token=YOUR_TOKENTokens are single-use and expire after 60 seconds, and they are scoped to the network that minted them — mint the token on the same host you connect to.
Every client→server and server→client message is a JSON envelope tagged with an op field. Subscribe to one channel per message:
{"op": "subscribe", "channel": "trades", "market": "BTC-USDX-PERP"}
{"op": "subscribe", "channel": "fills"}Unsubscribe with {"op": "unsubscribe", "channel": "...", "market": "..."}.
Public channels (the token is always required to upgrade): trades, book, candles, ticker — each requires a market field.
ticker carries the same payload GET /markets/{market_id}/ticker returns — both are built by one function, so the REST and streaming views of a ticker cannot disagree. It is published once per book-poll cycle (currently 1 s) rather than per trade, because the 24h high, low, open and volume come from the materialized candles and a running notional, not from any single fill; a ticker projected off trades would move last and leave the 24h fields stale. Frames are emitted every cycle whether or not the values moved, as book is — diff against the previous frame if you only care about changes. engine is public and venue-wide, so it takes no market field: subscribe and unsubscribe are accepted and acked, but no frames are published on it yet. Treat it as reserved — do not build against a payload shape.
Per-account channels (scoped to the wallet that minted the token): orders, fills, positions, balances, liquidations.
Server messages:
{"op": "subscribed", "channel": "fills", "market": null, "seq_at_join": 42}— subscribe ack;seq_at_joinis the channel's current sequence at attach time, used as the reconnect cursor.{"op": "unsubscribed", "channel": "fills", "market": null}— unsubscribe ack.{"op": "event", "channel": "fills", "market": null, "seq": 43, "payload": {...}}— a delivered event;seqis monotonic per channel.{"op": "out_of_sync", "channel": "fills", "market": null, "oldest_seq": 100}— the requestedsincecursor fell behind the replay buffer; refetch state over REST and resubscribe with a fresh cursor.{"op": "error", "message": "..."}— invalid op, unknown channel, or bad message format.
liquidations payloads: the payload of an event on this channel is externally tagged — exactly one key, naming the engine event variant. See the LiquidationEvent schema for the full shape. Monetary fields are lossless decimal strings, never JSON numbers.
LiquidationAlert— a pre-liquidation risk warning for the account.severityisWarning,CriticalorImminent, classified fromequity / maintenance_margin:Warningin (1.2, 1.5],Criticalin (1.05, 1.2],Imminentin (1.0, 1.05]. Nothing is emitted above 1.5 (safe) or at/below 1.0 (already liquidating).market_idisnullfor a portfolio-level alert over the whole cross-margin account — the only form the engine emits today — and names a market when the alert is scoped to one market.PortfolioLiquidation— terminal: the account's cross-margin positions have already been closed out.closurescarries the per-market closes.
Alerts are edge-triggered: one frame per worsening severity transition, nothing while a severity holds, and nothing when the account recovers. Subscribing does not replay the current alert state — unlike balances and positions, this channel is not seeded with a snapshot on subscribe — and there is no REST read of it, so a reconnecting client sees nothing until the next worsening transition.
Reconnection: tokens are single-use — mint a new one via POST /ws/token before reconnecting. To resume a channel without gaps, pass the last seq you received:
Control-plane limits. The WebSocket ceilings are a resource class of their own: they are independent of the REST request budget and of the trading-action budget, and exhausting one does not affect the others. Per tier — Pro: 5 concurrent connections, 50 subscriptions, 10 inbound client frames per second; MarketMaker: 100, 1,000 and 50/s; gateway (Unlimited) keys are exempt from all three. Those connection figures are per account: a separate per-IP cap (5 by default) is applied first, at upgrade time, and binds on every tier including Unlimited, so one origin address cannot reach the per-account figure on its own. A connection refused there gets an HTTP 429 (ws_conn_limit_exceeded) at the upgrade rather than a close frame. A 2× burst is tolerated above the sustained frame rate. The subscription ceiling is applied both per connection and across all of an account's connections at the same number, so opening more sockets does not buy more subscriptions; re-subscribing a key you already hold replaces it in place and is free. Exceeding it returns an error frame (subscription_limit_exceeded) rather than a 429 — there is no status code on this surface — and sustained inbound flooding closes the connection with 1008. These figures are current per-deployment configuration, not part of this contract. See “Rate limits” in the API description.
Parameters
token
query
string
Yes
—
Short-lived token from POST /ws/token
Responses
101
— (no schema declared)
Switching Protocols — WebSocket upgrade.
Example
Generated from eng/apps/exchange/api/openapi.json (spec 0.9.36). Do not edit by hand — regenerate with python3 product/docs/tools/api-reference/generate.py. Hand-authored context lives in the Guides pages.
Last updated

