Use GET /v1/{date}/{base} for a past date’s published rate; Plus and Pro reach 1999. The /v1/latest endpoint always returns today's value, so it has no place in a historical series. Check `is_forward_filled` on every row: when it is true, no rate was published that day and the prior value was carried forward.
- Query
/v1/{date}/{base}for historical work. Never use/v1/latest, which returns today's value regardless of date. is_forward_filled: truemeans no rate was published on that date (weekend or public holiday); the prior value is carried.- ECB daily history covers about 30 currencies since 1999.
- Look-ahead bias is a data-correctness problem: it means using a value that was not knowable on the date being simulated.
- Rates are indicative, published for reference and analytics, not for settlement or dealing.
What look-ahead bias means for FX data
Look-ahead bias occurs when your historical series contains a value that was not actually published on the date you are simulating. With FX rates this is easy to introduce accidentally: if you query /v1/latest/USD today and store the result against a past date, you are using today's rate in a historical slot. The data will appear self-consistent but it describes a world that did not exist on that date.
The fix is to query each date individually via the point-in-time path. The response reflects what was published that day, not a later revision.
The dated endpoint
Put the date in the path: GET /v1/{date}/{base}. Use a Basic or higher key for this example. Dates use ISO 8601 (YYYY-MM-DD); the base is a three-letter currency code.
$ curl -s https://api.exchangerate.dev/v1/2026-03-14/USD \ -H "Authorization: Bearer exr_live_..."The response shape is the same as /v1/latest with one addition: is_forward_filled. When that field is true, no rate was published on the requested date and the API returned the most recent prior value.
Reading is_forward_filled
ECB reference rates are only published on business days. Requesting a Saturday, Sunday, or public holiday returns the last published value with is_forward_filled: true. The flag lets you treat those rows deliberately rather than silently treating a carried value as a freshly published one.
| Approach | Naive /v1/latest pull | Dated /v1/{date}/{base} pull |
|---|---|---|
| Historical date | Returns today's rate stored against a past slot | Returns the rate published on that exact date |
| Weekend or holiday | Carries prior value with no signal | Carries prior value and sets is_forward_filled: true |
| Detectability | Bias is silent; no field indicates it | Forward-fill is explicit; you can filter or flag |
Python: looping dates to build a series
The example below requests USD rates for a week, printing each date's EUR rate and whether it was forward-filled. Use a Basic or higher key for this older window.
import timefrom datetime import date, timedeltaimport requestsAPI = "https://api.exchangerate.dev/v1"KEY = "exr_live_..." # Basic or higher: https://exchangerate.dev/pricingstart = date(2026, 6, 9)end = date(2026, 6, 15)current = startwhile current <= end: r = requests.get( f"{API}/{current}/USD", headers={"Authorization": f"Bearer {KEY}"}, timeout=10, ) r.raise_for_status() data = r.json() eur = data["rates"]["EUR"] ffwd = data.get("is_forward_filled", False) print(f"{current} EUR={eur} forward_filled={ffwd}") current += timedelta(days=1) time.sleep(6) # 10 req/min, within Basic’s 60 req/minOn June 14 (Saturday) and June 15 (Sunday) you will see forward_filled=True. Both are non-publication days for reference-rate sources.
A common pattern is to carry the flag alongside each rate in your datastore, then filter or annotate in your analysis layer. Dropping forward-filled rows silently creates gaps; keeping them without the flag conflates carried values with published ones.
Cross-checking with data_updated_at
The response also carries data_updated_at, the timestamp when the underlying rate was last written. On a forward-filled day this will point to the previous business day's publication time, giving you an additional signal about the age of the value you received.
data = r.json()print(data["data_updated_at"]) # e.g. 2026-06-13T16:15:00Z on a weekend dateprint(data["is_forward_filled"]) # TrueA note on rate type
All rates are indicative, published for reference, analytics, and display. They are not a dealing quote and should not be used to settle a transaction. The notice field on every response states this explicitly.