Install
openclaw skills install @nanookai/frankfurter-apiFetch exchange rates and convert currencies with the free Frankfurter API (no API key). Use for any exchange rate, currency conversion, forex, or historical FX data task, even if Frankfurter isn't mentioned.
openclaw skills install @nanookai/frankfurter-apiFrankfurter is a free, open-source exchange rate API. The public API lives at
https://api.frankfurter.dev — no API key, no signup, no monthly quota (only
abuse-prevention rate limiting). It tracks daily reference rates from 84 central
banks covering 201 currencies, with history back to 1948.
Use the v2 API (/v2/...) documented below. The older v1 API still works;
see references/v1-api.md only if you must maintain existing v1 code.
Base URL: https://api.frankfurter.dev
| Task | Request |
|---|---|
| Latest rates (base EUR) | GET /v2/rates |
| Change base currency | GET /v2/rates?base=USD |
| Limit target currencies | GET /v2/rates?quotes=USD,GBP,JPY |
| Rates on a specific date | GET /v2/rates?date=1999-01-04 |
| Time series | GET /v2/rates?from=2026-01-01&to=2026-03-31"es=USD |
| Downsample series | ...&group=week or ...&group=month |
| Single currency pair | GET /v2/rate/EUR/USD (optional ?date=YYYY-MM-DD) |
| List currencies | GET /v2/currencies (?scope=all includes legacy ones) |
| One currency's details | GET /v2/currency/TWD |
| List data providers | GET /v2/providers |
Currency codes are ISO 4217 (USD, EUR, TWD, JPY...). Dates are YYYY-MM-DD.
/v2/rates returns a JSON array of rows; /v2/rate/{BASE}/{QUOTE} returns a
single object:
// GET /v2/rates?base=USD"es=EUR,TWD
[
{"date": "2026-07-03", "base": "USD", "quote": "EUR", "rate": 0.87598},
{"date": "2026-07-03", "base": "USD", "quote": "TWD", "rate": 31.913}
]
// GET /v2/rate/EUR/USD
{"date": "2026-07-03", "base": "EUR", "quote": "USD", "rate": 1.1416}
A time series is the same array with one row per date per quote currency.
There is no conversion endpoint in v2. Fetch the pair rate and multiply:
import requests
def convert(base: str, quote: str, amount: float, date: str | None = None) -> float:
url = f"https://api.frankfurter.dev/v2/rate/{base}/{quote}"
params = {"date": date} if date else {}
r = requests.get(url, params=params, timeout=10)
r.raise_for_status()
return amount * r.json()["rate"]
print(convert("USD", "TWD", 100)) # e.g. 3191.3
async function convert(base, quote, amount) {
const r = await fetch(`https://api.frankfurter.dev/v2/rate/${base}/${quote}`);
const d = await r.json();
return amount * d.rate;
}
By default, rates are blended across all contributing central banks. This is fine for general use, but the last decimal places can shift as new data arrives.
?providers=ECB
(provider keys come from /v2/providers — e.g. ECB, BOE, FRED, CBC).?expand=providers: each row
gains a providers array of {key, rate} objects. Pegged currencies omit it
(their rate comes from the peg, not provider data)..csv to the path (/v2/rates.csv?...) or send
Accept: text/csv. Columns: date,base,quote,rate.Accept: application/x-ndjson. Each line is one independent
JSON object — use this to stream large time series without buffering.Errors return an HTTP status with a JSON body like {"status": 404, "message": "not found"}:
400 — invalid parameter or malformed request404 — currency, rate, or resource not found (check the ISO code and date)422 — request understood but cannot be processedPython-urllib User-Agent (Cloudflare
returns 403). curl, fetch, and the requests library work as-is; if you
use urllib, set any custom User-Agent header.date field in the response rather than assuming today.quotes to the currencies you need; it keeps responses small and fast.start_date in
/v2/currency/{CODE} before requesting old dates.For complete endpoint details with verified request/response examples, read
references/endpoints.md.