African FX exchange rate API — documentation
Integrate live African FX and provider pricing data into your applications using the Modan REST API.
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.
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
/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/ratesEvery 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
fromstringyesSource currency (ISO-4217), e.g. GBPtostringyesTarget currency (ISO-4217), e.g. NGNExample 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/convertConvert an amount across a corridor for every provider — gross, net-of-fee delivered value, and the best net amount for the recipient.
Query parameters
fromstringyesSource currency (ISO-4217), e.g. GBPtostringyesTarget currency (ISO-4217), e.g. NGNamountnumberyesAmount 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-oneA single base→quote pair: mid + best tracked provider.
Query parameters
fromstringyesBase currencytostringyesQuote currencyExample 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-multiOne base against up to 20 quote currencies in a single call.
Query parameters
fromstringyesBase currencytostringyes1–20 quote currencies, comma-separatedExample 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-matrixFull cross matrix of up to 10 bases × 10 quotes.
Query parameters
fromstringyes1–10 base currencies, comma-separatedtostringyes1–10 quote currencies, comma-separatedExample 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-oneUp to 20 base currencies into a single quote currency.
Query parameters
fromstringyes1–20 base currencies, comma-separatedtostringyesSingle quote currencyExample 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/historyRaw historical provider observations for a corridor, paginated and oldest-first by default.
Query parameters
fromstringyesSource currency (ISO-4217), e.g. GBPtostringyesTarget currency (ISO-4217), e.g. NGNperiodstringno1d | 7d | 30d (default) | 90dorderstringnoasc (default, oldest-first) | desclimitintegernoPage size, ≤ 5000 (default 500)offsetintegernoRows to skip (default 0). Response carries has_more.providerstringnoFilter to a single provider_idExample 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-seriesBucketed series of mid + best provider per interval. Untracked pairs return a mid-only series with a note.
Query parameters
fromstringyesSource currency (ISO-4217), e.g. GBPtostringyesTarget currency (ISO-4217), e.g. NGNintervalstringnoP1D (daily, default, ≤366 buckets) | PT1H (hourly, ≤168)periodstringno1d | 7d | 30d | 90d — or pass start & endstartstringnoISO-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/historicalThe corridor snapshot as of a past date — mid plus each provider's most recent rate at that point.
Query parameters
fromstringyesSource currency (ISO-4217), e.g. GBPtostringyesTarget currency (ISO-4217), e.g. NGNdatestringyesCalendar 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/changeAbsolute and percentage change of the mid and best rate over a period.
Query parameters
fromstringyesSource currency (ISO-4217), e.g. GBPtostringyesTarget currency (ISO-4217), e.g. NGNperiodstringno1d | 7d (default) | 30d | 90dExample 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/providerEvery corridor and current rate a single provider quotes.
Query parameters
providerstringyesA provider_id — see GET /providersExample 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/corridorsAll 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/providersAll 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/currenciesActive 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/usageYour 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/statusPublic 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/ratesIngest 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
50 req / day
Hourly rates
free tier
250 req / day
Hourly rates
pro tier
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
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}