Docs /

API reference

The complete REST and WebSocket contracts: authentication, limits, response fields, endpoints, and errors. If you are making your first request, the quickstart is the faster place to begin.

On this page

Introduction

exchangerate.dev is a REST API for indicative foreign exchange rates. It provides live intraday rates on trading days for actively traded currencies, with source, market_session, and freshness fields on every response. Try the free exchange rate API anonymously, or start with a free key: no credit card required.

Indicative rates: not for settlement
Rates are indicative only. Do not use them for settlement, regulated trading, or as a “source of record.” The market_session and source fields show the market state and data source for each response.

Quickstart

Make your first data call without signup. The standalone 60-second quickstart has the request, exact response shape, and optional API-key step.

Authentication

You can evaluate data routes without a key. Pass your key in the Authorization: Bearer header for account-tier quotas and attribution. Anonymous access is limited to 12 requests/minute and 100 requests/hour per IP. Never embed an account key in public browser JavaScript; use anonymous access or a server-side proxy. Account quota resets at 00:00 UTC on the 1st.

WebSocket clients authenticate in their first message. Do not put an API key in the WebSocket URL. Anonymous streaming is unavailable.

NameTypeDescription
AuthorizationheaderBearer <your-key>. Optional for anonymous evaluation; required for account-tier quotas.
X-RateLimit-RemainingresponseCalls remaining in the current window.
X-RateLimit-ResetresponseUnix timestamp when the window resets.

Rate limits

Anonymous limits are per IP; account limits are per key and calendar month. On 429 the response includes Retry-After.

Free10,000/month12 req/minFree
Basic100,000/month120 req/min$10/mo
Pro1,000,000/month500 req/min$30/mo
Realtime1,000,000/month500 req/min$80/mo

For new accounts, stream caps are separate from REST requests per minute: Free: 0 concurrent connections, 0 pairs per connection; Basic: 0 concurrent connections, 0 pairs per connection; Pro: 0 concurrent connections, 0 pairs per connection; Realtime: 10 concurrent connections, 100 pairs per connection. Existing accounts keep their earlier limits; the authenticated WebSocket frame reports effective caps. An accepted stream connection uses one monthly quota unit.

Response metadata

Keep this metadata with the rate. It is the API's freshness and derivation context.

FieldMeaning and applicability
source and sourcessource is the least-fresh contributing rate; sources maps returned currencies. Enum values: live (intraday indicative), ecb_daily and fred_daily (daily reference), banxico_daily only on /v1/rate/usd-mxn-banxico, and dof_daily only on /v1/rate/usd-mxn-dof. The two Mexican-peso reference values never apply to latest, historical, convert, or range routes.
market_sessionopen, weekend, or interbank_closed; it describes the market state for the response.
data_updated_at, effective_at, and timestampdata_updated_at is the oldest contributing observation. effective_at maps each currency to its own observation time on latest and batch-convert responses. timestamp is when this response was served. Times are UTC ISO-8601 strings.
derived_symbols, derived, and derivation_bps_maxderived_symbols lists triangulated rates in multi-currency responses. Single-pair responses use derived; derivation_bps_max can be null, including for a derived pair without a fixed ceiling, so use derived to detect triangulation.
noticeA short reminder that rates are indicative, not for settlement. Daily-reference responses include the reference-data notice.

Operation registry

Every public integration operation is listed once by method and OpenAPI path. See the OpenAPI 3.1 schema or ReDoc for complete schemas.

Path and query base values must match when both are sent; conflicts return 400.

