Zpět na blog

Where Do Exchange Rate APIs Get Their Data? (And Why Two APIs Disagree)

V
Vlado Grigirov
August 15, 2026
Currency API Exchange Rates FX Data API Integration Finexly Developer Guide

Ask five different services for the EUR/USD rate right now and you will get five slightly different numbers. Not wildly different — but different in the fourth decimal place, sometimes the third. If you have ever had a finance team open a ticket because your checkout showed 1.0847 while their bank statement says 1.0821, you already know this is not an academic question.

So where do exchange rate APIs get their data? The honest answer is that no API "knows" the exchange rate, because there is no single exchange rate to know. Foreign exchange is an over-the-counter market with no central exchange and no closing bell — just thousands of institutions quoting prices to each other across the globe. Every API you can call is a pipeline that samples that market, cleans it, and hands you one number. This guide walks through that pipeline layer by layer, explains exactly why two providers disagree, and shows you how to audit a feed before you build billing logic on top of it.

The Short Answer: Three Layers Between the Market and Your JSON

Every exchange rate API — free or paid, ours included — is built from the same three layers:

  1. Acquisition. Raw prices are pulled from upstream sources: institutional FX feeds, central bank publications, and broker or retail quotes.
  2. Normalization. Those raw prices are validated, outliers are discarded, multiple sources are blended, and a single reference rate per pair is derived.
  3. Delivery. The derived rate is snapshotted on a schedule, cached, and served over HTTP with a timestamp.

Differences at any one of those three layers produce a different number in your response body. Most developers assume disagreement comes from layer 1. In practice, layers 2 and 3 cause just as much of it.

Layer 1 — Where the Raw Prices Actually Come From

Interbank and Institutional Feeds

The closest thing to a "real" exchange rate is the interbank market: the prices at which large banks and liquidity providers deal with each other. These prices arrive as continuous streams of bid and ask quotes from trading venues, prime brokers, and market-data vendors.

Feeds like this are the highest-fidelity source available. They are also the most expensive, which is the main reason free APIs rarely use them as a primary source. When a provider says it offers "real-time" or "sub-minute" rates, it is almost always because institutional feeds sit at the top of its pipeline.

Note that an interbank feed gives you two prices, not one — a bid and an ask. The single rate you see in an API response is usually the midpoint between them. If that distinction is new to you, our guide to the bid-ask spread in currency exchange covers it in detail.

Central Bank Reference Rates

The second major source is official central bank publications. The best-known example is the European Central Bank, which publishes euro foreign exchange reference rates on each TARGET business day at around 16:00 CET, based on a concertation procedure among European central banks. Dozens of other central banks publish equivalent daily rates for their own currencies.

Central bank rates have two enormous advantages: they are free, and they are authoritative. Many tax authorities and accounting standards explicitly accept them for reporting. This is why so much of the free API ecosystem is built on top of them. Frankfurter, a widely used open-source project in this space, tracks daily rates from 84 central banks covering 201 currencies, with history reaching back to 1948 — all of it public data, redistributed.

They also have two serious limitations:

  • They are daily snapshots, not live prices. A 16:00 CET reference rate tells you nothing about what happened at 09:00 or 22:00.
  • They stop on weekends and holidays. If your API returns no data for a Saturday, or repeats Friday's number, an ECB-derived source is the usual explanation.

Retail and Broker Quotes

The third source is retail-facing pricing: what a bank, card network, payment processor, or money transfer service will actually give a customer. These rates already include a markup — a margin baked into the price on top of the market rate.

This is the reason a rate you see on a consumer comparison site does not match the rate on your bank statement. It is not an error in either place; they are measuring different things. Consumer-facing sites typically show the mid-market rate, while your bank quotes you the mid-market rate plus its spread. For most software use cases, you want the mid-market number and you want to apply your own markup explicitly, where you can see it and audit it.

Layer 2 — How Providers Turn Feeds Into a Single Rate

Once the raw prices land, the provider has to decide what number to publish. Four decisions happen here, and every one of them is a place where providers diverge.

Blending. Most commercial APIs do not rely on a single upstream source. Open Exchange Rates, for example, describes its data as collected from multiple providers and blended algorithmically. Blending smooths out a single bad tick, but the blending weights are proprietary — which is precisely why two blended feeds never agree exactly.

