Introduction

exchangerate.dev is a REST API for indicative foreign exchange rates — live intraday on trading days for actively traded currencies, with source, market_session, and honest freshness 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 — not for settlement, regulated trading, or as a “source of record.” The market_session and source fields tell you exactly what you are looking at on every call.

Quickstart

Make your first data call without signup. Add a free key from the dashboard when you want account-attributed quota.

bash · curl
# Anonymous evaluation — base=USD, all symbols
curl https://api.exchangerate.dev/v1/latest/USD

# Optional account key (keep it server-side)
# curl https://api.exchangerate.dev/v1/latest/USD \
#   -H "Authorization: Bearer YOUR_KEY"

# Response
{
  "result": "success",
  "base": "USD",
  "source": "ecb_daily",
  "market_session": "open",
  "timestamp": "2026-06-16T14:02:11Z",
  "data_updated_at": "2026-06-16T14:02:00Z",
  "rates": { "EUR": 0.92450, "GBP": 0.78910, "JPY": 157.32 },
  "sources": { "EUR": "live", "GBP": "live", "JPY": "ecb_daily" },
  "derived_symbols": [],
  "notice": "Indicative rates, not for settlement."
}

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.

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

The source field

Every response carries a source that tells you the quality of the rate. This is not a vendor name — it is a freshness class.

  • live — updates on short intervals throughout the trading week, aggregated from market data
  • ecb_daily — the European Central Bank's once-a-day reference rate, published each business day around 16:00 CET
  • fred_daily — the US Federal Reserve's (FRED) once-a-day reference rate, used for currencies the ECB doesn't publish

When a response contains several currencies, the top-level source reflects the least fresh one in the set. Two things follow from that:

  • A mixed basket can read ecb_daily even though the majors in it are live. A per-currency sourcesmap tells you which is which — it's on every endpoint except /v1/range, whose rows each carry a single source.
  • Request only live currencies (e.g. ?symbols=EUR,GBP) and source reads live.

Live rates are available on every tier, including Free.

The market_session field

The market_session field tells you whether the market is open and trading or in a reduced-liquidity period.

  • open — the interbank trading week is open
  • weekend — Saturday/Sunday; interbank closed, last consensus carried
  • interbank_closed — a known non-weekend market closure

Indicative rates

“Indicative” means the rate shows you where the market is — it is not a price anyone commits to trade at. Rates are built from aggregated market data and public reference rates, and can differ from an executable bank rate by roughly 5–15 basis points (a basis point is 0.01%).

Use them for display, analytics, and conversion estimates. Don't use them for settlement, regulated trading, or as a source of record — there is no SLA at v1. Responses built on ECB/FRED reference data say so in the notice field.

Derived crosses

Some pairs aren't quoted directly in the market, so we calculate them from two pairs that are — EUR/GBP, for example, is built from EUR/USD and GBP/USD. This is standard practice (called triangulation) and can add a small amount of extra spread. Derived rates are always flagged:

  • Multi-currency responses list triangulated rates in derived_symbols — filter on it client-side if you only want directly quoted rates.
  • Single-pair responses (/v1/convert, /v1/rate) carry a derived boolean instead.
  • When a pair isn't derived, derivation_bps_max is always null. When it is derived, that field usually carries the worst-case extra spread in basis points — but some triangulated pairs have no fixed ceiling and it reads null too. Check derived, not derivation_bps_max, to detect triangulation.
json · /v1/rate/gbp-jpy (a derived cross)
{
  "pair": "gbp-jpy",
  "rate": 199.83,
  "derived": true,
  "derivation_bps_max": 3.0
}

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}

Historical snapshot — returns the reference rate as published on that date. Dates back to 1999-01-04 (ECB series start; 1999-01-01 is accepted as a query date but has no fix — earlier holiday/weekend gaps return no data, never fabricated).

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}

Single-pair quote by slug (e.g. eur-usd) — a flat, single-pair shape distinct 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": { "EUR": "live", "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/currencies

Full list of supported pairs with name, type, decimal precision, and derived flag.

curl
GET /v1/currencies              # all
GET /v1/currencies?type=fiat    # fiat only

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" }
}

Python

No SDK required — a single requests call.

python
import requests

r = requests.get(
    "https://api.exchangerate.dev/v1/latest/USD",
    headers={"Authorization": "Bearer YOUR_KEY"},
    params={"symbols": "EUR,GBP"},
).json()
print(r["rates"])  # {"EUR": 0.9245, "GBP": 0.7891}

Node

No SDK required — built-in fetch.

node
const r = await fetch(
  "https://api.exchangerate.dev/v1/latest/USD?symbols=EUR,GBP",
  { headers: { Authorization: "Bearer YOUR_KEY" } }
).then(res => res.json());
console.log(r.rates); // { EUR: 0.9245, GBP: 0.7891 }

MCP server

Use exchangerate.dev as a tool in any MCP-compatible AI agent. The dedicated MCP quickstart includes a one-command Claude Code setup, keyed Claude Desktop and Cursor config, and the complete tool table.

bash
# HTTP endpoint (any MCP-compatible client):
# https://api.exchangerate.dev/v1/mcp

# Or run the stdio bridge:
npx exchangerate-dev-mcp

# Or add to your MCP config:
{
  "mcpServers": {
    "exchangerate": {
      "command": "npx",
      "args": ["exchangerate-dev-mcp"],
      "env": { "EXCHANGERATE_API_KEY": "YOUR_KEY" }
    }
  }
}