Guide/Migrating
Guide

Coming from Frankfurter? Here is the mapping.

Frankfurter is a solid open-source project backed by ECB data. If you want an API key, weekend coverage, and freshness metadata, this guide shows exactly what changes.

MMexchangerate.dev·Jun 19, 2026·6 min read

The path structure differs, parameters are renamed, and some response fields are new. This is a re-point with small code edits, not a zero-change swap. Once you have the mapping, an afternoon is enough to update your integration.

Key points
Paths differ: Frankfurter uses /latest?from=USD, exchangerate.dev uses /v1/latest/USD.
Time-series parameters are renamed: /{start}..{end} becomes /v1/range?start_date=&end_date=.
New freshness fields source and market_session on every response tell you exactly what kind of rate you have.
is_forward_filled on history endpoints flags weekends and holidays where no fix was published.
A free key gives you 50,000 calls a month with live weekend coverage, no card required.

What changes and what you gain

Frankfurter is open-source and wraps European Central Bank reference data. It works well for weekday reference rates when you do not need a key. exchangerate.dev covers the same ECB series but adds live weekend rates (ECB does not publish on weekends), freshness metadata on every response, and a keyed tier for production quotas.

The core difference is that you move from a public no-auth API to a keyed one. You get a source field that distinguishes live spot from ECB or FRED daily fixes, a market_session field that says whether the FX day is open or in a weekend session, and is_forward_filled on historical calls that tells you when a prior value was carried because no fix was published.

Endpoint mapping

The table below covers the three Frankfurter endpoints most developers use. The operation is the same; the path, parameter names, and response shape change.

Frankfurterexchangerate.devNotes
GET /latest?from=USDGET /v1/latest/USDBase goes in the path, not the query string. symbols filter is still a query param.
GET /{date}?from=USDGET /v1/{date}/USDSame path-date approach. Response adds is_forward_filled.
GET /{start}..{end}?from=USDGET /v1/range?start_date=&end_date=&base=No double-dot range. Named params start_date and end_date (snake_case).
GET /currenciesGET /v1/currenciesSame purpose, versioned path.
Check field names in the response
Frankfurter returns amount, base, and rates. exchangerate.dev returns result, base, rates, plus source, market_session, timestamp, and data_updated_at. If you read the response by key, update those references.

Before and after code

Here is a typical Frankfurter call converted to exchangerate.dev. The structure is familiar; the differences are the base URL, the path layout, and the Authorization header.

python · Frankfurter (before)copy
import requests

# Frankfurter — no auth, ECB reference rates only
resp = requests.get(
    "https://api.frankfurter.app/latest",
    params={"from": "USD", "to": "EUR,GBP"},
).json()

print(resp["rates"]["EUR"])   # 0.xxx
python · exchangerate.dev (after)copy
import requests

API = "https://api.exchangerate.dev/v1"
KEY = "exr_live_..."   # free key at https://exchangerate.dev/signup

# Base moves to the path; symbols stays as a query param
resp = requests.get(
    f"{API}/latest/USD",
    headers={"Authorization": f"Bearer {KEY}"},
    params={"symbols": "EUR,GBP"},
    timeout=10,
).json()

print(resp["rates"]["EUR"])        # 0.87531
print(resp["source"])              # ecb_daily
print(resp["market_session"])      # open

The key differences: from becomes the last path segment, to becomes symbols, and Authorization: Bearer exr_live_... is added. You also receive source and market_session in the response without any extra work.

Migrating time-series calls

Frankfurter uses a double-dot range in the path: /{start}..{end}?from=USD. exchangerate.dev uses /v1/range with explicit start_date and end_date query parameters. The double-dot syntax is not supported.

python · time series (before and after)copy
# Before — Frankfurter double-dot range
resp = requests.get(
    "https://api.frankfurter.app/2026-06-10..2026-06-13",
    params={"from": "USD", "to": "EUR,GBP"},
).json()

# After: exchangerate.dev named params
resp = requests.get(
    f"{API}/range",
    headers={"Authorization": f"Bearer {KEY}"},
    params={
        "base":       "USD",
        "symbols":    "EUR,GBP",
        "start_date": "2026-06-10",
        "end_date":   "2026-06-13",
    },
).json()

New freshness fields

Every response from exchangerate.dev includes source and market_session. These do not exist in Frankfurter, so they are additive; you do not need to remove anything, but they are worth reading.

FieldValuesWhat it tells you
sourcelive, ecb_daily, fred_dailyThe data class behind this rate. ecb_daily is the closest equivalent to Frankfurter.
market_sessionopen, weekend, session namesWhether the FX day is active. Only live source moves on weekends.
is_forward_filledtrue / falseHistory only. true means no fix was published that day; the prior value is carried.

On a weekend date request, Frankfurter returns the Friday close. exchangerate.dev does the same for ecb_daily but sets is_forward_filled: true to say so explicitly. For live rates, market_session: weekend means the rate is being updated even though the ECB is not publishing.

Indicative rates only
Like Frankfurter, exchangerate.dev rates are indicative, published for reference and analytics. They are not a dealing quote. Every response states this in its notice field.
MM
exchangerate.dev
Migration guides for developers moving between FX-rate APIs.

Keep reading

TutorialHow to get exchange rates in PythonRead GuideHistorical FX rates and time series in one callRead ReferenceReading source and market_sessionRead