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:/. The MCP tool is crypto, with one action for each route below.
Plans and limits
| What | Free | Basic | Pro |
|---|---|---|---|
| Latest price | 15 minutes late | Live | Live |
| 24-hour change | Not included | Included | Included |
| Daily bars | Last 12 months | Last 5 years | Full history |
| One-minute bars | Not included | Included, within the plan's history | Included, within the plan's history |
| Pair list | Included | Included | Included |
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
| Parameter | Where | Required | Type | Meaning |
|---|---|---|---|---|
quote | query | No | one of USD, EUR, GBP | Only pairs quoted in this currency (USD, EUR or GBP). |
Response fields
| Field | Type | Sent | Meaning |
|---|---|---|---|
pairs | list of object | Always | Every 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[].pair | string | Always | Canonical pair, for example BTC-USD. |
pairs[].base | string | Always | The coin's ticker. |
pairs[].quote | string | Always | The quote currency. |
pairs[].name | string | Always | The coin's name. |
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/crypto/pairs?quote=USD"{
"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_ is in percent.
MCP: tool crypto, action price.
Parameters
| Parameter | Where | Required | Type | Meaning |
|---|---|---|---|---|
pair | path | Yes | string | Crypto 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
| Field | Type | Sent | Meaning |
|---|---|---|---|
pair | string | Always | Canonical pair. |
base | string | Always | The coin's ticker. |
quote | string | Always | The quote currency; every price is in it. |
price | string | Always | Last price, up to 8 significant digits. |
change_ | string | Optional | price 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_ | string | Optional | The 24-hour change in percent (1.5 means 1.5%). Left out with change_. |
timestamp | date-time | Always | When the price was valid (UTC, whole seconds). |
stale | boolean | Always | True when the price is more than 5 minutes old or could not be refreshed. |
delayed | always true | Optional | Present, as true, only when the price is 15 minutes late (Free plan). |
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/crypto/BTC-USD/price"{
"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_ 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
| Parameter | Where | Required | Type | Meaning |
|---|---|---|---|---|
pairs | query | Yes | string | Comma-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
| Field | Type | Sent | Meaning |
|---|---|---|---|
prices | list of object | Always | One price per pair that resolved, by pair. |
prices[].pair | string | Always | Canonical pair. |
prices[].base | string | Always | The coin's ticker. |
prices[].quote | string | Always | The quote currency; every price is in it. |
prices[].price | string | Always | Last price, up to 8 significant digits. |
prices[].change_ | string | Optional | price 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_ | string | Optional | The 24-hour change in percent (1.5 means 1.5%). Left out with change_. |
prices[].timestamp | date-time | Always | When the price was valid (UTC, whole seconds). |
prices[].stale | boolean | Always | True when the price is more than 5 minutes old or could not be refreshed. |
prices[].delayed | always true | Optional | Present, as true, only when the price is 15 minutes late (Free plan). |
errors | list of object | Always | Pairs that returned no price. They are not counted against the quota. |
errors[].id | string | Always | The id as it was asked for. |
errors[].code | one of not_found, not_covered, upstream_busy | Always | not_found: no such listing. not_covered: the exchange has no prices yet. upstream_busy: the price could not be fetched now; try again shortly. |
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/crypto/prices?pairs=BTC-USD,ETH-USD"{
"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_; 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
| Parameter | Where | Required | Type | Meaning |
|---|---|---|---|---|
pair | path | Yes | string | Crypto 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. |
interval | query | No | one of 1d, 1m | 1d (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. |
start | query | No | string | First 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. |
end | query | No | string | Last 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). |
cursor | query | No | string | next_ of the previous page, with the same parameters. |
Response fields
| Field | Type | Sent | Meaning |
|---|---|---|---|
pair | string | Always | Canonical pair. |
start | string | Optional | Sent only when the plan raised the start: the first day (daily) or time (1m) served. |
end | string | Optional | Sent 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. |
bars | list of object | Always | At most 5,000 bars a page. |
bars[].timestamp | date-time | Always | Start of the bar's period (UTC): 00:00 for a daily bar, the minute for a one-minute bar. |
bars[].open | string | Always | First price of the period. |
bars[].high | string | Always | Highest price of the period. |
bars[].low | string | Always | Lowest price of the period. |
bars[].close | string | Always | Last price of the period. |
next_ | string | Always | Pass it back to read the next page. Null on the last page. |
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/crypto/BTC-USD/bars?interval=1d&start=2026-10-01"{
"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_. See Errors and quota for the shape and the quota headers. Failed requests are not charged.
| Code | Status | Meaning |
|---|---|---|
invalid_ | 400 | A parameter is missing, unknown, repeated or outside its range. |
invalid_ | 400 | An id or pair is not written the way this API expects. |
too_ | 400 | A batch holds more than 50 ids. |
invalid_ | 400 | The interval is not 1d or 1m, or not available for this instrument. |
range_ | 400 | The window is longer than the route allows. |
plan_ | 403 | The request needs a Basic or Pro plan. |
not_ | 404 | No such listing, fund or pair. |
invalid_ | 400 | The cursor is not one this request returned, or it comes from a different request. |
cursor_ | 409 | The prices behind the cursor changed. Start again without the cursor. |
quota_ | 429 | Your plan has fewer calls left than the request needs. |
upstream_ | 503 | A price could not be fetched now. In a batch it is listed in errors. Try again shortly. |
area_ | 503 | This area cannot answer right now. |
Docs