Errors & rate limits
Every error in the Allswap API uses the same JSON envelope, so your error handling only needs one shape. The code is your machine handle, the message is a human-readable hint, and requestId is what you give us when you open a ticket.
Error envelope
{
"error": {
"code": "quote_expired",
"message": "Quote qt_01HW9... expired at 2026-06-22T10:01:32Z.",
"requestId":"req_01HW9..."
}
}Common error codes
| Code | HTTP | Meaning | Safe to retry? |
|---|---|---|---|
unauthorized | 401 | Missing or invalid X-Key-Id / Bearer. | No |
forbidden | 403 | Key is valid but lacks the required scope. | No |
invalid_request | 400 | Malformed body / missing required field. | Fix first |
unsupported_pair | 400 | No supported route from `from` → `to`. | No |
amount_too_low | 400 | Below minimum tradeable for this pair. | Fix first |
amount_too_high | 400 | Above current liquidity ceiling for this pair. | Fix first |
quote_expired | 409 | quoteId is past its expiresAt — fetch a fresh quote. | Fix first |
quote_consumed | 409 | quoteId was already used by a prior /v1/swap call. | No |
rate_limited | 429 | RPM budget exhausted. Honor Retry-After. | Backoff |
upstream_timeout | 504 | Routes did not respond in time. Safe to retry. | Backoff |
internal_error | 500 | Something broke on our side. Report requestId. | Backoff |
Rate limiting
Every key has two limits: requests per minute (RPM, burst control) and quotes per month (monthly tier cap). Per-tier numbers are on the pricing page.
Every response includes the current state of your RPM bucket:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 248
X-RateLimit-Reset: 1718983261When you hit zero, the next request returns 429 rate_limited with a Retry-After header (seconds). Honor it.
Retry strategy
What we recommend, by category:
- 4xx that is your fault (
invalid_request,unauthorized,forbidden,unsupported_pair): do not retry. Surface the message to the developer/user; retrying will only burn quota. - 429 rate_limited: wait the number of seconds in
Retry-After, then retry. Use the value verbatim — don't guess; we may bump it during incidents. - 5xx (
upstream_timeout,internal_error): exponential backoff with jitter. Suggested base = 500ms, cap = 8s, max 4 retries. - Quote-related 409s: re-quote first, then retry the swap.
quote_consumedis terminal — that quoteId is dead forever.
Reference: backoff in pseudocode
async function withRetry<T>(fn: () => Promise<T>): Promise<T> {
let attempt = 0;
while (true) {
try {
return await fn();
} catch (err: any) {
const code = err?.body?.error?.code;
const status = err?.status;
if (status === 429) {
const wait = Number(err.headers["retry-after"] ?? 1) * 1000;
await sleep(wait);
continue;
}
const retriable = code === "upstream_timeout" || code === "internal_error";
if (!retriable || attempt >= 4) throw err;
const base = Math.min(8000, 500 * 2 ** attempt);
const jitter = Math.random() * base * 0.3;
await sleep(base + jitter);
attempt++;
}
}
}
