Best Free Historical Exchange Rate API (2026)
Historical exchange rate data is essential for financial reporting, tax calculations, audit trails, and trend analysis. Accountants need last quarter's EUR/USD rate for revenue reconciliation. Analysts need five years of GBP/JPY data to model volatility. Fintech developers need to display the rate that applied when a transaction was processed, not today's rate.
The problem: most currency APIs lock historical data behind expensive paid plans. The few that offer it for free either limit you to a handful of ECB currencies, restrict query ranges to 30 days, or require a credit card to sign up. If you are building a financial application that needs to look up past rates reliably, the wrong API choice will cost you money or force a migration later.
This article compares the 6 best historical exchange rate APIs in 2026, with code examples in Python and cURL. Spoiler: AllRatesToday is the best option for free historical data, offering interbank-quality rates for 160+ currencies with no credit card required.
Side-by-Side Comparison
Here is how the top 6 historical exchange rate APIs compare:
| API | History Depth | Free Tier | Granularity | Data Source | Credit Card Required |
|---|---|---|---|---|---|
| AllRatesToday | 20+ years | Free tier, no CC | Daily | Reuters/Refinitiv | No |
| Open Exchange Rates | Since 1999 | Paid only | Daily | Multiple aggregated | Yes |
| Frankfurter | Since 1999 | Unlimited | Daily (business days) | ECB reference rates | No |
| Fixer.io | Since 1999 | Paid only | Daily | ECB + market feeds | Yes |
| ExchangeRate-API | Limited | Paid only | Daily | Multiple aggregated | Yes |
| ECB (direct) | Since 1999 | Unlimited | Daily (business days) | ECB reference rates | No |
Key takeaway: AllRatesToday is the only API that provides free historical interbank rates for 160+ currencies sourced from Reuters/Refinitiv. Frankfurter and the ECB are free but limited to ~30 currencies with no weekend or holiday data. Every other API on this list locks historical data behind paid plans.
1. AllRatesToday — Best Overall for Historical Data
AllRatesToday provides historical exchange rate data on its free tier — a rarity among currency APIs. The data is sourced from Reuters/Refinitiv interbank feeds, covering 160+ currencies with daily granularity. You can query specific dates, date ranges, and time-series data without upgrading to a paid plan.
cURL: Fetch a historical rate for a specific date
curl -X GET "https://allratestoday.com/api/historical-rates?source=USD&target=EUR&date=2025-12-31" \
-H "Authorization: Bearer YOUR_API_KEY" Response:
{
"source": "USD",
"target": "EUR",
"date": "2025-12-31",
"rate": 0.9234,
"type": "mid-market"
} cURL: Fetch a date range
curl -X GET "https://allratestoday.com/api/historical-rates?source=USD&target=EUR&from=2026-01-01&to=2026-03-31" \
-H "Authorization: Bearer YOUR_API_KEY" Python: Historical rates with the SDK
from allratestoday import AllRatesToday
client = AllRatesToday("YOUR_API_KEY")
# Get historical rates for a specific period
history = client.get_historical_rates("USD", "EUR", "90d")
for point in history["rates"]:
print(f"{point['time']}: 1 USD = {point['rate']} EUR") Python: Historical rates with requests
import requests
API_KEY = "YOUR_API_KEY"
# Fetch USD/EUR rate for a specific date
response = requests.get(
"https://allratestoday.com/api/historical-rates",
params={
"source": "USD",
"target": "EUR",
"date": "2025-06-15"
},
headers={"Authorization": f"Bearer {API_KEY}"}
)
data = response.json()
print(f"USD/EUR on {data['date']}: {data['rate']}") Python: Build a historical rates DataFrame
import requests
import pandas as pd
API_KEY = "YOUR_API_KEY"
# Fetch an entire year of USD/EUR data
response = requests.get(
"https://allratestoday.com/api/historical-rates",
params={
"source": "USD",
"target": "EUR",
"from": "2025-01-01",
"to": "2025-12-31"
},
headers={"Authorization": f"Bearer {API_KEY}"}
)
data = response.json()
# Convert to pandas DataFrame
df = pd.DataFrame(data["rates"])
df["time"] = pd.to_datetime(df["time"])
df.set_index("time", inplace=True)
# Summary statistics for financial reporting
print(f"Period: {df.index.min().date()} to {df.index.max().date()}")
print(f"Average rate: {df['rate'].mean():.4f}")
print(f"Min rate: {df['rate'].min():.4f}")
print(f"Max rate: {df['rate'].max():.4f}")
print(f"Std dev: {df['rate'].std():.4f}")
print(f"Data points: {len(df)}")
# Export to CSV for accountants
df.to_csv("usd_eur_2025_rates.csv") Tip for accountants: Export historical rates to CSV and import directly into Excel, QuickBooks, or your ERP system. The DataFrame output matches standard accounting date formats.
- Data source: Reuters/Refinitiv and interbank feeds
- History depth: 20+ years of daily rates
- Currencies: 160+ including majors, minors, and exotics
- Rate type: Mid-market (no bank markup)
- Granularity: Daily (including weekends and holidays)
- Free tier: Historical data included — no credit card required
2. Open Exchange Rates
Open Exchange Rates has historical data going back to 1999, but it is locked behind paid plans. The free tier only provides the /latest.json endpoint. To access /historical/YYYY-MM-DD.json, you need at least the Unlimited plan.
# Requires paid plan
curl "https://openexchangerates.org/api/historical/2025-12-31.json?app_id=YOUR_APP_ID" import requests
# Note: This requires a paid plan
APP_ID = "YOUR_APP_ID"
response = requests.get(
"https://openexchangerates.org/api/historical/2025-12-31.json",
params={"app_id": APP_ID, "base": "USD"}
)
data = response.json()
print(f"USD/EUR on 2025-12-31: {data['rates']['EUR']}") - History depth: Since 1999
- Free tier: Latest rates only — historical data requires paid plan
- Granularity: Daily
- Limitation: Free tier locked to USD base; no time-series endpoint on basic plans
- Credit card: Required for historical access
3. Frankfurter
Frankfurter is a free, open-source API that wraps European Central Bank (ECB) reference rates. It is the best free option if you only need the ~30 currencies the ECB publishes. However, ECB data only covers business days (no weekends or holidays) and updates once per day at 16:00 CET.
# Fetch a specific historical date
curl "https://api.frankfurter.app/2025-12-31?from=USD&to=EUR,GBP,JPY" # Fetch a date range
curl "https://api.frankfurter.app/2025-01-01..2025-12-31?from=USD&to=EUR" import requests
# No API key needed
response = requests.get(
"https://api.frankfurter.app/2025-01-01..2025-03-31",
params={"from": "USD", "to": "EUR"}
)
data = response.json()
for date, rates in sorted(data["rates"].items()):
print(f"{date}: 1 USD = {rates['EUR']} EUR") - History depth: Since 1999 (ECB data)
- Free tier: Unlimited, no API key required
- Currencies: ~30 (ECB reference rates only)
- Granularity: Daily, business days only (no weekends or holidays)
- Limitation: No exotic currencies (no INR, BRL, MXN, etc.); no weekend data
- Credit card: Not required
When Frankfurter works: If you only need EUR, USD, GBP, JPY, CHF, and a handful of other major currencies with no weekend data, Frankfurter is a solid free option. For anything beyond ECB coverage, you need AllRatesToday.
4. Fixer.io
Fixer has historical data going back to 1999, but like Open Exchange Rates, it requires a paid plan to access it. The free tier is restricted to 100 requests per month with the /latest endpoint only, EUR base, and HTTP (not HTTPS).
# Requires paid plan
curl "http://data.fixer.io/api/2025-12-31?access_key=YOUR_KEY&base=EUR&symbols=USD,GBP" import requests
# Note: Historical endpoint requires paid plan
# Free tier: HTTP only, EUR base only
API_KEY = "YOUR_API_KEY"
response = requests.get(
"http://data.fixer.io/api/2025-12-31",
params={
"access_key": API_KEY,
"base": "EUR",
"symbols": "USD,GBP,JPY"
}
)
data = response.json()
if data.get("success"):
print(f"EUR/USD on 2025-12-31: {data['rates']['USD']}") - History depth: Since 1999
- Free tier: Latest rates only, 100 req/mo, EUR base, HTTP only
- Granularity: Daily
- Limitation: Historical data requires paid plan; free tier lacks HTTPS
- Credit card: Required for historical access
5. ExchangeRate-API
ExchangeRate-API offers historical data on paid plans only. The free tier provides 1,500 requests per month but is restricted to the /latest/ endpoint. There is no Python SDK or time-series endpoint.
# Requires paid plan (pair-specific historical lookup)
curl "https://v6.exchangerate-api.com/v6/YOUR_KEY/history/USD/2025/12/31" import requests
# Note: Historical endpoint requires paid plan
API_KEY = "YOUR_API_KEY"
response = requests.get(
f"https://v6.exchangerate-api.com/v6/{API_KEY}/history/USD/2025/12/31"
)
data = response.json()
if data.get("result") == "success":
print(f"USD/EUR on 2025-12-31: {data['conversion_rates']['EUR']}") - History depth: Limited (varies by plan)
- Free tier: Latest rates only, 1,500 req/mo
- Granularity: Daily
- Limitation: Historical data requires paid plan; no time-series or date range queries
- Credit card: Required for historical access
6. ECB (European Central Bank) — Direct Access
You can access the ECB's Statistical Data Warehouse directly via their SDMX API. This is the raw source that Frankfurter wraps. It is free and requires no API key, but the SDMX/XML response format is cumbersome compared to JSON APIs.
# Fetch daily USD/EUR from ECB's SDMX API (XML response)
curl "https://data-api.ecb.europa.eu/service/data/EXR/D.USD.EUR.SP00.A?startPeriod=2025-01-01&endPeriod=2025-12-31" import requests
import xml.etree.ElementTree as ET
# Fetch USD/EUR from ECB SDMX API
response = requests.get(
"https://data-api.ecb.europa.eu/service/data/EXR/D.USD.EUR.SP00.A",
params={
"startPeriod": "2025-01-01",
"endPeriod": "2025-03-31"
},
headers={"Accept": "application/xml"}
)
# Parse XML response (SDMX format)
root = ET.fromstring(response.text)
ns = {
"generic": "http://www.sdmx.org/resources/sdmxml/schemas/v2_1/data/generic"
}
for obs in root.findall(".//generic:Obs", ns):
date = obs.find("generic:ObsDimension", ns).get("value")
rate = obs.find("generic:ObsValue", ns).get("value")
print(f"{date}: 1 USD = {rate} EUR") - History depth: Since 1999
- Free tier: Unlimited, no API key
- Currencies: ~30 (ECB reference rates only)
- Granularity: Daily, business days only
- Limitation: XML/SDMX format is complex; no JSON; no exotic currencies; no weekends
- Credit card: Not required
Use Case: Monthly Revenue Reconciliation
Accountants frequently need the exchange rate on the exact date a transaction was invoiced. Here is how to build a reconciliation script with AllRatesToday:
import requests
import pandas as pd
from datetime import datetime
API_KEY = "YOUR_API_KEY"
# Sample invoice data (date, amount, currency)
invoices = [
{"date": "2026-01-15", "amount": 5000, "currency": "EUR"},
{"date": "2026-02-03", "amount": 12000, "currency": "GBP"},
{"date": "2026-02-20", "amount": 800000, "currency": "JPY"},
{"date": "2026-03-10", "amount": 25000, "currency": "CAD"},
{"date": "2026-04-01", "amount": 150000, "currency": "INR"},
]
base_currency = "USD"
results = []
for inv in invoices:
response = requests.get(
"https://allratestoday.com/api/historical-rates",
params={
"source": inv["currency"],
"target": base_currency,
"date": inv["date"]
},
headers={"Authorization": f"Bearer {API_KEY}"}
)
data = response.json()
rate = data["rate"]
usd_amount = round(inv["amount"] * rate, 2)
results.append({
"Invoice Date": inv["date"],
"Original Amount": inv["amount"],
"Currency": inv["currency"],
"Exchange Rate": rate,
"Amount (" + base_currency + ")": usd_amount
})
# Create accounting report
df = pd.DataFrame(results)
print(df.to_string(index=False))
col = "Amount (" + base_currency + ")"
total = df[col].sum()
print("\nTotal Revenue (USD): $" + format(total, ",.2f"))
# Export for accounting software
df.to_csv("revenue_reconciliation_q1_2026.csv", index=False)
print("\nExported to revenue_reconciliation_q1_2026.csv") Use Case: Currency Volatility Analysis
Analysts tracking exchange rate trends can use AllRatesToday's historical data to compute rolling averages and standard deviations:
import requests
import pandas as pd
API_KEY = "YOUR_API_KEY"
# Fetch 1 year of GBP/USD data
response = requests.get(
"https://allratestoday.com/api/historical-rates",
params={
"source": "GBP",
"target": "USD",
"from": "2025-05-01",
"to": "2026-05-01"
},
headers={"Authorization": f"Bearer {API_KEY}"}
)
data = response.json()
df = pd.DataFrame(data["rates"])
df["time"] = pd.to_datetime(df["time"])
df.set_index("time", inplace=True)
# Calculate rolling statistics
df["ma_30"] = df["rate"].rolling(window=30).mean()
df["ma_90"] = df["rate"].rolling(window=90).mean()
df["volatility_30d"] = df["rate"].rolling(window=30).std()
# Monthly summary
monthly = df["rate"].resample("M").agg(["mean", "min", "max", "std"])
monthly.columns = ["Avg Rate", "Min", "Max", "Volatility"]
print("\nMonthly GBP/USD Summary:")
print(monthly.to_string())
# Identify highest volatility months
most_volatile = monthly["Volatility"].idxmax()
print(f"\nMost volatile month: {most_volatile.strftime('%B %Y')}")
print(f"Volatility (std): {monthly.loc[most_volatile, 'Volatility']:.4f}") cURL Quick Reference
For analysts and developers who prefer command-line access:
# Specific date
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://allratestoday.com/api/historical-rates?source=USD&target=EUR&date=2025-12-31"
# Date range
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://allratestoday.com/api/historical-rates?source=USD&target=EUR&from=2025-01-01&to=2025-12-31"
# Multiple targets
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://allratestoday.com/api/historical-rates?source=USD&target=EUR,GBP,JPY&date=2025-12-31"
# Pipe to jq for clean output
curl -s -H "Authorization: Bearer YOUR_API_KEY" \
"https://allratestoday.com/api/historical-rates?source=USD&target=EUR&from=2026-01-01&to=2026-01-31" \
| jq '.rates[] | "\(.time): \(.rate)"' Why AllRatesToday Wins for Historical Data
After comparing all six options, here is why AllRatesToday is the best choice for historical exchange rates:
- Free historical data: Most APIs (Open Exchange Rates, Fixer, ExchangeRate-API) lock historical endpoints behind paid plans. AllRatesToday includes historical data on the free tier.
- 160+ currencies: ECB-based sources (Frankfurter, ECB direct) only cover ~30 currencies. AllRatesToday covers 160+, including exotic pairs like USD/NGN, EUR/BRL, and GBP/INR that accountants working with emerging markets need.
- Interbank mid-market rates: Sourced from Reuters/Refinitiv — the same data used by banks and financial institutions. ECB rates are reference rates published once per business day.
- Daily data including weekends: Unlike ECB data, AllRatesToday provides rates for every calendar day, which matters for accurate transaction-date lookups.
- Date range queries: Fetch months or years of data in a single API call. No need to loop through individual dates.
- No credit card required: Sign up and start querying historical data immediately. Open Exchange Rates, Fixer, and ExchangeRate-API all require a credit card for historical access.
- JSON responses: Clean, predictable JSON output that converts directly to pandas DataFrames or CSV. Compare this to the ECB's SDMX/XML format.
- Python SDK:
pip install allratestodayfor a Pythonic interface to historical data, or userequests/cURLdirectly with the REST API.
Quick Reference
- Historical rate (specific date):
GET /api/historical-rates?source=USD&target=EUR&date=2025-12-31 - Historical range:
GET /api/historical-rates?source=USD&target=EUR&from=2025-01-01&to=2025-12-31 - Python SDK:
client.get_historical_rates("USD", "EUR", "90d") - Auth:
Authorization: Bearer YOUR_API_KEY - PyPI: pypi.org/project/allratestoday
- GitHub: allratestoday/exchange-rates-api
Access Free Historical Exchange Rate Data
Get your free API key in 30 seconds. Query 20+ years of interbank mid-market rates for 160+ currencies with no credit card required. Compare all options on our Exchange Rate API page.
Get Your Free API Key