json · GET /v1/latest/USD?symbols=EUR,GBP
{
  "result": "success",
  "base": "USD",
  "source": "live",
  "sources": {
    "EUR": "live",
    "GBP": "live"
  },
  "market_session": "open",
  "timestamp": "2026-06-15T12:00:00Z",
  "data_updated_at": "2026-06-15T12:00:00Z",
  "effective_at": {
    "EUR": "2026-06-15T12:00:00Z",
    "GBP": "2026-06-15T12:00:00Z"
  },
  "rates": {
    "EUR": 0.86207,
    "GBP": 0.74627
  },
  "derived_symbols": [],
  "notice": "Indicative rates, not for settlement."
}
OperationPurposeParametersRequestResponseEndpoint errors
POST /v1/account/checkout-sessionOpen Stripe checkout or subscription management for the signed-in account.session auth; JSON body: plan (basic/pro), interval (monthly/yearly)POST /v1/account/checkout-sessionCheckoutResponseauthentication, checkout in progress, or billing unavailable
GET /v1/latestLatest rates with USD as the default base.base, path_base, symbols (query; optional)/v1/latest?base=USD&symbols=EUR,GBPLatestResponsedata and limit errors
GET /v1/latest/{path_base}Latest rates with a path base.path_base (path); base, symbols (query); path/query base must match/v1/latest/USD?symbols=EUR,GBPLatestResponseconflicting base, data, and limit errors
GET /v1/{date}Historical snapshot for a date.date (path); base, path_base, symbols (query)/v1/2024-01-15?base=EUR&symbols=USD,GBPHistoricalResponsebad date or unavailable data
GET /v1/{date}/{path_base}Historical snapshot with a path base.date, path_base (path); base, symbols (query); path/query base must match/v1/2024-01-15/EUR?symbols=USD,GBPHistoricalResponseconflicting base, bad date, or unavailable data
GET /v1/convert/{from_ccy}/{to_ccy}Convert one unit or a query-supplied amount.from_ccy, to_ccy (path); amount (query, default 1)/v1/convert/USD/EUR?amount=100ConvertResponseinvalid pair, amount, or unavailable data
GET /v1/convert/{from_ccy}/{to_ccy}/{amount}Convert a path-supplied amount.from_ccy, to_ccy, amount (path)/v1/convert/USD/EUR/100ConvertResponseinvalid pair, amount, or unavailable data
POST /v1/convertConvert several amounts in one request.JSON body: from and pairs (required)POST /v1/convertConvertBatchResponseinvalid request, amount, or unavailable data
GET /v1/rangeDaily time series with JSON, CSV, or Parquet output.symbols, start_date, end_date required by API validation; base, cursor, format optional/v1/range?base=USD&symbols=EUR,GBP&start_date=2024-01-01&end_date=2024-06-30RangeResponsemissing parameter, bad date, or unavailable data
GET /v1/timeseriesMarket observations from 1m through 4h, or existing daily reference history at 1d.symbols, interval, from, to required; base defaults to USD; cursor and format optional/v1/timeseries?base=USD&symbols=EUR,JPY&interval=5m&from=2026-09-14T12:00:00Z&to=2026-09-14T13:00:00ZTimeseriesResponseunsupported interval, missing parameter, bad date, or unavailable data
GET /v1/indices/{index_slug}Latest complete value for the six-currency reconstruction or 13-currency USD strength index.index_slug (path): dxy-reconstructed or usd-strength/v1/indices/usd-strengthIndexSnapshotResponseunknown index or incomplete/misaligned basket
GET /v1/indices/{index_slug}/timeseriesComplete aligned index observations at a public interval.index_slug (path); interval, from, to required; cursor optional/v1/indices/usd-strength/timeseries?interval=1d&from=2026-01-02T00:00:00Z&to=2026-09-15T23:59:59ZIndexTimeseriesResponseunknown index, unsupported interval, bad date, or unavailable data
GET /v1/currenciesList supported currencies and their display facts.none/v1/currenciesCurrenciesResponsestandard endpoint errors
GET /v1/rate/{slug}Return one flat pair quote by slug.slug (path)/v1/rate/eur-usdRateResponseinvalid pair or unavailable data
POST /v1/account/provisionCreate an account organization, with optional first-key creation.session auth; optional JSON body: defer_key, signup_attribution, marketing_consentPOST /v1/account/provisionApiKeyCreatedResponseauthentication or account errors
GET /v1/accountRead account, active key, and monthly usage.session authentication/v1/accountAccountResponseauthentication or account errors
GET /v1/account/keysList the signed-in account's current and retiring API keys.session authentication/v1/account/keyskey summaries and five-key limitauthentication or account errors
POST /v1/account/keysCreate, rename, rotate, or revoke an API key in the signed-in account.session auth; JSON body: action, optional id, request_id, namePOST /v1/account/keysmanaged keyauthentication, validation, or key-limit errors
GET /v1/account/usage/dailyRead daily usage for up to 90 days.session auth; days (query, optional)/v1/account/usage/daily?days=30DailyUsageResponseauthentication or account errors
DELETE /v1/accountCancel billing and delete account data.session authenticationDELETE /v1/accountDeleteAccountResponseauthentication, account, or billing errors
POST /v1/keys/rotateRotate the active API key with a grace period.session authenticationPOST /v1/keys/rotateApiKeyCreatedResponseauthentication or account errors
DELETE /v1/keysRevoke the active API key.session authenticationDELETE /v1/keyssuccess envelopeauthentication or account errors
POST /v1/consentRecord acceptance of a policy version.session auth; JSON body: policy_version (required)POST /v1/consentConsentResponseauthentication or validation errors

