Skip to content

Error Model

All API errors use a consistent JSON envelope. This means your client code can handle errors the same way regardless of whether they come from auth, validation, storage, or business logic.

Error envelope

{
  "schema_version": 1,
  "error": {
    "code": "not_found",
    "message": "Repository abc123 not found",
    "retryable": false,
    "details": {}
  }
}
Field Type Description
schema_version integer Always 1 for now. Will increment if the envelope shape changes.
error.code string Stable machine-readable error category. Use this for branching in client code.
error.message string Human-readable explanation. Safe to display directly to the user.
error.retryable boolean true if retrying the same request might succeed (e.g., a transient dependency error). false if the request will always fail until something changes (e.g., invalid input).
error.details object Optional structured context, e.g., which field failed validation.

How to use this in client code

const response = await fetch("/v1/repositories/" + repoId);
if (!response.ok) {
  const body = await response.json();
  const { code, message, retryable } = body.error;

  if (code === "not_found") {
    showError("Repository not found");
  } else if (retryable) {
    scheduleRetry();
  } else {
    showError(message);  // safe to show directly
  }
}

Don't parse exception text or rely on the HTTP status code alone — use code for any branching that needs to be specific.

Common error codes

Code HTTP status What it means
invalid_request 400 The request body or parameters are malformed
validation_error 422 Input failed validation (wrong type, missing field, etc.). details contains field-level errors.
invalid_token 401 The Bearer token is missing, expired, or invalid
forbidden 403 The user doesn't have the required group membership
not_found 404 The requested resource doesn't exist or isn't accessible to this user
conflict 409 The operation conflicts with existing state (e.g., scan already running)
quota_exceeded 429 The user has hit a quota limit (e.g., max active scans)
service_unavailable 503 A required dependency (Postgres, S3, SQS) is unreachable. retryable: true.
internal_error 500 Unexpected error. retryable may be true for transient failures.

Backend implementation

The api_error() helper in app/core/errors.py creates this envelope. Route handlers and services should use this helper rather than raising raw HTTP exceptions, to ensure consistent formatting.

A note on 422 responses

FastAPI automatically generates 422 Unprocessable Entity responses for Pydantic validation failures. These have a slightly different structure because FastAPI produces them before reaching the error helper. The details field will contain FastAPI's validation error list with field paths and messages.