Wiki/Finance & Settlements/Exchange Rates: One API Call a Day, and a Fallback That Needs No Key
06Finance & Settlements2 min read

Exchange Rates: One API Call a Day, and a Fallback That Needs No Key

The daily refresh job, the two sources behind it, and the history that can still tell you which rate was live on a Tuesday in March.

Exchange rates are the kind of infrastructure nobody thinks about until a settlement is in dollars and somebody asks where 1.0847 came from. The platform’s answer is deliberately small: one job, one cache, one history table, and an admin page with three controls on it.

One job, once a day

A cron calls /api/cron/refresh-rates at 07:00 UTC — 08:00 in Central European time. It fetches the latest EUR-based rates, keeps only the pairs whose currency is in the catalog, and writes them into the rate cache: one row per currency pair, replaced on each refresh. Cross-currency conversions are calculated at runtime from those EUR pairs, which is why a single API call a day covers every tenant on the platform. A Refresh now button exists for the mornings when waiting until tomorrow is not an option.

Two sources, one of them free

The primary source is ExchangeRate-API: 161 currencies, daily updates, and an API key the platform team stores once. Without a key — or when the primary call comes back empty — the job falls through to Frankfurter, which serves the European Central Bank’s reference rates: 31 currencies, published daily at 16:00 CET, no key required. The fallback is automatic, and every refresh records which source actually delivered the numbers, so a thin day of rates explains itself instead of looking like a bug.

The switch, and what happens when it is off

/admin/exchange-rates belongs to the platform team; everyone else gets the restricted-access page. It holds the API key, the automatic-daily-update switch, the manual refresh, and a summary of how many rates are cached, from which source, and how long ago. Turn the automatic update off and the cron does not quietly keep fetching: it records a skipped run with the reason and touches nothing. A job that lies about being disabled is worse than no job at all.

History, so a locked rate can be defended

The cache holds today’s rate for each pair. Every change to it is captured in a separate, append-only history with a valid-from and a valid-until, which is what lets anyone reconstruct months later which rate was live at a given moment. Records that locked a rate — an accepted Market deal, a settlement — keep the rate they locked no matter what the feed did afterwards. The history exists for the conversation about the rate, not to change it.