Browse all documentation

Indices API reference

Levels and history for 18 indices

Overview

Levels and level history for 18 stock and volatility indices. Every request needs a Bearer key, and REST and MCP share one account quota.

An id is the index's standard code, such as SPX for the S&P 500, NDX for the Nasdaq-100 or DJI for the Dow Jones Industrial Average. Any case works. The list route returns every id with its name, region and currency.

A price carries the previous close, the change and the change in percent once the index's daily history is in.

Base URL: https://api.narwhalapi.com. The MCP tool is indices, with one action for each route below.

Plans and limits

WhatFreeBasicPro
Latest level15 minutes lateLiveLive
Daily barsLast 12 monthsLast 5 yearsFull history
One-minute barsNot includedLast 12 monthsLast 12 months
Index listIncludedIncludedIncluded

See pricing for plan prices and monthly request limits. Each request counts as one call unless a route says otherwise.

  • Free levels are delayed by 15 minutes. A delayed answer carries delayed: true.
  • History starts where we hold it. The S&P 500 starts in February 2023.
  • A volatility index (VIX, OVX, GVZ) is not money, so its answers carry no currency.
  • market_session is open or closed by the index's published hours. A closed index answers with its last level and is not stale. An open index is stale when its level is more than 15 minutes old.
  • A daily bar is one trading day and appears at 00:00 UTC. On Free it appears 15 minutes later, and end comes back when that cut your range. Today's partial bar is never shown.
  • Bars carry no volume. One-minute bars are kept for 12 months, and one request covers at most 5 days. Free gets 403 plan_upgrade_required.
  • A batch holds up to 50 ids and counts one call for each id that returns a level. An unknown id is listed in errors and is not counted.

Endpoints

Every route is a GET and needs your key in the Authorization: Bearer header. The examples show the shape of an answer, not live data.

Indices we price

GET /v1/indices

Every indices series we price, by id, with its name and unit. Read from our own list, so it is open to every plan and counts one call.

MCP: tool indices, action list.

Parameters

This route takes no parameters.

Response fields

FieldTypeSentMeaning
indiceslist of objectAlwaysEvery index we price, by id.
indices[].idstringAlwaysIndex code, for example SPX.
indices[].namestringAlwaysThe index's name.
indices[].regionone of us, uk, europe, global, asiaAlwaysWhere its members trade.
indices[].currencystringOptionalThe currency the index is in. Left out for a volatility index, which is not money.
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/indices"
Example response with illustrative values
{
  "indices": [
    {
      "id": "SPX",
      "name": "S&P 500",
      "region": "us",
      "currency": "USD"
    },
    {
      "id": "VIX",
      "name": "Cboe Volatility Index",
      "region": "us"
    }
  ]
}

Latest price of one indices series

GET /v1/indices/{id}/price

The latest price. market_session is open or closed by the series' hours; a closed series answers with its last price, and stale is true only when an open series has no price from the last 15 minutes. Free sees the price 15 minutes late and the answer carries delayed: true. previous_close is the close of the previous trading day and change_percent is in percent.

MCP: tool indices, action price.

Parameters

ParameterWhereRequiredTypeMeaning
idpathYesstringIndex id, for example SPX, NDX or DJI. Any case.

Response fields

FieldTypeSentMeaning
idstringAlwaysIndex code.
namestringAlwaysName as our symbol list holds it.
currencystringOptionalThe currency the index is in. Left out for a volatility index, which is not money.
pricestringAlwaysLast level of the index.
previous_closestringOptionalClose of the previous trading day. Left out when it is not known.
changestringOptionalprice minus previous_close.
change_percentstringOptionalThe change in percent (1.5 means 1.5%). Left out with change.
market_sessionone of open, closedAlwaysopen while the series trades by its published hours, else closed. A closed series still answers with its last price; a holiday inside the hours reads open and shows as stale.
timestampdate-timeAlwaysWhen the price was valid (UTC, whole seconds).
stalebooleanAlwaysTrue when the series is open and its price is more than 15 minutes old, or could not be refreshed. A closed series is not stale.
delayedalways trueOptionalPresent, as true, only when the price is 15 minutes late (Free plan).
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/indices/SPX/price"
Example response with illustrative values
{
  "id": "SPX",
  "name": "S&P 500",
  "currency": "USD",
  "price": "7696.46",
  "previous_close": "7681.2",
  "change": "15.26",
  "change_percent": "0.2",
  "market_session": "open",
  "timestamp": "2026-10-29T18:38:00Z",
  "stale": false
}

Latest prices for up to 50 indices series

GET /v1/indices/prices

Latest price of each listed id (Free sees them 15 minutes late). One call is counted for each id that returns a price, taken in one step: if fewer calls are left than ids that resolved, the whole request is refused with 429 quota_exceeded. Ids that return no price are listed in errors (as id) and cost nothing; if no id resolves the answer is still 200, with an empty prices. previous_close is the close of the previous trading day and change_percent is in percent.

Send up to 50 ids. A batch is charged in one step: if you have fewer calls left than ids that resolve, the whole request returns 429 quota_exceeded.

MCP: tool indices, action prices.

Parameters

ParameterWhereRequiredTypeMeaning
idsqueryYesstringComma-separated ids, at most 50 after duplicates are removed. Each id follows the same rules as the single-price route. One call is counted for each id that returns a price.

Response fields

