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.
{
"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
resultthat 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-Afterseconds. 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.
{
"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.
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;
}