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_requiredwrite to us
The API is on every plan, Free included. You only see this if it has been switched off for your workspace's plan by arrangement. 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 [email protected].