API documentation

A field guide to current rates, conversions, keys, and request limits.

Authentication

Metered endpoints require an API key in the Authorization: Bearer <key> header. Get or create a key in API keys. The key is returned only once when created; keep it private and send requests over HTTPS in production.

Authorization header

HTTPS
Authorization: Bearer YOUR_API_KEY

Quotas and limits

The monthly quota applies per user across all keys, per UTC calendar month. Each plan sets its own quota and per-second limit; see Plans and limits. Accepted responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Quota and rate rejections return Retry-After.

Plans and limits

Each plan sets these limits. Compare them, with prices, on the pricing section.

Monthly quota
Every accepted request counts once toward the quota of your plan, across all of your keys. Rejected requests do not count. When the quota is used up, the API answers 429 API_QUOTA_EXCEEDED with Retry-After set to the seconds until the quota resets at 00:00 UTC on the 1st of the next month. You receive an email at 80% and at 100% of the quota.
Per-second limit
Each key may make up to the number of requests per second its plan allows. Going over returns 429 API_RATE_LIMITED with Retry-After: 1. The limit applies to each key separately. Requests rejected this way do not count toward the monthly quota.
Update interval
How often your plan can receive a new edition. Your plan sees a new edition at most that often. Responses include updatedAt (when the current edition was published) and nextUpdateAt (when the next one is due).
Webhooks and stream
Paid plans can push rates to you. Starter allows 1 webhook and 1 WebSocket connection, Pro 3 and 3, and Business 5 and 5. The free plan has neither; trying answers 403 RATES_PLAN_UPGRADE_REQUIRED (close code 4403 on the stream).
Historical rates
How far back /historical and /timeseries reach. The free plan reaches the last 30 days; Starter, Pro, and Business reach the full archive since 1999. An older date answers 403 RATES_HISTORY_NOT_IN_PLAN.

Endpoints

Base path /api/v1; API base URL https://api.currencyexchangerate-api.com/api/v1.

Using an AI assistant? The same data is available as an MCP server.

Import the OpenAPI 3.1 description or the Postman collection to try every endpoint.

GET /latest

Returns current USD-based rates. Metered; requires a key.

ParameterRequiredDescription
baseNoCurrency the rates are per one unit of, case-insensitive; default USD. Any currency in the latest snapshot works.
symbolsNoComma-separated currency codes, at most 200, case-insensitive. Omit to return all.

Latest rates

cURL · shell
curl --fail-with-body -sS "https://api.currencyexchangerate-api.com/api/v1/latest?symbols=EUR,GBP" \  -H "Authorization: Bearer $EXCHANGE_API_KEY"

Response

JSON
{  "base": "USD",  "updatedAt": "2026-10-08T00:00:01.000Z",  "nextUpdateAt": "2026-10-09T00:00:01.000Z",  "rates": { "EUR": 0.8931, "GBP": 0.7567 }}

Set base to read the rates from any currency in the snapshot. Each rate is derived from the same edition: EGP to EUR is usd[EUR] / usd[EGP], so any pair works from any base. Without symbols, the response includes the base itself at 1.

Request with base

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

Response with base

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

GET /convert

Converts any supported pair through USD. Metered; requires a key.

ParameterRequiredDescription
fromYesSource currency code, case-insensitive.
toYesTarget currency code, case-insensitive.
amountNoGreater than 0 and at most 1,000,000,000,000; defaults to 1.

Convert

cURL · shell
curl --fail-with-body -sS "https://api.currencyexchangerate-api.com/api/v1/convert?from=EUR&to=GBP&amount=100" \  -H "Authorization: Bearer $EXCHANGE_API_KEY"

Response

JSON
{  "from": "EUR",  "to": "GBP",  "amount": 100,  "rate": 0.8472735416,  "result": 84.727354,  "updatedAt": "2026-10-08T00:00:01.000Z"}

GET /historical

Returns the closing rates of one past UTC day. On a weekend or holiday the nearest earlier day with rates is served, and date says which day it is. Metered; requires a key. Free plans reach back 30 days; paid plans reach the full archive since 1999.

ParameterRequiredDescription
dateYesYYYY-MM-DD (UTC), no later than today and within your plan’s reach.
baseNoCurrency the rates are per one unit of, case-insensitive; default USD.
symbolsNoComma-separated currency codes, at most 200, case-insensitive. Omit to return all.

Request

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

JavaScript fetch

JavaScript
const response = await fetch('https://api.currencyexchangerate-api.com/api/v1/historical?date=2026-10-10&base=EUR&symbols=USD,GBP', {  headers: { Authorization: 'Bearer YOUR_API_KEY' },});const data = await response.json();

Response

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

GET /timeseries

Returns closing rates for every day with rates from start to end inclusive, at most 366 days, keyed by date. Days without rates, such as weekends in the ECB archive, are left out. One request counts once, however long the range. Metered; requires a key.