Outlier rejection. A bad quote from one venue can be an order of magnitude off. Providers apply filters that discard prices outside a tolerance band around the consensus. Aggressive filtering means stable rates but slower reaction to genuine moves. Loose filtering means fast reaction but occasional noise.

Mid derivation. If the upstream feed is bid/ask, the provider publishes a mid. A simple midpoint (bid + ask) / 2 is standard, but volume-weighted approaches produce a slightly different result.

Cross-rate triangulation. No provider sources every one of the 30,000+ possible currency pairs directly. Instead, most pairs are computed through a pivot currency — usually USD or EUR:

GBP/JPY = (USD/JPY) / (USD/GBP)

That means the rate you get for an exotic pair inherits rounding and timing from two other pairs. Providers that pivot on USD and providers that pivot on EUR will land on different numbers for the same cross. We cover the mechanics in cross exchange rates explained.

Layer 3 — How the Rate Reaches Your Code

The final layer is the one developers control most and think about least.

Update cadence is the biggest single differentiator between providers and between pricing tiers. Free plans commonly refresh once or twice a day. Paid tiers refresh hourly, every ten minutes, or every 60 seconds. Two APIs sourcing identical data will disagree simply because one snapshotted at 14:00 and the other at 14:47.

Caching compounds this. Most APIs sit behind a CDN, and most well-built clients cache locally on top of that. Add a 15-minute edge cache to a 10-minute refresh and your application is potentially working with a rate that is 25 minutes old. That is fine for displaying prices and unacceptable for settling a trade — the practical question is always how stale is too stale for this specific operation. Our guide to caching and error handling for currency APIs covers how to size those windows.

Timestamps are your defence. Every serious API returns the moment the rate was captured. Read it. Do not assume that the moment you received a response is the moment the rate was true:

const MAX_AGE_SECONDS = 900; // 15 minutes

async function getRate(base, symbol) {
  const res = await fetch(
    `https://api.finexly.com/v1/latest?base=${base}&symbols=${symbol}`,
    { headers: { Authorization: `Bearer ${process.env.FINEXLY_API_KEY}` } }
  );

  const data = await res.json();
  const ageSeconds = Math.floor(Date.now() / 1000) - data.timestamp;

  if (ageSeconds > MAX_AGE_SECONDS) {
    throw new Error(`Rate is ${ageSeconds}s old — refusing to price on stale data`);
  }

  return { rate: data.rates[symbol], ageSeconds };
}

Here is the underlying request and a representative response shape:

curl "https://api.finexly.com/v1/latest?base=USD&symbols=EUR,GBP,JPY" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "success": true,
  "base": "USD",
  "timestamp": 1755244800,
  "rates": {
    "EUR": 0.9241,
    "GBP": 0.7863,
    "JPY": 147.2150
  }
}

Full parameter and endpoint details are in the Finexly API documentation.

Why Two APIs Return Different Numbers for the Same Pair

Putting the three layers together, here are the six causes of disagreement, roughly in order of how much damage they do:

  1. Different snapshot times. The single most common cause. Nothing is wrong with either feed; they simply looked at different moments.
  2. Different source mixes. A central-bank-derived rate and an interbank-derived rate are measuring two different things by definition.
  3. Mid-market versus marked-up. One provider gives you the market midpoint, another gives you a consumer-facing price with a spread already inside it.
  4. Different pivot currencies for crosses. USD-pivoted and EUR-pivoted triangulation produce different results for the same non-USD pair.
  5. Precision and rounding. Six decimals truncated to four, or rates published as inverse pairs and re-inverted, both introduce drift.
  6. Caching layers you forgot about. Your CDN, your framework's HTTP cache, and your own Redis layer all add age.

A useful rule of thumb: for major pairs, a gap of a few basis points (0.01% = 1 bp) between two reputable mid-market sources is normal and expected. A gap of 50 bp or more means one of the two is either stale, marked up, or broken — and you should find out which before shipping.

How to Audit an Exchange Rate API Before You Trust It

Do not take a provider's accuracy claims on faith. Run this check for a week against whatever source your finance team considers authoritative:

import os
import requests
from datetime import datetime, timezone

FINEXLY_URL = "https://api.finexly.com/v1/latest"
HEADERS = {"Authorization": f"Bearer {os.environ['FINEXLY_API_KEY']}"}


