exchangerate.dev API
Foreign-exchange rates over a clean REST API. 465 currency pairs across 31 currencies; actively-traded currencies reprice live (~60 s) on trading days, with ECB/FRED daily reference rates filling the rest. Every response labels its own freshness (source, market_session) per currency, so you never have to guess whether a rate is current — don't hardcode which currencies are live; the sources map tells you per call.
Base URL: https://api.exchangerate.dev
Auth — start keyless, add a key for volume
Data endpoints need no API key. Anonymous calls are served at the Free tier's rate limit (12 req/min, per IP). Just call it:
curl "https://api.exchangerate.dev/v1/latest?base=USD&symbols=EUR,JPY"
For more volume, get a free key (10,000 calls/month, no credit card) at https://exchangerate.dev/signup and send it as a Bearer token:
curl "https://api.exchangerate.dev/v1/latest?base=USD" \
-H "Authorization: Bearer $EXCHANGERATE_API_KEY"
Store the key in the EXCHANGERATE_API_KEY environment variable — never hard-code it or pass it on a command line that lands in shell history. Rate-limit state is visible on every response via x-ratelimit-limit, x-ratelimit-remaining, and x-ratelimit-reset headers; back off when remaining hits 0 rather than retrying blind.
Endpoints
Latest rates — GET /v1/latest
curl "https://api.exchangerate.dev/v1/latest?base=EUR&symbols=USD,JPY,IDR"
{
"result": "success",
"base": "EUR",
"source": "live",
"sources": { "USD": "live", "JPY": "live", "IDR": "live" },
"market_session": "open",
"timestamp": "2026-07-06T10:41:30Z",
"data_updated_at": "2026-07-06T10:41:09Z",
"rates": { "USD": 1.14192, "JPY": 185.366, "IDR": 20512.88 },
"derived_symbols": [],
"notice": "Indicative rates, not for settlement."
}
GET /v1/latest/USD (base in the path) works too, as does the Frankfurter-style parameter set — code written against Frankfurter runs unchanged against this endpoint. Omit symbols to get all supported quote currencies.
Convert — GET /v1/convert/{FROM}/{TO}/{AMOUNT}
curl "https://api.exchangerate.dev/v1/convert/EUR/USD/100"
{
"result": "success",
"from": "EUR", "to": "USD", "amount": 100.0,
"rate": 1.14192, "converted": 114.19,
"derived": false, "derivation_bps_max": null,
"source": "live", "market_session": "open",
"timestamp": "2026-07-06T10:41:29Z"
}
converted is already rounded to the target currency's minor units — prefer it over multiplying rate yourself and re-rounding.
Batch convert — POST /v1/convert
One from currency, many targets. Each pair is metered as one call.
curl -X POST "https://api.exchangerate.dev/v1/convert" \
-H "Content-Type: application/json" \
-d '{"from": "USD", "pairs": [["EUR", 100], ["GBP", 50], ["JPY", 1000]]}'
The body shape is {"from": string, "pairs": [[symbol, amount], ...]} — an array of two-element tuples, not an array of objects. Response carries a conversions array with to, amount, rate, converted, derived per pair.
Single pair by slug — GET /v1/rate/{slug}
curl "https://api.exchangerate.dev/v1/rate/eur-usd"
Returns pair, base, quote, rate plus the standard freshness fields. Slug is case-insensitive (eur-usd); unknown pairs return code: "invalid_pair".
Historical snapshot — GET /v1/{YYYY-MM-DD}?base=...&symbols=...
Daily series back to 1999-01-04.
curl "https://api.exchangerate.dev/v1/2024-01-15?base=USD&symbols=EUR,JPY"
Weekend/holiday dates return the most recent business-day fix with "is_forward_filled": true. Always check that flag when the exact date matters (accounting, audits) — see the fx-rates-correctness skill for the full pitfall list.
Time-series — GET /v1/range
curl "https://api.exchangerate.dev/v1/range?base=USD&symbols=EUR&start_date=2024-01-01&end_date=2024-06-30"
Parameters are start_date / end_date (not start/end). Returns one row per business day (weekends are absent, not null). Max 366 rows per page; when has_more is true, pass next_cursor to get the next page — don't assume one page covers the whole range.
Currency metadata — GET /v1/currencies
{ "code": "AUD", "name": "Australian Dollar", "type": "fiat", "decimals": 5, "minor_units": 2, "is_derived": false }
Call this first when you need valid codes, per-pair decimal precision (decimals = quote precision, minor_units = cash rounding), or to filter out triangulated pairs.
Reading the freshness fields (the part most integrations get wrong)
Every response self-describes its freshness. Inspect these before presenting a rate as "current":
source— the least-fresh tier across returned rates:live(~60 s intraday),ecb_daily(ECB fix, ~16:00 CET business days),fred_daily(FRED reference).sources— per-currency map; a mixed request can belivefor EUR andecb_dailyfor MYR.market_session—open,weekend, orinterbank_closed.source: live+market_session: weekendmeans the value is the last trading-week consensus, not a fresh quote. Say so in any UI or answer.data_updated_at— when the pipeline last wrote the value;timestamp— when the response was produced.derived_symbols/derived: true— the pair was triangulated through USD (error ≈ 1–2 bps, bounded byderivation_bps_max). Filter these out for research that needs native quotes.
All rates are indicative (aggregated market data, ~5–15 bps) — fine for display, dashboards, analytics, and internal tooling; not for settlement or as a regulated source of record.
MCP server (for agent hosts)
Five tools — list_currencies, get_rate, convert, get_range, search_docs — on every plan including Free. HTTP endpoint: https://api.exchangerate.dev/v1/mcp/, or stdio:
{
"mcpServers": {
"exchangerate": {
"command": "npx",
"args": ["exchangerate-dev-mcp"],
"env": { "EXCHANGERATE_API_KEY": "YOUR_KEY" }
}
}
}
Prefer the MCP server when the host supports it (Claude Desktop/Code, Cursor); use REST when writing application code. Source: https://github.com/nusantara-ventures/exchangerate-dev-mcp
Error handling
Errors return {"result": "error", "code": "...", "message": "..."} with a stable code (missing_parameter, invalid_request, invalid_pair, ...). Branch on code, not on message text. HTTP 429 = rate limited; respect x-ratelimit-reset (unix seconds) before retrying.
Related skills in this repo
fx-rates-correctness— weekend gaps, forward-fill, precision, triangulation: the correctness pitfalls.multi-currency-pricing— adding multi-currency display prices to an app.portfolio-currency-translation— translating multi-market portfolios into one home currency.fx-accounting-rates— rate-of-record choices for invoicing and bookkeeping.fx-dashboard-widgets— live rate tickers, charts, and session-aware UI.