FieldTypeSentMeaning
priceslist of objectAlwaysOne price per id that resolved, by id.
prices[].idstringAlwaysIndex code.
prices[].namestringAlwaysName as our symbol list holds it.
prices[].currencystringOptionalThe currency the index is in. Left out for a volatility index, which is not money.
prices[].pricestringAlwaysLast level of the index.
prices[].previous_closestringOptionalClose of the previous trading day. Left out when it is not known.
prices[].changestringOptionalprice minus previous_close.
prices[].change_percentstringOptionalThe change in percent (1.5 means 1.5%). Left out with change.
prices[].market_sessionone of open, closedAlwaysopen while the series trades by its published hours, else closed. A closed series still answers with its last price; a holiday inside the hours reads open and shows as stale.
prices[].timestampdate-timeAlwaysWhen the price was valid (UTC, whole seconds).
prices[].stalebooleanAlwaysTrue when the series is open and its price is more than 15 minutes old, or could not be refreshed. A closed series is not stale.
prices[].delayedalways trueOptionalPresent, as true, only when the price is 15 minutes late (Free plan).
errorslist of objectAlwaysIds that returned no price. They are not counted against the quota.
errors[].idstringAlwaysThe id as it was asked for.
errors[].codealways "not_found"Alwaysnot_found: no such series.
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/indices/prices?ids=SPX,NDX,DJI"
Example response with illustrative values
{
  "prices": [
    {
      "id": "SPX",
      "name": "S&P 500",
      "currency": "USD",
      "price": "7696.46",
      "previous_close": "7681.2",
      "change": "15.26",
      "change_percent": "0.2",
      "market_session": "open",
      "timestamp": "2026-10-29T18:38:00Z",
      "stale": false
    },
    {
      "id": "NDX",
      "name": "Nasdaq-100",
      "currency": "USD",
      "price": "27310.5",
      "previous_close": "27264.9",
      "change": "45.6",
      "change_percent": "0.17",
      "market_session": "open",
      "timestamp": "2026-10-29T18:38:00Z",
      "stale": false
    }
  ],
  "errors": [
    {
      "id": "XX",
      "code": "not_found"
    }
  ]
}

Price bars of one indices series

GET /v1/indices/{id}/bars

Daily (1d, one bar per trading day) or one-minute (1m) bars, oldest first. A day's bar is out at 00:00 UTC after the day (Free: 15 minutes later, and end comes back when that cut the range); today's partial bar is never shown. One-minute bars are for Basic and Pro (Free gets 403 plan_upgrade_required) and kept for 12 months. History: Free 12 months, Basic 5 years, Pro everything; start is sent back when the plan raised it. Up to 5,000 bars a page, with next_cursor. One call a page.

MCP: tool indices, action bars.

Parameters

ParameterWhereRequiredTypeMeaning
idpathYesstringIndex id, for example SPX, NDX or DJI. Any case.
intervalqueryNoone of 1d, 1m1d (one bar per trading day, dated by the day it belongs to) or 1m (one-minute bars, Basic and Pro only; kept for 12 months). Anything else is 400 invalid_interval.
startqueryNostringFirst bar. 1d: a date (YYYY-MM-DD), inclusive; left out means the oldest bar your plan reaches. 1m: a UTC time (YYYY-MM-DDTHH:MM:SSZ, or a date for 00:00 UTC), inclusive; left out means one day before end.
endqueryNostringLast bar. 1d: a date, inclusive; left out means the latest finished day. 1m: a UTC time, exclusive; left out means now, or five days after start. At most 5 days between start and end for 1m (400 range_too_large).
cursorqueryNostringnext_cursor of the previous page, with the same parameters.

Response fields

FieldTypeSentMeaning
idstringAlways
currencystringOptionalThe currency the index is in. Left out for a volatility index, which is not money.
startstringOptionalSent only when the plan raised the start.
endstringOptionalSent only on Free daily bars, when the 15-minute delay cut the range.
barslist of objectAlwaysAt most 5,000 bars a page.
bars[].timestampdate-timeAlwaysStart of the bar's period (UTC): 00:00 of the trading day for a daily bar, the minute for a one-minute bar.
bars[].openstringAlwaysFirst price of the period.
bars[].highstringAlwaysHighest price of the period.
bars[].lowstringAlwaysLowest price of the period.
bars[].closestringAlwaysLast price of the period.
next_cursorstringAlwaysPass it back to read the next page. Null on the last page.
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/indices/SPX/bars?interval=1d&start=2026-10-01"
Example response with illustrative values
{
  "id": "SPX",
  "currency": "USD",
  "bars": [
    {
      "timestamp": "2026-10-28T00:00:00Z",
      "open": "7655.1",
      "high": "7702.4",
      "low": "7640.8",
      "close": "7681.2"
    }
  ],
  "next_cursor": null
}

Errors

Errors use the shared problem shape with a request_id. See Errors and quota for the shape and the quota headers. Failed requests are not charged.

CodeStatusMeaning
invalid_query400A parameter is missing, unknown, repeated or outside its range.
invalid_id400An id or pair is not written the way this API expects.
too_many_ids400A batch holds more than 50 ids.
invalid_interval400The interval is not 1d or 1m, or not available for this instrument.
range_too_large400The window is longer than the route allows.
plan_upgrade_required403The request needs a Basic or Pro plan.
not_found404No such listing, fund or pair.
invalid_cursor400The cursor is not one this request returned, or it comes from a different request.
quota_exceeded429Your plan has fewer calls left than the request needs.
area_unavailable503This area cannot answer right now.