GET /v1/rates/{base}/{quote}
Reads the current exchange rate for a currency pair, either directly observed or derived from other pairs.
Need this over MCP instead? See get_rate →.
Auth
Authorization: ApiKey <key> — required.
Missing, invalid, or revoked key → 401 unauthorized:
{ "code": "unauthorized", "message": "A valid API key is required.", "request_id": "..." }Path parameters
base,quote— trimmed, uppercased, must match^[A-Z0-9]{2,10}$.
Invalid format → 400 invalid_asset_symbol (checked before billing):
{ "code": "invalid_asset_symbol", "message": "Asset symbol format is invalid.", "request_id": "..." }Billing
Exactly one request unit is charged before the rate is read. If your
account’s quota is already exhausted, the response is 429 with extra
fields beyond the standard error envelope:
{
"code": "quota_exceeded",
"message": "Request-unit quota exceeded for the current billing period.",
"request_id": "...",
"limit": 500,
"remaining": 0,
"reset_at": "2026-11-01T00:00:00Z"
}Success — 200
curl https://your-getratesapi-host/v1/rates/EUR/USD \
-H "Authorization: ApiKey gra_live_0000000000000000000000000000000000000000000000000000000000000000"const res = await fetch("https://your-getratesapi-host/v1/rates/EUR/USD", {
headers: {
Authorization: "ApiKey gra_live_0000000000000000000000000000000000000000000000000000000000000000",
},
});
const rate = await res.json();{
"base": "EUR",
"quote": "USD",
"rate": "1.174582",
"as_of": "2026-09-29T11:41:32.241582Z",
"status": "fresh",
"type": "direct"
}rateis an exact decimal string, never a JSON number.as_ofis RFC3339Nano UTC — the stored observation time, not the request time.typeisdirectorderived(below).statusisfreshorstale(below).
Derived rates
Not every pair is quoted directly upstream. When no direct observation exists, the rate is computed, in this order:
- Identity —
EUR/EURis exactly1. - Direct — a stored observation for the pair.
- Reciprocal — the inverse of a stored
quote/baseobservation. - Pivot — two legs joined through
USD,EUR, orBTC, in that order of preference.
Division is exact decimal division; results are not rounded to a fixed
scale. A derived rate is marked "type": "derived" and its as_of is the
oldest of its contributing observations — a chain is never fresher than
its weakest link.
If no route exists, the response is 404 unsupported_pair.
Freshness
Crypto prices are refreshed from our providers every 10 seconds, fiat
(including metals and XDR) every 60 seconds. status reports whether the
underlying observation is recent, with a window that matches its feed:
| Pair | fresh | stale |
|---|---|---|
Both sides fiat (EUR/USD, XAU/CZK) | observed within the last 5 minutes | older — still returned, but flagged |
Any side crypto (BTC/USD, ETH/BTC) | observed within the last minute | older — still returned, but flagged |
A derived rate is fresh only if every rate it was computed from is fresh
under its own window — BTC→CZK via USD needs a fresh BTC/USD and a
fresh USD/CZK.
A stale rate is served rather than withheld, because for most uses an
hour-old rate is far better than no rate. Decide for yourself whether
stale is acceptable for your use case; the service will not make that
call for you.
Multiple quotes in one request
GET /v1/rates/{base}
Reads several quotes against one base in a single round trip. Pass up to
20 symbols in the quote parameter, either comma-separated or
repeated:
curl "https://your-getratesapi-host/v1/rates/EUR?quote=USD,GBP,JPY" \
-H "Authorization: ApiKey gra_live_0000000000000000000000000000000000000000000000000000000000000000"{
"base": "EUR",
"rates": [
{ "quote": "USD", "rate": "1.174582", "as_of": "...", "status": "fresh", "type": "direct" },
{ "quote": "GBP", "rate": "0.868421", "as_of": "...", "status": "fresh", "type": "derived" }
],
"unsupported": [
{ "quote": "JPY", "code": "unsupported_pair", "message": "The requested currency pair is not currently supported." }
]
}A multi-quote request is one billable unit, regardless of how many
quotes it carries. Quotes that cannot be served appear in unsupported[]
rather than failing the whole request — one missing pair should not cost
you the rest of your answer.
More than 20 quotes, or a malformed symbol anywhere in the list, is
rejected with 400 before billing.
Other error outcomes
These happen after a successful charge — the unit is already consumed, with no refund:
| Status | Code | Message |
|---|---|---|
404 | unsupported_pair | “The requested currency pair is not currently supported.” |
503 | temporarily_unavailable | “The service is temporarily unable to serve this rate. Please retry.” |
Any method other than GET/HEAD on this path returns 405 method_not_allowed.