African FX exchange rate API — documentation

    Integrate live African FX and provider pricing data into your applications using the Modan REST API.

    Base URL: https://modan.io/api/v1

    Quick Start

    Get live GBP→NGN provider rates in one request. Replace mdn_live_YOUR_KEY_HERE with your API key — created free in the terminal under Developers → My Keys.

    curl "https://modan.io/api/v1/rates?from=GBP&to=NGN" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Authentication

    All endpoints require an API key passed via the X-API-Key request header.

    X-API-Key: mdn_live_YOUR_KEY_HERE

    Keys are generated server-side and shown only once — store yours securely. Requests return 401 Unauthorized if the key is missing, invalid, or revoked.

    Endpoints

    MethodPathDescriptionAuth
    GET
    /rates?from=GBP&to=NGN
    Latest provider rates for a currency corridor
    Required
    GET
    /convert?from=GBP&to=NGN&amount=1000
    Convert an amount across a corridor, per provider (net of fees)
    Required
    GET
    /fetch-one?from=USD&to=NGN
    Single pair: mid-market rate + best tracked provider
    Required
    GET
    /fetch-multi?from=USD&to=NGN,KES,GHS
    One base to up to 20 quotes in one call
    Required
    GET
    /fetch-matrix?from=USD,GBP&to=NGN,KES
    Full cross matrix (up to 10×10 pairs)
    Required
    GET
    /fetch-many-to-one?from=USD,GBP,CAD&to=NGN
    Many bases into a single quote currency
    Required
    GET
    /rates/history?from=GBP&to=NGN&period=30d&order=asc&limit=500&offset=0
    Historical rate time-series (paginated, oldest-first by default)
    Required
    GET
    /time-series?from=GBP&to=NGN&period=30d&interval=P1D
    Bucketed daily/hourly series: mid + best provider per bucket
    Required
    GET
    /historical?from=GBP&to=NGN&date=2026-07-01
    Corridor snapshot as of a past date (mid + per-provider)
    Required
    GET
    /change?from=GBP&to=NGN&period=7d
    Absolute and % change of mid + best rate over a period
    Required
    GET
    /rates/provider?provider=lemfi
    Every corridor and current rate one provider quotes
    Required
    GET
    /corridors
    All available currency corridors with best rates
    Required
    GET
    /providers
    All active providers with metadata
    Required
    GET
    /currencies
    Active currencies and the corridors currently served
    Required
    GET
    /admin/usage
    Your account's usage, quota and per-key breakdown (does not consume quota)
    Required
    GET
    /status
    Public platform health: corridor count, data freshness (no key needed)
    POST
    /rates
    Ingest rate observations (data-team keys: admin/treasury accounts only)
    Required

    /rates/history: period = 1d/7d/30d (default)/90d, order = asc (default, oldest-first)/desc, limit (≤5000, default 500), offset, optional provider. The response includes has_more for paging. /convert takes amount and returns per-provider converted + net-of-fee values and the best. POST /rates ingests observations (JSON object or array ≤100, atomic) — it requires a key owned by an admin/treasury account and does not consume the read quota. Machine-readable spec: openapi.json.

    The /fetch-* family returns the independent mid-market rate per pair (cross-computed through the freshest USD reference snapshot), plus provider_best when the pair is a tracked corridor. One call = one quota unit regardless of pair count. Unsupported currencies return 400 with the supported list. /time-series accepts interval = P1D (daily, default, ≤366 buckets) or PT1H (hourly, ≤168), with period or explicit start/end. /admin/usage and /status never consume quota.

    Endpoint reference

    Full parameters, an example request and an example response for every endpoint. All paths are relative to https://modan.io/api/v1.

    Rates & conversion

    GET /api/v1/rates
    Key required

    Every tracked provider's live rate for a corridor, plus fee, spread vs the best provider (bps), and the independent mid-market reference when available.

    Query parameters

    NameTypeReqDescription
    fromstringyesSource currency (ISO-4217), e.g. GBP
    tostringyesTarget currency (ISO-4217), e.g. NGN

    Example request

    curl "https://modan.io/api/v1/rates?from=GBP&to=NGN" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "corridor": "GBP/NGN",
    "providers": [
    {
    "provider_id": "wise",
    "provider_name": "Wise",
    "rate": 2045.50,
    "fee": 2.99,
    "fee_currency": "GBP",
    "spread_bps": 24.5,
    "vs_mid_bps": -12.3,
    "rate_type": "retail",
    "provider_type": "fintech_psp",
    "transfer_time": "1 - 2 business days",
    "last_updated": "2026-07-06T09:30:00.000Z"
    }
    ],
    "count": 1,
    "mid_rate": 2048.02,
    "mid_source": "open.er-api.com",
    "mid_fetched_at": "2026-07-06T09:05:00.000Z",
    "timestamp": "2026-07-06T09:30:05.000Z",
    "data_freshness": "hourly",
    "as_of": "2026-07-06T09:00:00.000Z"
    }
    GET /api/v1/convert
    Key required

    Convert an amount across a corridor for every provider — gross, net-of-fee delivered value, and the best net amount for the recipient.

    Query parameters

    NameTypeReqDescription
    fromstringyesSource currency (ISO-4217), e.g. GBP
    tostringyesTarget currency (ISO-4217), e.g. NGN
    amountnumberyesAmount in the source currency (> 0)

    Example request

    curl "https://modan.io/api/v1/convert?from=GBP&to=NGN&amount=1000" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "from": "GBP",
    "to": "NGN",
    "amount": 1000,
    "mid": { "rate": 2048.02, "converted": 2048020, "source": "open.er-api.com", "fetched_at": "2026-07-06T09:05:00.000Z" },
    "best": { "provider_id": "wise", "provider_name": "Wise", "rate": 2045.50, "fee": 2.99, "fee_currency": "GBP", "converted": 2045500, "net_converted": 2039382.55, "spread_bps": 0, "vs_mid_bps": -12.3 },
    "providers": [
    { "provider_id": "wise", "provider_name": "Wise", "rate": 2045.50, "fee": 2.99, "fee_currency": "GBP", "converted": 2045500, "net_converted": 2039382.55, "spread_bps": 0, "vs_mid_bps": -12.3 }
    ],
    "count": 1,
    "timestamp": "2026-07-06T09:30:05.000Z",
    "data_freshness": "hourly",
    "as_of": "2026-07-06T09:00:00.000Z"
    }

    Fetch — multi-pair lookups

    fastforex-style convenience lookups. Every pair returns the independent mid (cross-computed through the freshest USD reference snapshot) plus the best tracked provider where the pair is a covered corridor. One call = one quota unit regardless of pair count. Unsupported currencies return 400 with the supported list.

    GET /api/v1/fetch-one
    Key required

    A single base→quote pair: mid + best tracked provider.

    Query parameters

    NameTypeReqDescription
    fromstringyesBase currency
    tostringyesQuote currency

    Example request

    curl "https://modan.io/api/v1/fetch-one?from=USD&to=NGN" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "base": "USD",
    "quote": "NGN",
    "mid": 1377.0469,
    "provider_best": { "rate": 1385, "provider_id": "worldremit", "last_updated": "2026-07-10T17:34:49.256Z" },
    "source": "open.er-api.com",
    "fetched_at": "2026-07-10T17:05:01.477Z",
    "timestamp": "2026-07-10T17:45:45.058Z",
    "data_freshness": "hourly",
    "as_of": "2026-07-10T17:00:00.000Z"
    }
    GET /api/v1/fetch-multi
    Key required

    One base against up to 20 quote currencies in a single call.

    Query parameters

    NameTypeReqDescription
    fromstringyesBase currency
    tostringyes1–20 quote currencies, comma-separated

    Example request

    curl "https://modan.io/api/v1/fetch-multi?from=USD&to=NGN,KES,GHS" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "base": "USD",
    "results": {
    "NGN": {
    "mid": 1377.0469,
    "provider_best": { "rate": 1385, "provider_id": "worldremit", "last_updated": "2026-07-10T17:34:49.256Z" }
    },
    "KES": {
    "mid": 129.16139,
    "provider_best": null
    }
    },
    "count": 2,
    "source": "open.er-api.com",
    "fetched_at": "2026-07-10T17:05:01.477Z",
    "timestamp": "2026-07-10T17:45:45.058Z",
    "data_freshness": "hourly",
    "as_of": "2026-07-10T17:00:00.000Z"
    }
    GET /api/v1/fetch-matrix
    Key required

    Full cross matrix of up to 10 bases × 10 quotes.

    Query parameters

    NameTypeReqDescription
    fromstringyes1–10 base currencies, comma-separated
    tostringyes1–10 quote currencies, comma-separated

    Example request

    curl "https://modan.io/api/v1/fetch-matrix?from=USD,GBP&to=NGN,KES" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "bases": ["USD", "GBP"],
    "quotes": ["NGN", "KES"],
    "results": {
    "USD": {
    "NGN": { "mid": 1377.0469, "provider_best": { "rate": 1385, "provider_id": "worldremit", "last_updated": "2026-07-10T17:34:49.256Z" } },
    "KES": { "mid": 129.16139, "provider_best": null }
    },
    "GBP": {
    "NGN": { "mid": 1846.85, "provider_best": { "rate": 1851.65, "provider_id": "accrue", "last_updated": "2026-07-10T17:34:49.256Z" } },
    "KES": { "mid": 173.24, "provider_best": null }
    }
    },
    "source": "open.er-api.com",
    "fetched_at": "2026-07-10T17:05:01.477Z",
    "timestamp": "2026-07-10T17:45:45.058Z",
    "data_freshness": "hourly",
    "as_of": "2026-07-10T17:00:00.000Z"
    }
    GET /api/v1/fetch-many-to-one
    Key required

    Up to 20 base currencies into a single quote currency.

    Query parameters

    NameTypeReqDescription
    fromstringyes1–20 base currencies, comma-separated
    tostringyesSingle quote currency

    Example request

    curl "https://modan.io/api/v1/fetch-many-to-one?from=USD,GBP&to=NGN" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "quote": "NGN",
    "results": {
    "USD": { "mid": 1377.0469, "provider_best": { "rate": 1385, "provider_id": "worldremit", "last_updated": "2026-07-10T17:34:49.256Z" } },
    "GBP": { "mid": 1846.85, "provider_best": { "rate": 1851.65, "provider_id": "accrue", "last_updated": "2026-07-10T17:34:49.256Z" } }
    },
    "count": 2,
    "source": "open.er-api.com",
    "fetched_at": "2026-07-10T17:05:01.477Z",
    "timestamp": "2026-07-10T17:45:45.058Z",
    "data_freshness": "hourly",
    "as_of": "2026-07-10T17:00:00.000Z"
    }

    Time series & history

    GET /api/v1/rates/history
    Key required

    Raw historical provider observations for a corridor, paginated and oldest-first by default.

    Query parameters

    NameTypeReqDescription
    fromstringyesSource currency (ISO-4217), e.g. GBP
    tostringyesTarget currency (ISO-4217), e.g. NGN
    periodstringno1d | 7d | 30d (default) | 90d
    orderstringnoasc (default, oldest-first) | desc
    limitintegernoPage size, ≤ 5000 (default 500)
    offsetintegernoRows to skip (default 0). Response carries has_more.
    providerstringnoFilter to a single provider_id

    Example request

    curl "https://modan.io/api/v1/rates/history?from=GBP&to=NGN&period=30d&limit=500" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "corridor": "GBP/NGN",
    "period": "30d",
    "from_date": "2026-06-10T09:30:00.000Z",
    "to_date": "2026-07-10T09:30:00.000Z",
    "order": "asc",
    "limit": 250,
    "offset": 0,
    "has_more": false,
    "data": [
    { "timestamp": "2026-06-10T09:30:00.000Z", "provider_id": "wise", "provider_name": "Wise", "rate": 2038.10, "fee": 2.99, "fee_currency": "GBP", "spread_bps": 12.4 }
    ],
    "count": 1,
    "data_freshness": "hourly",
    "as_of": "2026-07-10T09:00:00.000Z"
    }
    GET /api/v1/time-series
    Key required

    Bucketed series of mid + best provider per interval. Untracked pairs return a mid-only series with a note.

    Query parameters

    NameTypeReqDescription
    fromstringyesSource currency (ISO-4217), e.g. GBP
    tostringyesTarget currency (ISO-4217), e.g. NGN
    intervalstringnoP1D (daily, default, ≤366 buckets) | PT1H (hourly, ≤168)
    periodstringno1d | 7d | 30d | 90d — or pass start & end
    startstringnoISO-8601 window start (with end)
    endstringnoISO-8601 window end (defaults to now)

    Example request

    curl "https://modan.io/api/v1/time-series?from=GBP&to=NGN&period=7d&interval=P1D" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "corridor": "GBP/NGN",
    "interval": "P1D",
    "start": "2026-07-03T00:00:00.000Z",
    "end": "2026-07-10T17:45:00.000Z",
    "tracked_corridor": true,
    "data": [
    { "t": "2026-07-09T00:00:00.000Z", "mid": 2046.11, "best": 2044.90, "samples": 18 }
    ],
    "count": 1,
    "timestamp": "2026-07-10T17:45:45.058Z",
    "data_freshness": "hourly",
    "as_of": "2026-07-10T17:00:00.000Z"
    }
    GET /api/v1/historical
    Key required

    The corridor snapshot as of a past date — mid plus each provider's most recent rate at that point.

    Query parameters

    NameTypeReqDescription
    fromstringyesSource currency (ISO-4217), e.g. GBP
    tostringyesTarget currency (ISO-4217), e.g. NGN
    datestringyesCalendar date, YYYY-MM-DD (not in the future)

    Example request

    curl "https://modan.io/api/v1/historical?from=GBP&to=NGN&date=2026-07-01" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "corridor": "GBP/NGN",
    "providers": [
    { "provider_id": "wise", "provider_name": "Wise", "rate": 2041.30, "fee": 2.99, "fee_currency": "GBP", "spread_bps": 8.1, "vs_mid_bps": -10.2, "rate_type": "retail", "provider_type": "fintech_psp", "transfer_time": "1 - 2 business days", "last_updated": "2026-07-01T21:50:00.000Z" }
    ],
    "count": 1,
    "mid_rate": 2043.40,
    "mid_source": "open.er-api.com",
    "mid_fetched_at": "2026-07-01T21:05:00.000Z",
    "timestamp": "2026-07-10T17:45:45.058Z",
    "date": "2026-07-01",
    "as_of": "2026-07-02T00:00:00.000Z",
    "data_freshness": "hourly"
    }
    GET /api/v1/change
    Key required

    Absolute and percentage change of the mid and best rate over a period.

    Query parameters

    NameTypeReqDescription
    fromstringyesSource currency (ISO-4217), e.g. GBP
    tostringyesTarget currency (ISO-4217), e.g. NGN
    periodstringno1d | 7d (default) | 30d | 90d

    Example request

    curl "https://modan.io/api/v1/change?from=GBP&to=NGN&period=7d" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "corridor": "GBP/NGN",
    "period": "7d",
    "start": { "at": "2026-07-03T17:45:45.058Z", "mid": 2038.02, "best": 2036.50 },
    "end": { "at": "2026-07-10T17:45:45.058Z", "mid": 2048.02, "best": 2045.50 },
    "change": {
    "mid": { "abs": 10.00, "pct": 0.4906 },
    "best": { "abs": 9.00, "pct": 0.4419 }
    },
    "timestamp": "2026-07-10T17:45:45.058Z",
    "data_freshness": "hourly",
    "as_of": "2026-07-10T17:00:00.000Z"
    }

    Discovery

    GET /api/v1/rates/provider
    Key required

    Every corridor and current rate a single provider quotes.

    Query parameters

    NameTypeReqDescription
    providerstringyesA provider_id — see GET /providers

    Example request

    curl "https://modan.io/api/v1/rates/provider?provider=lemfi" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "provider": { "id": "lemfi", "name": "LemFi", "provider_type": "fintech_psp", "rate_type": "retail", "region": "West Africa", "transfer_time": "In Minutes", "website_url": "https://lemfi.com" },
    "corridors": [
    { "from": "GBP", "to": "NGN", "rate": 2050.50, "fee": 0, "fee_currency": "GBP", "last_updated": "2026-07-10T17:34:49.256Z" },
    { "from": "USD", "to": "KES", "rate": 129.40, "fee": 0, "fee_currency": "USD", "last_updated": "2026-07-10T17:34:49.256Z" }
    ],
    "count": 2,
    "timestamp": "2026-07-10T17:45:45.058Z",
    "data_freshness": "hourly",
    "as_of": "2026-07-10T17:00:00.000Z"
    }
    GET /api/v1/corridors
    Key required

    All covered currency corridors with provider count, best rate and average spread.

    Example request

    curl "https://modan.io/api/v1/corridors" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "corridors": [
    { "from": "GBP", "to": "NGN", "provider_count": 8, "best_rate": 2050.50, "avg_spread_bps": 34.2, "last_updated": "2026-07-10T17:34:49.256Z" }
    ],
    "count": 1,
    "timestamp": "2026-07-10T17:45:45.058Z",
    "data_freshness": "hourly",
    "as_of": "2026-07-10T17:00:00.000Z"
    }
    GET /api/v1/providers
    Key required

    All active providers with type, region, transfer time and payment methods.

    Example request

    curl "https://modan.io/api/v1/providers" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "providers": [
    { "id": "wise", "name": "Wise", "logo_url": "https://cdn.modan.io/providers/wise.png", "website_url": "https://wise.com", "provider_type": "fintech_psp", "rate_type": "retail", "region": "Global", "transfer_time": "1 - 2 business days", "payment_methods": ["bank_transfer", "card"] }
    ],
    "count": 1,
    "timestamp": "2026-07-10T17:45:45.058Z"
    }
    GET /api/v1/currencies
    Key required

    Active currencies and the corridors currently served.

    Example request

    curl "https://modan.io/api/v1/currencies" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "currencies": [
    { "code": "GBP", "name": "British Pound", "type": "source", "flag_url": "https://cdn.modan.io/flags/gb.svg", "is_active": true, "sort_order": 1 },
    { "code": "NGN", "name": "Nigerian Naira", "type": "target", "flag_url": "https://cdn.modan.io/flags/ng.svg", "is_active": true, "sort_order": 2 }
    ],
    "corridors": [ { "from": "GBP", "to": "NGN" } ],
    "currency_count": 2,
    "corridor_count": 1,
    "timestamp": "2026-07-10T17:45:45.058Z"
    }

    Account & status

    GET /api/v1/admin/usage
    Key required
    No quota

    Your account's metering: plan, limit, used, remaining, reset, a per-key breakdown and 7-day history. Checking usage never consumes quota.

    Example request

    curl "https://modan.io/api/v1/admin/usage" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Example response

    {
    "plan": "free",
    "period_start": "2026-07-10T00:00:00.000Z",
    "period_end": "2026-07-11T00:00:00.000Z",
    "limit": 50,
    "used": 4,
    "remaining": 21,
    "reset": 1783728000,
    "key": { "id": "b1c2d3e4", "name": "default" },
    "keys_today": [ { "key_id": "b1c2d3e4", "name": "default", "requests": 4 } ],
    "daily_history": [
    { "date": "2026-07-09", "requests": 12 },
    { "date": "2026-07-10", "requests": 4 }
    ],
    "timestamp": "2026-07-10T17:45:45.058Z"
    }
    GET /api/v1/status
    No key
    No quota

    Public platform health — corridor and provider counts, last rate update and mid-feed freshness. No API key required.

    Example request

    curl "https://modan.io/api/v1/status"

    Example response

    {
    "status": "ok",
    "version": "v1",
    "corridors": 12,
    "providers": 19,
    "last_rate_update": "2026-07-10T17:34:49.256Z",
    "mid_feed": { "source": "open.er-api.com", "last_fetched": "2026-07-10T17:05:01.477Z", "age_seconds": 2444 },
    "timestamp": "2026-07-10T17:45:45.058Z"
    }

    Ingestion

    Data-team accounts push observations. The batch is atomic (any invalid row → 422, nothing inserted). Ingestion requires a key owned by an admin or treasury account and does NOT consume the read quota.

    POST /api/v1/rates
    Key required
    No quota

    Ingest one rate observation or an array of up to 100. from/to alias source_currency/target_currency; fee, fee_currency, notes and effective_from are optional.

    Example request

    curl -X POST "https://modan.io/api/v1/rates" \
    -H "X-API-Key: mdn_live_TEAM_KEY_HERE" \
    -H "Content-Type: application/json" \
    -d '[
    { "provider_id": "wise", "from": "GBP", "to": "NGN", "rate": 2045.5, "fee": 2.99 },
    { "provider_id": "lemfi", "from": "USD", "to": "KES", "rate": 129.4 }
    ]'

    Example response

    { "inserted": 2 }

    Rate Limits

    Free

    50 req / day

    Hourly rates

    free tier

    Individual

    250 req / day

    Hourly rates

    pro tier

    Team

    1,000 req / day

    Real-time rates

    enterprise tier

    Limits reset at midnight UTC. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers. Exceeding your daily limit returns 429 Too Many Requests.

    Data freshness is tiered. Free and Individual keys serve rates as of the top of the current UTC hour; Team (enterprise) keys serve every observation in real time. Responses state which you got via the data_freshness field (plus as_of when hourly) and an X-Data-Freshness header — the delay is always explicit, never silent.

    Code Examples

    Fetching rates for GBP → NGN using different languages.

    curl "https://modan.io/api/v1/rates?from=GBP&to=NGN" \
    -H "X-API-Key: mdn_live_YOUR_KEY_HERE"

    For AI Agents & LLMs

    Client-by-client setup (Claude Code, Cursor, VS Code, any MCP client), the tool list and example prompts: modan.io/docs/mcp.

    Modan is built to be consumed by AI systems. Claude, Cursor, Codex and any MCP (Model Context Protocol) client can call Modan natively — live rates, conversion, history and discovery exposed as tools (get_rates, convert, fetch_rates, get_history, list_corridors, list_providers, list_currencies), authenticated with the same API key and daily quota.

    Claude Code

    claude mcp add --transport http modan https://modan.io/api/mcp \
    --header "X-API-Key: mdn_live_YOUR_KEY_HERE"

    Claude Desktop / Cursor / any MCP client

    {
    "mcpServers": {
    "modan": {
    "type": "http",
    "url": "https://modan.io/api/mcp",
    "headers": { "X-API-Key": "mdn_live_YOUR_KEY_HERE" }
    }
    }
    }

    Prefer plain HTTP? Point your agent at the machine-readable docs: llms.txt (index), llms-full.txt (complete reference in one file) and openapi.json (OpenAPI 3.1). Every error response is JSON with an actionable message, so agents can self-correct.

    Response Format

    // GET /rates?from=GBP&to=NGN → 200 OK
    {
    "corridor": "GBP/NGN",
    "providers": [
    {
    "provider_id": "wise",
    "provider_name": "Wise",
    "rate": 2045.50,
    "fee": 2.99,
    "fee_currency": "GBP",
    "spread_bps": 24.5,
    "vs_mid_bps": -12.3,
    "rate_type": "retail",
    "provider_type": "fintech_psp",
    "transfer_time": "1 - 2 business days",
    "last_updated": "2026-07-06T09:30:00.000Z"
    }
    ],
    "count": 1,
    "mid_rate": 2048.02,
    "mid_source": "open.er-api.com",
    "mid_fetched_at": "2026-07-06T09:05:00.000Z",
    "timestamp": "2026-07-06T09:30:05.000Z",
    "data_freshness": "hourly",
    "as_of": "2026-07-06T09:00:00.000Z"
    }

    spread_bps is the distance, in basis points, below the best rate of the same kind observed in the corridor at that moment (0 = best of its kind). It is not a spread against an independent mid-market rate.

    rate_type is what kind of price it is — official, interbank, retail, p2p or parallel — and provider_type is what kind of institution published it. They are separate questions: a commercial bank may post a retail board rate or an interbank one. Ranking across kinds is meaningless, so a central bank's official reference is never a corridor's best rate and never the best value on /convert — it is real, and nobody can deal on it. It is still returned, labelled for what it is.

    last_updated is the timestamp we last recorded an observation of that provider's rate for that corridor. All rates are timestamped, append-only observations.

    When an independent mid-market reference is available for the corridor, the response additionally carries top-level mid_rate, mid_source and mid_fetched_at, and each provider entry gains vs_mid_bps (basis points vs that mid; negative means below mid). The mid is cross-computed through USD from the reference feed, so every fiat corridor we track carries one; a crossed mid is timestamped with its staler leg. Stablecoins (USDT, USDC) are not quoted by a fiat reference feed, so these fields are omitted there — absence is explicit, never fabricated.

    Errors

    400Bad RequestMissing or malformed query parameters (e.g. from, to, amount, date, or an unsupported currency)
    401UnauthorizedAPI key missing, invalid, or revoked
    403ForbiddenKey not authorized for this action — POST /rates requires an admin/treasury account
    404Not FoundUnknown resource — e.g. an inactive provider on /rates/provider, or an unknown path
    422Unprocessable EntityIngestion validation failed — per-row errors are returned and nothing is inserted
    429Too Many RequestsDaily rate limit exceeded for your tier — see X-RateLimit-Reset
    500Internal Server ErrorServer-side error — retry after a moment
    503Service UnavailableThe mid-market reference feed is temporarily unavailable — retry shortly

    Every error is JSON with an actionable error message, so both humans and AI agents can self-correct. A 429 also echoes the tier limit and reset:

    // 429 Too Many Requests
    {
    "error": "Rate limit exceeded",
    "limit": 50,
    "reset": 1783728000
    }