Documentation

Caching Reference Rates

Cache CurrencyCore rates by date and base, use long TTLs for past dates, and keep today's cache within your business tolerance.


Serving a stale rate can misprice an invoice or a checkout. The safer default is to cache snapshots by date and base, keep historical dates as long-lived entries, and hold today’s snapshot for only as long as your business can tolerate a stale value.

CurrencyCore rates are central-bank reference rates polled hourly, and the underlying fixings are daily. Most values change once per business day, but that is not a promise of intraday freshness. Historical snapshots are stable enough to cache for a long time, but they are not versioned. If a source revises a fixing, keep both observations and flag the change. In a currency’s history, dates before its first quote are absent rather than filled.

DataCache keySuggested lifetime
Historical snapshotbase + dateLong lived; store the observation date
Today’s snapshotbase + dateA few minutes, chosen from your tolerance
/countries, /currenciesnone24 hours; public and unmetered

Do not build a conditional-request layer. /v1 does not send ETag or Last-Modified, so If-None-Match and If-Modified-Since will not produce a 304. Caching is yours to implement.

Pick the TTL for today’s snapshot from the decision it supports. A display-only dashboard can tolerate more staleness than an invoice that fixes a total. If you need the latest fixing as soon as it appears, shorten the TTL or refresh on a schedule that matches the hourly poll.

type Snapshot = {
  base: string;
  date: string;
  rates: Record<string, number>;
  requestedDate?: string;
  asOf?: Record<string, string>;
};
const cache = new Map<string, { expiresAt: number; body: Snapshot }>();

async function getSnapshot(base: string, date: string, key: string) {
  const id = `${base}:${date}`;
  const hit = cache.get(id);
  if (hit && hit.expiresAt > Date.now()) return hit.body;

  const url = new URL(`https://api.currency-core.com/v1/rates/${base}`);
  url.searchParams.set("date", date);
  const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } });
  if (!res.ok) throw new Error(`rates ${res.status}`);
  const body = (await res.json()) as Snapshot;

  const today = new Date().toISOString().slice(0, 10);
  const ttl = date < today ? 30 * 86_400_000 : 5 * 60_000;
  cache.set(id, { expiresAt: Date.now() + ttl, body });
  return body;
}

A long cache on a historical date is reasonable, but keep the observation date and handle a source revision. A cache miss outside your plan window is not a glitch. Free history is the last 30 days, and older dates return 403, so do not retry that response as though more waiting would help.