Home Documentation Pricing API Status Blog FAQ Support

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:

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:

  1. Never delete old cached data when a refresh fails. Stale data is always better than no data.
  2. Log cache misses and API errors. Monitor them so you know when something is wrong.
  3. Set a maximum stale age. A 1-hour-old rate is fine. A 24-hour-old rate during market volatility might not be.
  4. Show the rate age to users. "Rate as of 14:30 UTC" is better than silently showing stale data.

Common Mistakes to Avoid

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