Currency conversion API: real-time exchange rates over REST
Your e-commerce store sells to customers in 30 countries. You display prices in the buyer's local currency. That means you need real-time exchange rates, and you need them to be free during development and cheap in production.
Most forex APIs charge per request, require OAuth flows, or return XML from the early 2000s. The botoi currency API gives you two POST endpoints that return JSON, support 170+ currencies, and work anonymously on the free tier. No API key needed to get started.
Convert between any two currencies
The /v1/currency/convert endpoint takes a source currency, target currency, and
amount. It returns the converted result and the exchange rate used.
curl -X POST https://api.botoi.com/v1/currency/convert \
-H "Content-Type: application/json" \
-d '{"from": "USD", "to": "EUR", "amount": 99.99}' Response:
{
"success": true,
"data": {
"from": "USD",
"to": "EUR",
"amount": 99.99,
"result": 91.79,
"rate": 0.9180
}
}
The rate field is the raw exchange rate. The result field is the
converted amount, rounded to two decimal places. You get both so you can display the rate
to users or verify the math in your own code.
Fetch all exchange rates at once
If you need rates for multiple currencies, calling /v1/currency/convert for
each pair is wasteful. The /v1/currency/rates endpoint returns every available
rate for a given base currency in a single response.
curl -X POST https://api.botoi.com/v1/currency/rates \
-H "Content-Type: application/json" \
-d '{"base": "USD"}' Response (truncated to 9 currencies):
{
"success": true,
"data": {
"base": "USD",
"rates": {
"EUR": 0.9180,
"GBP": 0.7891,
"JPY": 149.52,
"CAD": 1.3612,
"AUD": 1.5340,
"CHF": 0.8821,
"INR": 83.4150,
"BRL": 4.9720,
"MXN": 17.1340
}
}
} One request, 170+ rates. Cache this response for an hour and you can convert any amount locally without making additional API calls. This is the approach to take for checkout flows and pricing pages where you need multiple currencies at once.
Price display component that converts on the fly
A pricing page where users pick their currency from a dropdown. This Preact component calls the convert endpoint when the selection changes and displays the result.
import { useState, useEffect } from "preact/hooks";
const SUPPORTED_CURRENCIES = ["USD", "EUR", "GBP", "JPY", "CAD"];
function PriceDisplay({ priceUsd }) {
const [currency, setCurrency] = useState("USD");
const [converted, setConverted] = useState(priceUsd);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (currency === "USD") {
setConverted(priceUsd);
return;
}
setLoading(true);
fetch("https://api.botoi.com/v1/currency/convert", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
from: "USD",
to: currency,
amount: priceUsd,
}),
})
.then((res) => res.json())
.then(({ data }) => {
setConverted(data.result);
setLoading(false);
});
}, [currency, priceUsd]);
return (
<div>
<select
value={currency}
onChange={(e) => setCurrency(e.target.value)}
>
{SUPPORTED_CURRENCIES.map((c) => (
<option key={c} value={c}>{c}</option>
))}
</select>
<span>
{loading ? "..." : `\${converted.toFixed(2)} \${currency}`}
</span>
</div>
);
} The component defaults to USD and only calls the API when the user selects a different currency. For production, wrap this in a cache layer (covered below) so switching back and forth between currencies doesn't repeat API calls.
Node.js middleware for multi-currency checkout
E-commerce checkouts need to convert the entire cart from your base currency to the buyer's local currency. This middleware fetches rates once, caches them for an hour, and converts every line item without extra API calls.
import express from "express";
const app = express();
app.use(express.json());
// Cache rates in memory with a 1-hour TTL
let ratesCache = null;
let cacheTimestamp = 0;
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
async function getRates(base) {
const now = Date.now();
if (ratesCache && ratesCache.base === base && now - cacheTimestamp < CACHE_TTL_MS) {
return ratesCache;
}
const res = await fetch("https://api.botoi.com/v1/currency/rates", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": process.env.BOTOI_API_KEY,
},
body: JSON.stringify({ base }),
});
const { data } = await res.json();
ratesCache = data;
cacheTimestamp = now;
return data;
}
function convertAmount(rates, from, to, amount) {
if (from === to) return amount;
// If "from" is the base currency, multiply by the target rate
if (from === rates.base) {
return Math.round(amount * rates.rates[to] * 100) / 100;
}
// Otherwise, convert to base first, then to target
const inBase = amount / rates.rates[from];
return Math.round(inBase * rates.rates[to] * 100) / 100;
}
app.post("/checkout", async (req, res) => {
const { items, currency } = req.body;
// Calculate total in USD (your base currency)
const totalUsd = items.reduce((sum, item) => sum + item.price * item.qty, 0);
// Convert to the buyer's currency
const rates = await getRates("USD");
const totalLocal = convertAmount(rates, "USD", currency, totalUsd);
res.json({
currency,
total: totalLocal,
rate: rates.rates[currency],
items: items.map((item) => ({
...item,
localPrice: convertAmount(rates, "USD", currency, item.price),
})),
});
});
app.listen(3000);
The convertAmount function handles two scenarios: direct conversion when
the source matches the base currency, and cross-rate conversion when it doesn't. The
in-memory cache means the API gets called once per hour, not once per checkout.
For high-traffic stores, move the cache to Redis or your edge KV store so all server instances share the same cached rates.
Caching exchange rates to reduce API calls
Exchange rates don't change by the second. For most applications, caching rates for 1-4 hours is enough. This pattern uses an in-memory Map with TTL-based expiration and warms the cache on startup for your most common currency pairs.
const RATES_CACHE = new Map();
const CACHE_TTL_MS = 4 * 60 * 60 * 1000; // 4 hours
async function getCachedRate(from, to) {
const key = `\${from}:\${to}`;
const cached = RATES_CACHE.get(key);
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
return cached.rate;
}
const res = await fetch("https://api.botoi.com/v1/currency/convert", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": process.env.BOTOI_API_KEY,
},
body: JSON.stringify({ from, to, amount: 1 }),
});
const { data } = await res.json();
RATES_CACHE.set(key, { rate: data.rate, timestamp: Date.now() });
return data.rate;
}
// Warm the cache on startup for your most common pairs
async function warmCache() {
const pairs = [
["USD", "EUR"],
["USD", "GBP"],
["USD", "JPY"],
["USD", "CAD"],
["USD", "AUD"],
];
await Promise.all(pairs.map(([from, to]) => getCachedRate(from, to)));
console.log("Rate cache warmed for", pairs.length, "pairs");
}
warmCache();
The warmCache function runs at startup and pre-fetches your five most common
pairs. After that, each pair is refreshed on its own schedule when the TTL expires. You stay
well within the free tier's 100 requests per day limit, even with dozens of currency pairs.
Choosing a cache TTL
- 1 hour: Good for checkout flows where you want rates to stay fresh during a user session.
- 4 hours: Good for pricing pages, dashboards, and reporting tools. Rates update once per business day, so 4-hour caching captures changes within the same day.
- 24 hours: Fine for analytics, invoicing, and financial reports where yesterday's rate is acceptable.
Key points
-
/v1/currency/convertconverts a specific amount between any two of 170+ supported currencies. Returns the converted result and the rate used. -
/v1/currency/ratesreturns all available exchange rates for a base currency in one response. Cache it locally to avoid per-conversion API calls. -
Both endpoints work anonymously at 5 requests per minute and 100 per day. Pass an
X-Api-Keyheader for higher limits. - Cache rates for 1-4 hours depending on your accuracy requirements. Exchange rates update once per business day.
- The API runs on Cloudflare's edge network. Response times are 20-50ms. Pair it with local caching and your currency conversion adds zero latency to user-facing requests.
Frequently asked questions
- How many currencies does the API support?
- The API supports 170+ fiat and common digital currencies. Send a POST to /v1/currency/rates with any supported base currency to get the full list of available targets in the response.
- Is the currency conversion API free?
- Anonymous access is available at 5 requests per minute and 100 requests per day with IP-based rate limiting. No API key or account required. Paid plans start at $9/month for higher throughput.
- How often are exchange rates updated?
- Rates are sourced from the European Central Bank and other public financial data providers. They update once per business day, typically around 16:00 CET. For most e-commerce and SaaS pricing use cases, daily rates are sufficient.
- Can I convert between two non-USD currencies?
- Yes. The /v1/currency/convert endpoint accepts any supported currency pair. You can convert EUR to JPY, GBP to INR, or any other combination directly. The API handles the cross-rate calculation.
- What is the response time?
- The API runs on Cloudflare Workers at the edge. Typical response times are 20-50ms depending on your region. Caching rates locally reduces this to zero for repeat lookups.
More tutorial posts
Start building with botoi
150+ API endpoints for lookup, text processing, image generation, and developer utilities. Free tier, no credit card.