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". Printdisplayand your page agrees with ours; formatvalueyourself 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, ascross:coingecko+frankfurter. -
staleandas_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_ofis 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
attributionfield 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.