fastapi-unirate

May 26, 2026 · View on GitHub

PyPI Python License

FastAPI integration for the UniRate currency-exchange API:

  • Async dependencyDepends(get_unirate_client) plus a typed UniRateDep alias for path-operation signatures.
  • Lifespan helper — one shared httpx.AsyncClient-backed UniRate client per app, opened on startup and closed on shutdown.
  • Money Pydantic type — amount + currency pair that round-trips through OpenAPI schemas.
  • CurrencyConversionMiddleware — auto-rewrites Money-shaped values in JSON responses to a request-scoped target currency (?currency=EUR).

UniRate covers 593+ fiat, crypto, and commodity codes. Latest rates and conversion are on the free tier; historical endpoints (convert_historical) require Pro.

Install

pip install fastapi-unirate

Or with uv / Poetry:

uv add fastapi-unirate
poetry add fastapi-unirate

Quick start

import os

from fastapi import FastAPI
from fastapi_unirate import (
    CurrencyConversionMiddleware,
    Money,
    UniRateDep,
    unirate_lifespan,
)

app = FastAPI(lifespan=unirate_lifespan())  # reads UNIRATE_API_KEY from env
app.add_middleware(CurrencyConversionMiddleware)


@app.get("/products/widget")
async def get_widget() -> dict[str, Money]:
    return {"price": Money(amount=19.99, currency="USD")}


@app.get("/rate/{base}/{quote}")
async def rate(base: str, quote: str, client: UniRateDep) -> dict[str, float]:
    return {"rate": await client.get_rate(base, quote)}

Then:

$ curl localhost:8000/products/widget
{"price":{"amount":19.99,"currency":"USD"}}

$ curl localhost:8000/products/widget?currency=EUR
{"price":{"amount":18.42,"currency":"EUR"}}

$ curl localhost:8000/rate/USD/JPY
{"rate":151.83}

The middleware finds every {"amount": <number>, "currency": "<code>"} shape — top-level, nested, or inside lists — and rewrites it. Conversions are de-duplicated per request and run concurrently.

Configuration

unirate_lifespan accepts overrides:

app = FastAPI(
    lifespan=unirate_lifespan(
        api_key="...",                         # default: $UNIRATE_API_KEY
        base_url="https://api.unirateapi.com", # default: production
        timeout=15.0,                          # default: 30s
    )
)

CurrencyConversionMiddleware accepts the query-parameter name and an explicit client (mostly for testing):

app.add_middleware(
    CurrencyConversionMiddleware,
    query_param="display_currency",
)

Why a middleware?

The dossier sketched two integration shapes — DI and middleware — and both pull their weight:

  • The DI provider is the right tool when a handler explicitly wants to call convert() or get_rate().
  • The middleware is the right tool when a service has a stable Money-shaped response and wants to expose one ?currency= knob to clients without rewriting every handler.

You can use either, or both together (as in the quick-start example).

Errors

The client raises UniRateAPIError on non-2xx responses (with status_code set). Common cases:

HTTPMeaning
401Missing or invalid API key
403Pro subscription required (historical, commodities)
404Currency not found
429Rate limit exceeded

The middleware swallows UniRateAPIError and passes the original response through unchanged — so an upstream UniRate outage never becomes a 500 on your service.

Compatibility

  • Python 3.9 – 3.13
  • FastAPI ≥ 0.100
  • Pydantic ≥ 2.0
  • unirate-api — sync UniRate Python client (this package vendors a small async equivalent so it doesn't pull in requests).
  • langchain-unirate — LangChain partner package.
  • Other UniRate integrations: dbt, Airflow, n8n, Raycast, MCP server. Full list at https://unirateapi.com.

Other UniRate clients

UniRate ships official client libraries and framework integrations across the ecosystem. The repos below are all maintained under the UniRate-API org.

Get a free API key at unirateapi.com.

License

MIT