A tempting shortcut when your rate source is unreachable: serve the last cached rate and let the request succeed. For most APIs that's a reasonable degradation strategy. For anything touching money, it's the wrong default.
The problem isn't that a stale rate is wrong — it's that a stale rate looks identical to a correct one from the caller's side. A 200 response with a slightly-off number is far more dangerous than an honest failure, because nothing downstream knows to treat the value with suspicion. A checkout flow that silently converts at yesterday's rate doesn't fail loudly; it just quietly overcharges or undercharges, and nobody notices until a reconciliation problem shows up much later.
async function getRateOrFail(base, target) {
try {
const res = await fetch(upstreamUrl(base, target));
if (!res.ok) throw new Error(`upstream returned ${res.status}`);
return await res.json();
} catch (err) {
// Explicit failure, not a stale fallback — the caller needs to know
// "we don't have a trustworthy number right now," not get a wrong one.
throw new UpstreamError(err);
}
}
The tradeoff this makes explicit: you're accepting worse availability (a 502 during an upstream outage) in exchange for a hard guarantee that every successful response is a real, current-as-of-its-timestamp rate — never a guess dressed up as data.
This is the one place Currency API's "no fabricated data" principle gets tested in practice: if Frankfurter (the ECB rate proxy it uses) is unreachable, the API returns a clean 502 upstream_error instead of quietly serving whatever it last had cached. Same principle sibling API Validate applies to its VIES/MX lookups — fail honestly rather than silently degrade on anything where the caller can't tell the difference between fresh and stale. Third sibling API on the same account: QR API.
Top comments (0)