Free Currency Converter API for JavaScript: Browser Code, No Backend (2026)
Most currency APIs are built to be called from a server. Try them from browser JavaScript and you hit one of two walls: a CORS error, or an API key sitting in your page source for anyone to copy.
This guide uses a free exchange rate API that needs no key and allows browser calls, so a converter on a static site, a CodePen or a GitHub Pages project works with no backend. Then it shows the right way to add real-time rates when you need them.
The endpoint
GET https://allratestoday.com/api/open/latest/{BASE} - No key, no signup.
- CORS open: it sends
Access-Control-Allow-Origin: *, sofetch()works from any site. - Data: the European Central Bank's daily reference rates for 30 currencies, re-based to whichever currency you put in the path.
- Terms: free for commercial sites too, as long as the page shows a visible link to allratestoday.com. The response includes the HTML to paste.
Other keyless options, and their limits, are in free currency APIs without a key.
1. Fetch the rates
const res = await fetch("https://allratestoday.com/api/open/latest/USD");
const data = await res.json();
console.log(data.date); // "2026-10-23"
console.log(data.rates.EUR); // 0.89341553
console.log(data.rates.GBP); // 0.74803895 That is the whole integration. data.rates maps each currency code to the amount you get for 1 unit of the base.
2. Convert an amount
With one base table you can convert between any two currencies in it, so you only need one request:
function convert(amount, from, to, table) {
const { base_code, rates } = table;
if (!(from in rates) || !(to in rates)) throw new Error(`Unsupported: ${from} or ${to}`);
// rates are "1 base = x currency", so go through the base
const inBase = from === base_code ? amount : amount / rates[from];
return to === base_code ? inBase : inBase * rates[to];
}
convert(250, "GBP", "JPY", data); // GBP to JPY via USD Keep full precision while you calculate and round only when you display the result.
3. Format it properly
Do not build currency strings by hand. Intl.NumberFormat knows every currency's symbol and decimal places:
const fmt = (value, currency, locale = navigator.language) =>
new Intl.NumberFormat(locale, { style: "currency", currency }).format(value);
fmt(1234.5, "EUR", "de-DE"); // "1.234,50 €"
fmt(1234.5, "JPY", "ja-JP"); // "¥1,235" (no decimals for yen)
fmt(1234.5, "INR", "en-IN"); // "₹1,234.50" 4. Cache until the next update
ECB rates change once per business day, so every visitor fetching on every page load is wasted work. The response tells you when the next update is due in time_next_update_unix, so cache until then:
async function getRates(base = "USD") {
const key = `fx:${base}`;
try {
const cached = JSON.parse(localStorage.getItem(key));
if (cached && Date.now() / 1000 < cached.time_next_update_unix) return cached;
} catch {} // storage can be blocked in private windows
const res = await fetch(`https://allratestoday.com/api/open/latest/${base}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
try { localStorage.setItem(key, JSON.stringify(data)); } catch {}
return data;
} 5. A complete converter in one HTML file
Paste this into an .html file and open it. No build step, no server:
<form id="cc">
<input id="amount" type="number" value="100" min="0" step="any">
<select id="from"></select> to <select id="to"></select>
<output id="result"></output>
<small>ECB rates for <span id="date"></span> ·
<a href="https://allratestoday.com">Exchange rates by AllRatesToday</a></small>
</form>
<script type="module">
const res = await fetch("https://allratestoday.com/api/open/latest/USD");
const table = await res.json();
const codes = Object.keys(table.rates).sort();
const $ = (id) => document.getElementById(id);
for (const sel of [$("from"), $("to")])
sel.innerHTML = codes.map((c) => `<option>${c}</option>`).join("");
$("from").value = "USD";
$("to").value = "EUR";
$("date").textContent = table.date;
function update() {
const { rates } = table;
const amount = Number($("amount").value) || 0;
const value = amount / rates[$("from").value] * rates[$("to").value];
$("result").textContent = new Intl.NumberFormat(undefined, {
style: "currency", currency: $("to").value,
}).format(value);
}
$("cc").addEventListener("input", update);
update();
</script> The link in the <small> line is what keeps it free. Leave it visible.
6. The same thing in React
import { useEffect, useState } from "react";
export function useRates(base = "USD") {
const [table, setTable] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
let live = true;
fetch(`https://allratestoday.com/api/open/latest/${base}`)
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`))))
.then((d) => live && setTable(d))
.catch((e) => live && setError(e));
return () => { live = false; };
}, [base]);
return { table, error };
} For a ready-made version, the MIT react-currency-converter template runs as soon as you clone it. To show prices in each visitor's local currency, see currency conversion in React.
Do not want to write code?
The embeddable currency converter widget is one script tag. It is free, keyless and renders the attribution link for you.
Adding real-time rates without leaking your key
The keyless endpoint gives daily ECB rates for 30 currencies. When you need rates that move during the day, or currencies the ECB does not publish (NGN, PKR, ARS and many more), you need an API key. Keyed endpoints deliberately do not send open CORS headers: a key in browser code is a public key.
The fix is a small proxy that holds the key. The browser calls your endpoint, your endpoint calls the API. On Cloudflare Workers:
export default {
async fetch(request, env) {
const url = new URL(request.url);
const from = url.searchParams.get("from") ?? "USD";
const to = url.searchParams.get("to") ?? "EUR";
const upstream = await fetch(
`https://allratestoday.com/api/v1/rates?source=${from}&target=${to}`,
{ headers: { Authorization: `Bearer ${env.ALLRATESTODAY_API_KEY}` } },
);
return new Response(upstream.body, {
status: upstream.status,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "https://your-site.example",
"Cache-Control": "public, max-age=60",
},
});
},
}; Store the key with wrangler secret put ALLRATESTODAY_API_KEY. A Next.js route handler, a Netlify function or an Express route works the same way. The 60-second cache matches how often real-time rates update, and it keeps your request count low. Node.js details are in best exchange rate API for Node.js.
Checklist
- Use a keyless, CORS-enabled endpoint in the browser. Never ship an API key.
- Fetch one base table and convert locally. Do not make one request per pair.
- Cache until
time_next_update_unix. - Show the rate date, so users know the figure is from the last ECB fixing.
- Format with
Intl.NumberFormat. - Keep the attribution link visible.
FAQ
Is there a free currency converter API for JavaScript?
Yes. https://allratestoday.com/api/open/latest/USD returns the latest ECB rates with any of 30 currencies as the base. It needs no key and sends CORS headers, so fetch() works directly from the browser. Commercial use is allowed with a visible link to allratestoday.com.
Why do I get a CORS error calling an exchange rate API?
The API is not sending Access-Control-Allow-Origin for your site. Keyed endpoints usually do not, on purpose: a key in browser code is public. Use a keyless endpoint that sends CORS headers, or call the keyed API from your own server and expose your own endpoint to the frontend.
Can I put my API key in front-end JavaScript?
No. Anything in browser code, including environment variables bundled at build time, can be read by every visitor. Keep keys on a server, a serverless function or an edge worker, and have the browser call that.
How often should a browser converter fetch rates?
Daily rates change once a day, so fetch once and cache until the next update. The keyless response includes time_next_update_unix, which tells you exactly how long to keep the cached copy.
How do I format the converted amount?
Use Intl.NumberFormat with style: 'currency'. It applies the right symbol, separators and decimals for each currency and locale, for example 0 decimals for JPY and 2 for EUR.
Need real-time rates?
Live mid-market rates for 160+ currencies, updated every 60 seconds. Free API key, no credit card.
Get your free API key