Menu

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.

Introduction

exchangerate.dev is a REST API for indicative foreign exchange rates. It provides live intraday rates on trading days for all 144 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.

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 10 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-API-KeyheaderAlias for Authorization: Bearer. Bearer takes precedence when both are supplied.
X-RateLimit-LimitresponseCall limit for the current window.
X-RateLimit-RemainingresponseCalls remaining in the current window.
X-RateLimit-ResetresponseUnix timestamp when the window resets.
X-RateLimit-ScoperesponseOn limit errors: tier-rate-minute, tier-quota-month, or anon-ip-hour.

Rate limits

Free, keyless callers, and exchangerate.dev pages receive live rates 15 minutes late; Basic, Plus and Pro receive the same feed live. Delayed responses carry X-Price-Delay-Seconds: 900. ECB daily fixes are published once daily without delay. Free and Basic have no WebSocket; Plus and Pro include it. Without an ECB fix, daily history (ranges, dates, daily time series) shows closed days only after midnight UTC; today appears tomorrow.

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

PlanMonthly callsRate limitPrice
Free5,000/month10 req/min$0/mo
Basic100,000/month60 req/min$19/mo
Plus300,000/month120 req/min$39/mo
Pro1,000,000/month300 req/min$89/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; Plus: 1 concurrent connection, 10 pairs per connection; Pro: 5 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 (ECB fix), and market_daily (stored daily market-feed close for currencies without an ECB fix, used in date history and daily time series).
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  },  "stale": {    "EUR": false,    "GBP": false  },  "derived_symbols": [],  "notice": "Indicative rates, not for settlement."}
OperationPurposeParametersRequestResponseEndpoint errors
POST /v1/account/checkoutOpen checkout or subscription management for the API key's organization.API key auth; JSON body: plan (basic_2026/plus_2026/pro_2026), interval (monthly/yearly)POST /v1/account/checkoutCheckoutResponseauthentication, checkout in progress, or billing unavailable
POST /v1/account/checkout-sessionOpen Stripe checkout or subscription management for the signed-in account.session auth; JSON body: plan (basic_2026/plus_2026/pro_2026), 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
GET /v1/account/webhooksList the account's webhook endpoints, the plan's endpoint limit and the event types.session authentication/v1/account/webhookswebhooks, limit, eventsauthentication or account errors
POST /v1/account/webhooksAdd a webhook endpoint for official.published and official.corrected events. The signing secret is returned once.session auth; JSON body: url, events, optional description (email_fallback is not offered yet and returns 400)POST /v1/account/webhooksendpoint with secret (201)authentication, plan limit (Plus 5, Pro 25), or URL and event validation errors
DELETE /v1/account/webhooks/{endpoint_id}Remove a webhook endpoint.session auth; endpoint_id (path)DELETE /v1/account/webhooks/{endpoint_id}204 No Contentauthentication or not_found
POST /v1/account/webhooks/{endpoint_id}/rotate-secretReplace an endpoint's signing secret. The new secret is returned once.session auth; endpoint_id (path)POST /v1/account/webhooks/{endpoint_id}/rotate-secretid, secretauthentication or not_found
POST /v1/account/webhooks/{endpoint_id}/testQueue a test event to one endpoint.session auth; endpoint_id (path)POST /v1/account/webhooks/{endpoint_id}/testqueued, event_id (202)authentication or not_found
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}]# Examplescurl /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 for Plus and Pro; Free and keyless history cover the last 30 days, Basic the last year. The historical data guide covers weekends, ranges, pagination, and downloads.

curl
curl "https://api.exchangerate.dev/v1/2024-01-15/EUR?symbols=USD,GBP" \  -H "Authorization: Bearer YOUR_API_KEY"# Daily history this old requires Plus or Pro# 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
# SingleGET /v1/convert/USD/EUR/100# Batch POST (counts as one call, up to 100 pairs)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" },  "stale": false,  "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
curl "https://api.exchangerate.dev/v1/range?base=USD&symbols=EUR,GBP&start_date=2024-01-01&end_date=2024-06-30" \  -H "Authorization: Bearer YOUR_API_KEY"# This range requires Plus or Pro# Response includes next_cursor + has_more for pagination

GET /v1/timeseries

Use a Pro key for the 15m example on the intraday exchange rate API page. Plus includes 1h and 4h for 30 days.

Aligned rate observations for 1m, 5m, 15m, 1h, 4h, or 1d. Plus: 1h/4h, last 30 days; Pro: 1m to 4h, full depth. The daily interval uses ECB fixes for about 30 currencies and market daily closes for the other 113 currencies.

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"# Pro required for 5m. JSON uses next_cursor + has_more. CSV and Parquet export one file.
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

WebSocket covers 14 currencies: AUD, CAD, CHF, EUR, GBP, JPY, MXN, NOK, NZD, PLN, SEK, SGD, USD, ZAR. Intraday history covers 221 pairs at 1m: every USD pair plus 78 crosses. 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
1m221Market observations
5m221Market observations
15m221Market observations
1h221Market observations
4h209Market observations
1d10296Daily reference across 144 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 WebSocket list have daily history and intraday USD-pair coverage. 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 the official index. 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. While the interbank week is closed, state frames report unavailable with reason market_closed and next_open. Snapshot and update frames report market_session: weekend for the current time; stale follows the same rule as /v1/latest.

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_2026", "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": "required parameter missing: 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.

Plan access errors return HTTP 403 with plan_gated, tier and interval. History-depth errors also include history_days and earliest_allowed. When an eligible upgrade allows the request, upgrade_plans, upgrade_offers (plan and loses), and upgrade_url are included.

json · plan access error
{  "result": "error",  "code": "plan_gated",  "message": "The 15m interval is not included in your plan.",  "tier": "plus_2026",  "interval": "15m",  "upgrade_plans": ["pro_2026"],  "upgrade_offers": [{ "plan": "pro_2026", "loses": [] }],  "upgrade_url": "https://exchangerate.dev/pricing?plan=pro_2026&source=api&reason=plan_gated"}

WebSocket subscriptions reject an unknown currency with unknown_currency and 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, urlopenimport 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  },  "stale": {    "EUR": false,    "GBP": false  },  "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.