Skip to content

Rate limits & errors

Every failure answers with a stable code you can branch on and a sentence you can show a person. Every answer to a known key says how much of this minute is left.

The error shape

Errors are an error object instead of data. Branch on code — it will not change. message is written for a person and may be reworded.

Response · 404
{
  "error": {
    "code": "not_found",
    "message": "No check \"AUTH-09\" in Checkout."
  }
}

Codes

Status and code

400 invalid_requestfix the request
A field is missing or malformed — an unknown platform code, an empty issue, a result that is not pass, fail or retest, a cursor that was edited.
401 unauthorizedcheck the key
No key, a mistyped key, a revoked key, or a personal qarb_ MCP token.
402 plan_requiredupgrade
The workspace is on Free or Early access. The body also carries upgrade: true.
403 forbiddenuse another key
The key's role is too low — a viewer key writing, a tester key fixing.
404 not_foundcheck the name
No such app, check or issue — or one this key's app scope does not include. The two answer alike on purpose.
409 ambiguous_checkbe specific
A check named by title matches more than one. Pass section, or use the ref.
429 rate_limitedwait
Over 120 requests this minute. Wait for Retry-After seconds.
500 internal_errorretry
Our fault. Safe to retry with backoff; if it persists, write to us.

Rate limits

Each key may make 120 requests a minute. The window is the calendar minute, counted in one shared place for the whole service — so the limit holds however many servers answer you. Separate integrations with separate keys never use up each other's allowance.

Headers on every response to a known key

X-RateLimit-Limitinteger
Requests allowed per minute — 120.
X-RateLimit-Remaininginteger
How many are left in this minute.
X-RateLimit-Resetunix seconds
When this minute ends and the count starts again.
Retry-Afterseconds
Only on a 429: how long to wait before the next request.
Response · 429
{
  "error": {
    "code": "rate_limited",
    "message": "This key has made more than 120 requests this minute. Wait for the reset and try again."
  }
}

Retrying

Retry 429 and 5xx, waiting longer each time. Don't retry anything else — a 400 will answer the same way however often it is sent. Writes are safe to repeat in the sense that matters: recording the same result twice leaves one result, though raising the same issue twice raises two.

retry.js
async function call(url, init = {}, attempt = 0) {
  const res = await fetch(url, {
    ...init,
    headers: { Authorization: `Bearer ${process.env.QARUNBOOK_KEY}`, ...init.headers },
  });
  // 429 and 5xx are worth another go; everything else is an answer.
  if ((res.status === 429 || res.status >= 500) && attempt < 4) {
    const wait = Number(res.headers.get("Retry-After")) || 2 ** attempt;
    await new Promise((r) => setTimeout(r, wait * 1000));
    return call(url, init, attempt + 1);
  }
  const body = await res.json();
  if (body.error) throw new Error(`${body.error.code}: ${body.error.message}`);
  return body;
}

Something unclear or missing? Write to hello@qarunbook.com.