# Currency Exchange Rate API: full API reference

> USD-based exchange rates for about 160 currencies through a metered JSON API, with daily historical rates back to 1999. Any currency can be the base, and the same edition backs webhooks and a WebSocket stream. Current rates come from the newest edition for the key’s plan, and each plan sets how often that edition can change and how far back historical rates reach.

Base URL: https://api.currencyexchangerate-api.com/api/v1
Website: https://currencyexchangerate-api.com/
Human docs: https://currencyexchangerate-api.com/docs
OpenAPI 3.1 description: https://currencyexchangerate-api.com/openapi.json

## Authentication and quotas

- Keys are sent as Authorization: Bearer <key>. Format cxr_ followed by 43 base64url characters. Only a SHA-256 digest is stored, so a lost key cannot be recovered; create a new one.
- A user can have at most 10 active keys. Revoked keys stop working at once.
- The monthly quota is per user, shared by all of the user’s keys, per UTC calendar month. A user without a live subscription is on the free plan.
- Every request that is accepted counts once. Rejected requests are not counted.
- Accepted responses carry X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix seconds when the period resets).
- Over the monthly quota the API answers 429 API_QUOTA_EXCEEDED with Retry-After (seconds until the next period).
- Each key has a per-second limit set by its plan. Over it the API answers 429 API_RATE_LIMITED with Retry-After: 1.
- A missing, malformed, unknown, or revoked key answers 401 API_KEY_INVALID. An IP with 30 or more failed keys in the current UTC minute gets 429 API_RATE_LIMITED until the minute ends.
- Quota emails are sent to the account owner at 80% and 100% of the monthly quota.

### Plans

Limits at the time of writing; prices and the current catalog are on https://currencyexchangerate-api.com/#pricing.

| Plan | Monthly requests | Requests per second per key | Edition updates | Historical rates | Webhooks | WebSocket connections |
| --- | --- | --- | --- | --- | --- | --- |
| Free | 3,000 | 2 | Daily | Last 30 days | None | None |
| Starter | 10,000 | 5 | Hourly | Since 1999 | 1 | 1 |
| Pro | 100,000 | 15 | Every 5 minutes | Since 1999 | 3 | 3 |
| Business | 1,000,000 | 50 | Every 5 minutes | Since 1999 | 5 | 5 |

## Any base currency

Stored rates are per 1 USD. For any other base in the edition, each rate is derived from the same edition: rate(base to code) = usd[code] / usd[base], rounded to 10 significant digits. The base itself is 1. A base that is not in the edition answers 422 RATES_UNSUPPORTED_CURRENCY.

## Endpoints

### GET /latest

Current rates per one unit of a base currency. Every currency is returned unless symbols narrows the list.

Metered: yes, one request against the monthly quota. Requires a bearer key.

Query parameters:

- `base` (optional): Currency the rates are per one unit of. Case-insensitive, default USD. Any currency in the edition works, such as EGP.
- `symbols` (optional): Comma-separated currency codes, at most 200, case-insensitive. Without it, every currency is returned, including the base at 1.

Example request:

```
curl "https://api.currencyexchangerate-api.com/api/v1/latest?base=EGP&symbols=EUR,USD" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Example response:

```json
{
  "base": "EGP",
  "updatedAt": "2026-10-08T00:00:01.000Z",
  "nextUpdateAt": "2026-10-09T00:00:01.000Z",
  "rates": {
    "EUR": 0.01895679012,
    "USD": 0.02057613169
  }
}
```

Errors: `RATES_INVALID_INPUT`, `RATES_UNSUPPORTED_CURRENCY`, `RATES_UNAVAILABLE`, `API_KEY_INVALID`, `API_QUOTA_EXCEEDED`, `API_RATE_LIMITED`, `RATES_INTERNAL_ERROR`.

### GET /convert

Converts an amount between any two currencies in the edition. The pair is converted through USD.

Metered: yes, one request against the monthly quota. Requires a bearer key.

Query parameters:

- `from` (required): Currency code to convert from. Case-insensitive.
- `to` (required): Currency code to convert to. Case-insensitive.
- `amount` (optional): Amount in the from currency. Greater than 0 and at most 1,000,000,000,000. Default 1.

Example request:

```
curl "https://api.currencyexchangerate-api.com/api/v1/convert?from=EUR&to=GBP&amount=100" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Example response:

```json
{
  "from": "EUR",
  "to": "GBP",
  "amount": 100,
  "rate": 0.8524910453,
  "result": 85.249105,
  "updatedAt": "2026-10-08T00:00:01.000Z"
}
```

Errors: `RATES_INVALID_INPUT`, `RATES_UNSUPPORTED_CURRENCY`, `RATES_UNAVAILABLE`, `API_KEY_INVALID`, `API_QUOTA_EXCEEDED`, `API_RATE_LIMITED`, `RATES_INTERNAL_ERROR`.

### GET /historical

Closing rates of one past UTC day per one unit of a base currency. On a weekend or holiday the nearest earlier day with rates is served, and date says which day it is.

Metered: yes, one request against the monthly quota. Requires a bearer key.

Query parameters:

- `date` (required): The day, YYYY-MM-DD (UTC), no later than today and within the plan’s historical reach.
- `base` (optional): Currency the rates are per one unit of. Case-insensitive, default USD.
- `symbols` (optional): Comma-separated currency codes, at most 200, case-insensitive. Without it, every currency of that day is returned.

Example request:

```
curl "https://api.currencyexchangerate-api.com/api/v1/historical?date=2026-10-10&base=EUR&symbols=USD,GBP" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Example response:

```json
{
  "requestedDate": "2026-10-10",
  "date": "2026-10-09",
  "base": "EUR",
  "rates": {
    "GBP": 0.8524910453,
    "USD": 1.085422772
  }
}
```

Errors: `RATES_INVALID_INPUT`, `RATES_UNSUPPORTED_CURRENCY`, `RATES_HISTORY_NOT_IN_PLAN`, `RATES_HISTORY_UNAVAILABLE`, `API_KEY_INVALID`, `API_QUOTA_EXCEEDED`, `API_RATE_LIMITED`, `RATES_INTERNAL_ERROR`.

### GET /timeseries

Closing rates for every day with rates from start to end inclusive, at most 366 days, keyed by date. Weekends and holidays without rates are left out.

Metered: yes, one request against the monthly quota. Requires a bearer key.

Query parameters:

- `start` (required): First day, YYYY-MM-DD (UTC), within the plan’s historical reach.
- `end` (required): Last day, YYYY-MM-DD (UTC), no later than today and at most 365 days after start.
- `base` (optional): Currency the rates are per one unit of. Case-insensitive, default USD. Days without the base are left out.
- `symbols` (optional): Comma-separated currency codes, at most 200, case-insensitive. Codes a day lacks are left out of that day.

Example request:

```
curl "https://api.currencyexchangerate-api.com/api/v1/timeseries?start=2026-10-05&end=2026-10-07&symbols=EUR" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Example response:

```json
{
  "base": "USD",
  "start": "2026-10-05",
  "end": "2026-10-07",
  "rates": {
    "2026-10-05": {
      "EUR": 0.9205
    },
    "2026-10-06": {
      "EUR": 0.9198
    },
    "2026-10-07": {
      "EUR": 0.9213
    }
  }
}
```

Errors: `RATES_INVALID_INPUT`, `RATES_UNSUPPORTED_CURRENCY`, `RATES_HISTORY_NOT_IN_PLAN`, `RATES_HISTORY_UNAVAILABLE`, `API_KEY_INVALID`, `API_QUOTA_EXCEEDED`, `API_RATE_LIMITED`, `RATES_INTERNAL_ERROR`.

