Exchange rates API

Two GET endpoints that return the same rates this site shows: live mid-market rates, and daily closes for one pair. No account, no API key, no request quota, and CORS open to any origin.

Every rate carries the feed it came from and the time it was fetched, so a figure is never silently stale.

Quick start

One request, no headers, no key:

curl "https://cxrates.com/api/v1/rates?base=USD"

GET /api/v1/rates

The current rate from one base currency to many quote currencies.

Parameters

Name Required Meaning
base yes The currency or asset the rates are quoted from, as its code — case-insensitive.
quote no Comma-separated codes to return. Codes the catalogue does not carry are dropped rather than failing the request, so one bad code still returns the other rows. Maximum per request: 200. Omit it and you get the highest-ranked 40 instruments instead.

Response

{
  "base": "USD",
  "fetched_at": "2026-10-25T06:48:12Z",
  "rates": {
    "EUR": {
      "value": "0.861235",
      "display": "0.861235",
      "name": "Euro",
      "source": "frankfurter",
      "stale": false,
      "as_of": null
    },
    "JPY": {
      "value": "151.204",
      "display": "151.204",
      "name": "Japanese Yen",
      "source": "er-api",
      "stale": true,
      "as_of": "2026-10-24"
    }
  },
  "attribution": {
    "text": "Exchange rates by CxRates",
    "url": "https://cxrates.com"
  }
}
  • value — the figure to compute with, rounded to the precision the feed supports and nothing further.
  • display — the same number already formatted the way this site prints it. The two differ once a rate passes 1,000: "value": "118432.65" against "display": "118,432.65". Print display and your page agrees with ours; format value yourself and it will not.
  • source — which upstream feed this particular rate came from. A pair no feed quotes directly names both legs of the cross it was computed from, as cross:coingecko+frankfurter.
  • stale and as_of — true when no live quote was available and the most recent daily close is being served instead, with the date of that close. as_of is null on a live rate.
  • fetched_at — the newest timestamp among the rates in this response, not the moment the request landed. Two requests a second apart return a byte-identical body, which is what makes the ETag below worth having.

Errors

Status Body Cause
400 {"error": "invalid_base"} base is missing, or is not a well-formed code.
404 {"error": "unknown_base"} The code is well-formed but the catalogue has no such instrument.

GET /api/v1/history

The daily closing rate for one pair, oldest point first.

Parameters

Name Required Meaning
pair yes The pair as this site spells it in a URL — base-to-quote, for example usd-to-eur. Anything else returns 400; a bare hyphen between the two codes is not the spelling.
days no How far back to go. A whole number above 365 clamps to 365; anything we cannot read as a positive whole number — missing, zero, negative or junk — falls back to the default of 30. Neither errors: a widget with a stale config degrades to a sensible chart instead of a broken embed.

Request and response

curl "https://cxrates.com/api/v1/history?pair=usd-to-eur&days=30"

{
  "pair": "usd-to-eur",
  "base": "USD",
  "quote": "EUR",
  "days": 30,
  "points": [
    { "date": "2026-09-25", "close": "0.857412" },
    { "date": "2026-09-26", "close": "0.858003" }
  ],
  "attribution": {
    "text": "Exchange rates by CxRates",
    "url": "https://cxrates.com"
  }
}

A date no feed covered produces no point rather than an interpolated one, so the series can have gaps. A pair no feed quotes directly is crossed through USD, and a crossed series carries the close only — the high of a ratio is not the ratio of the highs, so we do not invent one.

A close carries the precision the pair page prints: six significant digits below 1000, two decimals above it, and no thousands separators. That is the same rule the rates endpoint applies to its value field, so a close can be printed as it arrives.

Errors

Status Body Cause
400 {"error": "invalid_pair"} pair is missing, or is not two codes joined by -to-. This is the only error this endpoint returns.
200 "points": [] A well-formed pair we hold no closes for. Note the difference from /api/v1/rates, which answers 404 for a base the catalogue does not carry: here an unknown code and a known pair with no history in the window are the same empty series, so check the codes against the catalogue rather than inferring they exist from a 200.

Caching and revalidation

A response may be reused for 30 seconds, or 10 when any instrument in it is crypto, which moves faster. Poll more often than that and you are paying for a number that has not changed.

Every 200 carries an ETag. Send it back as If-None-Match and an unchanged rate answers 304 with no body, which is most ticks. Error responses carry no ETag, deliberately: an entity tag on an error body invites a cache to treat the error as the resource.

curl -H 'If-None-Match: "a1b2c3d4"' "https://cxrates.com/api/v1/rates?base=USD"

If the service is taken down for maintenance you get 503 with {"error": "unavailable"} and Cache-Control: no-store, so a cached outage cannot outlive the outage. Treat a 503 as retryable and keep your last good figure, with its timestamp, until it clears.

Where the rates come from

Rates are aggregated from several free public feeds — the European Central Bank via Frankfurter, open.er-api.com, Binance and CoinGecko. A quote that deviates from the median across sources is flagged and dropped rather than blended into it, so one bad upstream figure does not reach a response.

USD is the hub currency: a pair no feed quotes directly is computed by crossing through USD, which is why a rate exists here for pairs no single source publishes. Which feed stands behind a given pair, and how often it refreshes, differs by pair — each currency's own page states its source , and that statement is the authority rather than this one.

Using it, and crediting it

There is no key and no quota, and the endpoints are the ones this site's own pages call — so they are as available as the site is. Two things we ask in return.

  • Credit us where the figure appears: Exchange rates by CxRates, linking this site. Every response carries that string in its attribution field so you do not have to type it.
  • Cite the data, do not resell it. The upstream terms behind the free sources prohibit resale, which is a limit on us as much as on you.

These two endpoints are the whole public API. There is no convert endpoint and no OpenAPI document; if you have seen either advertised, it was aspirational rather than built.

If JSON is not what you want

  • Embeddable widgets — a converter, a ticker, a chart and a rates table, as three lines of HTML. They call these same endpoints, so you can start there and drop to the API later.
  • /llms.txt — the same facts written for an AI assistant: what we publish, how to cite it, and what not to infer from us.