Skip to content

FX reference rate (USD → EUR)

The admin UI can display cost figures in EUR as well as USD. The USD→EUR conversion uses the European Central Bank daily reference rate, fetched and cached server-side by the gateway and served to the SPA from the gateway's own admin API. The SPA never contacts a third-party FX service directly (so no external FX host appears in the SPA's Content-Security-Policy).

GET /admin/v1/fx/usd-eur

Returns the current USD→EUR reference rate. Requires an authenticated session (any role — the rate is non-sensitive).

Response 200

{ "rate": 0.92, "date": "2026-07-29", "stale": false }
Field Type Description
rate number EUR per 1 USD. Always a validated, finite, positive value inside a sanity band (a lying or out-of-range upstream value is rejected, never served). Multiply a USD amount by rate to get EUR.
date string | absent The UTC day (YYYY-MM-DD) the served rate was fetched. Absent only when the gateway is serving the baked last-resort constant (it has never successfully fetched a rate).
stale boolean true when the served rate could not be refreshed for longer than the freshness window (5 days) — i.e. a sustained upstream FX outage. false in normal operation (including the routine daily first-request, which serves the previous day's rate while an asynchronous refresh runs).

Behaviour and reliability

  • Never blocks. The handler only reads the server-side cache and returns immediately; it never performs the upstream FX fetch inline, so an admin request is never delayed by the FX provider's latency.
  • Serve-stale-while-revalidate. On a cache miss or a not-today cache, the gateway serves the best value it has (a recent cached rate, or the baked ECB constant 0.92) and triggers a single-flighted background refresh; the next request sees the fresh rate.
  • Scheduled daily refresh (backstop). Independently of any request, a worker-0 background timer refreshes the rate once per day (single-flighted via the same lock), so the cached rate stays current even on a gateway with no display or wallet traffic. The request-driven refresh above still runs on top of it. A rate that cannot be refreshed keeps being served (serve-stale) and is flagged stale only after it is more than 5 days old.
  • Fail-closed validation (untrusted upstream). The background refresh validates the FX provider's response — the rate must be a finite, positive number inside a sanity band — and writes the cache only on success. A malformed, missing, non-numeric, or out-of-band value is discarded and the previous cached rate stands.
  • Observability. A synthetic probe (fx-eur-rate) reads this endpoint and asserts stale === false, so a sustained FX outage is surfaced server-side rather than relying on each client to self-heal.

Client fallback

If this endpoint is unreachable (e.g. an expired session, or the gateway itself is down), the SPA falls back to its own last-known cached rate and, failing that, a baked ECB constant — so a EUR cost figure is never rendered at a 1:1 rate or as NaN. The cache is per browser (a localStorage entry keyed by the day it was fetched): a cached rate older than 30 days is ignored in favour of the constant, and a same-day cache is served without any request to this endpoint at all — at most one successful fetch per open tab (SPA session) per UTC day; a failed fetch persists nothing and is retried on the next EUR view. A served-but-stale answer (the cold-boot constant, returned with stale: true and no date in the first seconds after a gateway restart) is likewise not persisted — it is shown for the current view but re-fetched on the next page load, so a transient cold-boot rate is never locked in for the rest of the day.

Deployment note. Because the FX fetch now runs on the gateway, the gateway container must be able to reach the FX provider host over HTTPS. In an egress-restricted environment, that host must be permitted for the gateway.