Developers · analysts

    spread_bps vs vs_mid_bps: reading FX quotes in basis points

    Modan's rate responses carry two numbers in basis points that look alike and mean different things. One tells you how a provider compares with its peers; the other tells you how far the market as a whole sits from a neutral reference. Confusing them produces confident, wrong conclusions.

    Updated 28 Sep 2026 · 5 min read · by Modan

    A basis point

    One basis point (bp) is one hundredth of one percent: 100 bps = 1%. FX is quoted in basis points because the differences that matter are small in percentage terms and large in money terms. On a transfer of 10,000 units, a 50 bps difference is 50 units.

    spread_bps: distance from the best of its kind

    spread_bps is how far a provider's rate sits below the best current rate on that corridor, at that moment, from the same `provider_type` and the same `rate_type`. The best quote of each kind carries 0.0; every other current quote of that kind carries a positive number. It is computed within a kind on purpose. A money-transfer operator's quote, a fintech's in-app rate, a bank's board rate and a central bank's print behave differently, and ranking them together says little about who is cheaper. So an IMTO's quote is measured against the best IMTO quote, a fintech's against the best fintech quote, a bank's interbank print against the best bank interbank print, and an official reference is never in the comparison. Until 28 September 2026 spread was measured within the rate type alone, across every kind of provider.

    A stale quote, one not checked for 24 hours, carries spread_bps: null and stale: true. It is still listed, but it is not ranked and it never sets the benchmark the others are measured against: a price nobody has confirmed for a day cannot be the one everyone else is behind.

    Suppose the best IMTO quote on a corridor is 1,900 and another IMTO quotes 1,881. Its spread is (1,900 − 1,881) ÷ 1,900 × 10,000 ≈ 100 bps. The number says nothing about whether 1,900 itself is a good price — only that this provider is about 1% behind the leader of its kind.

    spread_bps = (best_current_rate_of_same_kind − rate) / best_current_rate_of_same_kind × 10,000
    kind = provider_type + rate_type
    0.0  = best of its kind; a corridor with three kinds carries three zeros
    null = stale (not checked for 24 hours): listed, never ranked

    vs_mid_bps: distance from an independent reference

    vs_mid_bps is how far a rate sits from an independent mid-market reference that Modan sources separately from every provider and refreshes hourly. It is signed: negative means the quote is below the mid (the customer receives less than the reference implies), positive means above. Because the reference is not a provider, this number is the one that answers "is this market expensive right now?".

    The mid is crossed through USD from the reference feed, so any fiat pair the feed quotes on both legs has one; a crossed mid is timestamped with its staler leg, never the fresher one. Stablecoins are the exception: no fiat reference feed quotes USDT or USDC, so on those corridors vs_mid_bps is omitted rather than estimated. The mid_source field in every response names the feed.

    Which one to use

    • Choosing a provider for a transfer: ask for the kind of provider you would use (provider_type=imto, say), sort its executable quotes that are not stale by spread_bps (a stale quote's is null), then check the fee — or let the convert endpoint compute the net amount per provider, which never picks a stale quote and names the best within each provider type in best_by_type.
    • Benchmarking a desk, a bank or a whole corridor: use vs_mid_bps. It is the only measure anchored outside the providers themselves.
    • Reading market structure: the gap between the best and the worst current executable quote (the *Dispersion* tile on every corridor page) shows how fragmented pricing is on that corridor right now.

    Where the numbers appear

    Both fields are on every provider entry returned by /rates, /convert and /historical and by the MCP get_rates and convert tools, and both columns are on every corridor page. The same definitions apply everywhere; the API documentation has the field-by-field reference. Each of those reads takes provider_type to show one kind of provider at a time, and a quote's spread_bps is the same either way, because it is only ever measured within its own kind.

    Frequently asked questions

    Can spread_bps be negative?
    No. It measures distance below the best current rate of the same kind, so the best quote of each kind carries 0.0, every other current quote of that kind carries a positive number, and a stale quote carries null rather than a number.
    Why does one corridor show several 0.0 spreads?
    Because spread is measured within a kind: a provider type and a rate type together. A corridor with money-transfer operators, fintechs, a bank's interbank print and an official reference has a best of each kind, and each best carries 0.0. Two providers of one kind at the same top rate both carry 0.0 as well: a tie is shown as a tie, and neither is singled out. Only current quotes of executable kinds are ranked.
    Is vs_mid_bps the provider's fee?
    No. It is the distance between the quoted rate and an independent mid-market reference. A provider's explicit fee is a separate field; the convert endpoint combines rate and fee into the net amount delivered.

    See the data

    More guides