> TickerLayer documentation for AI agents and LLMs, the Markdown version of https://tickerlayer.com/docs/websocket/messages
> 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

# Message types

Every message is a single JSON object with a type string. Every quote and trade frame carries its event time in ts (unix ms) on every channel; numeric field encoding still differs by asset family.

## All frame types

| Type | Sent on | Carries |
| --- | --- | --- |
| `system` | Every connection | Ready, subscribe and unsubscribe acknowledgements, pong, disconnect |
| `quote` | `*.quotes` | Bid, ask and their sizes |
| `trade` | `*.trades` | Price and size of a trade |
| `change` | `*.quotes` `*.trades` | Previous close and the move against it, with [view "full"](https://tickerlayer.com/docs/websocket/subscriptions#full-view) |
| `mark` | `perpetuals.marks` | Composite mark price |
| `rate` | `bonds.quotes` | Daily government bond yield |
| `status` | `stocks.status` | US trading halt and resume events |
| `agg` | `stocks.agg` `etfs.agg` | One settled OHLCV bar |
| `error` | Any channel | A failed client action or a breached limit |

## System

Control frames, `type: "system"`, sent in response to connection events and client actions.

| Event | Meaning |
| --- | --- |
| `ready` | Sent right after a successful upgrade, with `ts` (unix ms). Wait for it before subscribing. |
| `subscribed` | Echoes the normalized `channels` and `symbols`. |
| `unsubscribed` | Echoes the removed `channels` and `symbols`. |
| `pong` | Reply to `{ "action": "ping" }`, with the server `ts`. |
| `disconnect` | The server is closing the socket: `HEARTBEAT_TIMEOUT` after missed native pongs, or `SERVER_RESTART` right before a release closes the stream with code `1012`. |

**ready frame**

```json
{
  "type": "system",
  "event": "ready",
  "ts": 1743512400000
}
```

## Quote

`type: "quote"` on the `*.quotes` channels. Every frame carries `ts` (event time, unix ms) and `asset`; how numbers are encoded depends on the asset family.

| Asset family | Numbers as | Time fields |
| --- | --- | --- |
| Crypto, forex, stocks, perpetuals, futures CFDs | Strings, for example `"bid": "62.41"` | `ts` |
| Indices, ETFs, commodities | Numbers on most frames; some tick-driven ETF and index frames arrive as strings. Accept both: one frame never mixes them. | `ts`, plus `timestamp`, a mirror kept for older clients. Read `ts` in new code. |

- **Sizes:** `bid_size`, `ask_size` and trade `size` are `number | null` (string-encoded on the string channels); `null` means the consolidated feed has no size at that moment.
- **Snapshot frames:** Frames replayed from cache right after subscribe carry `"snapshot": true`. They appear on stocks, ETFs, indices and commodities; crypto and forex stream continuously and replay only with `"view": "full"`. See [Subscriptions](https://tickerlayer.com/docs/websocket/subscriptions#snapshot).
- **Futures CFDs:** Quotes on `futures.quotes` add the contract month, its calendar dates, a per-symbol `seq` and microsecond times. Full reference: [Futures CFDs](https://tickerlayer.com/docs/products/futures#futures-stream).
- **Entitlements:** A channel can stay silent until coverage is enabled for your account.

**crypto.quotes**

```json
{
  "type": "quote",
  "channel": "crypto.quotes",
  "asset": "crypto",
  "symbol": "BTCUSD",
  "bid": "84210.12",
  "ask": "84210.45",
  "bid_size": "1.2",
  "ask_size": "0.8",
  "ts": 1743512400000
}
```

**stocks.quotes**

```json
{
  "type": "quote",
  "channel": "stocks.quotes",
  "asset": "stocks",
  "symbol": "US:KO",
  "bid": "62.41",
  "ask": "62.43",
  "bid_size": "100",
  "ask_size": "200",
  "ts": 1743512400000
}
```

**indices.quotes**

```json
{
  "type": "quote",
  "channel": "indices.quotes",
  "asset": "indices",
  "symbol": "US500",
  "bid": 4788.12,
  "ask": 4788.12,
  "bid_size": 62,
  "ask_size": 58,
  "ts": 1743512400000,
  "timestamp": 1743512400000
}
```

**commodities.quotes**

```json
{
  "type": "quote",
  "channel": "commodities.quotes",
  "asset": "commodities",
  "symbol": "XAUUSD",
  "bid": 2318.1,
  "ask": 2318.74,
  "bid_size": 58,
  "ask_size": 48,
  "ts": 1743512400000,
  "timestamp": 1743512400000
}
```

## Trade

`type: "trade"` on the `*.trades` channels, with the same encoding rules as quotes. `size` may be left out or `null` when the tape has no lot size.

**crypto.trades**

```json
{
  "type": "trade",
  "channel": "crypto.trades",
  "asset": "crypto",
  "symbol": "BTCUSD",
  "price": "84215.42",
  "size": "0.53",
  "ts": 1743512400000
}
```

**etfs.trades**

```json
{
  "type": "trade",
  "channel": "etfs.trades",
  "asset": "etfs",
  "symbol": "US500ETF",
  "price": 718.66,
  "size": 90,
  "ts": 1743512400000,
  "timestamp": 1743512400000
}
```

## Change

`type: "change"` goes only to a subscribe with [`"view": "full"`](https://tickerlayer.com/docs/websocket/subscriptions#full-view). It carries the previous close and the move against it, the same values [GET /{asset}/snapshot](https://tickerlayer.com/docs/rest) returns. One frame per symbol, sent whenever a value moves.

- **Numbers:** `prev_close`, `change` and `change_percent` are JSON numbers, on every asset class. `change` and `change_percent` can be `null` when no move can be measured. When the previous close rolls to a new day, a frame with all three `null` takes the old values back until the new ones follow.
- **`ts`:** Event time of the price the change is measured from: the last trade, or the book while nothing has printed. It is not unique per symbol, so apply change frames in the order they arrive.
- **`channel`:** The trades channel of the asset class when you subscribed to it, otherwise the quotes channel. The first frame after subscribe is marked `"snapshot": true`.

**stocks.trades**

```json
{
  "type": "change",
  "channel": "stocks.trades",
  "asset": "stocks",
  "symbol": "US:KO",
  "prev_close": 88.05,
  "change": 0.92,
  "change_percent": 1.0449,
  "ts": 1791818728130
}
```

## Mark

`type: "mark"` on `perpetuals.marks`: the composite mark price of a perpetual futures contract, the reference its market uses to value positions, as a string with `ts`. Full reference: [Perpetuals](https://tickerlayer.com/docs/products/perpetuals#perpetuals-stream).

- **Quotes and trades:** Perpetuals reuse `quote` on `perpetuals.quotes` and `trade` on `perpetuals.trades`.
- **`source_count`:** On every quote and mark: the number of independent sources behind the value.
- **`side`:** On a trade, `buy` or `sell` when the aggressor is known.

**perpetuals.marks**

```json
{
  "type": "mark",
  "channel": "perpetuals.marks",
  "asset": "perpetuals",
  "symbol": "XAUUSDT",
  "mark_price": "4316.244",
  "source_count": 6,
  "ts": 1789640505932
}
```

## Rate

`type: "rate"` on `bonds.quotes`: the latest published government bond yield for a `CC:TENOR` symbol. Yields are daily observations, not ticks: the current one arrives as a snapshot right after you subscribe, then one frame per new observation, in practice once per business day, so a quiet hour is normal. Numbers are numbers, and the fields match [GET /bond/snapshot](https://tickerlayer.com/docs/products/bonds#bond-snapshot).

**bonds.quotes**

```json
{
  "type": "rate",
  "channel": "bonds.quotes",
  "asset": "bonds",
  "symbol": "US:10Y",
  "rate": 4.79,
  "unit": "percent",
  "date": "2026-09-02",
  "prev_rate": 4.76,
  "prev_date": "2026-09-01",
  "change": 0.03,
  "change_bps": 3,
  "change_percent": 0.6303,
  "ts": 1788307200000,
  "timestamp": 1788307200000
}
```

- **Dates:** `ts` and `timestamp` are the observation date (UTC midnight of `date`), never the send time. `date` against `prev_date` is the interval the change covers.
- **Change fields:** `change` in percentage points, `change_bps` in basis points, `change_percent` relative to `prev_rate`. The `prev_*` and change fields are `null` while only one observation exists.
- **Snapshot:** The frame replayed on subscribe carries `"snapshot": true`; frames for a new observation do not.

## Status

`type: "status"` on `stocks.status`: trading halt and resume events for US-listed equities. Subscribe with symbols (`US:KO`) or, with the market wildcard add-on, `US:*` for every US listing. Events arrive from subscribe time on and are never replayed, because a cached status may no longer hold.

**stocks.status**

```json
{
  "type": "status",
  "channel": "stocks.status",
  "asset": "stocks",
  "symbol": "US:KO",
  "status": "halted",
  "reason": "news_pending",
  "ts": 1743512400000
}
```

| status | Meaning |
| --- | --- |
| `halted` | Trading halted. |
| `paused` | A volatility or limit-up/limit-down pause. |
| `quoting` | Quotations resumed while trading is still halted, the step before some resumptions. |
| `resumed` | Trading resumed. |

- **`reason`:** Our classification of why: `volatility`, `market_wide`, `news_pending`, `news_released`, `information_requested`, `regulatory`, `corporate_action`, `operational` or `ipo`. Left out when none applies; new values may be added, so decode it tolerantly.
- **Scope:** Halt lifecycle events for US equities only: no auction imbalances, price indications or short sale restriction notices. Both fields are derived classifications, under the data notice that covers every feed.

## Agg (OHLCV bar)

`type: "agg"` on `stocks.agg`: one settled OHLCV bar per subscribed US symbol and interval, with the letters the REST aggregate routes use (`o h l c v`), the trade count `n` and the volume-weighted average price `vw`. `ts` is the bar start, never the send time; the frame arrives a few seconds after the window closes, and nothing is replayed on subscribe.

**stocks.agg**

```json
{
  "type": "agg",
  "channel": "stocks.agg",
  "asset": "stocks",
  "symbol": "US:KO",
  "interval": "1m",
  "session": "regular",
  "o": 88.48,
  "h": 88.52,
  "l": 88.46,
  "c": 88.5,
  "v": 41230,
  "n": 312,
  "vw": 88.4917,
  "ts": 1789133400000
}
```

- **`interval`:** `1m`, `5m`, `15m`, `1h`, `4h` or `1d`, chosen per subscribe (default `1m`).
- **`session`:** `pre`, `regular` or `post`: where in the trading day the bar starts. Intraday bars cover the regular session (09:30 to 16:00 ET, early closes honored) on the `/stocks/agg` grid unless you subscribe with `"session": "extended"`, which adds pre-market and after-hours bars for 1m, 5m and 15m. The daily bar carries the official open and close, labelled at the session date's UTC midnight like the daily REST bar.
- **`n` and `vw`:** Left out when not available for a bar.

The full contract, including when each interval settles, is in [Stocks: OHLCV bars over WebSocket](https://tickerlayer.com/docs/products/stocks#stocks-agg-stream).

`etfs.agg` sends the same frame for US-listed ETFs, with `"channel": "etfs.agg"`, `"asset": "etfs"` and the ETF symbol as `/etfs/symbols` lists it. See [ETFs: OHLCV bars over WebSocket](https://tickerlayer.com/docs/rest/etfs#etfs-agg-stream).

## Error

`type: "error"` when a client action fails validation or a limit is breached after the connection is open. Subscribe failures use `event: "subscribe_failed"` (or `subscribe_partial_failed`). There is no `details` object; rejected symbols appear in `rejected` when present.

**Symbol cap**

```json
{
  "type": "error",
  "event": "subscribe_failed",
  "code": "SYMBOL_LIMIT_EXCEEDED",
  "message": "rate limit exceeded"
}
```

**Invalid symbols**

```json
{
  "type": "error",
  "event": "subscribe_failed",
  "code": "INVALID_SYMBOL",
  "message": "invalid symbol",
  "channel": "stocks.quotes",
  "rejected": [
    { "symbol": "NOTREAL", "reason": "unknown" }
  ]
}
```

Every error code is listed on [Errors & troubleshooting](https://tickerlayer.com/docs/websocket/troubleshooting).