GET /v1/latest

Latest rates for all supported pairs, or a filtered subset via ?symbols=.

curl
GET /v1/latest[/{BASE}]

# Examples
curl /v1/latest/USD                         # base=USD (default)
curl /v1/latest/EUR?symbols=USD,GBP,JPY     # EUR base, 3 symbols
NameTypeDescription
basestringBase currency (default USD). Also accepts path: /v1/latest/EUR.
symbolsstringComma-separated list to filter rates (e.g. EUR,GBP). Omit for all.

GET /v1/{YYYY-MM-DD}

Returns the reference rate as published on that date. Published coverage starts on 1999-01-04. The historical data guide covers weekends, ranges, pagination, and downloads.

curl
GET /v1/2024-01-15/EUR?symbols=USD,GBP

# On a weekend or holiday: returns the most recent published fix
# is_forward_filled: true will be set in the response

GET /v1/convert

Single or batch currency conversion. The POST form accepts multiple pairs in one call.

curl
# Single
GET /v1/convert/USD/EUR/100

# Batch POST (counts as N calls)
POST /v1/convert
{
  "from": "USD",
  "pairs": [["EUR", 100], ["GBP", 50], ["JPY", 1000]]
}

GET /v1/rate/{slug}

Returns a flat, single-pair quote by slug (e.g. eur-usd). This differs from /v1/convert, which nests per-pair results under conversions[] and adds amount/converted. Slug is case-insensitive; malformed or unknown pairs return invalid_pair.

curl
GET /v1/rate/{slug}

curl https://api.exchangerate.dev/v1/rate/eur-usd \
  -H "Authorization: Bearer YOUR_KEY"

# Response
{
  "result": "success",
  "pair": "eur-usd",
  "base": "EUR",
  "quote": "USD",
  "rate": 1.0824,
  "source": "live",
  "sources": { "USD": "live" },
  "market_session": "open",
  "timestamp": "2026-06-16T14:02:11Z",
  "data_updated_at": "2026-06-16T14:01:41Z",
  "derived": false,
  "derivation_bps_max": null,
  "notice": "Indicative rates, not for settlement."
}

GET /v1/range

Daily time-series for a date window. JSON responses are capped at 366 rows/page with keyset pagination; pass format=csv or format=parquet to download the full window as a single file.

curl
GET /v1/range?base=USD&symbols=EUR,GBP&start_date=2024-01-01&end_date=2024-06-30

# Response includes next_cursor + has_more for pagination

GET /v1/timeseries

Start with a working anonymous example on the intraday exchange rate API page.

Aligned rate observations for 1m, 5m, 15m, 1h, 4h, or 1d. Intraday rows are market observations. The daily interval continues to use the existing official reference history; it is never replaced with a market close.

curl · five-minute observations
curl "https://api.exchangerate.dev/v1/timeseries?base=EUR&symbols=JPY&interval=5m&from=2026-09-14T12:00:00Z&to=2026-09-14T13:00:00Z" \
  -H "Authorization: Bearer YOUR_KEY"

# JSON uses next_cursor + has_more. Add format=csv or format=parquet for one-file export.
NameTypeDescription
basestringThree-letter base currency; defaults to USD.
symbolsstringRequired comma-separated quote currencies.
intervalenumRequired: 1m, 5m, 15m, 1h, 4h, or 1d.
fromtimestampRequired inclusive UTC ISO-8601 start.
totimestampRequired inclusive UTC ISO-8601 end.
cursortimestampJSON keyset cursor returned by the prior page.
formatenumjson (default), csv, or parquet.