ParameterRequiredDescription
startYesFirst day, YYYY-MM-DD (UTC), within your plan’s reach.
endYesLast day, YYYY-MM-DD (UTC), no later than today and at most 365 days after start.
baseNoCurrency the rates are per one unit of; default USD. Days without the base are left out.
symbolsNoComma-separated currency codes, at most 200. Codes a day lacks are left out of that day.

Request

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

JavaScript fetch

JavaScript
const response = await fetch('https://api.currencyexchangerate-api.com/api/v1/timeseries?start=2026-10-05&end=2026-10-07&symbols=EUR', {  headers: { Authorization: 'Bearer YOUR_API_KEY' },});const data = await response.json();

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

GET /currencies

Returns every code in the newest snapshot, sorted by code, with an English display name. Public and not metered. Cache-Control: public, max-age=3600.

Request

HTTPS
curl https://api.currencyexchangerate-api.com/api/v1/currencies

JavaScript fetch

JavaScript
const response = await fetch('https://api.currencyexchangerate-api.com/api/v1/currencies');const data = await response.json();

Response

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

GET /preview

Returns a fixed selection of current rates. Public and not metered; no key required. Missing currencies are skipped. Cache-Control: public, max-age=300.

Request

HTTPS
curl https://api.currencyexchangerate-api.com/api/v1/preview

JavaScript fetch

JavaScript
const response = await fetch('https://api.currencyexchangerate-api.com/api/v1/preview');const data = await response.json();

Response

JSON
{  "base": "USD",  "updatedAt": "2026-10-08T00:00:01.000Z",  "nextUpdateAt": "2026-10-09T00:00:01.000Z",  "rates": { "EUR": 0.8931, "GBP": 0.7567 }}

Errors

Errors use application/problem+json and include a machine-readable code.

CodeStatusMeaning
RATES_INVALID_INPUT422A query parameter is missing or invalid.
RATES_UNSUPPORTED_CURRENCY422Code is validly formed but absent from the snapshot.
RATES_UNAVAILABLE503No snapshot is available.
API_KEY_INVALID401Missing or invalid API key.
API_QUOTA_EXCEEDED429Monthly quota is used.
API_RATE_LIMITED429Request limit exceeded.
RATES_HISTORY_NOT_IN_PLAN403The date is older than your plan’s historical reach.
RATES_HISTORY_UNAVAILABLE404No rates are stored for the date or the 7 days before it.

Data

Rates are USD-based and cover about 160 currencies. Responses include updatedAt and nextUpdateAt. Current endpoints serve the newest edition for your plan.

Rates are mid-market reference rates compiled from multiple central-bank and commercial sources. They suit pricing, invoicing, reporting, and display. They are not suitable for trading, for executing foreign exchange transactions, or for any other forex operation.

Historical rates are one closing set per UTC day. Days recorded since launch cover every currency we serve. Earlier days, back to 4 January 1999, are the euro foreign exchange reference rates published by the European Central Bank (about 30 currencies, on TARGET working days), converted to a USD base. Source: European Central Bank; the reference rates are also available free of charge from the ECB.

The currencies page lists every code with its rate per 1 USD and a rate page for each currency.

Supported currencies

Search the currencies available in the latest snapshot.

Loading currencies…

Webhooks

A webhook is a signed POST to an HTTPS address you choose. Create one in Webhooks; Starter allows 1, Pro 3, and Business 5. The free plan has no webhooks. Deliveries don't count against your monthly quota. After a downgrade, only your oldest enabled webhooks up to the new limit receive deliveries.

When a delivery is sent

A delivery is sent when the edition for your webhook changes, which depends on your plan's update interval (see Plans and limits). A webhook follows the base currency and currencies it was created with. Its first delivery is the next change after it is created, so use Send test in the console to check an endpoint right away.

Headers

HeaderValueNotes
Content-Typeapplication/jsonThe body is JSON.
User-AgentCurrencyExchangeRate-API-Webhooks/1.0Fixed value.
X-Webhook-IdWebhook idIdentifies the webhook.
X-Webhook-DeliveryDelivery idSame on every retry, so you can ignore duplicates.
X-Webhook-Eventrates.updated or webhook.testThe event type.
X-Webhook-Signaturet=<unix seconds>,v1=<hex>HMAC-SHA256 signature. See Verify signatures below.

Bodies

rates.updated carries the edition for your base and currencies. The id matches the X-Webhook-Delivery header.

rates.updated

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

webhook.test

JSON
{  "id": "<delivery id>",  "type": "webhook.test",  "createdAt": "2026-10-08T00:00:31.000Z",  "data": {    "message": "This is a test delivery from CurrencyExchangeRate-API.com."  }}

Verify signatures

The header is t=<unix seconds>,v1=<hex>. v1 is the HMAC-SHA256 of <t>.<raw body>, keyed with the webhook secret. Check the signature against the raw bytes you received, before you parse the JSON. Compare with a constant-time function, and reject a t more than 5 minutes old. The secret starts with whsec_ and is shown once, when you create or rotate the webhook.

Verify a delivery

