Frankfurter (open source, ECB data)
Frankfurter — ECB reference rates as a clean JSON API — thirty currencies, history to 1999, no key
When you need reference exchange rates — one fix per day, any base, any of thirty currencies, back to 1999 — with the simplest API on this list, for charts, conversions and notebooks.
Reach for it when…
When you need reference exchange rates — one fix per day, any base, any of thirty currencies, back to 1999 — with the simplest API on this list, for charts, conversions and notebooks.
Not for: Intraday rates or Asian-morning freshness (the ECB fixes at about 16:00 CET), currencies outside the ECB's list, anything requiring a trading price.
Units, revisions, traps
Units. Units of the quote currency per one unit of the base; the ECB's own base is the euro and other bases are cross-computed.
Revisions. Not revised.
- One fix per business day; weekends and ECB holidays return the previous fix.
- Cross rates via the euro can differ slightly from a direct market quote.
- The date range endpoint returns a dict keyed by date; pandas needs a transpose.
Classic mistake: Using the 16:00 CET fix as 'today's rate' in an Asian morning newsletter.
Three recipes
- Recipe 1 · A pair's daily history
- Recipe 2 · Latest rates for one base
- Recipe 3 · Convert a series into another currency
Each recipe: Python · Excel · Sheets, with how to read the result. Python recipes run under pandas; recipe 1 is re-run by the weekly check where the source allows it.
How to use it in your own work
Copy the snippet, change the series id, read the result the way the footer says. Replace YOUR_…_KEY with your own key where one is needed.
Recipe 1A pair's daily history
import requests, pandas as pd
u = "https://api.frankfurter.dev/v1/2024-01-01..?base=USD&symbols=KRW,JPY"
j = requests.get(u, timeout=30).json()
df = pd.DataFrame(j["rates"]).T
df.index = pd.to_datetime(df.index)
print(df.sort_index().tail())- Data → From Web → the same URL; expand 'rates' and transpose.
- No native JSON import — use the Python recipe, or ECB's CSV endpoint on the ECB card.
Recipe 2Latest rates for one base
import requests, pandas as pd
j = requests.get("https://api.frankfurter.dev/v1/latest?base=SGD", timeout=30).json()
print(j["date"]); print(pd.Series(j["rates"]).sort_values().head())- From Web with the same URL.
- Use the Python recipe.
Recipe 3Convert a series into another currency
# a monthly series in USD (say, an index) into EUR at month-end fixes
import requests, pandas as pd
fx = pd.DataFrame(requests.get("https://api.frankfurter.dev/v1/2023-01-01..?base=USD&symbols=EUR", timeout=30).json()["rates"]).T
fx.index = pd.to_datetime(fx.index)
monthly = fx["EUR"].resample("ME").last()
print(monthly.tail())- From Web, then a month-end lookup.
- Use the Python recipe.
Series → question map
The ids we use from Frankfurter, each with the question it answers. The catalog's compare view reads the concept tags behind these rows.
| Series | Answers | Region | Concept |
|---|---|---|---|
| latest?base=USD | Today's reference rates against the dollar | global | fx_cross |
| ..?base=USD&symbols=KRW | Won per dollar, daily reference | Korea | fx_usd |
| ..?base=USD&symbols=JPY | Yen per dollar, daily reference | Japan | fx_usd |
| ..?base=EUR&symbols=USD | Dollars per euro, daily reference | euro area | fx_usd |
Compare with
Same question, different source: ECB Data Portal, FRED, BOK ECOS. The compare view lines up coverage, frequency, history and access side by side and lists what the combination makes possible.
Open compare: Frankfurter · ECB Data Portal · FRED →
Curated: Is the won weak or the dollar strong?.
Questions readers ask
Is Frankfurter free?
Yes, no key; the software is open source and can be self-hosted.
Which currencies?
The ECB's reference list — about thirty, including USD, JPY, KRW, CNY, SGD, GBP, AUD, INR, MYR, THB, IDR, PHP.
When does it update?
Once a day, shortly after the ECB's 16:00 CET fix.
From the paper
Educational only — we explain, we never advise · snippet licence: public domain · corrections to [email protected], fixed within a day and logged in the changelog.