Every call to /v1/latest, /v1/convert, or /v1/range returns a source field and a market_session field alongside the rate data. Read both before you act on a number. source tells you the quality class of the data; market_session tells you the state of the FX trading day when that data was captured.
sourcehas three values:live(spot),ecb_daily(ECB fix), ormarket_daily(stored daily market-feed close for currencies without an ECB fix).market_sessionreports the state of the FX trading day:open,weekend, orinterbank_closed.- For a live-only pipeline, request a single pair and reject the response client-side unless its per-currency
sourcesvalue islive. - Use
data_updated_atalongsidesourceto see exactly when the underlying rate was last written. - Rates are indicative. They are published for reference and analytics, not for settlement.
What each field contains
source classifies where the rate came from. ecb_daily means the European Central Bank reference fix, published once per business day around 16:00 CET. live means live market data that updates approximately every 60 seconds through the trading week.
market_session describes the state of the interbank trading week. It is open during the trading week, weekend from the Friday 17:00 New York close until Sunday 17:00 New York, or interbank_closed for a known non-weekend closure. It does not name Sydney, Tokyo, London, or New York sessions.
| Value | What it means | When it moves |
|---|---|---|
source: live | Indicative spot rate | Intraday (~60s), trading week |
source: ecb_daily | ECB official reference fix | Once per business day, ~16:00 CET |
source: market_daily | Stored close, no ECB fix | Daily market feed |
market_session: open | An active trading session is running | Changes as sessions open and close |
market_session: weekend | Saturday or Sunday (interbank closed) | Reference rates carry Friday's fix; live feed not updating |
market_session: interbank_closed | Known non-weekend market closure | Set when a closure is detected |
Reading both fields together
Neither field alone gives you the full picture. A rate with source: live and market_session: weekend means the interbank market is closed and the live field is carrying its last intraday rate from the trading week, not a fresh weekend quote. A rate with source: ecb_daily and market_session: weekend means you have a reference fix that has not moved since Friday.
The example below reads both fields and logs a freshness decision. It calls /v1/latest/USD and checks whether the rate is from the live source before using it in a downstream calculation:
import requestsAPI = "https://api.exchangerate.dev/v1"KEY = "exr_live_..."resp = requests.get( f"{API}/latest/USD", headers={"Authorization": f"Bearer {KEY}"}, timeout=10,)resp.raise_for_status()data = resp.json()source = data["source"] # e.g. "ecb_daily"session = data["market_session"] # e.g. "open"updated = data["data_updated_at"] # e.g. "2026-06-19T15:05:12Z"if source == "live": print(f"Live rate, session: {session}, updated: {updated}") eur = data["rates"]["EUR"] # proceed with calculationelif source == "ecb_daily": print(f"Reference fix, updated: {updated}, session: {session}") # decide whether to proceed or waitelif source == "market_daily": print(f"Daily market close, updated: {updated}")data_updated_at is when the underlying rate was last written. timestamp is when the response was built. For freshness decisions, read data_updated_at: it tells you the age of the rate itself, not the age of the HTTP response.
Enforcing live-only data in your client
The API does not accept a source=live query filter. If your pipeline requires live spot, request only the symbols you need and reject any response whose per-currency sources value is not live. This keeps the freshness policy in your application and makes a daily-reference fallback explicit.
resp = requests.get( f"{API}/latest/USD", headers={"Authorization": f"Bearer {KEY}"}, params={"symbols": "EUR"}, timeout=10,)resp.raise_for_status()data = resp.json()if data["sources"]["EUR"] != "live": raise RuntimeError( f"Live EUR unavailable; received {data['sources']['EUR']}" )eur = data["rates"]["EUR"]print(eur, data["data_updated_at"], data["market_session"])For a multi-symbol request, use the sources map rather than the top-level source: the top-level value reports the least-fresh class anywhere in the response. A single daily-reference currency can therefore make a mixed basket report ecb_daily even when the other symbols are live.
Weekend behavior by source
On weekends, the source field separates two situations. source: ecb_daily with market_session: weekend means the ECB has not published since Friday, so the rate carries Friday's fix. For source: live with market_session: weekend, the interbank market is closed, the last intraday rate from the trading week is carried.
A weekend live response and a weekend ECB fix are both labeled weekend. On Monday, the live source resumes intraday updates; the ECB fix publishes at ~16:00 CET.
The notice field
Every response also carries a notice field. For live data it reads: "Indicative rates, not for settlement." For reference-rate data it adds attribution to the ECB. The notice is machine-readable so you can surface it to your own users if you display rates in a UI.
All rates are published for reference, analytics, and display. They are not a dealing quote and must not be used to settle a transaction. The notice field on every response states this explicitly.