6 min read

Currency API with CORS Enabled — Use Live Exchange Rates Directly in the Browser

The Currency Exchange Tool API has CORS enabled (Access-Control-Allow-Origin: *). Call live exchange rates directly from browser JavaScript — no proxy, no server, no API key.

Currency API with CORS Enabled — No Proxy, No Server, No Key

Last updated: June 28, 2026 — Read time: 6 min

Most free currency APIs have a fatal flaw for frontend developers: they don't support CORS. You can't call them from the browser. You need a backend proxy, a serverless function, or a build-time fetch. It's a headache.

The Currency Exchange Tool API is different. Every endpoint returns Access-Control-Allow-Origin: * and supports OPTIONS preflight. You can call it directly from fetch() in the browser — no proxy, no server, no API key.

Try the API now →


What Is CORS and Why Does It Matter?

CORS (Cross-Origin Resource Sharing) is a browser security mechanism. By default, fetch() from https://yoursite.com to https://api.someother.com is blocked — unless the API explicitly allows it with CORS headers.

API CORS Support Browser Call Possible?
Currency Exchange Tool Access-Control-Allow-Origin: * Yes — directly
ExchangeRate-API No — needs backend proxy
Fixer.io No — needs backend proxy
Open Exchange Rates No — needs backend proxy
Frankfurter Yes

If an API lacks CORS, you need one of these workarounds:

  1. A backend proxy (Express, FastAPI, Cloudflare Worker) — adds cost and latency
  2. A serverless function (Vercel, Netlify) — another thing to deploy and maintain
  3. JSONP (deprecated, insecure) — not recommended

With CORS enabled, none of that is necessary. Just fetch() and go.


Browser Fetch — The Simplest Example

// This works directly in the browser console — no proxy, no server
const res = await fetch(
  'https://www.currencyexchangetool.com/api/v1/convert?amount=100&from=USD&to=EUR'
);
const data = await res.json();
console.log(`100 USD = ${data.result} EUR`);

Every response includes the CORS header:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json

Building a Live Rate Widget

