WebSocket exchange rate API

Subscribe to supported pairs and receive exchange-rate updates over a persistent connection. Keep your API key on your server.

Create a free keyRead the API reference

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+ · server-side WebSocket client
// 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.

JSON · sample frame; values and time are illustrative
{
  "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 coverage

Plan limits

Connection count and pairs per connection depend on the plan. Each accepted connection consumes one monthly quota unit.

PlanConnectionsPairs per connectionCommercial use
Free15No
Basic325Yes
Pro10100Yes

Each accepted connection consumes one monthly quota unit.

View pricing

Commercial 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.