Esc

Concepts

Rate limits

How many requests a key can make each minute, the headers that report it, and when to retry.

The limit

Each key can make 60 requests a minute, whatever they ask for. The limit applies to each key separately, so two apps with their own keys don’t slow each other down. Credits, by contrast, are shared by all of an account’s keys (Credits).

Rate limit headers

Every response to a request with a key includes these headers:

HeaderValue
X-RateLimit-LimitThe requests a key can make in one window: 60
X-RateLimit-RemainingThe requests left in the current window
X-RateLimit-ResetWhen the current window ends, in Unix seconds

Exceeding the limit

A request over the limit returns 429 with the code rate_limited and a Retry-After header that gives the seconds to wait. The request costs nothing.

Response over the limit
HTTP/2 429
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1762866060
Retry-After: 12

{"error": {"code": "rate_limited", …}}

Note

Several limits return 429, so branch on the error’s code, not only its status. quota_exceeded, key_limit_reached and scan_limit_reached mean a monthly or daily limit is used up: retrying fails the same way until the limit resets.

When to retry

StatusCodeRetry?
409idempotency_key_in_useYes, with backoff
429rate_limitedYes, after Retry-After
429quota_exceededNo
429key_limit_reachedNo
429scan_limit_reachedNo
500internal_errorYes, with backoff
503capacityYes, after Retry-After
4xxAny otherNo. Change the request first; the message says what to change
  • Back off between attempts: wait 1, 2, 4 and 8 seconds, with a little random jitter, for up to five attempts. When the response has a Retry-After header, wait that long instead.
  • Polling a scan is free and doesn’t use credits. Read its links.self as often as Retry-After asks, about every 3 seconds.
  • Send the same Idempotency-Key with every attempt of a POST request, so a retry never starts or charges for anything twice (Idempotency).

The following function retries a request this way:

JavaScript
const RETRYABLE = new Set([
  'idempotency_key_in_use',
  'rate_limited',
  'capacity',
  'internal_error',
]);

async function callApi(path, init = {}, tries = 5) {
  // Send the same key with every try of a POST, so a retry never starts
  // or charges for a second scan.
  const idempotency =
    init.method === 'POST' ? { 'Idempotency-Key': crypto.randomUUID() } : {};
  for (let attempt = 1; ; attempt += 1) {
    const response = await fetch(`https://api.checkanysite.com/v1${path}`, {
      ...init,
      headers: {
        Authorization: `Bearer ${process.env.CHECKANYSITE_API_KEY}`,
        ...idempotency,
        ...init.headers,
      },
    });
    if (response.ok) return response.json();
    const { error } = await response.json();
    if (!RETRYABLE.has(error.code) || attempt === tries) {
      throw new Error(`${error.code}: ${error.message} (${error.requestId})`);
    }
    // Wait as long as Retry-After asks, or back off: 1, 2, 4… seconds.
    const retryAfter = Number(response.headers.get('retry-after'));
    const seconds = retryAfter || 2 ** (attempt - 1);
    const delay = seconds * 1000 + Math.random() * 250;
    await new Promise((resolve) => setTimeout(resolve, delay));
  }
}

const site = await callApi('/sites/wikipedia.org?checks=ai');

The Python SDK retries like this automatically.