def get_rate(base: str, symbol: str) -> dict:
    r = requests.get(
        FINEXLY_URL,
        headers=HEADERS,
        params={"base": base, "symbols": symbol},
        timeout=5,
    )
    r.raise_for_status()
    data = r.json()
    return {
        "rate": data["rates"][symbol],
        "captured_at": datetime.fromtimestamp(data["timestamp"], tz=timezone.utc),
    }


def basis_points(a: float, b: float) -> float:
    """Difference between two rates, in basis points."""
    return abs(a - b) / ((a + b) / 2) * 10_000


primary = get_rate("EUR", "USD")
reference = 1.0839  # whatever your accounting source published

diff = basis_points(primary["rate"], reference)
print(f"Finexly: {primary['rate']}  captured {primary['captured_at']:%H:%M UTC}")
print(f"Reference: {reference}")
print(f"Delta: {diff:.1f} bp  ->  {'OK' if diff < 25 else 'INVESTIGATE'}")

Three things to look for in the results:

  • Is the delta stable or drifting? A constant offset suggests a systematic markup. A random one suggests timing.
  • Does the delta spike at particular hours? That points to snapshot timing, usually around a central bank publication window.
  • What happens on weekends? If your source freezes Friday afternoon and resumes Monday, it is central-bank-derived — plan your Monday reconciliation around that.

You can also sanity-check triangulation by fetching a cross directly and computing it through USD; the two should agree to within a basis point or two.

Choosing a Data Source for Your Use Case

There is no universally "best" source — only the right source for what you are building.

Use caseWhat you needAcceptable staleness
Displaying prices to shoppersMid-market rate, your own markup applied on topHours
SaaS subscription billingMid-market, one snapshot per billing run, stored with the invoiceHours, but must be recorded
Accounting and tax reportingCentral bank reference rate for the specific dateDaily, by definition
Analytics and dashboardsConsistent historical time series from one sourceDaily
Payouts and remittanceFresh mid-market with an explicit tolerance bandMinutes
Trading and hedgingTrue bid/ask from an institutional feedSeconds
Two practical rules cut across all of these. First, always store the rate you actually used alongside the transaction, with its timestamp and source — reconstructing it later is impossible and auditors will ask. Second, use one source per system of record. Mixing providers between your checkout and your ledger guarantees pennies of drift that nobody can explain six months later.

If you are still evaluating options, our comparison of free versus paid currency APIs breaks down what changes as you move up tiers, and the pricing plans page shows where refresh frequency and request limits land. For a quick manual sanity check on any pair, the currency converter uses the same underlying feed as the API.

Frequently Asked Questions

Where do free currency APIs get their data?

Almost always from central bank publications, most commonly the European Central Bank's daily euro reference rates, sometimes blended with a handful of other public sources. That is why free tiers typically refresh once a day, skip weekends, and cover fewer exotic currencies than paid tiers.

Why is the exchange rate on my API different from Google?

Google displays a mid-market reference rate, which is a snapshot rather than a continuous live price, and it is not necessarily sampled at the same moment as your API call. A small difference is normal. A large one usually means one of the two is a marked-up retail rate rather than a mid-market rate.

Which exchange rate should I use for accounting and tax reporting?

Use the official reference rate published by the relevant central bank for the transaction date — that is what most tax authorities expect. Fetch it from a historical endpoint with an explicit date rather than reusing a live rate, and store it with the transaction record.

Is a real-time exchange rate API actually real-time?

Rarely in the literal sense. "Real-time" typically means the provider refreshes on a short interval — 60 seconds is common at the top tier — not that it streams tick-by-tick. Check the timestamp in the response and the documented refresh interval, not the marketing copy.

Can I just scrape exchange rates instead of using an API?

You can, but you inherit every failure mode of the page you scrape: layout changes, rate limiting, no timestamps, no historical backfill, and frequently a terms-of-service violation. We covered the full trade-off in currency API versus web scraping.

Build on a Feed You Can Audit

Knowing where your exchange rate data comes from is the difference between a currency bug you can explain in one sentence and one that eats a week of engineering time. Ask any provider three questions before you integrate: what are the sources, how often does it refresh, and does every response carry a capture timestamp.

Ready to integrate exchange rates you can actually audit? Get your free Finexly API key — no credit card required. Start with 1,000 free requests per month across 170+ currencies, with timestamped responses and historical data from day one.

Vlado Grigirov

Senior Currency Markets Analyst & Financial Strategist

Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...

View full profile →

Sdílet tento článek