Quickstart
From zero to your first successful call — usually under two minutes.
Base URL: https://api.crestrate.com/v1
Get an API key
Sign up and create a key in the dashboard. The free plan includes 1,000 requests a month — no credit card required. The full key is shown only once. Save it to your secrets manager right away.
Look up a rate
base is the currency your price is in; quote is the currency your customer pays in. The returned rate is how much quote one unit of base is worth — use it directly to compute amounts.
Every /v1 request needs a key in X-API-Key or Authorization: Bearer. Requests without one get 401 api_key_required.
curl -H "X-API-Key: $CR_API_KEY" \
"https://api.crestrate.com/v1/rate?base=JPY"e=HKD"0.05117904 loses precision when parsed and your totals drift. Parse with Decimal, BigDecimal, decimal.js or bcmath.Lock the price at checkout
Rates move between the moment a customer sees a price and the moment they pay. Create a quote with POST /v1/quotes: its payable stays fixed until it expires, so your books reconcile.
curl -X POST "https://api.crestrate.com/v1/quotes" \
-H "X-API-Key: $CR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"base":"JPY","quote":"HKD","amount":"10000","reference":"ORDER-8823"}'
# → {"quote_id":"4ac0…","payable":"524.15","rate":"0.05241460",
# "expires_at":"2026-09-14T12:53:01Z","ttl_seconds":900, …}Handle rate limits and quotas
Every response carries six headers. Back off based on them instead of retrying blindly.
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Token bucket size (burst limit) |
X-RateLimit-Remaining | Tokens left right now |
X-RateLimit-Reset | When the next token arrives (epoch seconds) — schedule your next call by it |
X-Quota-Limit | Monthly quota |
X-Quota-Used | Used this month |
X-Quota-Remaining | Remaining this month |
429 quota_exceeded is a hard stop until the next calendar month (UTC) or an upgrade — retrying won't help. Only 429 rate_limited is worth retrying after Retry-After.
async function getRate(base, quote, attempt = 0) {
const res = await fetch(
`https://api.crestrate.com/v1/rate?base=${base}"e=${quote}`,
{ headers: { "X-API-Key": process.env.CR_API_KEY } }
);
if (res.status === 429) {
const body = await res.json();
// quota_exceeded is a hard stop: retrying won't help, upgrade or wait for next month
if (body.error === "quota_exceeded") {
throw new Error(`Monthly quota exhausted: ${body.used}/${body.quota}`);
}
// only rate_limited is worth retrying, after Retry-After
if (attempt < 3) {
const wait = Number(res.headers.get("Retry-After") || 1);
await new Promise(r => setTimeout(r, wait * 1000));
return getRate(base, quote, attempt + 1);
}
throw new Error("rate limited");
}
// 503 no_rate: not enough valid sources. We return no price rather than a doubtful one.
// Fall back to your own rate; never treat it as 0.
if (res.status === 503) throw new Error("no reliable rate available");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}503 no_rate instead of inventing a price. That's deliberate: fall back to your own rate or pause checkout — never treat it as a rate of 0.