> TickerLayer documentation for AI agents and LLMs, the Markdown version of https://tickerlayer.com/docs/websocket/subscriptions
> REST base URL https://api.tickerlayer.com, API key in the `x-api-key` header. WebSocket wss://stream.tickerlayer.com?apiKey=YOUR_API_KEY.
> Any docs page reads as Markdown at its URL plus `.md`. Index: https://tickerlayer.com/llms.txt · Full API reference: https://tickerlayer.com/llms-full.txt

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

To try a subscribe first, use the live playground on the [WebSocket overview](https://tickerlayer.com/docs/websocket#try-live).

## Subscribe

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `action` | string | Yes | The action to run. Values: `subscribe`. |
| `channels` | string[] | Yes | Non-empty array, for example `["crypto.quotes", "crypto.trades"]`. |
| `symbols` | string \| string[] | Yes | Array or comma-separated string. Values are trimmed, uppercased, de-duplicated and sorted before validation. |
| `snapshot` | boolean \| string | No | `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](https://tickerlayer.com/docs/websocket/subscriptions#snapshot). Values: `true`, `false`, `"always"`. Default: `true`. |
| `view` | string | No | 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](https://tickerlayer.com/docs/websocket/subscriptions#full-view). Values: `"default"`, `"full"`. Default: `"default"`. |
| `spread` | string | No | `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](https://tickerlayer.com/docs/rest/etfs#etfs-full-spread)); overnight quotes widen to the night spread ([Overnight full spread](https://tickerlayer.com/docs/products/stocks#overnight-spread)). Any other channel in the same subscribe rejects it. Values: `"normalized"`, `"full"`. Default: `"normalized"`. |
| `interval` | string \| string[] | No | `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. Values: `1m`, `5m`, `15m`, `1h`, `4h`, `1d`. Default: `1m`. |
| `session` | string | No | `stocks.agg` and `etfs.agg` only: `extended` adds pre-market and after-hours bars for 1m, 5m and 15m. Any other channel rejects it. Values: `regular`, `extended`. Default: `regular`. |
| `rolls` | string \| string[] | No | `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](https://tickerlayer.com/docs/products/futures#futures-rolls). Values: `r5`, `r5c`, `cycle`, `notice`, `all`. |

**Example: subscribe to two channels**

```json
{
  "action": "subscribe",
  "channels": ["crypto.quotes", "crypto.trades"],
  "symbols": ["BTCUSD", "ETHUSD"]
}
```

**Acknowledgement**

```json
{
  "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](https://tickerlayer.com/docs/products/stocks#stocks-agg-stream), and `etfs.agg` carries the same bars for US-listed ETFs ([ETFs: OHLCV bars over WebSocket](https://tickerlayer.com/docs/rest/etfs#etfs-agg-stream)).

**Example: OHLCV bars at two intervals, extended hours included**

```json
{
  "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.

| Channel | Snapshot on subscribe |
| --- | --- |
| Crypto and forex | Nothing: both stream continuously, so the next live update comes first. `"view": "full"` replays the last quote and trade. |
| Most other quote and trade channels | The last value, when it is recent. |
| US and Asian equities, ETFs | Only 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.status` | Never: a halt status is about the present, so only events after you subscribe arrive. |
| `stocks.agg` | Never: the first settled bar arrives within one interval. Backfill with [GET /stocks/agg](https://tickerlayer.com/docs/products/stocks#stocks-agg-range). |
| `etfs.agg` | Never, like `stocks.agg`. History is on [GET /etfs/agg](https://tickerlayer.com/docs/rest/etfs#etfs-agg-range). |
| `bonds.quotes` | Always the latest daily rate, whatever its date; only `"snapshot": false` suppresses it. |
| `futures.quotes` | Always 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}](https://tickerlayer.com/docs/products/stocks#stocks-snapshot) returns a last known value at any hour.

**Example: keep the last known value while the market is closed**

```json
{
  "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)**

```json
{
  "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](https://tickerlayer.com/docs/rest) 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**

```json
{
  "action": "subscribe",
  "channels": ["stocks.quotes", "stocks.trades"],
  "symbols": ["US:KO"],
  "view": "full"
}
```

**Confirmation after the acknowledgement**

```json
{
  "type": "system",
  "event": "view",
  "view": "full",
  "symbols": ["US:KO"]
}
```

The frame itself is on [Message types](https://tickerlayer.com/docs/websocket/messages#change). 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.

| code | Meaning |
| --- | --- |
| `INVALID_VIEW` | The value is not "full" or "default". |
| `VIEW_UNAVAILABLE` | A market wildcard: the full view applies to individual symbols. |
| `VIEW_LIMIT` | No 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**

```json
{
  "action": "unsubscribe",
  "channels": ["crypto.quotes"],
  "symbols": ["BTCUSD"]
}
```

**Acknowledgement**

```json
{
  "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)**

```json
{
  "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](https://tickerlayer.com/docs/limits) page.
