DocsSubscriptions

Subscriptions

After the system ready frame, send UTF-8 JSON text frames with an action field. The server acknowledges each subscribe and unsubscribe with a matching system event.

Open .md

To try a subscribe first, use the live playground on the WebSocket overview.

Subscribe

actionstringRequired
The action to run.
Valuessubscribe
channelsstring[]Required
Non-empty array, for example ["crypto.quotes", "crypto.trades"].
symbolsstring | string[]Required
Array or comma-separated string. Values are trimmed, uppercased, de-duplicated and sorted before validation.
snapshotboolean | string
false skips the initial snapshot and delivers live updates only; "always" replays the cached value even when it is no longer recent. See Initial snapshot.
Valuestruefalse"always"Defaulttrue
viewstring
Quote and trade channels only. "full" adds the snapshot record: a replay at any age on every channel, crypto and forex included, and change frames. See Full view.
Values"default""full"Default"default"
spreadstring
etfs.quotes and overnight.quotes only: "full" shows the spread the market is quoting. US ETFs outside the regular session also get the books wider than 0.5% of the mid (ETFs: full spread); overnight quotes widen to the night spread (Overnight full spread). Any other channel in the same subscribe rejects it.
Values"normalized""full"Default"normalized"
intervalstring | string[]
stocks.agg and etfs.agg only: which bar intervals to receive for the symbols in this request. A later subscribe for the same symbol replaces its intervals; any other channel rejects it.
Values1m5m15m1h4h1dDefault1m
sessionstring
stocks.agg and etfs.agg only: extended adds pre-market and after-hours bars for 1m, 5m and 15m. Any other channel rejects it.
ValuesregularextendedDefaultregular
rollsstring | string[]
futures.quotes only, opt-in: adds the roll fields of these methods to every .C1 frame. The latest futures subscribe on the connection sets them; a value outside the list fails with INVALID_ROLLS. See Futures CFDs: roll methods.
Valuesr5r5ccyclenoticeall
Example: subscribe to two channels
{
  "action": "subscribe",
  "channels": ["crypto.quotes", "crypto.trades"],
  "symbols": ["BTCUSD", "ETHUSD"]
}
Acknowledgement
{
  "type": "system",
  "event": "subscribed",
  "channels": ["crypto.quotes", "crypto.trades"],
  "symbols": ["BTCUSD", "ETHUSD"]
}

Settled OHLCV bars on stocks.agg follow the bar contract in Stocks: OHLCV bars over WebSocket, and etfs.agg carries the same bars for US-listed ETFs (ETFs: OHLCV bars over WebSocket).

Example: OHLCV bars at two intervals, extended hours included
{
  "action": "subscribe",
  "channels": ["stocks.agg"],
  "symbols": ["US:KO", "US:JPM"],
  "interval": ["1m", "5m"],
  "session": "extended"
}

Initial snapshot

Right after subscribed, the stream sends the last known quote or trade for each subscribed symbol that has a recent value, so you can show a price before the next tick. Symbols without a recent value are skipped until their first live tick.

Same shape as live
Crypto, forex, stocks, perpetuals and futures CFDs use ts and string numbers; indices, ETFs and commodities use timestamp and numbers. Snapshot frames stay marked, so you can drop them.
Event time
The time field is when the market event happened, not the send time. Outside trading hours it can be older than the clock; compare it with your own receive time.
ChannelSnapshot on subscribe
Crypto and forexNothing: both stream continuously, so the next live update comes first. "view": "full" replays the last quote and trade.
Most other quote and trade channelsThe last value, when it is recent.
US and Asian equities, ETFsOnly while the value is recent, about two minutes; outside the session nothing arrives until the first tick. "snapshot": "always" replays the last value anyway.
stocks.statusNever: a halt status is about the present, so only events after you subscribe arrive.
stocks.aggNever: the first settled bar arrives within one interval. Backfill with GET /stocks/agg.
etfs.aggNever, like stocks.agg. History is on GET /etfs/agg.
bonds.quotesAlways the latest daily rate, whatever its date; only "snapshot": false suppresses it.
futures.quotesAlways the last price of each month or series, held or live (the live flag says which); only "snapshot": false suppresses it.
"trade_mode": "full_volume"Never, whatever snapshot says: a replayed trade is not an execution and would add size that never happened.

