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
  1. Generic index codes and what each one measures
  2. Six REST routes and two WebSocket channels
  3. Anatomy of an index snapshot
  4. Stream index levels over WebSocket
  5. Historical index data: daily and intraday bars
  6. When index levels move: cash sessions on a UTC clock
  7. Index, ETF, future or perpetual: which symbol to use
  8. From index levels to signals
  9. Production checklist for index data
  10. 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.

curlShell
curl -sS "https://api.tickerlayer.com/indices/quote/US500" \
  -H "x-api-key: $TICKERLAYER_API_KEY"
Response, 28 September 2026, 10:25 UTCJSON
{"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.

CodeNameWhat it tracksWeighting
US500US 500About 500 large US companiesFloat-adjusted market cap
US100US 100The 100 largest non-financial companies on one US exchangeModified market cap
US30US 3030 large US companiesPrice
US2000US 20002,000 smaller US companiesFloat-adjusted market cap
DE40Germany 4040 large German companies, dividends reinvestedFree-float market cap
UK100UK 100100 large UK-listed companiesFree-float market cap
EU50Europe 5050 large eurozone companiesFree-float market cap, capped
JP225Japan 225225 large Japanese companiesPrice, with adjustment factors
HK33Hong Kong 33Large Hong Kong-listed companiesFree-float market cap, capped
KOR200South Korea 200200 large Korean companiesFree-float market cap
USDXUS Dollar IndexThe dollar against six major currenciesFixed currency weights
USVOLUS VolatilityExpected 30-day volatility of the US 500, from option pricesDerived from options
The weighting column describes the benchmark each code follows. TickerLayer publishes levels, not constituent lists or weights.

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.

RequestReturnsUse it for
GET /indices/symbolsEvery code with name and an aggregates flagDiscovery and validation
GET /indices/quote/{symbol}bid, ask, sizes, timestampThe current level
GET /indices/trade/last/{symbol}price, size, timestamp of the latest printA single "last" number
GET /indices/snapshot/{symbol}Quote plus last_price, prev_close, changeTickers and watchlists
GET /indices/agg/{symbol}/prevThe last completed daily barYesterday's close
GET /indices/agg/{symbol}/{n}/{span}/{from}/{to}OHLCV bars, paginatedCharts and backtests
indices.quotes, indices.tradesStreamed quotes and printsLive tickers
Bar intervals: 1, 5 or 15 minutes, 1 or 4 hours, 1 day. Dates are UTC and inclusive; limit defaults to 500 and goes up to 5,000.

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
}
  1. bidAn indicative two-sided level around the index calculation. Most charts plot the midpoint.
  2. bid_sizeIndicative, not depth. An index has no order book, so never read sizes as liquidity.
  3. last_timestampUnix milliseconds, UTC. This one is 10:25 on a Monday, about three hours before the US cash open.
  4. prev_closeA floating-point artifact. Round before you display it: 7,753.80.
  5. change_percentAlready a percentage: -0.6985 means -0.70%, not -69.85%.
Captured on 28 September 2026. Indices, commodities and ETFs share this snapshot shape.

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.

Your appTickerLayer stream
  1. Open wss://stream.tickerlayer.com/?apiKey=…Your app to TickerLayer stream
  2. {"type":"system","event":"ready"}TickerLayer stream to Your app
  3. subscribe to indices.quotes: US500, DE40, JP225Your app to TickerLayer stream
  4. subscribed, symbols echoed in sorted orderTickerLayer stream to Your app
  5. one quote per symbol with "snapshot": truethe last known levelTickerLayer stream to Your app
  6. live quote frames as the level changesTickerLayer stream to Your app
  7. protocol pingyour library answers with a pongTickerLayer stream to Your app
The subscribe handshake. Send nothing before the ready frame.
index-stream.mjsJavaScript
// 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();
Output (trimmed), 28 September 2026
{
  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.75

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

index_history.pyPython
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)}")
Output
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: 0

US500 daily close, one year, sampled weekly

Weekly samples of the daily closes returned by the script above.TickerLayer daily bars for US500, 22 September 2025 to 25 September 2026.

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

Japan 225JP225
Hong Kong 33HK33
Germany 40DE40Cash session
UK 100UK100Cash session
US 500, US 100, US 30US500Regular session

Hours in UTC

Cash sessions of the underlying stock markets in late September, with Europe and the US on summer time. Gaps are midday breaks.

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.

FeatureUS500US500ETFUS500FUTUS500USDT
What the number isIndex levelFund share priceFutures reference levelPerpetual composite price
A tradable instrument
Historical bars
Route family/indices/etfs/indices/perpetuals
Typical useBenchmarks, charts, signalsPortfolio values, spreadsFutures reference displayWeekend and overnight pricing
TickerLayer publishes data for all four and executes trades in none of them.

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/symbols at startup, cache it, and validate every code against it. No market prefix.
  • Check the aggregates flag before calling /agg; futures-style codes have no bars.
  • Convert WebSocket numerics with Number() or float(); REST already sends numbers.
  • Round index levels for display, two decimals in most interfaces.
  • Treat bid_size, ask_size and trade size on indices as indicative, never as depth.
  • Handle v: null on index bars instead of plotting it as zero volume.
  • Pick one definition of the previous close, snapshot or daily bar, and label it.
  • Store ts and timestamp values as UTC milliseconds and convert only for display.
  • Read the X-RateLimit-Limit header, 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.

Keep reading

Ready to integrate?

Start with the free tier, explore the docs, and connect via REST or WebSocket in minutes.