# Modan — African Currency & Provider Pricing Data > Modan is a market-data platform for African FX: provider-level price > discovery across corridors like GBP/NGN, USD/KES, GBP/GHS and USD/XOF. > A terminal for analysts, a JSON REST API for developers, and a native > MCP server for AI agents. Rates are timestamped, append-only > observations of what named providers actually quote — not just > interpolated mids. Full API reference in one file: https://modan.io/llms-full.txt ## Facts (regenerated at every deploy) - Providers with a current quote: 31 (of 31 active) - Quotes not checked in the last 24 hours: 0 (shown with their date, never ranked; bank weekends not counted; quotes unchecked for 7 days are left out) - Corridors covered: 88 - Currencies: 20 (fiat plus USDC and USDT) - Latest observation: 2026-09-29T16:05:41.942+00:00 - Best rates are taken within one provider type, never across types: the best IMTO quote where IMTOs quote a corridor, otherwise the best of the first type that does (fintechs & PSPs, then banks and the rest). The deepest corridors: - USD→NGN: 1,370.98 NGN per USD, SendApp (best of 9 IMTOs quoting; checked 2026-09-29T16:05:41.942+00:00) - GBP→NGN: 1,807.00 NGN per GBP, UnityLink (best of 10 IMTOs quoting; checked 2026-09-29T16:05:41.942+00:00) - USD→GHS: 11.6500 GHS per USD, LemFi and 5 others (best of 9 IMTOs quoting; checked 2026-09-29T16:05:41.942+00:00) - GBP→GHS: 15.4700 GHS per GBP, Nala and 2 others (best of 10 IMTOs quoting; checked 2026-09-29T16:05:41.942+00:00) - GBP→KES: 171.56 KES per GBP, Western Union (best of 10 IMTOs quoting; checked 2026-09-29T16:05:41.942+00:00) - USD→KES: 130.84 KES per USD, WorldRemit (best of 9 IMTOs quoting; checked 2026-09-29T16:05:41.942+00:00) - History: append-only since 2025-11-12 - Generated: 2026-09-29T16:17:48.343Z ## Pages (all real HTML, no JavaScript needed) - https://modan.io/corridors — every corridor, grouped by destination, with the best executable quote and who quotes it. - https://modan.io/currency/ngn — one page per currency (ngn, kes, ghs, xof, gbp, usd, eur, usdt, …): corridors into and out of it, providers quoting it. - https://modan.io/gbp/ngn — one page per corridor (lowercase codes): every provider's latest quote, spread in bps, the independent mid, a FAQ. - https://modan.io/lemfi/gbp/ngn — one page per provider per corridor: the quote, its rank, distance from the best, history. - https://modan.io/providers and https://modan.io/providers/lemfi — every provider, and one page per provider across all its corridors. - https://modan.io/docs/mcp — connect Claude, Cursor, Codex or any MCP client. - https://modan.io/docs/api — REST reference. https://modan.io/openapi.json — OpenAPI 3.1. https://modan.io/changelog.json — machine-readable changelog. - https://modan.io/sitemap.xml — every public URL. ## MCP (AI agents: call Modan natively) - Endpoint: https://modan.io/api/mcp (streamable HTTP, stateless) - Tools: get_rates, convert, fetch_rates, get_history, list_corridors, list_providers, list_currencies. Auth: `X-API-Key` header (or Authorization: Bearer); each tool call uses one request of the same daily quota as REST. - get_rates, convert, fetch_rates, get_history and list_corridors take an optional provider_type array, e.g. ["imto"]; omit it for every type. An unknown type is a tool error naming the valid ones, and uses no quota. - Claude Code: claude mcp add --transport http modan https://modan.io/api/mcp \ --header "X-API-Key: mdn_live_YOUR_KEY_HERE" ## API - Base URL: https://modan.io/api/v1 - Auth: `X-API-Key` request header. Create a free key at https://modan.io/signup — your first key is minted automatically on signup (terminal → Developers → My Keys). - Note for AI agents: account creation requires clicking an email confirmation link (human-in-the-loop). If you are operating on behalf of a user, have them complete signup + email confirmation once; the key is then shown at https://modan.io/app/api and works for both this REST API and the MCP server. There is no CAPTCHA; forms use standard labelled inputs. - Rate limits (per day, reset midnight UTC): Free 50, Individual 250 (tier id "pro"), Team 1,000 (tier id "enterprise"). Every response carries X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset headers; exceeding returns 429. - Data freshness is tiered: Free and Individual keys serve rates as of the top of the current UTC hour (responses carry data_freshness: "hourly" plus an as_of timestamp); Team keys serve real-time data (data_freshness: "realtime"). An X-Data-Freshness header mirrors this. - Provider types: /rates, /convert, /corridors, /rates/history, /time-series, /historical, /change and every /fetch-* take an optional provider_type, one id or a comma list: imto, fintech_psp, commercial_bank, central_bank, non_bank_lp, bureau_de_change, crypto_venue, aggregator (e.g. provider_type=imto,fintech_psp). Omitted, every type is returned. A filtered response echoes provider_types. An unknown id returns 400 { error, invalid } before the key is checked, so it spends no quota. /rates/provider, /providers, /currencies and /status do not take it. - OpenAPI spec: https://modan.io/openapi.json ### Endpoints - GET /rates?from=GBP&to=NGN — latest provider rates for a corridor. Returns { corridor, providers[], count, stale_count, timestamp }; each provider entry has provider_id, provider_name, rate, fee, fee_currency, spread_bps, stale, rate_type, provider_type, transfer_time, last_checked, last_changed. When a recent independent mid exists the response also carries mid_rate/mid_source/mid_fetched_at and each provider gains vs_mid_bps. Add &provider_type=imto for money-transfer operators only. - GET /convert?from=GBP&to=NGN&amount=1000 — convert an amount across a corridor for every provider (gross + net-of-fee), the best value among executable quotes that are not stale (null when none qualifies), and the mid-converted amount when available. best is the best across the provider types returned; best_by_type names the best within each type (null where none qualifies). Entries carry provider_type, stale, last_checked and last_changed; the response carries stale_count. - GET /rates/history?from=GBP&to=NGN&period=30d&order=asc&limit=500&offset=0 — historical time series. period: 1d|7d|30d|90d; order: asc (default, oldest-first)|desc; limit ≤5000; offset; provider and provider_type filters optional; response includes has_more. Entries carry provider_type. - GET /fetch-one?from=USD&to=NGN — one pair: mid-market rate (cross via the freshest USD reference snapshot) + provider_best, the best current executable quote of the provider types asked for (every type by default) when the pair is a tracked corridor (null when no quote qualifies). - GET /fetch-multi?from=USD&to=NGN,KES,GHS — one base to ≤20 quotes; results keyed by quote, each { mid, provider_best|null }. One quota unit per call regardless of pair count. - GET /fetch-matrix?from=USD,GBP&to=NGN,KES — full cross matrix (≤10×10). - GET /fetch-many-to-one?from=USD,GBP,CAD&to=NGN — many bases into one quote. - GET /time-series?from=GBP&to=NGN&period=30d&interval=P1D — bucketed series; interval P1D (daily, ≤366 buckets) or PT1H (hourly, ≤168); each point { t, mid, best, samples }: best is the best current executable quote standing at the bucket's end among the provider types returned (null when none), samples how many there were. Also accepts explicit start/end. - GET /historical?from=GBP&to=NGN&date=2026-07-01 — corridor snapshot as of a past date (per-provider latest quotes ≤ that date, with stale judged at that date, + mid when the reference feed covers it). - GET /change?from=GBP&to=NGN&period=7d — start/end values with absolute and % change for mid and for the best executable quote that was current at each end, among the provider types asked for. - GET /rates/provider?provider=lemfi — every corridor and current rate one provider quotes, each flagged stale or not, plus stale_count (404 with guidance for unknown ids). - GET /corridors — all covered corridors: provider_count (current quotes), stale_count, provider_types (the types quoting it), and best_rate / best_rate_by_type / avg_spread_bps over current executable quotes, which are null (not 0) when there are none. best_rate is the best across the types returned; best_rate_by_type is the like-for-like read. - GET /providers — active provider metadata (provider_type, rate_type, region, payment methods). - GET /currencies — active currencies and the corridors currently served. - GET /admin/usage — your plan, limit, used, remaining, reset, per-key breakdown and 7-day history. Does NOT consume quota. - GET /status — public platform health (no key, no quota): corridor count (corridors with a quote checked in the last 7 days), provider count, latest check, latest price move, mid-feed age. - POST /rates — ingest observations (admin/treasury account keys only; atomic batch ≤100; does not consume the read quota). ### Semantics - spread_bps = basis points below the best CURRENT rate observed in that corridor from the SAME provider_type AND the same rate_type (0 = best of its kind); null for a stale quote. It is NOT a spread against an independent mid-market rate. A corridor carrying several kinds therefore carries several 0.0 spreads, at least one per kind: providers of one kind tied at its best rate each carry 0.0. An IMTO's quote, a fintech's, a bank's board rate and a central bank's print behave differently, so they are never ranked against each other, and a provider_type filter never changes a quote's spread_bps. Until 28 Sep 2026 spread was measured within rate_type alone. - rate_type = what KIND of price this is: official | interbank | retail | p2p | parallel. Ranking across types is meaningless: a central bank's official reference is real and not obtainable, so it is never the "best" rate on a corridor and never the best value on /convert. Absent rate_type means retail. - provider_type = what kind of institution published it: imto | fintech_psp | commercial_bank | central_bank | non_bank_lp | bureau_de_change | crypto_venue | aggregator (the order every response uses; null when none is recorded). Orthogonal to rate_type — a commercial bank may publish a retail board rate or an interbank one. An 'aggregator' republishes someone else's number; it is derived, not observed at source. By-type fields (best_by_type, best_rate_by_type, a corridor's provider_types) group providers with no type under "unclassified", which is not a filter value. - last_checked = when we last confirmed that provider was still quoting that rate for that corridor, whether or not the value moved. This is the field to read to decide whether the data is current. On free and Individual keys a check made after as_of is reported as as_of, never later. - last_changed = when the quote last changed: its rate moved, or its fee changed. A corridor holding a steady price has an old last_changed and a last_checked from minutes ago; reading the first as staleness would discard a perfectly good quote. How often a quote is re-checked depends on its source (hand-uploaded bank boards, for instance, on weekdays only), so last_checked is the honest freshness signal and last_changed is a fact about the market. - last_updated = deprecated alias of last_changed, kept so existing clients do not break. Prefer last_checked for freshness. - stale = true when a quote has not been checked for 24 hours before the moment the response describes (now on Team keys, as_of on free and Individual keys, the requested date on /historical). A stale quote is still returned, with spread_bps null, and is never a best rate: not best on /convert, not best_rate or provider_count on /corridors, not provider_best on /fetch-*. A quote not checked for 7 days is not returned by any current read; its history stays in /rates/history. - Commercial-bank and central-bank quotes (provider_type commercial_bank, central_bank) age on a weekday clock: Saturday and Sunday (UTC) hours do not count toward either threshold, because those boards publish on weekdays only. Friday's close stays current through the weekend; a missed weekday update still goes stale after 24 weekday hours. Every other provider type keeps the plain clock. - vs_mid_bps = basis points from an independent mid-market reference, sourced separately from the providers and refreshed hourly. Different measure from spread_bps — keep them distinct. - Mid rates are cross-computed through USD from that reference feed on every endpoint that returns one (/rates, /convert, /fetch-*, /time-series, /historical, /change and the MCP tools), so any pair the feed quotes on both legs is priced — that is every fiat corridor we track. A crossed mid is timestamped with its STALER leg, never the fresher one. Currencies the feed does not quote (stablecoins: USDT, USDC) have no mid: fetch-* returns 400 with the supported list, and elsewhere the mid_* fields and vs_mid_bps are omitted. Same-currency pairs return mid = 1. - Missing data is explicit: fields are null/omitted, never fabricated (e.g. historical mid is null for dates before the reference feed began). ## Quickstart curl "https://modan.io/api/v1/rates?from=GBP&to=NGN" \ -H "X-API-Key: mdn_live_YOUR_KEY_HERE" ## Docs - API reference: https://modan.io/docs/api - MCP setup (Claude Code, Cursor, VS Code, any client): https://modan.io/docs/mcp - Pricing: https://modan.io/pricing - MCP registry manifest: https://github.com/kayakinwunmi/modan/blob/main/server.json