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

# ETFs

Indicative ETF reference instruments under /etfs: discovery, quotes, last trade, snapshots, and aggregates.

- **Auth:** x-api-key header
- **Base path:** /etfs
- **WebSocket:** etfs.quotes · etfs.trades · etfs.agg
- **Symbol format:** Product codes (e.g. US500ETF, US100ETF, USBOND)

`/etfs` and `/v1/etfs` are equivalent mounts.

## Symbols

Examples include `US500ETF`, `US100ETF`, `USBOND`, `USGOLD`, and `WORLDETF`. A few Asian listings carry a market prefix, for example `HK:2800` and `KR:226490`. Use `GET /etfs/symbols` for the full set enabled for your key (see [Symbols → ETFs](https://tickerlayer.com/docs/rest/symbols#etfs-symbols)).

## Symbol list

**Endpoint:** `GET /etfs/symbols`

Curated ETF-style symbols available on the REST API.

### Request

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

```python
import requests

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

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

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

### Response

```json
{
  "symbols": [
    { "symbol": "US100ETF", "name": "US growth 100" },
    { "symbol": "US500ETF", "name": "US 500" }
  ]
}
```

## Latest quote

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

Consolidated bid/ask and timestamp from the REST quote path.

### Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `symbol` | string | Yes | Enabled ETF-style symbol (e.g. US500ETF, US100ETF, HK:2800). Most are product codes; a few Asian listings carry a market prefix. Trimmed and uppercased before lookup. Full list: GET /etfs/symbols. |

### Query parameters

| Parameter | Type | Description |
| --- | --- | --- |
| `spread` | string | `full` answers outside the US regular session with the newest book even when it is wider than 0.5% of the mid ([full spread](https://tickerlayer.com/docs/rest/etfs#etfs-full-spread)). Leave it out for the default quote. Values: `normalized`, `full`. Default: `normalized`. |

### Request

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

```python
import requests

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

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

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

### Response

```json
{
  "symbol": "US500ETF",
  "bid": 718.65997,
  "ask": 718.65997,
  "bid_size": 90,
  "ask_size": 40,
  "timestamp": 1777555800000
}
```

### Notes

`404 unknown symbol` if the code is not enabled. `404 no quote available` when no consolidated quote can be resolved. Outside the US regular session (09:30 to 16:00 ET) a quote is published only while its spread stays within 0.5% of the mid; wider pre-market and after-hours books are held back and the last quote within that limit remains the served value. Add `?spread=full` for the newest book, wide ones included. Build candles from [trades](https://tickerlayer.com/docs/rest/etfs#etfs-last-trade) rather than from quotes.

## Last trade

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

Latest trade-style print for the ETF. size may be null.

### Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `symbol` | string | Yes | Enabled ETF-style symbol (e.g. US500ETF, US100ETF, HK:2800). Most are product codes; a few Asian listings carry a market prefix. Trimmed and uppercased before lookup. Full list: GET /etfs/symbols. |

### Request

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

```python
import requests

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

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

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

### Response

```json
{
  "symbol": "US500ETF",
  "price": 718.66,
  "size": 90,
  "timestamp": 1777555800000
}
```

### Notes

`404 unknown symbol` if not enabled. `404 symbol not available` or `503 market data temporarily unavailable` when no trade-style value can be resolved yet.

## Snapshot

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

Combined bid/ask, last reference level, previous daily close, and change metrics.

### Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `symbol` | string | Yes | Enabled ETF-style symbol (e.g. US500ETF, US100ETF, HK:2800). Most are product codes; a few Asian listings carry a market prefix. Trimmed and uppercased before lookup. Full list: GET /etfs/symbols. |

### Request

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

```python
import requests

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

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

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

### Response

```json
{
  "symbol": "US100ETF",
  "bid": 528.90,
  "ask": 528.90,
  "bid_size": 39,
  "ask_size": 85,
  "last_price": 528.90,
  "last_timestamp": 1743512400000,
  "prev_close": 525.00,
  "change": 3.90,
  "change_percent": 0.7429
}
```

### Notes

`change_percent` is `null` when `prev_close` is zero or unavailable. Responses do not include `last_size`.

## Previous bar

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

Most recent fully completed daily OHLCV bar in UTC (previous session close).

### Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `symbol` | string | Yes | Enabled ETF-style symbol (e.g. US500ETF, US100ETF, HK:2800). Most are product codes; a few Asian listings carry a market prefix. Trimmed and uppercased before lookup. Full list: GET /etfs/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/etfs/agg/US500ETF/prev" \
  -H "x-api-key: <YOUR_API_KEY>"
```

```python
import requests

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

```javascript
const res = await fetch("https://api.tickerlayer.com/etfs/agg/US500ETF/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/etfs/agg/US500ETF/prev", nil)
req.Header.Set("x-api-key", "<YOUR_API_KEY>")
```

### Response

```json
{
  "symbol": "US500ETF",
  "result": {
    "o": 610.0,
    "h": 614.0,
    "l": 608.0,
    "c": 612.0,
    "v": 82000000,
    "t": 1732924800000
  }
}
```

### 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. While the market is closed the last real bar is returned, still labelled with its own `bar_start`.

## Aggregate bars (OHLCV)

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

Historical bars between two UTC calendar dates.

### Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `symbol` | string | Yes | An enabled symbol of this asset class, for example `BTCUSD` or `US:KO`. Trimmed before lookup. |
| `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/etfs/agg/US100ETF/1/day/2025-11-01/2025-11-30?sort=desc&limit=2" \
  -H "x-api-key: <YOUR_API_KEY>"
```

```python
import requests

res = requests.get(
    "https://api.tickerlayer.com/etfs/agg/US100ETF/1/day/2025-11-01/2025-11-30?sort=desc&limit=2",
    headers={"x-api-key": "<YOUR_API_KEY>"},
)
print(res.json())
```

```javascript
const res = await fetch("https://api.tickerlayer.com/etfs/agg/US100ETF/1/day/2025-11-01/2025-11-30?sort=desc&limit=2", {
  headers: { "x-api-key": "<YOUR_API_KEY>" },
});
const data = await res.json();
console.log(data);
```

```go
req, _ := http.NewRequest("GET", "https://api.tickerlayer.com/etfs/agg/US100ETF/1/day/2025-11-01/2025-11-30?sort=desc&limit=2", nil)
req.Header.Set("x-api-key", "<YOUR_API_KEY>")
```

### Response

```json
{
  "symbol": "US100ETF",
  "results_count": 2,
  "results": [
    { "o": 520.0, "h": 525.0, "l": 518.0, "c": 523.0, "v": 1200000, "t": 1733011200000 },
    { "o": 518.0, "h": 522.0, "l": 516.0, "c": 520.0, "v": 980000, "t": 1732924800000 }
  ],
  "next_offset": 2
}
```

### Notes

- `v` may be null when no consolidated volume exists for that interval.
- One request covers at most 31 days of 1-minute bars, 90 of 5-minute, 180 of 15-minute, 365 of hourly, 730 of 4-hour and 5000 of daily bars, counting `from` and `to` as whole UTC days. A longer range returns `400` with the limit in the message.

## WebSocket

Stream live quotes and trades for this asset class over the public WebSocket.

**Connect**

```
wss://stream.tickerlayer.com/?apiKey=<YOUR_API_KEY>
```

### Available channels

- `etfs.quotes`: quotes
- `etfs.trades`: trades
- `etfs.agg`: settled bars ([documented here](https://tickerlayer.com/docs/rest/etfs#etfs-agg-stream))

### Subscribe example

After the connection is ready, send a JSON text frame:

```json
{
  "action": "subscribe",
  "channels": [
    "etfs.quotes",
    "etfs.trades"
  ],
  "symbols": [
    "US500ETF",
    "US100ETF"
  ]
}
```

### Example messages

**etfs.quotes**

```json
{
  "type": "quote",
  "channel": "etfs.quotes",
  "asset": "etfs",
  "symbol": "US500ETF",
  "bid": 718.6,
  "ask": 718.72,
  "bid_size": 40,
  "ask_size": 55,
  "ts": 1743512400000,
  "timestamp": 1743512400000
}
```

**etfs.trades**

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

### General WebSocket documentation

For authentication, subscribe/unsubscribe lifecycle, errors, limits, and heartbeats, see the [WebSocket overview](https://tickerlayer.com/docs/websocket), [Subscriptions](https://tickerlayer.com/docs/websocket/subscriptions) and [Message types](https://tickerlayer.com/docs/websocket/messages).

## Extended hours full spread

Outside the US regular session (09:30 to 16:00 ET) a US ETF quote is published only while its spread stays within 0.5% of the mid, and the last quote within that limit stays the served value. Subscribe to `etfs.quotes` with `"spread": "full"` (or add `?spread=full` to `GET /etfs/quote`) and the wider books arrive too, so a thin after-hours market shows as a wide spread instead of a held quote. Same frame, same fields. It applies to US ETFs on `etfs.quotes`, so send it in a subscribe of its own; Asia ETFs are never held back and stay on the default stream.

| Value | Outside regular hours |
| --- | --- |
| `"normalized"` | Default. Books wider than 0.5% of the mid are held back. |
| `"full"` | The wider books too. A subscribe is answered with a spread message after the ack. |

**Subscribe at full spread**

```json
{
  "action": "subscribe",
  "channels": ["etfs.quotes"],
  "symbols": ["US500ETF", "US100ETF"],
  "spread": "full"
}
```

## OHLCV bars over WebSocket

Settled bars for US-listed ETFs, included with ETF access. Subscribe to `etfs.agg` with ETF symbols and receive one `agg` frame per symbol each time a bar settles, built from the fund's consolidated tape on the US session grid.

**1-minute (default)**

```json
{
  "action": "subscribe",
  "channels": ["etfs.agg"],
  "symbols": ["US500ETF", "USGOLD"]
}
```

**Several intervals**

```json
{
  "action": "subscribe",
  "channels": ["etfs.agg"],
  "symbols": ["US500ETF"],
  "interval": ["1m", "5m", "1d"]
}
```

**Extended hours**

```json
{
  "action": "subscribe",
  "channels": ["etfs.agg"],
  "symbols": ["US500ETF"],
  "interval": ["1m", "5m"],
  "session": "extended"
}
```

**agg frame**

```json
{
  "type": "agg",
  "channel": "etfs.agg",
  "asset": "etfs",
  "symbol": "US500ETF",
  "interval": "1m",
  "session": "regular",
  "o": 718.52,
  "h": 718.8,
  "l": 718.4,
  "c": 718.66,
  "v": 152340,
  "n": 1204,
  "vw": 718.6312,
  "ts": 1789133400000
}
```

Numbers are numbers: `o h l c v` the bar, `n` the trade count, `vw` the volume-weighted average price, `session` where the bar starts (`pre`, `regular`, `post`) and `ts` the bar start in unix ms. A frame arrives a few seconds after its window closes.

| Interval | Arrives | Grid |
| --- | --- | --- |
| `1m` | Right after each traded minute; a minute without a trade has no bar. | Every minute of the session. |
| `5m` `15m` | As each window closes, rolled up from the minute bars. | On the clock from 09:30 ET: 09:30, 09:35 … and 09:30, 09:45 … |
| `1h` `4h` | As each window closes; the last one closes with the session. | Anchored at the open: 09:30, 10:30 … and 09:30, 13:30 ET. |
| `1d` | About two minutes after the closing bell. | Official open and close with consolidated volume, labelled at the session date's UTC midnight. |

- **Sessions:** Intraday bars cover the regular session, 09:30 to 16:00 ET with early closes honored. With `"session": "extended"`, 1m, 5m and 15m bars also arrive from 04:00 to 20:00 ET (17:00 on early closes), tagged `pre`, `regular` or `post`; 1h, 4h and 1d always follow the regular grid.
- **Closed bars only:** One frame per symbol, interval and window, no in-progress updates. Build a live candle from `etfs.trades` if you need one.
- **No snapshot:** The first frame arrives within one interval, and a bar that settles while you are disconnected is not replayed. For history use `/etfs/agg`.
- **Intervals per symbol:** `interval` takes a string or an array. The latest subscribe for a symbol sets its intervals, and an unsubscribe removes it from all of them.
- **US-listed ETFs:** Use ETF symbols as `/etfs/symbols` lists them (`GLD`); a `US:` prefix is accepted and dropped, and frames carry the listed symbol. Asian ETFs answer `UNSUPPORTED_SYMBOL`. Symbols count toward your distinct-symbol cap like any channel.
