Browse all documentation

Crypto API reference

Prices and history for crypto pairs

Overview

Prices and price history for crypto pairs. Every request needs a Bearer key, and REST and MCP share one account quota.

A pair id is the coin, a dash and the quote currency, such as BTC-USD or ETH-EUR. Any case works, and a slash in place of the dash is accepted. Every answer carries the canonical pair. Quote currencies are USD, EUR and GBP. Prices are decimal strings with up to 8 significant digits.

Crypto trades all day, so there is no market session. A price is stale when it is more than 5 minutes old.

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

Plans and limits

WhatFreeBasicPro
Latest price15 minutes lateLiveLive
24-hour changeNot includedIncludedIncluded
Daily barsLast 12 monthsLast 5 yearsFull history
One-minute barsNot includedIncluded, within the plan's historyIncluded, within the plan's history
Pair listIncludedIncludedIncluded

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

  • Free prices are delayed by 15 minutes. A delayed answer carries delayed: true.
  • Crypto has no volume. Prices and bars carry no volume field.
  • A daily bar is one UTC 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.
  • The pair list holds the pairs we price. A pair is listed while it trades at least once every 30 seconds and dropped when it falls below once every 2 minutes.
  • Basic reaches back 5 years and Pro has full history. When your plan cuts older rows, start comes back with the first date served.

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.

Crypto pairs we price

GET /v1/crypto/pairs

Every pair we price, by pair (BTC-USD style). Optional quote (USD, EUR or GBP). A pair is listed while it trades at least once every 30 seconds and dropped when it falls below once every 2 minutes. Read from our own pair list, so it is open to every plan and counts one call.

MCP: tool crypto, action pairs.

Parameters

ParameterWhereRequiredTypeMeaning
quotequeryNoone of USD, EUR, GBPOnly pairs quoted in this currency (USD, EUR or GBP).

Response fields

FieldTypeSentMeaning
pairslist of objectAlwaysEvery pair we price, by pair. A pair is listed while it trades at least once every 30 seconds and dropped when it falls below once every 2 minutes.
pairs[].pairstringAlwaysCanonical pair, for example BTC-USD.
pairs[].basestringAlwaysThe coin's ticker.
pairs[].quotestringAlwaysThe quote currency.
pairs[].namestringAlwaysThe coin's name.
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/crypto/pairs?quote=USD"
Example response with illustrative values
{
  "pairs": [
    {
      "pair": "BTC-USD",
      "base": "BTC",
      "quote": "USD",
      "name": "Bitcoin"
    },
    {
      "pair": "ETH-EUR",
      "base": "ETH",
      "quote": "EUR",
      "name": "Ethereum"
    }
  ]
}

Latest price of one crypto pair

GET /v1/crypto/{pair}/price

The latest composite price of a pair, with the 24-hour change (left out on Free and when it is not known). Crypto trades all day, so there is no market session; stale is true when the price is more than 5 minutes old. Free sees the price 15 minutes late and the answer carries delayed: true. change_24h_percent is in percent.

MCP: tool crypto, action price.

Parameters

ParameterWhereRequiredTypeMeaning
pairpathYesstringCrypto pair: the coin, a dash and the quote currency, for example BTC-USD or ETH-EUR. Any case; a slash in place of the dash is accepted. The answer carries the canonical pair.

Response fields

FieldTypeSentMeaning
pairstringAlwaysCanonical pair.
basestringAlwaysThe coin's ticker.
quotestringAlwaysThe quote currency; every price is in it.
pricestringAlwaysLast price, up to 8 significant digits.
change_24hstringOptionalprice minus the price 24 hours earlier, worked out from the 24-hour change in percent. Left out on Free (its price is 15 minutes late) and when it is not known.
change_24h_percentstringOptionalThe 24-hour change in percent (1.5 means 1.5%). Left out with change_24h.
timestampdate-timeAlwaysWhen the price was valid (UTC, whole seconds).
stalebooleanAlwaysTrue when the price is more than 5 minutes old or could not be refreshed.
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/crypto/BTC-USD/price"
Example response with illustrative values
{
  "pair": "BTC-USD",
  "base": "BTC",
  "quote": "USD",
  "price": "68250.5",
  "change_24h": "812.4",
  "change_24h_percent": "1.2",
  "timestamp": "2026-10-26T15:42:00Z",
  "stale": false
}

