How to Cache Exchange Rates and Avoid Rate Limit Errors
You integrated an exchange rate API, deployed to production, and then hit HTTP 429: Rate limit exceeded. This is the most common operational issue developers face with exchange rate APIs — and it is entirely preventable with proper caching.
This guide covers practical caching strategies for exchange rate data, with code examples you can drop into your JavaScript or Python application today.
Why You Need to Cache Exchange Rates
Exchange rates don't change every millisecond. Even "real-time" APIs like AllRatesToday update every 60 seconds. Fetching the same rate on every page load, every API call, or every user request is wasteful and will quickly exhaust your quota.
Consider a simple e-commerce site with 10,000 daily visitors. If each page load fetches an exchange rate:
- Without caching: 10,000 API calls/day = 300,000/month. You'll need a Large plan.
- With 5-minute cache: 288 API calls/day = 8,640/month. A Small plan covers it.
- With 1-hour cache: 24 API calls/day = 720/month. The Free tier is enough.
The math: If your API updates every 60 seconds, caching for 60 seconds means you never miss an update. Caching for 5 minutes means you are at most 4 minutes behind — which is perfectly acceptable for price displays, dashboards, and most applications.
Choosing Your Cache Duration (TTL)
| Use Case | Recommended TTL | Why |
|---|---|---|
| Trading / Forex | No cache or 1–5s | Every second counts |
| Payment checkout | 30–60 seconds | Rate should be current at time of charge |
| E-commerce price display | 1–5 minutes | Visitors tolerate slight delays |
| Dashboard / analytics | 5–15 minutes | Refresh rates match dashboard refresh |
| Invoicing / billing | 1–24 hours | Rate is locked at invoice generation time |
| Accounting reports | 24 hours | End-of-day rates are standard |
Strategy 1: Simple In-Memory Cache (JavaScript)
The simplest approach — a JavaScript Map with TTL. No external dependencies needed.
import AllRatesToday from '@allratestoday/sdk';
const client = new AllRatesToday('YOUR_API_KEY');
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes in milliseconds
async function getRate(source, target) {
const key = `${source}_${target}`;
const cached = cache.get(key);
// Return cached value if still fresh
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.rate;
}
// Fetch fresh rate
const rate = await client.getRate(source, target);
cache.set(key, { rate, timestamp: Date.now() });
return rate;
}
// Usage — only hits the API once per 5 minutes per currency pair
const rate1 = await getRate('USD', 'EUR'); // API call
const rate2 = await getRate('USD', 'EUR'); // Served from cache
const rate3 = await getRate('USD', 'GBP'); // API call (different pair) Strategy 2: Simple In-Memory Cache (Python)
import time
from allratestoday import AllRatesToday
client = AllRatesToday("YOUR_API_KEY")
_cache = {}
CACHE_TTL = 300 # 5 minutes in seconds
def get_rate(source: str, target: str) -> float:
key = f"{source}_{target}"
cached = _cache.get(key)
if cached and time.time() - cached["timestamp"] < CACHE_TTL:
return cached["rate"]
rate = client.get_rate(source, target)
_cache[key] = {"rate": rate, "timestamp": time.time()}
return rate
# Usage
rate = get_rate("USD", "EUR") # API call
rate = get_rate("USD", "EUR") # Served from cache Strategy 3: Stale-While-Revalidate
This is the best strategy for production applications. Serve the cached (possibly stale) rate immediately, then refresh in the background. Users never wait for an API call.
import AllRatesToday from '@allratestoday/sdk';
const client = new AllRatesToday('YOUR_API_KEY');
const cache = new Map();
const FRESH_TTL = 60 * 1000; // Consider fresh for 1 minute
const STALE_TTL = 10 * 60 * 1000; // Serve stale for up to 10 minutes
const refreshing = new Set();
async function getRate(source, target) {
const key = `${source}_${target}`;
const cached = cache.get(key);
const now = Date.now();
if (cached) {
const age = now - cached.timestamp;
if (age < FRESH_TTL) {
// Fresh — return immediately
return cached.rate;
}
if (age < STALE_TTL) {
// Stale but usable — return immediately, refresh in background
if (!refreshing.has(key)) {
refreshing.add(key);
client.getRate(source, target)
.then(rate => {
cache.set(key, { rate, timestamp: Date.now() });
})
.finally(() => refreshing.delete(key));
}
return cached.rate;
}
}
// No cache or expired — must wait for fresh data
const rate = await client.getRate(source, target);
cache.set(key, { rate, timestamp: now });
return rate;
}
// Every call returns instantly after the first fetch
const rate = await getRate('USD', 'EUR'); Why stale-while-revalidate is ideal: Exchange rates don't jump dramatically in 10 minutes (except during rare market events). Serving a 5-minute-old rate instantly is better than making your user wait 200ms for a rate that's 0.01% more accurate.
Strategy 4: Redis Cache (High-Traffic Apps)
For applications running multiple server instances, an in-memory Map won't work — each instance has its own cache and makes its own API calls. Use Redis to share a single cache across all instances.
import AllRatesToday from '@allratestoday/sdk';
import Redis from 'ioredis';
const client = new AllRatesToday('YOUR_API_KEY');
const redis = new Redis(process.env.REDIS_URL);
const CACHE_TTL = 300; // 5 minutes in seconds
async function getRate(source, target) {
const key = `exchange_rate:${source}:${target}`;
// Check Redis cache
const cached = await redis.get(key);
if (cached) {
return parseFloat(cached);
}
// Fetch and cache
const rate = await client.getRate(source, target);
await redis.setex(key, CACHE_TTL, rate.toString());
return rate;
}
// All server instances share the same cache
// Only one instance fetches per currency pair per 5 minutes Strategy 5: Batch Prefetch
If you know which currency pairs your app needs, fetch them all upfront on a schedule instead of on-demand. This is the most efficient approach for apps with a known set of currencies.
import AllRatesToday from '@allratestoday/sdk';
const client = new AllRatesToday('YOUR_API_KEY');
let ratesCache = {};
const SUPPORTED_CURRENCIES = ['EUR', 'GBP', 'JPY', 'CAD', 'AUD', 'CHF', 'INR'];
async function refreshAllRates() {
try {
const rates = {};
// Single API call gets all rates from USD
const allRates = await client.getRates('USD');
for (const currency of SUPPORTED_CURRENCIES) {
rates[`USD_${currency}`] = allRates[currency];
}
rates.lastUpdated = Date.now();
ratesCache = rates;
console.log('Rates refreshed at', new Date().toISOString());
} catch (err) {
console.error('Rate refresh failed, keeping stale cache:', err.message);
// Don't clear the cache on error — stale data is better than no data
}
}
// Refresh every 5 minutes
refreshAllRates();
setInterval(refreshAllRates, 5 * 60 * 1000);
// Synchronous access — no await needed
function getRate(source, target) {
return ratesCache[`${source}_${target}`];
} Request math: With batch prefetch every 5 minutes, you make 1 API call per 5 minutes = 288 calls/day = 8,640/month. That is well within the Small plan (5,000/month) and serves unlimited concurrent users.
Handling Cache Failures Gracefully
What happens when the API is down or your cache is empty? Here are the rules:
- Never delete old cached data when a refresh fails. Stale data is always better than no data.
- Log cache misses and API errors. Monitor them so you know when something is wrong.
- Set a maximum stale age. A 1-hour-old rate is fine. A 24-hour-old rate during market volatility might not be.
- Show the rate age to users. "Rate as of 14:30 UTC" is better than silently showing stale data.
Common Mistakes to Avoid
- Fetching on every request: The #1 cause of rate limit errors. Always cache.
- Client-side API calls: Never expose your API key in frontend JavaScript. Fetch server-side, cache, and serve via your own endpoint.
- Cache per user: Exchange rates are the same for everyone. Use a global cache, not per-session.
- Ignoring cache stampede: When the cache expires, 100 concurrent requests all hit the API at once. Use stale-while-revalidate or a lock mechanism to prevent this.
Security: Never call the exchange rate API directly from the browser. Your API key will be visible in the network tab. Always proxy through your backend and cache there.
Summary
| Strategy | Best For | Complexity |
|---|---|---|
| Simple TTL cache | Small apps, single server | Low |
| Stale-while-revalidate | User-facing apps needing instant response | Medium |
| Redis cache | Multi-instance / high-traffic apps | Medium |
| Batch prefetch | Known currency set, predictable traffic | Low |
Build Smarter with AllRatesToday
60-second updates, 160+ currencies, official SDKs with built-in best practices. Start with 300 free requests/month — plenty for a cached application.
Get Your Free API Key