Use "snapshot": "always" while developing against a closed market. Shortly after a service restart in a closed session there may be nothing to replay yet; GET /stocks/snapshot/{symbol} returns a last known value at any hour.

Example: keep the last known value while the market is closed
{
  "action": "subscribe",
  "channels": ["stocks.quotes", "stocks.trades"],
  "symbols": ["US:KO"],
  "snapshot": "always"
}

Use "snapshot": false when a cached value must never count as current, for example an on-chain oracle at session open: the first frame is then a genuine live update.

Example: live updates only (no initial snapshot)
{
  "action": "subscribe",
  "channels": ["indices.quotes", "indices.trades"],
  "symbols": ["JP225", "HK50"],
  "snapshot": false
}

Full view

Add "view": "full" to a quote and trade subscribe to stream everything GET /{asset}/snapshot returns, with no REST call. Quotes and trades keep their usual frames on the same connection.

On subscribe
The last quote and trade of every symbol at any age, crypto and forex included, then one change frame per symbol, all marked "snapshot": true. Commodities replay a quote from the last day and no trade. A symbol with nothing yet starts with its first live update.
Then
A change frame whenever prev_close, change or change_percent moves, against the same previous close and with the same arithmetic as the snapshot endpoint. Where it would first ask a vendor (an ETF before the US open or on a closed day, an index whose live feed has stalled), the stream measures from the newest price it holds.
Options
"snapshot": false skips the replay and the first change value: change frames then arrive only when a value moves. A later subscribe without view returns its symbols to the default stream.
Example: quotes, trades and change values for one symbol
{
  "action": "subscribe",
  "channels": ["stocks.quotes", "stocks.trades"],
  "symbols": ["US:KO"],
  "view": "full"
}
Confirmation after the acknowledgement
{
  "type": "system",
  "event": "view",
  "view": "full",
  "symbols": ["US:KO"]
}

The frame itself is on Message types. An unknown value never fails the subscribe: it is answered with a view notice carrying a code, and those symbols stay on the default stream.

codeMeaning
INVALID_VIEWThe value is not "full" or "default".
VIEW_UNAVAILABLEA market wildcard: the full view applies to individual symbols.
VIEW_LIMITNo change values for these symbols on this subscribe; subscribe again to retry. Their quotes and trades are unaffected.

Unsubscribe

Same channels and symbols rules as subscribe. It removes the symbols from the listed channels on this connection; subscriptions on other channels stay.

Example
{
  "action": "unsubscribe",
  "channels": ["crypto.quotes"],
  "symbols": ["BTCUSD"]
}
Acknowledgement
{
  "type": "system",
  "event": "unsubscribed",
  "channels": ["crypto.quotes"],
  "symbols": ["BTCUSD"]
}

Ping and pong

Client ping
Send { "action": "ping" }; the server answers with a pong event carrying its ts.
Server ping
The server sends native WebSocket ping frames. A client that stops answering gets { "type": "system", "event": "disconnect", "code": "HEARTBEAT_TIMEOUT" } and a close with code 4008.
Server → client (JSON pong)
{
  "type": "system",
  "event": "pong",
  "ts": 1743512400000
}

Subscription limits

Symbols per subscribe
Up to 500 in one message. A server cap, not a plan field: send more messages for more symbols.
Distinct symbols
Your plan sets how many different symbols you can stream at once, across all connections and channels. The dashboard shows usage.
Channels
Only the asset classes in your active packages. Add or upgrade a package to unlock more.

Defaults by plan are on the Rate limits page.