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

# Perpetuals

Perpetual futures on stocks, ETFs, commodities, indices, currencies, pre-IPO companies and crypto, priced around the clock.

- **Auth:** x-api-key header
- **Base path:** /perpetuals
- **WebSocket:** perpetuals.quotes, perpetuals.marks · perpetuals.trades
- **Symbol format:** XAUUSDT, TSMUSDT, BTCUSDT

## How the feed is built

A perpetual is a futures contract with no expiry, traded 24 hours a day, 7 days a week. The methodology in full is on the [Perpetuals product page](https://tickerlayer.com/data-assets/perpetuals).

- **One composite price:** Built from every independent source that lists the contract: the bid is the median of the live source bids and the ask the median of the live source asks, so the book is never crossed and no single source moves the price.
- **`source_count`:** On every response and frame: how many sources stand behind the value.
- **Underlying class:** A perpetual is an instrument type, not an asset class, so each symbol reports its underlying: `equity`, `etf`, `commodity`, `index`, `forex`, `pre_ipo` or `crypto`.
- **Units:** US dollar stablecoin terms, as the contracts trade.

## Symbol list

**Endpoint:** `GET /perpetuals/symbols`

Every perpetual contract with its name, underlying class and source count. Open to any valid key, with or without Perpetuals on the account.

### Query parameters

| Parameter | Type | Description |
| --- | --- | --- |
| `underlying` | string | Optional filter: equity, etf, commodity, index, forex, pre_ipo or crypto. An unknown value is ignored. |

### Request

```bash
curl -sS "https://api.tickerlayer.com/perpetuals/symbols?underlying=commodity" \
  -H "x-api-key: <YOUR_API_KEY>"
```

```python
import requests

res = requests.get(
    "https://api.tickerlayer.com/perpetuals/symbols?underlying=commodity",
    headers={"x-api-key": "<YOUR_API_KEY>"},
)
print(res.json())
```

```javascript
const res = await fetch("https://api.tickerlayer.com/perpetuals/symbols?underlying=commodity", {
  headers: { "x-api-key": "<YOUR_API_KEY>" },
});
const data = await res.json();
console.log(data);
```

```go
req, _ := http.NewRequest("GET", "https://api.tickerlayer.com/perpetuals/symbols?underlying=commodity", nil)
req.Header.Set("x-api-key", "<YOUR_API_KEY>")
```

### Response

```json
{
  "symbols": [
    {
      "symbol": "XAUUSDT",
      "name": "Gold",
      "instrument_type": "perpetual",
      "underlying_asset_class": "commodity",
      "base": "XAU",
      "source_count": 6
    }
  ],
  "count": 8
}
```

### Notes

`source_count` here is how many sources list the contract. On a price it is how many of them are live behind that value right now.

## Latest quote

**Endpoint:** `GET /perpetuals/quote/:symbol`

Composite best bid and ask with the displayed size summed across the contributing sources.

### Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `symbol` | string | Yes | Perpetual contract symbol (for example XAUUSDT, TSMUSDT, BTCUSDT). Trimmed and uppercased before lookup. Retrieve the full list from GET /perpetuals/symbols. |

### Request

```bash
curl -sS "https://api.tickerlayer.com/perpetuals/quote/XAUUSDT" \
  -H "x-api-key: <YOUR_API_KEY>"
```

```python
import requests

res = requests.get(
    "https://api.tickerlayer.com/perpetuals/quote/XAUUSDT",
    headers={"x-api-key": "<YOUR_API_KEY>"},
)
print(res.json())
```

```javascript
const res = await fetch("https://api.tickerlayer.com/perpetuals/quote/XAUUSDT", {
  headers: { "x-api-key": "<YOUR_API_KEY>" },
});
const data = await res.json();
console.log(data);
```

```go
req, _ := http.NewRequest("GET", "https://api.tickerlayer.com/perpetuals/quote/XAUUSDT", nil)
req.Header.Set("x-api-key", "<YOUR_API_KEY>")
```

### Response

```json
{
  "symbol": "XAUUSDT",
  "bid": 4316.31,
  "ask": 4316.36,
  "bid_size": 118.049,
  "ask_size": 51.115,
  "source_count": 6,
  "timestamp": 1789640504033
}
```

### Notes

`timestamp` is the event time of the freshest contributing source, in epoch milliseconds, and never steps backwards. A composite can carry one more decimal than the contract tick: the median of two sources one tick apart is a half tick. `403 perpetuals access not enabled for your account`: the account does not include Perpetuals. `404 unknown symbol`: the symbol is not in the perpetuals list. `404` with a `no ... available` message: the contract is known but has no live value right now.

## Last trade

**Endpoint:** `GET /perpetuals/trade/last/:symbol`

The most recent execution across sources, with the aggressor side when the source reports it.

### Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `symbol` | string | Yes | Perpetual contract symbol (for example XAUUSDT, TSMUSDT, BTCUSDT). Trimmed and uppercased before lookup. Retrieve the full list from GET /perpetuals/symbols. |

### Request

```bash
curl -sS "https://api.tickerlayer.com/perpetuals/trade/last/TSMUSDT" \
  -H "x-api-key: <YOUR_API_KEY>"
```

```python
import requests

res = requests.get(
    "https://api.tickerlayer.com/perpetuals/trade/last/TSMUSDT",
    headers={"x-api-key": "<YOUR_API_KEY>"},
)
print(res.json())
```

```javascript
const res = await fetch("https://api.tickerlayer.com/perpetuals/trade/last/TSMUSDT", {
  headers: { "x-api-key": "<YOUR_API_KEY>" },
});
const data = await res.json();
console.log(data);
```

```go
req, _ := http.NewRequest("GET", "https://api.tickerlayer.com/perpetuals/trade/last/TSMUSDT", nil)
req.Header.Set("x-api-key", "<YOUR_API_KEY>")
```

### Response

```json
{
  "symbol": "TSMUSDT",
  "price": 423.36,
  "size": 1.5,
  "side": "buy",
  "timestamp": 1789640506413
}
```

### Notes

Trades are the one per-event stream, so the trade tape runs on demand: a contract joins it when a client subscribes to its trades or when this route is called, and stays on it for ten minutes after the last request. The first call for a quiet contract can answer `404 no trade available`; the next print fills it. `side` is `buy`, `sell` or `null`. `403 perpetuals access not enabled for your account`: the account does not include Perpetuals. `404 unknown symbol`: the symbol is not in the perpetuals list. `404` with a `no ... available` message: the contract is known but has no live value right now.

## Snapshot

**Endpoint:** `GET /perpetuals/snapshot/:symbol`

Quote, mark price and last trade in one call, with the contract name and underlying class. Built for first paint.

### Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `symbol` | string | Yes | Perpetual contract symbol (for example XAUUSDT, TSMUSDT, BTCUSDT). Trimmed and uppercased before lookup. Retrieve the full list from GET /perpetuals/symbols. |

### Request

```bash
curl -sS "https://api.tickerlayer.com/perpetuals/snapshot/TSMUSDT" \
  -H "x-api-key: <YOUR_API_KEY>"
```

```python
import requests

res = requests.get(
    "https://api.tickerlayer.com/perpetuals/snapshot/TSMUSDT",
    headers={"x-api-key": "<YOUR_API_KEY>"},
)
print(res.json())
```

```javascript
const res = await fetch("https://api.tickerlayer.com/perpetuals/snapshot/TSMUSDT", {
  headers: { "x-api-key": "<YOUR_API_KEY>" },
});
const data = await res.json();
console.log(data);
```

```go
req, _ := http.NewRequest("GET", "https://api.tickerlayer.com/perpetuals/snapshot/TSMUSDT", nil)
req.Header.Set("x-api-key", "<YOUR_API_KEY>")
```

### Response

```json
{
  "symbol": "TSMUSDT",
  "name": "Taiwan Semiconductor Manufacturing Company",
  "instrument_type": "perpetual",
  "underlying_asset_class": "equity",
  "bid": 423.31,
  "ask": 423.39,
  "bid_size": 61.2,
  "ask_size": 48.7,
  "mark_price": 423.35,
  "last_price": 423.36,
  "last_size": 1.5,
  "source_count": 6,
  "timestamp": 1789640504033,
  "mark_timestamp": 1789640505932,
  "last_timestamp": 1789640506413,
  "prev_close": 421.12,
  "change": 2.23,
  "change_percent": 0.5295
}
```

### Notes

A snapshot reads the last trade but never starts the trade tape, so polling it keeps `last_*` only as fresh as other demand makes it; they are `null` when no print is held. `prev_close` is the close of the last completed UTC daily bar, and `change` and `change_percent` measure the composite mid price against it (the mark price when there is no book). A perpetual has no session, so the day turns at 00:00 UTC. The three fields are `null` for a moment after the UTC day turns, until the new close is read. `403 perpetuals access not enabled for your account`: the account does not include Perpetuals. `404 unknown symbol`: the symbol is not in the perpetuals list. `404` with a `no ... available` message: the contract is known but has no live value right now.

## Previous bar

**Endpoint:** `GET /perpetuals/agg/:symbol/prev`

The most recently completed UTC daily candle for the contract.

### Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `symbol` | string | Yes | Perpetual contract symbol (for example XAUUSDT, TSMUSDT, BTCUSDT). Trimmed and uppercased before lookup. Retrieve the full list from GET /perpetuals/symbols. |

### Query parameters

| Parameter | Type | Description |
| --- | --- | --- |
| `interval` | string | Returns the most recently settled bar at that interval instead of the previous daily bar, and adds `interval`, `bar_start`, `bar_end` and `as_of` to the response. Leave it out for the daily bar. Values: `1m`, `5m`, `15m`, `1h`, `4h`, `1d`. |

### Request

```bash
curl -sS "https://api.tickerlayer.com/perpetuals/agg/XAUUSDT/prev" \
  -H "x-api-key: <YOUR_API_KEY>"
```

```python
import requests

res = requests.get(
    "https://api.tickerlayer.com/perpetuals/agg/XAUUSDT/prev",
    headers={"x-api-key": "<YOUR_API_KEY>"},
)
print(res.json())
```

```javascript
const res = await fetch("https://api.tickerlayer.com/perpetuals/agg/XAUUSDT/prev", {
  headers: { "x-api-key": "<YOUR_API_KEY>" },
});
const data = await res.json();
console.log(data);
```

```go
req, _ := http.NewRequest("GET", "https://api.tickerlayer.com/perpetuals/agg/XAUUSDT/prev", nil)
req.Header.Set("x-api-key", "<YOUR_API_KEY>")
```

### Response

```json
{
  "symbol": "XAUUSDT",
  "result": {
    "o": 4305.9,
    "h": 4312.7,
    "l": 4287.5,
    "c": 4298.4,
    "v": 214380.51,
    "t": 1789257600000
  }
}
```

### Notes

`404 no bar available` when the daily bar cannot be resolved yet. A bar requested with `?interval=` is held for a short settle lag after it closes, so small clock differences between callers do not change which bar comes back, and it is the same bar the range endpoint returns for that window. `Cache-Control` and `ETag` mark when the value can next change.

## Aggregate bars (OHLCV)

**Endpoint:** `GET /perpetuals/agg/:symbol/:multiplier/:timespan/:from/:to`

Historical OHLCV bars between two UTC calendar dates (inclusive).

### Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `symbol` | string | Yes | Perpetual contract symbol (for example XAUUSDT, TSMUSDT, BTCUSDT). Trimmed and uppercased before lookup. Retrieve the full list from GET /perpetuals/symbols. |
| `multiplier` | number | Yes | Bar size in units of `timespan`: `1`, `5` or `15` with `minute`; `1` or `4` with `hour`; `1` with `day`. Other pairs return `400`, and the message lists the valid ones. |
| `timespan` | string | Yes | Case-insensitive. Other values return `400`. Values: `minute`, `hour`, `day`. |
| `from` | string | Yes | First UTC date, inclusive, as `YYYY-MM-DD`. |
| `to` | string | Yes | Last UTC date, inclusive, as `YYYY-MM-DD`. Not before `from`. |

### Query parameters

| Parameter | Type | Description |
| --- | --- | --- |
| `limit` | number | Page size, up to `5000`. `results_count` is the size of this page and `total_count` the bars in the whole range; follow `next_offset` until it is `null`. Default: `500`. |
| `offset` | number | Zero-based row offset, applied after sorting. Default: `0`. |
| `sort` | string | Order of bars by timestamp `t`, applied before pagination. Values: `asc`, `desc`. Default: `desc`. |

### Request

```bash
curl -sS "https://api.tickerlayer.com/perpetuals/agg/XAUUSDT/1/day/2026-09-01/2026-09-14?sort=desc&limit=3" \
  -H "x-api-key: <YOUR_API_KEY>"
```

```python
import requests

res = requests.get(
    "https://api.tickerlayer.com/perpetuals/agg/XAUUSDT/1/day/2026-09-01/2026-09-14?sort=desc&limit=3",
    headers={"x-api-key": "<YOUR_API_KEY>"},
)
print(res.json())
```

```javascript
const res = await fetch("https://api.tickerlayer.com/perpetuals/agg/XAUUSDT/1/day/2026-09-01/2026-09-14?sort=desc&limit=3", {
  headers: { "x-api-key": "<YOUR_API_KEY>" },
});
const data = await res.json();
console.log(data);
```

```go
req, _ := http.NewRequest("GET", "https://api.tickerlayer.com/perpetuals/agg/XAUUSDT/1/day/2026-09-01/2026-09-14?sort=desc&limit=3", nil)
req.Header.Set("x-api-key", "<YOUR_API_KEY>")
```

### Response

```json
{
  "symbol": "XAUUSDT",
  "results_count": 3,
  "total_count": 14,
  "results": [
    { "o": 4298.4, "h": 4331.2, "l": 4290.1, "c": 4316.3, "v": 278600.08, "t": 1789344000000 },
    { "o": 4305.9, "h": 4312.7, "l": 4287.5, "c": 4298.4, "v": 214380.51, "t": 1789257600000 },
    { "o": 4279.2, "h": 4310.0, "l": 4271.8, "c": 4305.9, "v": 236911.37, "t": 1789171200000 }
  ],
  "next_offset": 3
}
```

### Notes

- Bars are built from traded prices on the contract's primary market source, one source per contract so a chart is never stitched from two series. `v` is that source's volume in base units, not the whole market. The live composite quote can sit a few ticks away from the latest bar close.
- Maximum span per request: 7 days for 1 minute bars, 31 for 5 minute, 90 for 15 minute, 365 for 1 hour, 730 for 4 hour. A few contracts carry shorter windows; the `400` message states the limit for that symbol.
- History starts when the contract was first listed, which for many perpetuals is recent. Bars are UTC, the day opens at 00:00 UTC, and the newest bar is still forming.

## Mark price

**Endpoint:** `GET /perpetuals/mark/:symbol`

Composite mark price: the median of the live source mark prices. The mark is the reference a perpetual market uses to value positions and trigger liquidations.

### Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `symbol` | string | Yes | Perpetual contract symbol (for example XAUUSDT, TSMUSDT, BTCUSDT). Trimmed and uppercased before lookup. Retrieve the full list from GET /perpetuals/symbols. |

### Request

```bash
curl -sS "https://api.tickerlayer.com/perpetuals/mark/XAUUSDT" \
  -H "x-api-key: <YOUR_API_KEY>"
```

```python
import requests

res = requests.get(
    "https://api.tickerlayer.com/perpetuals/mark/XAUUSDT",
    headers={"x-api-key": "<YOUR_API_KEY>"},
)
print(res.json())
```

```javascript
const res = await fetch("https://api.tickerlayer.com/perpetuals/mark/XAUUSDT", {
  headers: { "x-api-key": "<YOUR_API_KEY>" },
});
const data = await res.json();
console.log(data);
```

```go
req, _ := http.NewRequest("GET", "https://api.tickerlayer.com/perpetuals/mark/XAUUSDT", nil)
req.Header.Set("x-api-key", "<YOUR_API_KEY>")
```

### Response

```json
{
  "symbol": "XAUUSDT",
  "mark_price": 4316.244,
  "source_count": 6,
  "timestamp": 1789640505932
}
```

### Notes

`403 perpetuals access not enabled for your account`: the account does not include Perpetuals. `404 unknown symbol`: the symbol is not in the perpetuals list. `404` with a `no ... available` message: the contract is known but has no live value right now.

## WebSocket

Three channels on the same stream, key and subscribe message as every other feed, on the Business plan. Frames follow the stream dialect: prices and sizes are strings and the event time is `ts`.

| Channel | Carries | Snapshot on subscribe |
| --- | --- | --- |
| `perpetuals.quotes` | The composite top of book. | The last known quote. |
| `perpetuals.marks` | The composite mark price. | The last known mark. |
| `perpetuals.trades` | Executions across sources, in arrival order. | Never. |

- **Conflation:** Quotes and marks are conflated to the latest value ten times a second: a price change goes out on the next flush, a size-only change waits up to 250 ms. With source-level access, the book of each source can come [tick by tick](https://tickerlayer.com/docs/products/perpetuals#perpetuals-tick-by-tick) instead.
- **Snapshot:** Replayed frames carry `snapshot: true`, because a quiet contract can sit unchanged for a while and silence would be ambiguous. Send `snapshot: false` to skip them.

**Subscribe**

```json
{
  "action": "subscribe",
  "channels": ["perpetuals.quotes", "perpetuals.marks", "perpetuals.trades"],
  "symbols": ["XAUUSDT", "TSMUSDT"]
}
```

**quote frame**

```json
{
  "type": "quote",
  "channel": "perpetuals.quotes",
  "asset": "perpetuals",
  "symbol": "XAUUSDT",
  "bid": "4316.31",
  "ask": "4316.36",
  "bid_size": "118.049",
  "ask_size": "51.115",
  "source_count": 6,
  "ts": 1789640504033
}
```

**mark frame**

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

**trade frame**

```json
{
  "type": "trade",
  "channel": "perpetuals.trades",
  "asset": "perpetuals",
  "symbol": "TSMUSDT",
  "price": "423.36",
  "size": "1.5",
  "side": "buy",
  "ts": 1789640506413
}
```

## Source-level streams

The standard feed is the composite. Enterprise accounts that run their own aggregation, such as oracle networks and quantitative desks, can also get every quote, mark and trade per source, next to the composite, on the same routes and channels. Without it, a `?sources=true` request answers `403 source-level access not enabled for your account` and the stream sends composite frames only. [Talk to us](https://tickerlayer.com/pricing?plan=enterprise&feed=perpetuals).

| Field | Meaning |
| --- | --- |
| `source_id` | The identifier of the source. |
| `venue_symbol` | The symbol on that source. |
| `native_currency` | The currency it quotes in. Native values are never converted; [USD values](https://tickerlayer.com/docs/products/perpetuals#perpetuals-usd-values) come next to them on request. |
| `market_type` | `perpetual` or `spot`. |

**REST request and response**

```http
GET /perpetuals/quote/XAUUSDT?sources=true

{
  "symbol": "XAUUSDT",
  "bid": 4316.31,
  "ask": 4316.36,
  "bid_size": 118.049,
  "ask_size": 51.115,
  "source_count": 6,
  "timestamp": 1789640504033,
  "sources": [
    {
      "source_id": "pv_01",
      "venue_symbol": "XAUUSDT",
      "native_currency": "USDT",
      "market_type": "perpetual",
      "bid": 4316.3,
      "ask": 4316.38,
      "bid_size": 15.15,
      "ask_size": 15.3,
      "timestamp": 1789640504021
    }
  ]
}
```

**WebSocket frames**

```json
{
  "type": "venue_quote",
  "channel": "perpetuals.quotes",
  "asset": "perpetuals",
  "symbol": "XAUUSDT",
  "source_id": "pv_01",
  "venue_symbol": "XAUUSDT",
  "native_currency": "USDT",
  "market_type": "perpetual",
  "bid": "4316.3",
  "ask": "4316.38",
  "bid_size": "15.15",
  "ask_size": "15.3",
  "ts": 1789640504021
}
```

- **REST:** One row per live source. `/perpetuals/mark` returns the same rows with `mark_price` in place of the book, and `/perpetuals/trade/last` adds the four fields to the print itself.
- **Stream:** `perpetuals.marks` carries `venue_mark` frames and every `trade` frame gains the four fields. Composite frames keep their `quote` and `mark` types, so the composite is never mistaken for one source.

## USD values

Source rows are priced in the stablecoin each source quotes in, USDT or USDC. Ask for USD values and every source row also carries its prices in USD, next to the native ones and never in place of them. Without the flag, rows and frames are exactly as above.

| Where | Flag | Applies to |
| --- | --- | --- |
| REST | `usd=true` | The source rows of `/perpetuals/quote`, `/perpetuals/mark` and `/perpetuals/trade/last`, together with `sources=true`. |
| Stream | `"usd": true` | The `venue_quote`, `venue_mark` and source-level `trade` frames of the symbols in that subscribe. |

| Field | Meaning |
| --- | --- |
| `usd_bid` | `bid` in USD (quote rows). |
| `usd_ask` | `ask` in USD (quote rows). |
| `usd_mark_price` | `mark_price` in USD (mark rows). |
| `usd_price` | `price` in USD (trades with source fields). |
| `usd_rate` | The rate applied, from `native_currency` to USD: the mid of a USD-quoted spot market for that stablecoin. Every USD field is the native value times this rate. |

**Subscribe with USD values**

```json
{
  "action": "subscribe",
  "channels": ["perpetuals.quotes", "perpetuals.marks"],
  "symbols": ["XAUUSDT"],
  "usd": true
}
```

**venue_quote frame with USD values**

```json
{
  "type": "venue_quote",
  "channel": "perpetuals.quotes",
  "asset": "perpetuals",
  "symbol": "XAUUSDT",
  "source_id": "pv_01",
  "venue_symbol": "XAUUSDT",
  "native_currency": "USDT",
  "market_type": "perpetual",
  "bid": "4316.3",
  "ask": "4316.38",
  "bid_size": "15.15",
  "ask_size": "15.3",
  "usd_bid": "4314.595062",
  "usd_ask": "4314.67503",
  "usd_rate": "0.999605",
  "ts": 1789640504021
}
```

- **No fresh rate:** The USD fields are still there, as `null`. A rate is never assumed to be 1. Rows not priced in a stablecoin, such as the `USDTARS` reference book, always carry `null`.
- **Per symbol:** The most recent subscribe for a symbol decides: subscribe again without `usd` to go back to native values only. Composite frames never carry USD fields.
- **Errors:** `usd` on a channel other than the three perpetuals channels fails with `UNSUPPORTED_USD`, and a value that is not `true` or `false` with `INVALID_USD`.

## Every book change

Source rows are conflated like the composite by default. Subscribe with `"quote_mode": "tick_by_tick"` and every change to the top of book of a source, price or size, goes out as its own `venue_quote` frame, in the order the source published it, with its event time in `ts`. Same frame and fields, plus `seq`, a count of that book's changes.

| Value | Source rows on perpetuals.quotes |
| --- | --- |
| `"conflated"` | Default. The latest book of each source per flush, like the composite. |
| `"tick_by_tick"` | Every change to the top of book of each source, one frame per change. |

**Subscribe tick by tick**

```json
{
  "action": "subscribe",
  "channels": ["perpetuals.quotes"],
  "symbols": ["BTCUSDT", "XAUUSDT"],
  "quote_mode": "tick_by_tick"
}
```

**venue_quote frame, tick by tick**

```json
{
  "type": "venue_quote",
  "channel": "perpetuals.quotes",
  "asset": "perpetuals",
  "symbol": "BTCUSDT",
  "source_id": "pv_02",
  "venue_symbol": "BTCUSDT",
  "native_currency": "USDT",
  "market_type": "perpetual",
  "bid": "86512.4",
  "ask": "86512.5",
  "bid_size": "3.112",
  "ask_size": "0.415",
  "seq": 1791460812437213,
  "ts": 1791460812437
}
```

- **Applies to:** The `venue_quote` frames of the `perpetuals.quotes` symbols in that subscribe. Composite `quote` frames, `venue_mark` frames and trades are unchanged. It combines with `usd` and `snapshot`.
- **Per symbol:** The most recent subscribe for a symbol decides: subscribe again with `"conflated"` or without `quote_mode` to go back.
- **Starting point:** When a symbol starts tick by tick, the current book of each source comes first, flagged `snapshot: true`, then every change after it. A symbol you already stream gets that restatement whenever it switches mode, either way, even with `snapshot: false`, and so does every tick-by-tick symbol after a restart on our side or when source-level access comes back. A new symbol with `snapshot: false` gets no rows and starts with the next change.
- **seq:** An integer that starts at a large arbitrary value (it needs 64 bits and fits a JSON number exactly), goes up by one with every change of that source's book and never goes down. A bigger step means changes were missed: frames a connection could not take, a source that dropped, a restart on our side. The same value again means the same book. Compare it only within one source and symbol; to resync, read the source rows with `?sources=true` or subscribe again.
- **Withdrawals:** Still announced with `venue_status`. The next `venue_quote` for that source and symbol means the book is back, even when its values are the same as before.
- **Volume:** A busy book changes many times a second, mostly in size, so read the socket without blocking. Frames a connection cannot take in time are skipped, not queued, and `seq` shows the gap.
- **Connections:** A symbol streams tick by tick on a limited number of connections at a time, per account and across the service. Past that, or without source-level access, the subscribe still succeeds: those symbols come conflated, and a `quote_mode` message after the acknowledgement names them, with `QUOTE_MODE_LIMIT`, `scope` and `limit`, or `QUOTE_MODE_UNAVAILABLE`. A capped symbol switches to tick by tick by itself once a connection frees up, announced by a `quote_mode` message with `"tick_by_tick"`.
- **Messages:** Every tick-by-tick subscribe is answered with a `quote_mode` message naming the symbols that got `"tick_by_tick"`, and another for any that stay conflated. One can also arrive on its own: when a capped symbol switches, when source-level access ends, after a reconnect on our side. The latest one for a symbol is its mode, and a `venue_quote` that carries `seq` is a tick-by-tick frame.
- **Errors:** `quote_mode` without `perpetuals.quotes`, or next to a channel other than the perpetuals ones, fails with `UNSUPPORTED_QUOTE_MODE`. Any other value fails with `INVALID_QUOTE_MODE`.

**quote_mode message**

```json
{
  "type": "system",
  "event": "quote_mode",
  "channel": "perpetuals.quotes",
  "quote_mode": "conflated",
  "code": "QUOTE_MODE_LIMIT",
  "message": "These symbols already stream tick by tick on as many of this account's connections as allowed. This connection gets their conflated rows and switches to tick by tick when one frees up.",
  "symbols": ["BTCUSDT"],
  "scope": "account",
  "limit": 2
}
```

## Normalized rows

With source-level access, `"format": "normalized"` renders every source row as one flat row priced in USD, the same shape for every source. The symbols in that subscribe get these rows in place of `venue_quote` and `venue_mark`; composite frames and trades do not change.

| Field | Meaning |
| --- | --- |
| `exchange` | The source id, as in `source_id` on the other source rows. |
| `symbol` | The source's base with USD: `MSFTUSD`, `xyz:MSFTUSD`, `MSFT-USD-SWAP`; USD/JPY as `USDJPYUSD` (`xyz:USDJPYUSD`). |
| `type` | `top_of_book` on `perpetuals.quotes`, `mark_price` on `perpetuals.marks`, `impact_price` with [impact prices](https://tickerlayer.com/docs/products/perpetuals#perpetuals-impact). |
| `bid, mid, ask` | USD, as strings. Always bid <= mid <= ask, all positive. A mark row carries its price in all three. |
| `timestamp` | The source's event time, ISO 8601 with milliseconds. |
| `quote` | Always `USD`. |
| `quote_source` | The stablecoin the source quotes in, USDT or USDC. |
| `venue_symbol` | The source's own instrument. |
| `fx_rate` | The stablecoin/USD rate applied to the source prices. |
| `fx_sources` | Ids of the reference markets behind `fx_rate`. |
| `fx_timestamp` | Time of the oldest reading behind `fx_rate`, ISO 8601. |
| `contract` | The perpetuals symbol of the subscribe. |
| `heartbeat_at` | Only on repeats: when the row was sent again. Never on a new update. |
| `impact_usd` | Only on `impact_price` rows: the order size in US dollars. |

**Subscribe to normalized rows**

```json
{
  "action": "subscribe",
  "channels": ["perpetuals.quotes", "perpetuals.marks"],
  "symbols": ["XAUUSDT"],
  "format": "normalized"
}
```

**Normalized row**

```json
{
  "exchange": "pv_01",
  "symbol": "XAUUSD",
  "type": "top_of_book",
  "bid": "4314.595062",
  "mid": "4314.635046",
  "ask": "4314.67503",
  "timestamp": "2026-10-10T12:00:03.477Z",
  "quote": "USD",
  "quote_source": "USDT",
  "venue_symbol": "XAUUSDT",
  "fx_rate": "0.999605",
  "fx_sources": ["fx_01", "fx_02"],
  "fx_timestamp": "2026-10-10T12:00:00.012Z",
  "contract": "XAUUSDT",
  "channel": "perpetuals.quotes"
}
```

- **Options:** `exchanges` keeps only those sources (`["pv_01", "pv_04"]`). `heartbeat_ms` is the repeat interval, 5000 by default, 1000 to 60000, 0 for none. `heartbeat_cap_ms` is the price age at which repeats stop, 300000 by default, 10000 to 1800000. `impact` and `impact_usd` add [impact prices](https://tickerlayer.com/docs/products/perpetuals#perpetuals-impact). The most recent subscribe for a symbol decides; subscribe again without `format` to go back to the default frames.
- **USD rate:** A new row goes out only while the reference markets agree on the stablecoin's rate and every reading behind it is at most 60 seconds old. Otherwise the row is not sent at all: no row is better than a price on a rate that cannot be confirmed. A repeat is an exact copy of a row you were sent, its rate included.
- **Repeats:** A top of book with no real update for `heartbeat_ms` is sent again exactly as it was, plus `heartbeat_at`. Its `timestamp` never moves, so `heartbeat_at` minus `timestamp` is the price age. Repeats stop once that age reaches `heartbeat_cap_ms`, and at once when the row is withdrawn or the subscription changes. Without a rate, or while the source is not delivering, they pause and resume inside the cap. Marks are never repeated.
- **Silence:** A withdrawn row, a source that is down and a lost rate are silence: this format has no status rows. A book seen again after a withdrawal or a restart on our side comes back as a repeat with `snapshot: true`, never as a new update.
- **Snapshots:** The snapshot on subscribe and every restatement are repeats: they carry `heartbeat_at` and `snapshot: true`. A repeat with `snapshot: true` may hold prices you were not sent yet, for example a book that moved during a pause: take its values. A repeat without it is always an exact copy of the last row you were sent. A top of book older than `heartbeat_cap_ms` is not sent. With `snapshot: false`, quiet books start repeating one interval after the subscribe, the first time with `snapshot: true`.
- **Applies to:** `perpetuals.quotes` and `perpetuals.marks`. It combines with `quote_mode`: tick-by-tick rows carry `seq`. Spot reference books have no normalized rows. A source that quotes a contract in another unit (`in_composite: false`) is delivered in that unit.
- **REST:** `/perpetuals/quote/{symbol}?format=normalized` and the same on `/perpetuals/mark` answer `symbol`, `format` and `rows`; add `exchange=pv_01` for one source. `/perpetuals/symbols?format=normalized` lists every source instrument this way, with `base`, `quote_source`, `in_composite` and `impact` (whether impact prices are kept for it). Impact prices are on the stream only.
- **Messages:** A normalized subscribe is answered with a `format` message naming its symbols, with `impact_usd` when impact prices were asked for. Without source-level access the subscribe still succeeds on the composite frames, and that message carries `FORMAT_UNAVAILABLE`. `IMPACT_UNAVAILABLE` names the symbols for which no source of the subscribe keeps impact prices; the rest of the subscribe is in effect.
- **Errors:** `INVALID_FORMAT`, `INVALID_EXCHANGES`, `INVALID_HEARTBEAT` and `INVALID_IMPACT` for values outside the above; `UNSUPPORTED_FORMAT` next to a channel other than the perpetuals ones; `UNSUPPORTED_FORMAT_OPTION` for the options without `format`.

## Impact prices

With `"impact": true` in a normalized subscribe on `perpetuals.quotes`, each source also sends `impact_price` rows: the average price of a market order of `impact_usd` US dollars walked through that source's own book, best level first, on each side. `bid` is the average price of selling that amount, `ask` of buying it, fees not included.

**Subscribe with impact prices**

```json
{
  "action": "subscribe",
  "channels": ["perpetuals.quotes"],
  "symbols": ["XAUUSDT"],
  "format": "normalized",
  "impact": true,
  "impact_usd": 10000
}
```

**Impact row**

```json
{
  "exchange": "pv_01",
  "symbol": "XAUUSD",
  "type": "impact_price",
  "bid": "4314.211407",
  "mid": "4314.79381",
  "ask": "4315.376213",
  "timestamp": "2026-10-10T12:00:03.402Z",
  "quote": "USD",
  "quote_source": "USDT",
  "venue_symbol": "XAUUSDT",
  "fx_rate": "0.999605",
  "fx_sources": ["fx_01", "fx_02"],
  "fx_timestamp": "2026-10-10T12:00:00.012Z",
  "contract": "XAUUSDT",
  "channel": "perpetuals.quotes",
  "impact_usd": 10000
}
```

- **Size:** `impact_usd` is one of 1000, 5000, 10000, 25000, 50000 or 100000; 10000 when left out. The order is sized in US dollars at the same agreed rate the prices are converted with.
- **Thin books:** A side the source's book cannot fill at that size has no price, and the row is not sent: an impact price is never extrapolated past the levels a source shows. Always bid <= mid <= ask, with bid at or below the best bid and ask at or above the best ask.
- **Updates:** A row goes out when the impact price moves; `timestamp` is the source's time of the book it was computed from. A quiet impact price is repeated like the top of book: the same row plus `heartbeat_at` every `heartbeat_ms`, until `heartbeat_cap_ms` after its `timestamp`. The snapshot on subscribe carries the current impact prices as repeats. A book that is lost, stops following the source, or no longer fills the size is silence: its repeats stop.
- **Coverage:** Impact prices are kept for a set of source instruments, flagged `impact: true` in `/perpetuals/symbols?format=normalized`. A symbol without one gets the rest of its normalized rows, and an `IMPACT_UNAVAILABLE` message names it.

## Row liveness and withdrawal

A source row's `timestamp` is the source's own event time, so a quiet book shows an old timestamp while the row is alive, which is common while the underlying market is closed. Do not alert on timestamp age: one rule, below, applies to every source and does not look at market hours.

| Rule | Milliseconds | Description |
| --- | --- | --- |
| `fresh_book_ms` | 90,000 | A book that changed within this window is live. |
| `quiet_book_max_ms` | 1,800,000 | Past 90 seconds an unchanged book stays live only while the same source's mark keeps arriving, and never longer than this. |
| `withdraw_after_lane_loss_ms` | 5,000 | A source connection that drops, goes silent or skips a sequence has its rows withdrawn after this, until a fresh book arrives. |

- **On REST:** A withdrawn row leaves at once and is never carried forward.
- **On the stream:** Its own frame, `venue_status`, so a consumer that switches on `type` never meets an empty price. The next `venue_quote` or `venue_mark` for that source and symbol means it is back; no frame in between means live and unchanged.
- **Status route:** `/perpetuals/sources/status` serves the same values in milliseconds.

**Withdrawal frame**

```json
{
  "type": "venue_status",
  "channel": "perpetuals.quotes",
  "asset": "perpetuals",
  "symbol": "XAUUSDT",
  "source_id": "pv_02",
  "venue_symbol": "XAUUSDT",
  "native_currency": "USDT",
  "market_type": "perpetual",
  "status": "withdrawn",
  "ts": 1789640504021
}
```

`ts` is the event time of the last row delivered for that source. The same frame arrives on `perpetuals.marks` when a source mark is withdrawn.

## Rows outside the composite

- **Spot reference books:** Tagged `market_type: spot`: top of book only, no mark and no funding. Delivered, never in the composite.
- **Different units:** A source that prices a contract in another unit than the rest, today one source on the two pre-IPO contracts. Its row is delivered raw and must not be rescaled, because the source publishes no fixed conversion factor: treat it as its own series.
- **Outliers:** A live row too far from the cross-source median is left out of the composite for as long as it strays, while the row itself is still delivered unfiltered; `source_count` on the composite says how many rows counted.

| Contracts | Left out beyond |
| --- | --- |
| Crypto | 1.5% from the median |
| Pre-IPO | 20% from the median |
| Everything else | 4% from the median |

Rows that never enter the composite carry `in_composite: false` in `/perpetuals/symbols?sources=true`, the complete list.

## Per-source health

**Endpoint:** `GET /perpetuals/sources/status`

One row per source, for monitoring that runs on the source rows rather than the composite. Needs source-level access. Computed on every request from live state, so poll it as often as your alerting needs; there is no cache to wait out.

### Request

```bash
curl -sS "https://api.tickerlayer.com/perpetuals/sources/status" \
  -H "x-api-key: <YOUR_API_KEY>"
```

```python
import requests

res = requests.get(
    "https://api.tickerlayer.com/perpetuals/sources/status",
    headers={"x-api-key": "<YOUR_API_KEY>"},
)
print(res.json())
```

```javascript
const res = await fetch("https://api.tickerlayer.com/perpetuals/sources/status", {
  headers: { "x-api-key": "<YOUR_API_KEY>" },
});
const data = await res.json();
console.log(data);
```

```go
req, _ := http.NewRequest("GET", "https://api.tickerlayer.com/perpetuals/sources/status", nil)
req.Header.Set("x-api-key", "<YOUR_API_KEY>")
```

### Response

```json
{
  "timestamp": 1789992789776,
  "thresholds": {
    "withdraw_after_lane_loss_ms": 5000,
    "fresh_book_ms": 90000,
    "quiet_book_max_ms": 1800000
  },
  "sources": [
    {
      "source_id": "pv_01",
      "role": "composite",
      "status": "up",
      "symbols_listed": 229,
      "symbols_live": 229,
      "last_update": 1789992789712,
      "oldest_live_update": 1789992731004
    }
  ],
  "fx": {
    "USDT": { "status": "ok", "rate": 0.999605, "fx_sources": ["fx_01", "fx_02"], "fx_timestamp": "2026-09-21T11:33:05.012Z" },
    "USDC": { "status": "ok", "rate": 0.99985, "fx_sources": ["fx_01", "fx_03"], "fx_timestamp": "2026-09-21T11:33:01.230Z" }
  },
  "normalization": {
    "since_process_start": { "since": 1789900000000, "prices": 1520344, "usd": 1520101, "share": 0.99984, "by_source": [] },
    "month_to_date": { "month": "2026-10", "prices": 18403210, "usd": 18399871, "share": 0.999819, "by_source": [] }
  }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `timestamp` | integer | When this answer was computed, epoch milliseconds. |
| `thresholds` | object | The row liveness rule above, in milliseconds. The same for every source. |
| `source_id` | string | Permanent identifier of the source. It never changes and is never reused. |
| `role` | string | composite: its rows can enter the composite. reference: spot books, source rows only. |
| `status` | string | up: at least 90% of the listed symbols are live. degraded: fewer than 90% but at least one. down: none, and every row from this source has been withdrawn. |
| `symbols_listed` | integer | Symbols this source can deliver a row for. |
| `symbols_live` | integer | Of those, how many have a live row right now. |
| `last_update` | integer \| null | When we last received anything from this source, epoch milliseconds on our clock. Null when nothing is live. |
| `oldest_live_update` | integer \| null | Receive time of the quietest row that is still live. Null when nothing is live. |
| `fx` | object | Per stablecoin, the rate [normalized rows](https://tickerlayer.com/docs/products/perpetuals#perpetuals-normalized) use right now (`rate`, `fx_sources`, `fx_timestamp` in ISO 8601), or `unavailable` with the reason. |
| `normalization` | object | Share of source prices that had an agreed USD rate when they happened, per source: since our last restart and month to date (UTC). |

## Symbol and source universe

**Endpoint:** `GET /perpetuals/symbols?sources=true`

The symbol list with every source row each symbol can deliver. Needs source-level access. Poll it, for example once an hour, and diff it to learn about a new symbol, a new source row or a removed one before it reaches your ingest.

### Request

```bash
curl -sS "https://api.tickerlayer.com/perpetuals/symbols?sources=true" \
  -H "x-api-key: <YOUR_API_KEY>"
```

```python
import requests

res = requests.get(
    "https://api.tickerlayer.com/perpetuals/symbols?sources=true",
    headers={"x-api-key": "<YOUR_API_KEY>"},
)
print(res.json())
```

```javascript
const res = await fetch("https://api.tickerlayer.com/perpetuals/symbols?sources=true", {
  headers: { "x-api-key": "<YOUR_API_KEY>" },
});
const data = await res.json();
console.log(data);
```

```go
req, _ := http.NewRequest("GET", "https://api.tickerlayer.com/perpetuals/symbols?sources=true", nil)
req.Header.Set("x-api-key", "<YOUR_API_KEY>")
```

### Response

```json
{
  "symbols": [
    {
      "symbol": "BNBUSDT",
      "name": "BNB",
      "instrument_type": "perpetual",
      "underlying_asset_class": "crypto",
      "base": "BNB",
      "source_count": 6,
      "sources": [
        {
          "source_id": "pv_01",
          "venue_symbol": "BNBUSDT",
          "native_currency": "USDT",
          "market_type": "perpetual",
          "in_composite": true
        },
        {
          "source_id": "pv_07",
          "venue_symbol": "BNBUSDT",
          "native_currency": "USDT",
          "market_type": "spot",
          "in_composite": false
        }
      ]
    }
  ],
  "count": 266
}
```

### Notes

`source_id` values are permanent. `in_composite` is the fixed rule for that row, not a live verdict. A symbol that exists only as a spot row is listed with `instrument_type: spot` and answers with an empty composite and its source rows. Additions are also announced in the docs updates rail.