Here is a complete embeddable currency rate widget that works with zero backend:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Live Currency Rate</title>
  <style>
    .rate-widget {
      font-family: -apple-system, BlinkMacSystemFont, sans-serif;
      background: #f9fafb;
      border: 1px solid #e5e7eb;
      border-radius: 12px;
      padding: 16px;
      max-width: 320px;
    }
    .rate-widget .amount { font-size: 28px; font-weight: 800; color: #111827; }
    .rate-widget .rate { font-size: 13px; color: #6b7280; margin-top: 4px; }
    .rate-widget .change { font-size: 12px; margin-top: 2px; }
    .rate-widget .change.up { color: #059669; }
    .rate-widget .change.down { color: #dc2626; }
    .rate-widget .updated { font-size: 11px; color: #9ca3af; margin-top: 8px; }
  </style>
</head>
<body>
  <div class="rate-widget">
    <div id="loading">Loading rate...</div>
    <div id="content" style="display: none;">
      <div class="amount" id="result"></div>
      <div class="rate" id="rate"></div>
      <div class="change" id="change"></div>
      <div class="updated" id="updated"></div>
    </div>
  </div>

  <script>
    (async function () {
      const API = 'https://www.currencyexchangetool.com/api/v1/convert';

      try {
        const res = await fetch(`${API}?amount=1&from=USD&to=UAH`);
        const data = await res.json();

        if (!data.success) throw new Error(data.error);

        // Show result
        document.getElementById('loading').style.display = 'none';
        document.getElementById('content').style.display = 'block';
        document.getElementById('result').textContent =
          `1 ${data.from} = ${data.result} ${data.to}`;
        document.getElementById('rate').textContent =
          `Rate: ${data.rate}`;

        // 24h change
        const changeEl = document.getElementById('change');
        if (data.changePct24h) {
          const change = data.changePct24h;
          changeEl.textContent =
            `24h: ${change > 0 ? '+' : ''}${change.toFixed(2)}%`;
          changeEl.className = `change ${change >= 0 ? 'up' : 'down'}`;
        }

        // When was this updated
        document.getElementById('updated').textContent =
          `Updated: ${new Date(data.updatedAt).toLocaleTimeString()}`;
      } catch (err) {
        document.getElementById('loading').textContent =
          `Error: ${err.message}`;
        document.getElementById('loading').style.color = '#dc2626';
      }
    })();
  </script>
</body>
</html>

Copy this HTML file anywhere — it works. No build step, no npm install, no API key.


React Component with CORS

import { useState, useEffect } from 'react';

export default function LiveRate({ from = 'USD', to = 'EUR' }) {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;

    async function fetchRate() {
      try {
        // CORS-enabled — no proxy needed
        const res = await fetch(
          `https://www.currencyexchangetool.com/api/v1/convert?amount=1&from=${from}&to=${to}`
        );
        const json = await res.json();
        if (!cancelled && json.success) setData(json);
        if (!cancelled && !json.success) setError(json.error);
      } catch (e) {
        if (!cancelled) setError(e.message);
      }
    }

    fetchRate();
    const interval = setInterval(fetchRate, 60000); // refresh every 60s
    return () => { cancelled = true; clearInterval(interval); };
  }, [from, to]);

  if (error) return <div style={{ color: '#dc2626' }}>⚠ {error}</div>;
  if (!data) return <div>Loading...</div>;

  const change = data.changePct24h || 0;
  const changeColor = change >= 0 ? '#059669' : '#dc2626';

  return (
    <div style={{ fontFamily: 'system-ui', padding: 12 }}>
      <div style={{ fontSize: 24, fontWeight: 800 }}>
        1 {data.from} = {data.result} {data.to}
      </div>
      <div style={{ fontSize: 13, color: '#6b7280' }}>
        Rate: {data.rate} ·
        <span style={{ color: changeColor }}>
          {' '}{change > 0 ? '+' : ''}{change.toFixed(2)}% 24h
        </span>
      </div>
    </div>
  );
}

How CORS Works — Technical Details

When your browser calls the API, it sends an Origin header:

GET /api/v1/convert?amount=1&from=USD&to=EUR HTTP/1.1
Host: www.currencyexchangetool.com
Origin: https://yoursite.com

The API responds with:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type

Access-Control-Allow-Origin: * means any origin can call this API. The browser sees this header, verifies it, and allows your JavaScript to read the response.

For OPTIONS preflight requests (browsers send these automatically for non-simple requests), the API returns:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS

No configuration needed — it just works.


Common Pitfalls (and Solutions)

"Failed to fetch" or "Network Error"

This usually means the API is blocked by a content blocker, ad blocker, or corporate firewall. Test with:

fetch('https://www.currencyexchangetool.com/api/v1/currencies')
  .then(r => r.json())
  .then(d => console.log('OK:', d.count, 'currencies'))
  .catch(e => console.error('Blocked:', e));

Rate Limiting (429)

The browser API allows 100 req/min. If you exceed this:

  • Cache responses for 60 seconds
  • Check X-RateLimit-Remaining before making requests
  • Honor the Retry-After header

Caching Stale Rates

For display purposes, cache the rate for 60 seconds in localStorage:

function getCachedRate(from, to) {
  const key = `rate_${from}_${to}`;
  const cached = localStorage.getItem(key);
  if (cached) {
    const { rate, timestamp } = JSON.parse(cached);
    if (Date.now() - timestamp < 60_000) return rate;
  }
  return null;
}

function setCachedRate(from, to, rate) {
  localStorage.setItem(
    `rate_${from}_${to}`,
    JSON.stringify({ rate, timestamp: Date.now() })
  );
}

Why This Matters for Developers

  1. No backend needed — build a fully static site with live exchange rates
  2. No API key management — nothing to leak, rotate, or store in env vars
  3. Faster development — prototype directly in the browser console
  4. No cost — no monthly cap, no paid tier upsell
  5. Simpler architecture — fewer moving parts, fewer failure points

📖 Read the full API docs → 📘 JavaScript API guide → 🐍 Python API guide →

C
Written by
Currency Converter Team
Financial Technology Experts

We are a team of financial technology developers dedicated to providing accurate, real-time currency conversion tools. Our mission is to make financial data accessible to everyone.