### GET /currencies

Every currency code in the newest snapshot, sorted by code, with an English name. Public and not metered.

Metered: no. No key needed.

Example request:

```
curl "https://api.currencyexchangerate-api.com/api/v1/currencies"
```

Example response:

```json
[
  {
    "code": "AED",
    "name": "United Arab Emirates Dirham"
  }
]
```

### GET /preview

Rates for a fixed selection of popular currencies from the newest snapshot. Public and not metered.

Metered: no. No key needed.

Example request:

```
curl "https://api.currencyexchangerate-api.com/api/v1/preview"
```

Example response:

```json
{
  "base": "USD",
  "updatedAt": "2026-10-08T00:00:01.000Z",
  "nextUpdateAt": "2026-10-09T00:00:01.000Z",
  "rates": {
    "EUR": 0.9213,
    "GBP": 0.7854
  }
}
```

## Errors

Errors are application/problem+json with a stable `code`, for example:

```json
{
  "type": "about:blank",
  "title": "Rates unavailable",
  "status": 503,
  "code": "RATES_UNAVAILABLE",
  "detail": "No rates are available yet. Try again later."
}
```

| Code | Status | Meaning |
| --- | --- | --- |
| `RATES_INVALID_INPUT` | 422 | A query parameter is missing or invalid. |
| `RATES_UNSUPPORTED_CURRENCY` | 422 | A currency code or base is well-formed but not in the snapshot. |
| `RATES_UNAVAILABLE` | 503 | No snapshot has been stored yet. |
| `API_KEY_INVALID` | 401 | The key is missing, malformed, unknown, or revoked. |
| `API_QUOTA_EXCEEDED` | 429 | The monthly quota is used up. Retry-After gives the seconds until the next UTC month. |
| `API_RATE_LIMITED` | 429 | Too many requests per second for the key, or too many failed keys from this IP. Retry-After is set. |
| `RATES_PLAN_UPGRADE_REQUIRED` | 403 | The plan has no webhooks or WebSocket stream, or the webhook is beyond the plan’s limit. On the stream this is close code 4403. |
| `RATES_HISTORY_NOT_IN_PLAN` | 403 | The date, or the start of the range, is older than the plan’s historical reach (Free: 30 days). |
| `RATES_HISTORY_UNAVAILABLE` | 404 | No rates are stored for the date or the 7 days before it, such as a date before 1999. |
| `RATES_INTERNAL_ERROR` | 500 | Unexpected failure. |

## Webhooks

- Set an endpoint in the console. The URL must be absolute, https only, at most 2048 characters, with no username, password, or fragment. Localhost and private or reserved IP addresses are refused. Webhooks are a paid feature: Starter allows 1, Pro 3, Business 5. On the free plan, creating one answers 403 RATES_PLAN_UPGRADE_REQUIRED, and over the limit 409 RATES_WEBHOOK_LIMIT_REACHED. After a downgrade, only the oldest enabled webhooks up to the limit receive deliveries; Send test on another answers 403 RATES_PLAN_UPGRADE_REQUIRED.
- A signed HTTPS POST is sent each time the subscriber’s edition changes. The free plan changes once a day, Starter hourly, and Pro and Business every 5 minutes. Send test checks an endpoint right away.
- Headers: Content-Type application/json; User-Agent CurrencyExchangeRate-API-Webhooks/1.0; X-Webhook-Id (the webhook id); X-Webhook-Delivery (the delivery id, the same on every retry); X-Webhook-Event (rates.updated or webhook.test); X-Webhook-Signature.
- Signature: X-Webhook-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>. Sign and compare the exact body bytes, use a constant-time compare, and reject a t more than 5 minutes old. The secret starts with whsec_ and is shown only when created or rotated.
- rates.updated bodies carry { id, type, createdAt, data } where data is the edition: base, updatedAt, nextUpdateAt, and rates. webhook.test carries a message only.
- Any 2xx response marks a delivery delivered. rates.updated retries after 1 min, 5 min, 15 min, 1 h, 3 h, 6 h, then 12 h, and the endpoint is disabled after 3 consecutive failed deliveries. Redirects are not followed and each attempt times out after 10 seconds. webhook.test is attempted once.