JSON pages contain up to 1,000 timestamps. Maximum windows are 7 days for 1m, 31 days for 5m, 93 days for 15m, 366 days for 1h, and 1,830 days for 4h or 1d. Missing market observations stay missing. For intraday derived crosses, both required legs must be in the same interval bucket and their closing observations must be no more than five seconds apart. The derived value keeps the oldest contributing observation timestamp; it is a derived rate observation, not an exact synchronized OHLC close. The row timestamp is its bucket start;effective_at maps each symbol to its actual observation time. Candle backfills have null observation times and serve direct rates only. CSV and Parquet use SYMBOL_effective_at columns. A null observation timestamp and a missing row indicate unavailable data; clients should not forward-fill them.

Market collection began on 2026-09-14. The example above uses a populated trading-hour window from that day. Retention is not the amount of history already collected: 1m rows are retained for 30 days; 5m and coarser rows have no scheduled expiry. Availability still varies by pair and window, and the current open bucket can change.

Pair and interval coverage

Market history and WebSocket cover 14 currencies: AUD, CAD, CHF, EUR, GBP, JPY, MXN, NOK, NZD, PLN, SEK, SGD, USD, ZAR. Both directions are supported. EUR/USD and USD/EUR count as one unique pair in this table; self-pairs are excluded. Latest-rate freshness on the currency table does not establish intraday or stream eligibility.

IntervalUnique pairsData
1m91Market observations
5m91Market observations
15m91Market observations
1h91Market observations
4h79Market observations
1d465Daily reference across 31 currencies
WebSocket91Live market observations

At 4h, the 12 non-USD SGD crosses are excluded; USD/SGD and SGD/USD remain supported. HKD, THB, TRY and other currencies outside the list above have daily reference coverage but are not in the market-history or WebSocket scope. There is no 2h interval. An eligible pair may still have missing or stale observations.

An unknown interval returns HTTP 400 unsupported_interval. A known interval with an unsupported pair returns HTTP 400 unsupported_pair_interval with rejected_pairs; mixed requests fail in full. A supported request with no observations returns HTTP 200 success, data: [], has_more: false and next_cursor: null. An empty result is not a service error.

json · unsupported pair at 4h
{
  "result": "error",
  "code": "unsupported_pair_interval",
  "message": "requested pair is outside approved time-series coverage",
  "rejected_pairs": ["EUR/SGD"]
}

GET /v1/indices

Two versioned USD indices share the same source and alignment rules as the rate API.dxy-reconstructed applies the published six-currency basket formula to exchangerate.dev observations. It is an independent reconstruction, not official or licensed ICE data. usd-strength is an equal-weight geometric basket of 13 currencies with 2026-01-02 = 100.

curl · latest and history
curl "https://api.exchangerate.dev/v1/indices/usd-strength" \
  -H "Authorization: Bearer YOUR_KEY"

curl "https://api.exchangerate.dev/v1/indices/dxy-reconstructed/timeseries?interval=1d&from=2026-01-02T00:00:00Z&to=2026-09-15T23:59:59Z" \
  -H "Authorization: Bearer YOUR_KEY"

Latest responses include the value, methodology version, weights, constituent rates, effective times, and log contributions. History accepts every public interval and the same maximum windows as /v1/timeseries. Every constituent must be present. Intraday timestamps must be within 30 seconds; otherwise the point stays missing and the weights are not redistributed.

Start with the basket comparison and worked API examples; use this reference for the index response fields and limits.

WebSocket /v1/stream

Connect to wss://api.exchangerate.dev/v1/stream, authenticate, then subscribe by base currency and symbol list. The same 14 currencies above form 91 unique pairs (182 directions). Authenticate within 10 seconds of opening the connection. The service sends an initial snapshot, rate updates, heartbeat frames, and explicit stale or unavailable states. Each requested pair emits at most one update per second.

Run this example on a server with Node.js 22 or later and set EXCHANGERATE_API_KEY in its environment. Keep the key off public browser pages. Derived stream pairs require both legs to be fresh and no more than five seconds apart; otherwise the pair is unavailable or stale.

javascript · Node.js WebSocket
const ws = new WebSocket("wss://api.exchangerate.dev/v1/stream");

ws.addEventListener("open", () => {
  ws.send(JSON.stringify({ action: "auth", api_key: process.env.EXCHANGERATE_API_KEY }));
});

