WebSocket exchange rate API
Subscribe to supported pairs and receive exchange-rate updates over a persistent connection. Keep your API key on your server.
Start a connection
Connect to wss://api.exchangerate.dev/v1/stream, send auth within 10 seconds, then subscribe after authenticated.
Connection example
This server-side sequence authenticates a key and subscribes to USD/EUR and USD/JPY.
// Node.js 22+ · run on your server · set EXCHANGERATE_API_KEY in the environment
const key = process.env.EXCHANGERATE_API_KEY;
if (!key) throw new Error("EXCHANGERATE_API_KEY is required");
let retries = 0;
let stopped = false;
function connect() {
const ws = new WebSocket("wss://api.exchangerate.dev/v1/stream");
ws.addEventListener("open", () => {
ws.send(JSON.stringify({ action: "auth", api_key: key }));
});
ws.addEventListener("message", ({ data }) => {
const frame = JSON.parse(String(data));
if (frame.type === "authenticated") {
retries = 0;
ws.send(JSON.stringify({ action: "subscribe", base: "USD", symbols: ["EUR", "JPY"] }));
} else if (frame.type === "snapshot" || frame.type === "update") {
if (frame.stale) return; // Keep the observation time; don't treat it as a fresh tick.
console.log(frame.base, frame.symbol, frame.rate, frame.effective_at);
} else if (frame.type === "state") {
console.log(frame.base, frame.symbol, frame.state); // stale or unavailable
} else if (frame.type === "error") {
console.error(frame.code, frame.message);
if (["invalid_api_key", "quota_exceeded", "connection_limit_exceeded", "subscription_limit_exceeded"].includes(frame.code)) {
stopped = true;
ws.close();
}
}
});
ws.addEventListener("close", () => {
if (stopped) return;
const delay = Math.min(30_000, 1_000 * 2 ** Math.min(retries++, 5));
setTimeout(connect, delay + Math.random() * 250);
});
}
connect();Authentication is required for WebSocket connections.
Read each message
A message includes the pair, rate, observed time and freshness state. Treat unavailable and stale as explicit states.
{
"type": "update",
"base": "USD",
"symbol": "JPY",
"rate": "154.792",
"effective_at": "2026-09-15T02:59:44Z",
"source": "live",
"source_type": "market",
"market_session": "open",
"derived": false,
"stale": false
}Coverage
Only the pairs listed in the current coverage table can be subscribed to. Crosses require fresh observations for both legs.
AUD, CAD, CHF, EUR, GBP, JPY, MXN, NOK, NZD, PLN, SEK, SGD, USD, ZAR · 182 Supported pairs
Updates are indicative observations. Check the status and observed time in each message.
Check current coveragePlan limits
Connection count and pairs per connection depend on the plan. Each accepted connection consumes one monthly quota unit.
| Plan | Connections | Pairs per connection | Commercial use |
|---|---|---|---|
| Free | 1 | 5 | No |
| Basic | 3 | 25 | Yes |
| Pro | 10 | 100 | Yes |
Each accepted connection consumes one monthly quota unit.
View pricingCommercial use
Check the current plan and commercial-use terms before distributing a product that depends on the stream.
Reconnect safely
On disconnect, back off before reconnecting, authenticate again, wait for authenticated, and resubscribe. Never put the key in a URL or public JavaScript.
Missing updates remain missing; the stream does not invent values.
Can I connect anonymously?
No. WebSocket connections require an API key. Anonymous evaluation is available through supported REST endpoints.
Does every currency pair stream?
No. WebSocket coverage is pair-specific. Check the coverage table and handle unsupported subscriptions explicitly.