JavaScript · Node.js
import { createHmac, timingSafeEqual } from 'node:crypto'; const MAX_AGE_SECONDS = 5 * 60; // rawBody: the exact request body as a string, before JSON.parse.// header: the X-Webhook-Signature value. secret: your whsec_ secret.export function verifyWebhook(rawBody, header, secret) {  const fields = Object.fromEntries(    header.split(',').map((part) => part.split('=')),  );  const age = Math.floor(Date.now() / 1000) - Number(fields.t);  if (!Number.isFinite(age) || age > MAX_AGE_SECONDS) return false;   const expected = createHmac('sha256', secret)    .update(`${fields.t}.${rawBody}`)    .digest('hex');  const received = Buffer.from(fields.v1 ?? '', 'utf8');  const actual = Buffer.from(expected, 'utf8');  return (    received.length === actual.length && timingSafeEqual(received, actual)  );}

Retries

Your endpoint has 10 seconds to answer. Any 2xx response counts as delivered; a timeout, a connection error, a non-2xx response, or a redirect counts as a failed attempt. Failed rates.updated deliveries are retried after 1 minute, 5 minutes, 15 minutes, 1 hour, 3 hours, 6 hours, and 12 hours: 8 attempts in total. Retries reuse the delivery id and body, with a fresh t and signature. After the eighth failed attempt the delivery is marked failed.

If three deliveries in a row fail all eight attempts, the webhook is disabled automatically. Fix the endpoint, then enable the webhook again in the console.

Endpoint address

  • Use an absolute https:// URL of at most 2048 characters. Query strings are allowed.
  • The URL can't contain a username, a password, or a #fragment.
  • The host must be public. localhost and private, loopback, link-local, and other reserved addresses are refused, both when you save the URL and on every send.

Streaming

A WebSocket connection receives a rates message whenever the edition for its base and symbols changes, so you don't have to poll. Streaming is available on paid plans, with the same API keys as the REST endpoints.

Connect

Stream URL

WebSocket
wss://api.currencyexchangerate-api.com/api/v1/stream?base=EGP&symbols=EUR,USD

The optional query takes the same base and symbols as /latest. Without them, the connection uses USD and every currency. Authenticate with one of the two methods below. Keys never go in the URL.

  • Server clients send Authorization: Bearer YOUR_API_KEY on the upgrade request.
  • Browsers send a first message, {"type":"auth","key":"YOUR_API_KEY"}, within 10 seconds. Without it, the server closes the connection with 4408. Keys in browser code are visible to every visitor, so prefer a server-side relay for production.

Messages

MessageDirectionMeaning
ratesServer to clientThe current edition for this connection. Sent after authentication, after each subscribe, and whenever the edition changes.
errorServer to clientA code such as RATES_UNAVAILABLE (no snapshot yet; the connection stays open) or RATES_UNSUPPORTED_CURRENCY.
pongServer to clientAnswer to a ping.
authClient to serverSends the key as the first message. Only valid once.
subscribeClient to serverReplaces the base and symbols filter. Omit symbols for every currency. The server answers with a rates message.
pingClient to serverGets a pong. Optional; the server pings you every 30 seconds.

Every message is a JSON text frame, for example {"type":"rates","data":{"base":"EGP","updatedAt":"…","nextUpdateAt":"…","rates":{"EUR":0.01895679012}}}. A malformed message or an unknown type gets an error with RATES_INVALID_INPUT, and the connection stays open.

Browser example

JavaScript
const socket = new WebSocket('wss://api.currencyexchangerate-api.com/api/v1/stream?base=EGP&symbols=EUR,USD'); socket.addEventListener('open', () => {  // Browsers can't set headers, so send the key as the first message.  socket.send(JSON.stringify({ type: 'auth', key: 'YOUR_API_KEY' }));}); socket.addEventListener('message', (event) => {  const message = JSON.parse(event.data);  if (message.type === 'rates') console.log(message.data.rates);  if (message.type === 'error') console.error(message.code);});

Limits

  • Your plan sets how many connections you can hold across all of your keys: Starter 1, Pro 3, Business 5. One more closes with 4409. The free plan has no stream and closes with 4403.
  • Each connection may send 30 messages in any 60 seconds. More closes it with 1008.
  • Connecting counts as one request against your quota. Pushed rates don't count. Each connection re-checks its key and plan every 5 minutes, so a revoked key closes with 4401.

Close codes

CodeReasonMeaning
1001—The server is shutting down.
1008—Too many client messages.
1011RATES_INTERNAL_ERRORUnexpected failure.
4401API_KEY_INVALIDMissing, unknown, or revoked key.
4403RATES_PLAN_UPGRADE_REQUIREDYour plan has no WebSocket stream.
4408RATES_STREAM_AUTH_TIMEOUTNo auth message within 10 seconds.
4409RATES_STREAM_LIMIT_REACHEDYour plan’s connections are all open.
4422RATES_INVALID_INPUT or RATES_UNSUPPORTED_CURRENCYBad base or symbols in the query.
4429API_RATE_LIMITED or API_QUOTA_EXCEEDEDA limit from API access.