Code Examples
FastAPI with CurrencyCore and httpx
Add one async FastAPI route that calls CurrencyCore with httpx, validates the response with Pydantic, and applies a timeout.
Create one httpx.AsyncClient for the application lifespan and reuse it across requests. Opening a client per request throws away the connection pool and adds TLS setup to every call. The route below forwards the API error status and validates success with Pydantic.
The official Python SDK is synchronous. This async route uses httpx deliberately so it does not block the event loop; a synchronous worker would use create_client from currencycore instead.
The example needs Python 3.10 or later, fastapi, httpx, and uvicorn. Free includes 1,000 requests per month at 60 per minute. Live keys require a paid plan; test keys use the same reference data and quota, capped at 60 per minute.
import os
from contextlib import asynccontextmanager
import httpx
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ConfigDict, Field
BASE = "https://api.currency-core.com/v1"
@asynccontextmanager
async def lifespan(app: FastAPI):
async with httpx.AsyncClient(base_url=BASE, timeout=10.0) as client:
app.state.client = client
yield
app = FastAPI(lifespan=lifespan)
class ConversionResult(BaseModel):
to: str
result: float
rate: float
class ConvertResponse(BaseModel):
model_config = ConfigDict(populate_by_name=True)
from_currency: str = Field(alias="from")
amount: float
date: str
results: list[ConversionResult]
@app.get("/convert", response_model=ConvertResponse)
async def convert(
request: Request,
from_currency: str = "USD",
to: str = "INR",
amount: float = 100.0,
):
key = os.environ.get("CURRENCYCORE_API_KEY")
if not key:
raise HTTPException(status_code=500, detail="Set CURRENCYCORE_API_KEY")
client: httpx.AsyncClient = request.app.state.client
res = await client.get(
"/convert",
params={"from": from_currency, "to": to, "amount": amount},
headers={"Authorization": f"Bearer {key}"},
)
if not res.is_success:
raise HTTPException(status_code=res.status_code, detail=res.json())
return res.json()
from is aliased because it is a Python keyword. The API returns one entry per target, and the base at 1 for a snapshot:
{
"from": "USD",
"amount": 100,
"date": "2026-11-10",
"results": [{ "to": "INR", "result": 8345.0, "rate": 83.45 }]
}
{ "base": "EUR", "date": "2026-11-10", "rates": { "EUR": 1, "USD": 1.0861 } }
Return the upstream status for 400, 401, 403, and 429, and preserve error.code in the detail. Do not retry a 400 or a 403; the request or plan will not change on a retry.
Cache the snapshot in your own store by base and date. The API has no ETag or Last-Modified, so conditional requests do not produce a 304.
Next steps
- API reference and OpenAPI specification
- Python guide for a script client
- Testing guide for fixtures and contract tests
- All developer guides