# 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.

Canonical: https://currency-core.com/docs/guides/caching/
Updated: 2026-11-11
Published: 2026-11-11

---
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.

| Data | Cache key | Suggested lifetime |
|---|---|---|
| Historical snapshot | `base` + `date` | Long lived; store the observation date |
| Today's snapshot | `base` + `date` | A few minutes, chosen from your tolerance |
| `/countries`, `/currencies` | none | 24 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.

```ts
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.

## Related

- [OpenAPI specification](/docs/openapi/)
- [Authentication](/docs/authentication/)
- [Errors](/docs/errors/)
- [Rate limits](/docs/rate-limits/)
- [Guides](/docs/guides/)
