# Exchangerate Dev

> Use the exchangerate.dev API correctly for anything involving foreign-exchange rates — fetching live or historical currency rates, converting amounts between currencies, building FX time-series, or wiring the exchangerate.dev MCP server into an agent. Use this skill whenever the user mentions exchange rates, currency conversion, forex/FX data, "USD to EUR", multi-currency features, or needs a rates API — even if they don't name a provider. Works keyless out of the box (no signup needed to try it).

- Skill: `nusantara-ventures/exchangerate-dev` (Agent Skill)
- Install (CLI): `npx skillmds@latest add nusantara-ventures/exchangerate-dev`
- Raw SKILL.md: https://api.skillmd.com/api/skills/nusantara-ventures/exchangerate-dev/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- License: MIT
- Author: nusantara-ventures (https://skillmd.com/u/nusantara-ventures)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/nusantara-ventures/exchangerate-dev

---


# 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:

```bash
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:

```bash
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`

```bash
curl "https://api.exchangerate.dev/v1/latest?base=EUR&symbols=USD,JPY,IDR"
```

```json
{
  "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}`

```bash
curl "https://api.exchangerate.dev/v1/convert/EUR/USD/100"
```

```json
{
  "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.

```bash
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}`

```bash
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**.

```bash
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`

```bash
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`

```json
{ "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 be `live` for EUR and `ecb_daily` for MYR.
- `market_session` — `open`, `weekend`, or `interbank_closed`. `source: live` + `market_session: weekend` means 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 by `derivation_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:

```json
{
  "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.

