Firsty

Search...

Search...

Getting started

Rate limits

Request ceilings, burst behaviour and backoff guidance.

Limits are applied per second. The rate-limit response headers tell you what yours are.

Cache tokens for their 24-hour lifetime rather than calling POST /auth/token per request; token churn is the most common way to burn through a limit.

Responses carry the current window state, and a 429 always does:

RateLimit: limit=10, remaining=9, reset=1
RateLimit-Policy: 10;w=1

limit is your ceiling for the window, remaining is what is left of it, and reset is the number of seconds until it refills. RateLimit-Policy states the policy itself: 10 requests per 1-second window here.

Handling 429

When you exceed a limit the API returns 429 with a Retry-After header in seconds. Respect it, and add jitter so a fleet of workers doesn't retry in lockstep:

async function withRetry(fn: () => Promise<Response>, attempt = 0): Promise<Response> {
  const res = await fn();
  if (res.status !== 429 || attempt >= 5) return res;

  const base = Number(res.headers.get("retry-after")) || 2 ** attempt;
  const jitter = Math.random() * base * 0.3;
  await new Promise((r) => setTimeout(r, (base + jitter) * 1000));
  return withRetry(fn, attempt + 1);
}

Idempotent retries

Send an X-Idempotency-Key header (any unique string up to 255 characters, e.g. a UUID) on mutating requests. Replays with the same key within 48 hours return the original response instead of, say, ordering a second eSIM. That makes retrying on timeouts and 429s safe.

Two replies mean you got the key wrong rather than the request: 423 if the first request with that key is still in flight (retry in a moment, with the same key), and 422 if the key was already used with a different body.

One exception worth knowing: on POST /esims a 409 can mean the externalProfileId you sent already belongs to another profile. Read the problem detail before retrying, or a retry loop will never clear.