GuidesCURRENCY APIS
Free currency APIs tested from the browser
We tested 12 free currency exchange rate APIs from a browser on 26 October 2026: 11 send CORS headers and work with fetch() directly, only the ECB XML feed needs a proxy, and 8 need no API key. Frankfurter is the default: no key, 228 currencies, one request per conversion. Keyed free tiers give just 100 to 1,000 requests a month.
Every rates API we tested
We ran each endpoint on 26 October 2026 with curl and an Origin: https://example.com header from a German datacenter IP and a US residential IP, then in Chromium 147.0.7727.15 from http://127.0.0.1:8001. More browser-ready endpoints are in our list of 70 free public APIs.
| API | Key | CORS | Free limit | Updates | Notes |
|---|---|---|---|---|---|
| Frankfurter | None | Yes, ACAO * | No quota published | As each source publishes | 228 currencies from 106 sources in v2; open source |
| fawazahmed0 currency-api | None | Yes, ACAO * | No rate limits | Daily | 340 currencies incl. crypto; use the pages.dev fallback |
| ExchangeRate-API open access | None | Yes, ACAO * | Hourly requests never limited; a 429 lasts 20 minutes | Every 24 hours | 166 currencies; attribution link required |
| ECB reference rates (XML) | None | No, use a proxy | One small XML file | About 16:00 CET on working days | 29 currencies against the euro |
| exchangerate.fun | None | Yes, ACAO * | No rate limits | Hourly | 172 currencies; behind a Cloudflare bot check |
| ohmyfin | None | Yes, ACAO * | No quota published | ECB working days | ECB rates with convert and history endpoints |
| FloatRates | None | Yes, ACAO * | No quota published | Every 12 hours | 146 currencies, one JSON file per base |
| Coinbase exchange-rates | None | Yes, ACAO * | Public, no auth | Live | 659 rates incl. crypto |
| exchangerate.host | Access key, now required | Yes, ACAO * | 100 requests/month | Daily | Non-commercial on the free plan |
| Fixer | Access key | Yes, ACAO * | 100 requests/month | Hourly | Key goes in access_key= |
| Open Exchange Rates | App ID | Yes, ACAO * | 1,000 requests/month | Hourly | USD base only on the free plan |
| CurrencyAPI.com | API key | Yes, echoes Origin | 300 requests/month | Not stated for free | Includes historical rates; non-commercial |
Frankfurter: convert without a key
Frankfurter (https://api.frankfurter.dev) needs no key, sends Access-Control-Allow-Origin: *, publishes no quota, and is open source if you want to self-host it. The old host api.frankfurter.app now answers with a 301 redirect, so update old code.
Version 2 covers 228 currencies from 106 central banks and official sources, with data back to 1948. GET /v2/rate/usd/eur returned {"date":"2026-10-26","base":"USD","quote":"EUR","rate":0.89153}. By default v2 blends its sources; add ?providers=ecb for European Central Bank rates only, which on Monday 26 October returned Friday's rate, 0.89342 for 2026-10-23. The older v1 still answers and lists 30 currencies.
const amount = 250;
const from = 'usd';
const to = 'eur';
const response = await fetch(`https://api.frankfurter.dev/v2/rate/${from}/${to}`);
const body = await response.json().catch(() => null);
if (!response.ok) {
throw new Error(`Frankfurter failed: ${body?.message ?? response.status}`);
}
console.log(`${amount} ${body.base} = ${(amount * body.rate).toFixed(2)} ${body.quote}`);
console.log(`rate ${body.rate} for ${body.date}`);fawazahmed0 currency-api with a fallback
The fawazahmed0 currency-api is a set of static JSON files on jsDelivr, such as https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/usd.json. No key, no rate limits, updated daily, Access-Control-Allow-Origin: *, and 340 currencies including crypto. For a past date, replace @latest with a date like @2024-03-06.
Its README asks you to fall back to https://latest.currency-api.pages.dev/v1/currencies/usd.json when jsDelivr fails. Direct calls worked every time in our test; through the cors.dev proxy, jsDelivr failed once with 502 upstream_unreachable and then worked, and since the files send CORS headers, a proxy adds nothing here.
const base = 'usd';
const mirrors = [
`https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/${base}.min.json`,
`https://latest.currency-api.pages.dev/v1/currencies/${base}.min.json`,
];
let rates = null;
for (const url of mirrors) {
try {
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
if (response.ok) {
rates = await response.json();
break;
}
console.warn(`${url} answered ${response.status}`);
} catch (error) {
console.warn(`${url} failed: ${error.message}`);
}
}
if (!rates) {
throw new Error('Both currency-api mirrors failed');
}
console.log(rates.date, { eur: rates[base].eur, gbp: rates[base].gbp, btc: rates[base].btc });ExchangeRate-API open access rules
https://open.er-api.com/v6/latest/USD needs no key, sends Access-Control-Allow-Origin: * and covers 166 currencies. Its terms:
- Data refreshes once every 24 hours, and each response includes
time_next_update_utc. - Request once a day, or at most once an hour, and you never get rate limited.
- A rate-limited IP gets HTTP 429, lifted after 20 minutes.
- Show the attribution link
Rates By Exchange Rate API. - Caching is allowed, and so are personal and commercial conversions; redistributing the data is not.
ECB reference rates through a proxy
The European Central Bank publishes https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml with no key but no CORS headers, so Chromium 147 blocked it: No 'Access-Control-Allow-Origin' header is present on the requested resource. Prefix the URL with https://proxy.cors.dev/ and it returns 200, text/xml, 1,547 bytes; the 90-day history file eurofxref-hist-90d.xml (70,590 bytes) works too. The free CORS proxy needs no key or signup and handles GET and HEAD for text responses up to 1 MiB, with a 10-second deadline and 600 requests per minute per client IP.
The ECB usually updates the file around 16:00 CET every working day, except TARGET closing days, after a concertation between central banks around 14:10 CET. On Monday 26 October before 16:00 CET, the file was still dated 23 October and quoted 29 currencies against the euro. The ECB publishes these rates for information purposes only and strongly discourages using them for transactions.
const proxy = 'https://proxy.cors.dev/';
const url = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml';
const response = await fetch(proxy + url, { credentials: 'omit' });
if (!response.ok) {
throw new Error(`ECB via proxy failed: ${response.headers.get('X-Cors-Error') ?? response.status}`);
}
const xml = new DOMParser().parseFromString(await response.text(), 'application/xml');
const date = xml.querySelector('Cube[time]').getAttribute('time');
const rates = [...xml.querySelectorAll('Cube[currency]')].map((cube) => ({
currency: cube.getAttribute('currency'),
perEuro: Number(cube.getAttribute('rate')),
}));
console.log('ECB reference rates for', date);
console.table(rates);Keyed free tiers run out fast
All four keyed APIs answered a fake key with 401 and CORS headers, so they work from the browser once you have a key:
- exchangerate.host now needs an access key: 100 requests per month, daily updates, non-commercial use.
- Fixer: access key, 100 calls per month, hourly updates.
- Open Exchange Rates:
app_id, 1,000 requests per month, hourly updates, USD base only. - CurrencyAPI.com:
apikey, 300 requests per month including historical rates; commercial use needs an upgrade.
Polling once an hour costs about 720 requests a month (24 x 30), so a 100 to 300 request quota only works with a shared cache on your backend. A key in frontend code is visible to every visitor, who can copy it and spend your quota; see when a frontend needs an API proxy. These APIs already send CORS headers, and the cors.dev free tier refuses URLs with key=, apikey=, access_key= and similar parameters (HTTP 400, X-Cors-Error: credential_in_url).
Crypto and other no-key sources
Coinbase (https://api.coinbase.com/v2/exchange-rates?currency=USD) needs no auth and returned 659 rates, fiat and crypto, in 17,370 bytes. For crypto prices, see crypto price APIs from the browser. FloatRates (https://www.floatrates.com/daily/usd.json) returned 146 currencies and updates every 12 hours.
ohmyfin (https://ohmyfin.org/api/exchange/latest?base=USD&symbols=EUR) re-serves ECB rates with an attribution field and has convert, pair and historical endpoints; convert?from=USD&to=EUR&amount=100 returned 89.341553 for 2026-10-23. exchangerate.fun (https://api.exchangerate.fun/latest?base=USD) says it updates hourly with no rate limits, for personal and commercial use, and returned 172 currencies. It sits behind a Cloudflare bot check: headless Chromium on our datacenter IP got a 403 challenge page, which the browser reports as a CORS error, while a residential IP got 200.
Good questions.
Which currency API is free and needs no API key?
Eight of the 12 we tested: Frankfurter, the fawazahmed0 currency-api, ExchangeRate-API open access, the ECB XML feed, exchangerate.fun, ohmyfin, FloatRates and Coinbase. Frankfurter is the easiest default with 228 currencies and Access-Control-Allow-Origin: *.
When do ECB exchange rates update?
Usually around 16:00 CET on every working day, except TARGET closing days. Before 16:00 CET on a Monday, you still get Friday's rates.
Can I use free exchange rates for payments or invoices?
The ECB says its reference rates are for information only and strongly discourages using them for transactions. ExchangeRate-API open access and exchangerate.fun allow commercial use; the free plans of exchangerate.host and CurrencyAPI.com are non-commercial.
Do I have to credit the rate provider?
Check each provider's terms. ExchangeRate-API open access requires a visible Rates By Exchange Rate API link, and ohmyfin returns an attribution line for the ECB data with every response.
Why does the ECB XML file fail with a CORS error?
www.ecb.europa.eu sends no Access-Control-Allow-Origin header, so the browser won't let fetch() read the file. Fetch it through https://proxy.cors.dev/, or use Frankfurter with ?providers=ecb or ohmyfin, which serve ECB rates with CORS headers.
How often should my app fetch exchange rates?
ECB-based sources and ExchangeRate-API change once a day and FloatRates every 12 hours, so polling more than once an hour wastes requests. ExchangeRate-API open access never rate limits hourly or daily polling.