API guide
Indices API: real-time index levels for US500, US100, DE40 and JP225
An index is a formula, not an order book. A useful indices API tells you which formula sits behind each code, when the number moves, and which fields not to over-read.
On this page
- Generic index codes and what each one measures
- Six REST routes and two WebSocket channels
- Anatomy of an index snapshot
- Stream index levels over WebSocket
- Historical index data: daily and intraday bars
- When index levels move: cash sessions on a UTC clock
- Index, ETF, future or perpetual: which symbol to use
- From index levels to signals
- Production checklist for index data
- Questions
Key takeaways
- TickerLayer serves 45 indices under generic codes such as `US500`, `US30`, `DE40` and `JP225`, with the same quote, snapshot and bar routes as stocks, forex and crypto.
- Index symbols take no market prefix: `/indices/quote/US500` works, `HK:HK33` is rejected, and an index requested on a stocks route returns 404.
- Index levels are indicative, derived reference values, and the sizes on an index quote are not order-book depth, because an index has no order book.
- Check the `aggregates` flag in `/indices/symbols` before asking for bars: futures-style codes such as `US500FUT` have no history bars.
- Round what you display and parse tolerantly: REST numbers can carry float artifacts such as 7753.800020000001, and WebSocket frames may send numbers as strings.
An indices API returns the level of stock market indices as JSON: the current value, a history of bars, and a stream of updates. With TickerLayer, GET /indices/quote/US500 returns the latest bid and ask for the US 500 large-cap benchmark, /indices/agg returns bars, and the indices.quotes channel streams each change. One key covers US500, US100, US30, DE40, JP225 and the rest of the 45 indices in the catalog.
This guide goes through the codes and what each one measures, the routes you need, the fields that cause bugs, and working code in curl, Python and JavaScript. If the difference between a price-weighted and a cap-weighted index is new to you, read what a stock index is first: it changes how you should read a move in US30 compared with US500.
curl -sS "https://api.tickerlayer.com/indices/quote/US500" \
-H "x-api-key: $TICKERLAYER_API_KEY"{"symbol":"US500","bid":7697.93,"ask":7701.77,"bid_size":85,"ask_size":99,"timestamp":1790591118038}Generic index codes and what each one measures
Index names are trademarks, so TickerLayer publishes every index under a generic code: a region and, usually, the number of companies in the basket. US500 is US 500, DE40 is Germany 40, JP225 is Japan 225. The same codes appear in GET /indices/symbols, on symbol pages such as US500, and on the WebSocket channel, so one identifier follows the instrument through your whole stack.
| Code | Name | What it tracks | Weighting |
|---|---|---|---|
US500 | US 500 | About 500 large US companies | Float-adjusted market cap |
US100 | US 100 | The 100 largest non-financial companies on one US exchange | Modified market cap |
US30 | US 30 | 30 large US companies | Price |
US2000 | US 2000 | 2,000 smaller US companies | Float-adjusted market cap |
DE40 | Germany 40 | 40 large German companies, dividends reinvested | Free-float market cap |
UK100 | UK 100 | 100 large UK-listed companies | Free-float market cap |
EU50 | Europe 50 | 50 large eurozone companies | Free-float market cap, capped |
JP225 | Japan 225 | 225 large Japanese companies | Price, with adjustment factors |
HK33 | Hong Kong 33 | Large Hong Kong-listed companies | Free-float market cap, capped |
KOR200 | South Korea 200 | 200 large Korean companies | Free-float market cap |
USDX | US Dollar Index | The dollar against six major currencies | Fixed currency weights |
USVOL | US Volatility | Expected 30-day volatility of the US 500, from option prices | Derived from options |
Read the name field rather than guessing from the digits. KOR200 is South Korea 200, a 200-company index, while KR200 is Korea Composite, which covers the whole main board. HK33 and HK50 are both Hong Kong benchmarks, and neither number is today's constituent count. The rest of the list covers Australia (AU200), Canada (CA60), Switzerland (CH20), Spain (ES35), Italy (IT40), India (IN50), Saudi Arabia (SA50), Brazil (BR50), China (CN300), Taiwan (TW100), a metals index (LMEX) and more.
Six REST routes and two WebSocket channels
Stocks, forex, crypto, indices, ETFs and commodities share the same route shapes on TickerLayer, so if you have called /stocks or /forex you already know these. Authenticate with the x-api-key header. Index symbols take no market prefix: US500 works, HK:HK33 is rejected as invalid, and asking a stocks route for an index returns 404.
| Request | Returns | Use it for |
|---|---|---|
GET /indices/symbols | Every code with name and an aggregates flag | Discovery and validation |
GET /indices/quote/{symbol} | bid, ask, sizes, timestamp | The current level |
GET /indices/trade/last/{symbol} | price, size, timestamp of the latest print | A single "last" number |
GET /indices/snapshot/{symbol} | Quote plus last_price, prev_close, change | Tickers and watchlists |
GET /indices/agg/{symbol}/prev | The last completed daily bar | Yesterday's close |
GET /indices/agg/{symbol}/{n}/{span}/{from}/{to} | OHLCV bars, paginated | Charts and backtests |
indices.quotes, indices.trades | Streamed quotes and prints | Live tickers |
Indices is its own feed: $49 a month on the Individual plan and $449 on Business, with two and ten years of history respectively (pricing). The indices REST reference lists every parameter and error.
Anatomy of an index snapshot
GET /indices/snapshot/US500
{
"symbol": "US500",
"bid": 7699.64,1
"ask": 7703.5,
"bid_size": 63,2
"ask_size": 49,
"last_price": 7699.64,
"last_timestamp": 1790591117344,3
"prev_close": 7753.800020000001,4
"change": -54.16002,
"change_percent": -0.69855
}
bidAn indicative two-sided level around the index calculation. Most charts plot the midpoint.bid_sizeIndicative, not depth. An index has no order book, so never read sizes as liquidity.last_timestampUnix milliseconds, UTC. This one is 10:25 on a Monday, about three hours before the US cash open.prev_closeA floating-point artifact. Round before you display it: 7,753.80.change_percentAlready a percentage: -0.6985 means -0.70%, not -69.85%.
The snapshot prev_close and the close of the previous daily bar are two different definitions, and they do not have to match. In our captures that morning the snapshot said 7,753.80 while GET /indices/agg/US500/prev returned a close of 7,743.41, the level the regular session ended on. If a screen shows a daily change, compute it from one of the two, label which one, and never mix them in the same view.
Stream index levels over WebSocket
Polling a quote route for a dozen indices burns requests and still misses the moves between polls. The WebSocket stream sends each change on one connection. Connect to wss://stream.tickerlayer.com/?apiKey=... with the key URL-encoded, wait for the ready frame, then subscribe. WebSocket access comes with paid plans; free accounts can request a trial from the dashboard.
- Open wss://stream.tickerlayer.com/?apiKey=…Your app to TickerLayer stream
- {"type":"system","event":"ready"}TickerLayer stream to Your app
- subscribe to indices.quotes: US500, DE40, JP225Your app to TickerLayer stream
- subscribed, symbols echoed in sorted orderTickerLayer stream to Your app
- one quote per symbol with "snapshot": truethe last known levelTickerLayer stream to Your app
- live quote frames as the level changesTickerLayer stream to Your app
- protocol pingyour library answers with a pongTickerLayer stream to Your app
// npm install ws
import WebSocket from "ws";
const URL = "wss://stream.tickerlayer.com/?apiKey=" +
encodeURIComponent(process.env.TICKERLAYER_API_KEY);
const SUBSCRIBE = { action: "subscribe", channels: ["indices.quotes"], symbols: ["US500", "DE40", "JP225"] };
// Index frames usually carry numbers, but some arrive as strings: convert every field.
const num = (v) => (v === undefined || v === null ? NaN : Number(v));
let backoffMs = 1000;
function connect() {
const ws = new WebSocket(URL, { perMessageDeflate: false });
ws.on("message", (data) => {
const msg = JSON.parse(data.toString());
if (msg.type === "system" && msg.event === "ready") {
backoffMs = 1000;
ws.send(JSON.stringify(SUBSCRIBE));
} else if (msg.type === "quote") {
const mid = (num(msg.bid) + num(msg.ask)) / 2;
const time = new Date(msg.ts).toISOString().slice(11, 19);
console.log(`${time}Z ${msg.symbol.padEnd(6)} ${mid.toFixed(2)}${msg.snapshot ? " (snapshot)" : ""}`);
} else if (msg.type === "error") {
console.error("subscribe error:", msg.code, msg.symbol ?? "", msg.message);
} else {
console.log(msg); // subscribed ack, pong and other system frames
}
});
ws.on("close", (code) => {
const wait = backoffMs + Math.random() * backoffMs / 2;
console.log(`closed with ${code}; reconnecting in ${Math.round(wait)} ms`);
backoffMs = Math.min(backoffMs * 2, 30000);
setTimeout(connect, wait);
});
ws.on("error", (err) => console.error("socket error:", err.message));
}
connect();{
type: 'system',
event: 'subscribed',
channels: [ 'indices.quotes' ],
symbols: [ 'DE40', 'JP225', 'US500' ]
}
10:55:53Z DE40 25420.75 (snapshot)
10:55:56Z JP225 65767.50 (snapshot)
10:55:51Z US500 7704.44 (snapshot)
10:55:56Z JP225 65765.00
10:55:56Z DE40 25420.25
10:55:57Z DE40 25422.75Two details in that output are worth copying into your own code. Snapshot frames keep the time of the original quote, which is why US500 printed 10:55:51 after JP225 printed 10:55:56. And the time comes from ts, the quote time, not from the moment your process received the frame. Store ts; it is the one that lines up with bars and with other symbols.
Historical index data: daily and intraday bars
Bars come from /indices/agg/{symbol}/{multiplier}/{timespan}/{from}/{to}. Each bar has o, h, l, c, v and t, the bar start in Unix milliseconds; daily bars are stamped at UTC midnight of the session date. Results are newest first unless you pass sort=asc, and long ranges come back in pages: pass next_offset back as offset until it is null.
import os
import sys
from datetime import datetime, timezone
import requests
BASE_URL = "https://api.tickerlayer.com"
HEADERS = {"x-api-key": os.environ["TICKERLAYER_API_KEY"]}
def index_bars(symbol, start, end, multiplier=1, timespan="day"):
"""Every bar in the range, oldest first, following next_offset to the end."""
url = f"{BASE_URL}/indices/agg/{symbol}/{multiplier}/{timespan}/{start}/{end}"
params = {"sort": "asc", "limit": 5000, "offset": 0}
bars = []
while True:
resp = requests.get(url, headers=HEADERS, params=params, timeout=15)
if resp.status_code != 200:
sys.exit(f"{symbol}: HTTP {resp.status_code} {resp.text[:160]}")
body = resp.json()
bars.extend(body["results"])
if body.get("next_offset") is None:
return bars
params["offset"] = body["next_offset"]
def day(bar):
return datetime.fromtimestamp(bar["t"] / 1000, tz=timezone.utc).date()
bars = index_bars("US500", "2025-09-22", "2026-09-25")
if not bars:
sys.exit("No bars: check the code's aggregates flag in /indices/symbols.")
first, last = bars[0], bars[-1]
low = min(bars, key=lambda b: b["c"])
high = max(bars, key=lambda b: b["c"])
print(f"{len(bars)} daily bars, {day(first)} to {day(last)}")
print(f"close {first['c']:,.2f} -> {last['c']:,.2f} ({last['c'] / first['c'] - 1:+.2%})")
print(f"lowest close {low['c']:,.2f} on {day(low)}")
print(f"highest close {high['c']:,.2f} on {day(high)}")
print(f"bars with no volume: {sum(1 for b in bars if b.get('v') is None)}")255 daily bars, 2025-09-22 to 2026-09-25
close 6,693.75 -> 7,743.41 (+15.68%)
lowest close 6,343.72 on 2026-03-30
highest close 7,798.99 on 2026-08-13
bars with no volume: 0US500 daily close, one year, sampled weekly
Two things to handle before you chart anything. Codes marked "aggregates": false in /indices/symbols, such as the futures-style US500FUT and US100FUT, have no bars, so check the flag first. And v can be null on index bars: in our pull the first five-minute bar of 25 September came back without volume. Treat volume on an index as informational at best.
When index levels move: cash sessions on a UTC clock
An index is calculated from its members' prices, so the cash level moves while its home market trades. The index-style quotes on the API can keep moving outside those hours, as the 10:25 UTC snapshot above shows. That is handy for a pre-market view, and it means a moving quote is not proof that a market is open.
Where index levels come from over a UTC day
Hours in UTC
For the authoritative answer to "is the US market open right now", call GET /markets/status or check the US market hours page, which also carries the holiday calendar. For US500, the intraday bars we pulled for 25 September cover exactly the regular session: 78 five-minute bars from 13:30 to 19:55 UTC.
Index, ETF, future or perpetual: which symbol to use
The same US 500 exposure shows up under four codes on TickerLayer, and they answer different questions.
| Feature | US500 | US500ETF | US500FUT | US500USDT |
|---|---|---|---|---|
| What the number is | Index level | Fund share price | Futures reference level | Perpetual composite price |
| A tradable instrument | ||||
| Historical bars | ||||
| Route family | /indices | /etfs | /indices | /perpetuals |
| Typical use | Benchmarks, charts, signals | Portfolio values, spreads | Futures reference display | Weekend and overnight pricing |
The ETF answers a different question from the index: what one share of a fund costs, spread included. ETF vs index fund walks through the difference with intraday data, and the ETF API guide covers the /etfs routes, symbols and the NAV data you still need from elsewhere.
From index levels to signals
Most index work ends in a signal of some kind: a moving-average trend filter, a momentum reading, a correlation with rates or the dollar. The golden cross backtest runs a 50/200-day crossover on ten years of US500 bars from this API, with trading costs, and shows where the textbook signal helped and where it cost money.
Production checklist for index data
- Load
/indices/symbolsat startup, cache it, and validate every code against it. No market prefix. - Check the
aggregatesflag before calling/agg; futures-style codes have no bars. - Convert WebSocket numerics with
Number()orfloat(); REST already sends numbers. - Round index levels for display, two decimals in most interfaces.
- Treat
bid_size,ask_sizeand tradesizeon indices as indicative, never as depth. - Handle
v: nullon index bars instead of plotting it as zero volume. - Pick one definition of the previous close, snapshot or daily bar, and label it.
- Store
tsandtimestampvalues as UTC milliseconds and convert only for display. - Read the
X-RateLimit-Limitheader, and stream instead of polling once you follow more than a handful of codes.
Questions
What is an indices API?
An indices API returns stock index levels as structured data: the current level, historical bars and, over WebSocket, live updates. TickerLayer serves 45 indices under generic codes such as US500, DE40 and JP225 through its /indices routes.
What is US30?
US30 is the generic code for the US 30 benchmark: 30 large US companies combined in a price-weighted index, so the highest-priced shares carry the most weight. Its level is at /indices/quote/US30.
What is the difference between US500 and US100?
US500 follows about 500 large US companies weighted by float-adjusted market value. US100 follows the 100 largest non-financial companies on one US exchange, so it leans much more heavily toward technology.
Is an index CFD the same as the index?
No. A CFD is a contract with a broker that pays the change in an index level, and its price is quoted around the index, often around the clock. TickerLayer publishes indicative index levels; it does not offer CFDs or execute trades.
Can I get historical index data from the API?
Yes. /indices/agg returns bars from one minute to one day for any code whose aggregates flag is true. History depth follows the plan: two years on Individual, ten years on Business.
Does the API return index constituents or weights?
No. It returns levels, quotes and bars. Constituent lists and official weights are published by the index providers themselves.