Latest prices for up to 50 crypto pairs

GET /v1/crypto/prices

Latest price of each listed pair (Free sees them 15 minutes late). One call is counted for each pair that returns a price, taken in one step: if fewer calls are left than pairs that resolved, the whole request is refused with 429 quota_exceeded. Pairs that return no price are listed in errors (as id) and cost nothing. change_24h_percent is in percent.

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

MCP: tool crypto, action prices.

Parameters

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

Response fields

FieldTypeSentMeaning
priceslist of objectAlwaysOne price per pair that resolved, by pair.
prices[].pairstringAlwaysCanonical pair.
prices[].basestringAlwaysThe coin's ticker.
prices[].quotestringAlwaysThe quote currency; every price is in it.
prices[].pricestringAlwaysLast price, up to 8 significant digits.
prices[].change_24hstringOptionalprice minus the price 24 hours earlier, worked out from the 24-hour change in percent. Left out on Free (its price is 15 minutes late) and when it is not known.
prices[].change_24h_percentstringOptionalThe 24-hour change in percent (1.5 means 1.5%). Left out with change_24h.
prices[].timestampdate-timeAlwaysWhen the price was valid (UTC, whole seconds).
prices[].stalebooleanAlwaysTrue when the price is more than 5 minutes old or could not be refreshed.
prices[].delayedalways trueOptionalPresent, as true, only when the price is 15 minutes late (Free plan).
errorslist of objectAlwaysPairs that returned no price. They are not counted against the quota.
errors[].idstringAlwaysThe id as it was asked for.
errors[].codeone of not_found, not_covered, upstream_busyAlwaysnot_found: no such listing. not_covered: the exchange has no prices yet. upstream_busy: the price could not be fetched now; try again shortly.
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/crypto/prices?pairs=BTC-USD,ETH-USD"
Example response with illustrative values
{
  "prices": [
    {
      "pair": "BTC-USD",
      "base": "BTC",
      "quote": "USD",
      "price": "68250.5",
      "change_24h": "812.4",
      "change_24h_percent": "1.2",
      "timestamp": "2026-10-26T15:42:00Z",
      "stale": false
    }
  ],
  "errors": [
    {
      "id": "XYZ-USD",
      "code": "not_found"
    }
  ]
}

Price bars of one crypto pair

GET /v1/crypto/{pair}/bars

Daily (1d, one UTC day, closing at 00:00 UTC) or one-minute (1m) bars, oldest first. A day's bar is out at 00:00 UTC (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). 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; a cursor that no longer matches the prices is 409 cursor_expired. One call a page.

If the prices behind a cursor change, the cursor stops working and returns 409 cursor_expired.

MCP: tool crypto, action bars.

Parameters

ParameterWhereRequiredTypeMeaning
pairpathYesstringCrypto pair: the coin, a dash and the quote currency, for example BTC-USD or ETH-EUR. Any case; a slash in place of the dash is accepted. The answer carries the canonical pair.
intervalqueryNoone of 1d, 1m1d (one bar per UTC day, closing at 00:00 UTC) or 1m (one-minute bars, Basic and Pro only). 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
pairstringAlwaysCanonical pair.
startstringOptionalSent only when the plan raised the start: the first day (daily) or time (1m) served.
endstringOptionalSent only on Free daily bars, when the 15-minute delay cut the range: the last day served. A day's bar reaches Free 15 minutes after 00:00 UTC.
barslist of objectAlwaysAt most 5,000 bars a page.
bars[].timestampdate-timeAlwaysStart of the bar's period (UTC): 00:00 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/crypto/BTC-USD/bars?interval=1d&start=2026-10-01"
Example response with illustrative values
{
  "pair": "BTC-USD",
  "bars": [
    {
      "timestamp": "2026-10-25T00:00:00Z",
      "open": "67210.3",
      "high": "68540.9",
      "low": "66980.1",
      "close": "68100.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.
cursor_expired409The prices behind the cursor changed. Start again without the cursor.
quota_exceeded429Your plan has fewer calls left than the request needs.
upstream_busy503A price could not be fetched now. In a batch it is listed in errors. Try again shortly.
area_unavailable503This area cannot answer right now.