```json
{
  "id": "<delivery id>",
  "type": "rates.updated",
  "createdAt": "2026-10-08T00:00:31.000Z",
  "data": {
    "base": "EGP",
    "updatedAt": "2026-10-08T00:00:01.000Z",
    "nextUpdateAt": "2026-10-09T00:00:01.000Z",
    "rates": {
      "EUR": 0.01895679012,
      "USD": 0.02057613169
    }
  }
}
```

## WebSocket stream

- Connect with a WebSocket to wss://<api host>/api/v1/stream. Optional query parameters base (default USD) and symbols follow the same rules as /latest. Keys never go in the URL.
- Authenticate with either an Authorization: Bearer <key> header on the upgrade request, or a first message {"type":"auth","key":"cxr_..."} within 10 seconds. Otherwise the server closes with 4408.
- The server sends the current edition as {"type":"rates","data":{...}}, or {"type":"error","code":"RATES_UNAVAILABLE"} until rates are stored. Each live tick pushes a new rates message when the edition changes.
- Client messages: {"type":"subscribe","base":"EGP","symbols":["EUR","USD"]} replaces the filter; {"type":"ping"} is answered with {"type":"pong"}. More than 30 client messages in any 60 seconds closes with 1008.
- The stream is a paid feature. Each user may hold as many authenticated connections as the plan allows, across all of the user’s keys, per server instance: Starter 1, Pro 3, Business 5. On the free plan the server closes with 4403 RATES_PLAN_UPGRADE_REQUIRED before metering. Opening a connection counts one request against the monthly quota; pushes do not count. After a downgrade, the oldest connections up to the new limit stay open and the rest close with 4409 at their next re-check (every 5 minutes).
- Close codes: 1001 server shutting down; 1008 too many messages; 1011 internal error (RATES_INTERNAL_ERROR); 4401 API_KEY_INVALID (also when a key is revoked); 4403 RATES_PLAN_UPGRADE_REQUIRED (the plan has no stream); 4408 RATES_STREAM_AUTH_TIMEOUT; 4409 RATES_STREAM_LIMIT_REACHED (the plan’s connections are all open); 4422 RATES_INVALID_INPUT or RATES_UNSUPPORTED_CURRENCY; 4429 API_RATE_LIMITED or API_QUOTA_EXCEEDED.

## MCP server

Endpoint: https://api.currencyexchangerate-api.com/api/v1/mcp (Streamable HTTP, stateless, JSON responses, POST only). Setup for Claude Code, Cursor, VS Code, and Windsurf: https://currencyexchangerate-api.com/docs/mcp

Authentication: Authorization: Bearer <key>, the same keys as the REST API. initialize, tools/list, and list_currencies need no key.

- `get_latest_rates` (base?, symbols?): Current rates per one unit of any base currency (default USD), with updatedAt and nextUpdateAt. Counts one request against the monthly quota.
- `convert_currency` (from, to, amount?): Converts an amount (default 1) at the current rate. Counts one request against the monthly quota.
- `get_historical_rates` (date, base?, symbols?): Closing rates for one past UTC day. Free plan: last 30 days; paid plans: back to 1999. Counts one request against the monthly quota.
- `get_rates_timeseries` (start, end, base?, symbols?): Daily closing rates from start to end inclusive, at most 366 days. Counts one request against the monthly quota.
- `list_currencies` (none): Every supported ISO 4217 code with its English name. Not metered.

## Terms and privacy

- Terms of Service: https://currencyexchangerate-api.com/terms
- Privacy Policy: https://currencyexchangerate-api.com/privacy
