Rate limits

Each API key has a maximum rate, counted over a fixed one-second window. Counting is shared across all instances of the service: the limit is the same whichever server answers.

The applied rate is the key's own rate if it has one, otherwise the workspace plan's (Pro: 25 requests per second, Business: 50). GET /v1/workspace returns it in limits.requests_per_second.

Headers#

Every authenticated response carries the state of the current window:

HTTP
RateLimit-Limit: 25
RateLimit-Remaining: 19
RateLimit-Reset: 1
RateLimit-Policy: 25;w=1
Header Meaning
RateLimit-Limit Requests allowed per window.
RateLimit-Remaining Requests left in the current window.
RateLimit-Reset Seconds until the next window (always 1).
RateLimit-Policy Applied policy: <limit>;w=<window in seconds>.

Going over the limit#

Past the limit, the API answers 429 with Retry-After: 1:

JSON
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded: 25 requests per second per API key.",
    "details": { "limit": 25, "window_seconds": 1 }
  }
}

Wait for the Retry-After duration, then retry. Do not tell rate_limited (rate, wait a moment) from daily_quota_exceeded (daily quota, wait until midnight UTC) by status alone: read the code.

Retrying properly#

JavaScript
async function call(url, init = {}, attempt = 0) {
  const res = await fetch(url, {
    ...init,
    headers: { Authorization: `Bearer ${process.env.AICSCAN_API_KEY}`, ...init.headers },
  });
  if (res.status === 429 && attempt < 5) {
    const body = await res.json();
    if (body.error.code === 'daily_quota_exceeded') throw new Error('daily quota exceeded');
    const wait = Number(res.headers.get('Retry-After') ?? 1) * 1000;
    await new Promise((r) => setTimeout(r, wait + Math.random() * 250));
    return call(url, init, attempt + 1);
  }
  return res;
}

For a write (POST), send the same Idempotency-Key on every attempt: a retry never creates a duplicate.

Tips#

  • Rather than polling GET /v1/matches, receive alerts by webhook or on the real-time stream.
  • To walk a long history, use limit=100: a page of 100 items counts as a single call.
  • Spread bulk jobs: a few requests per second are enough to go through thousands of items.