Questions or problems? Post in GitHub Discussions.

You don't need an SDK. Frankfurter is plain HTTPS returning flat JSON, so the HTTP library you already use is all it takes. No API key, no account.

Quick Start

Fetch a rate and convert:

import requests

res = requests.get("https://api.frankfurter.dev/v2/rate/USD/EUR")
data = res.json()
total = 100 * data["rate"]

The response is one flat object: {"date": "2026-07-17", "base": "USD", "quote": "EUR", "rate": 0.8739}. An invalid currency code returns a 422 with a JSON message.

Money-Safe Math

Floats are fine for display and wrong for accounting. Parse rates as Decimal by passing parse_float through to json.loads:

from decimal import Decimal

import requests

res = requests.get("https://api.frankfurter.dev/v2/rate/USD/EUR")
data = res.json(parse_float=Decimal)
total = Decimal("100") * data["rate"]

Several Quotes at Once

/v2/rates returns a flat array, one row per quote. Reshape it into a lookup with a comprehension. Omit quotes to get every currency.

res = requests.get(
    "https://api.frankfurter.dev/v2/rates",
    params={"base": "USD", "quotes": "EUR,GBP,JPY"},
)
rates = {row["quote"]: row["rate"] for row in res.json()}

Cache What Doesn't Change

Historical rates are immutable, so cache them forever. Latest rates change only as providers publish, at most a few times a working day, so a short TTL is plenty. With requests-cache, caching is one line:

import requests_cache

session = requests_cache.CachedSession("frankfurter", expire_after=3600)
res = session.get("https://api.frankfurter.dev/v2/rate/USD/EUR")
data = res.json()

Async

In an async web app, use httpx:

import httpx

API = "https://api.frankfurter.dev"


async def rate(base, quote):
    async with httpx.AsyncClient(base_url=API) as client:
        res = await client.get(f"/v2/rate/{base}/{quote}")
        res.raise_for_status()
        return res.json()["rate"]

Pin an Official Provider

By default you get the blend across every provider. Add providers to pin one named authority, which matters when a jurisdiction requires its central bank's published rate, like the ECB reference rate for EU VAT.

res = requests.get(
    "https://api.frankfurter.dev/v2/rate/USD/EUR",
    params={"providers": "ECB"},
)
data = res.json()

Browse all providers. A pinned provider follows its own publishing schedule, so its latest rate can trail the blended latest by a day.

Analyzing a Series?

For date ranges, monthly grouping, and charts, the CSV endpoint loads straight into a DataFrame. See pandas.