Docs/API & Webhooks/Errors & Responses

Errors & Responses

Every public API endpoint returns predictable response shapes. Errors carry a stable machine-readable code in the body so you can branch on it without parsing English.

Response envelope

Successful responses return the resource (or a list of resources) directly. There is no top-level wrapper — clients can deserialize straight into a typed model:

GET /api/public/v1/org

{
  "id": "e188fc87-…",
  "name": "Acme Corp",
  "plan": "growth",
  …
}

Failure responses use a small uniform shape with an error code as the only required field. Optional fields give context that helps you fix the call:

{
  "error": "insufficient_scope",
  "required": "assignments:write",
  "present": ["assignments:read", "users:read"]
}

Status code reference

StatusWhen you see itWhat to do
200Request succeeded. Body holds the resource or a confirmation message.Process the body.
400Malformed request: missing required field, invalid enum value, body too large, unparsable JSON.Fix the client request — do not retry the same payload.
401Missing, malformed, revoked, or expired token.Verify the Authorization header. Rotate the token if it has been revoked.
403Token is valid but lacks the required scope.Add the scope in the admin UI; the response carries the exact scope name.
404Resource doesn't exist or belongs to a different organization.Re-fetch the parent collection. Don't follow stale IDs across orgs.
409Conflict — e.g. an email is already in use.Treat as success if your operation is idempotent.
413Request body exceeds the per-endpoint cap. Returned by Kestrel for hard overflow; the SARIF endpoint also returns this as 400 body_too_large when the declared Content-Length exceeds 10 MB.Trim the payload. For SARIF, increment-ingest one tool's findings at a time.
429Rate limit hit on this API key.Honor Retry-After; see Rate Limits.
5xxServer-side problem.Retry with exponential backoff. Capture the response X-Request-Id header and quote it in any support ticket.

Error codes

The error field is stable across versions — branch on it in client code rather than on the HTTP status, since the same status can carry different codes:

CodeStatusMeaning
unauthorized401No usable bearer token on the request.
insufficient_scope403Token is fine, scope is missing. The response includes required and present.
rate_limited429Cap exceeded. See Retry-After header.
user_not_found404User ID doesn't exist or isn't in your organization.
team_not_found404Team ID doesn't exist or isn't in your organization.
assignment_not_found404Assignment ID doesn't exist or isn't in your organization.
empty_body400POST or PATCH sent with no body where one is required.
body_too_large400 / 413Request body exceeded the endpoint cap. SARIF emits this as 400 with maxBytes in the body when the declared Content-Length is too large; Kestrel emits a bare 413 when the body actually overruns.

Request IDs

Every response carries an X-Request-Id header (UUID v4). Capture it client-side and include it in support requests — it lets us pull the exact server-side trace for the failing call. For 5xx responses, the request ID is the single fastest path to diagnosis.

Validation errors

Validation failures on POST and PATCH bodies return 400 with a flat error describing the first failing rule. Multi-field validation surfacing is not part of v1 — fix the first reported field and retry. Future versions may add per-field detail; the top-level error contract will remain.

Branching on errors — example

const res = await fetch(url, { headers });
if (res.ok) return res.json();

const body = await res.json().catch(() => ({}));
switch (body.error) {
  case 'rate_limited':
    return retryWithBackoff(url);
  case 'insufficient_scope':
    throw new Error(`API key missing scope: ${body.required}`);
  case 'unauthorized':
    throw new Error('API key revoked or invalid — rotate the secret');
  default:
    throw new Error(`API error ${res.status}: ${body.error || 'unknown'}`);
}