ws.addEventListener("message", ({ data }) => {
  const message = JSON.parse(data);
  if (message.type === "authenticated") {
    ws.send(JSON.stringify({ action: "subscribe", base: "EUR", symbols: ["USD", "JPY"] }));
    ws.send(JSON.stringify({ action: "subscribe_index", index: "usd-strength" }));
  }
  console.log(message);
});
json · update frame
{
  "type": "update",
  "base": "EUR",
  "symbol": "JPY",
  "rate": "180.12",
  "effective_at": "2026-09-14T09:00:10Z",
  "source": "live",
  "source_type": "market",
  "market_session": "open",
  "derived": true,
  "stale": false
}

Send {"action":"unsubscribe","base":"EUR","symbols":["JPY"]} to remove pairs, or {"action":"unsubscribe_index","index":"usd-strength"} to remove an index. Index subscriptions do not consume the pair allowance. Reconnect with exponential backoff after a network failure. Error codes distinguish invalid authentication, exhausted monthly quota, connection and subscription caps, malformed messages, and temporary stream unavailability.

Build a reconnecting server-side WebSocket client with the integration guide, then use this reference for every frame and error.

GET /v1/currencies

Full list of supported currencies with name, type, decimal precision, minor units, and derived flag. This endpoint takes no parameters.

curl
GET /v1/currencies

GET /v1/account

Your org, active key, and current-month usage.

response
{
  "result": "success",
  "email": "you@example.com",
  "org": { "id": "...", "tier": "pro", "status": "active" },
  "key": { "prefix": "exr_live_ab12", "plaintext": null, "created_at": "2026-06-01T00:00:00Z", "last_used_at": "2026-07-01T18:02:00Z" },
  "usage": { "calls_this_month": 142820, "limit": 1000000, "resets_on": "2026-08-01" }
}

Errors

Every REST error uses the same envelope. A 429 response also includes a Retry-After header.

json · error envelope
{
  "result": "error",
  "code": "missing_parameter",
  "message": "missing required parameter: symbols"
}

Data errors: invalid_pair, unsupported_base, unsupported_pair_interval, bad_date, invalid_amount, invalid_request, missing_parameter, data_unavailable, no_data_for_date, live_unavailable, source_unavailable, and service_unavailable. Time-series validation also emits unsupported_interval and timeseries_disabled.

WebSocket subscriptions reject an unknown currency with unknown_currencyand a pair outside live coverage with unsupported_stream_pair. Mixed valid and invalid symbol lists are rejected as one request; no partial subscription is created.

Authentication and limit errors: invalid_api_key, auth_unavailable, forbidden, rate_limited, ip_rate_limited, quota_exceeded, and quota_unavailable.

Python

Use any HTTP client. This standard-library example includes a timeout and reads the same response shown above. The Python integration guide has a compact production example.

python · standard library
from urllib.request import Request, urlopen
import json

# Identify your application explicitly when calling the API.
request = Request("https://api.exchangerate.dev/v1/latest/USD?symbols=EUR,GBP", headers={"User-Agent": "exchangerate-docs/1.0"})
with urlopen(request, timeout=10) as response:
    data = json.load(response)

print(data["rates"]["EUR"])

Node

Use built-in fetch or another HTTP client. This example checks the HTTP status and uses a ten-second timeout. The JavaScript conversion guide covers server-safe use.

javascript · Node.js
const response = await fetch("https://api.exchangerate.dev/v1/latest/USD?symbols=EUR,GBP", {
  signal: AbortSignal.timeout(10_000),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
console.log(data.rates.EUR);

Example response

Numbers and timestamps change as rates update; the response shape and freshness fields are stable.

json · representative response
{
  "result": "success",
  "base": "USD",
  "source": "live",
  "sources": {
    "EUR": "live",
    "GBP": "live"
  },
  "market_session": "open",
  "timestamp": "2026-06-15T12:00:00Z",
  "data_updated_at": "2026-06-15T12:00:00Z",
  "effective_at": {
    "EUR": "2026-06-15T12:00:00Z",
    "GBP": "2026-06-15T12:00:00Z"
  },
  "rates": {
    "EUR": 0.86207,
    "GBP": 0.74627
  },
  "derived_symbols": [],
  "notice": "Indicative rates, not for settlement."
}

MCP server

Use exchangerate.dev as a tool in any MCP-compatible AI agent. The dedicated MCP quickstart has setup, authentication, and the tool table.