AllSwap

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

CodeHTTPMeaningSafe to retry?
unauthorized401Missing or invalid X-Key-Id / Bearer.No
forbidden403Key is valid but lacks the required scope.No
invalid_request400Malformed body / missing required field.Fix first
unsupported_pair400No supported route from `from` → `to`.No
amount_too_low400Below minimum tradeable for this pair.Fix first
amount_too_high400Above current liquidity ceiling for this pair.Fix first
quote_expired409quoteId is past its expiresAt — fetch a fresh quote.Fix first
quote_consumed409quoteId was already used by a prior /v1/swap call.No
rate_limited429RPM budget exhausted. Honor Retry-After.Backoff
upstream_timeout504Routes did not respond in time. Safe to retry.Backoff
internal_error500Something 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:     1718983261

When 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_consumed is 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++;
    }
  }
}