Skip to main content
Every error from https://api.gavai.app/v1 returns the same JSON envelope and a matching HTTP status code. The status tells you the problem class; the error.code tells you the specific reason. By the end of this page you will know which codes are safe to retry, what details carries for validation failures, and how to read scope errors.

The envelope

{
  "error": {
    "code":    "insufficient_scope",
    "message": "Token lacks pages:write",
    "details": {
      "required_scope": "pages:write"
    }
  }
}
Validation errors (400) extend the envelope with a fields array, one entry per offending field:
{
  "error": {
    "code":    "validation_failed",
    "message": "Request body failed validation",
    "fields": [
      { "path": "name",  "message": "must not be empty" },
      { "path": "count", "message": "must be greater than or equal to 0" }
    ]
  }
}

Status codes

Common error codes

Inspecting details by error class

The details object varies by error.code. The shapes worth knowing because handlers branch on them:

Validation failures

400 validation_failed carries fields[] — one entry per offending field. Each entry has a JSON-pointer-style path and a reason that is one of a small enumerated set. Branch on reason if your client needs to render structured form errors:
{
  "error": {
    "code": "validation_failed",
    "fields": [
      { "path": "/email",      "reason": "format_mismatch", "message": "must be a valid email address" },
      { "path": "/name",       "reason": "too_short",       "message": "must be at least 2 characters" },
      { "path": "/permissions","reason": "unknown_value",   "message": "unknown scope: pages:nuke" }
    ]
  }
}
The common reasons are missing, too_short, too_long, format_mismatch, out_of_range, unknown_value, and unknown_field. Treat anything you don’t recognize as a generic validation error.

Conflict kinds

409 conflict carries details.kind so you can tell what kind of conflict happened. Each kind has a different recovery path:

Auth hints

401 unauthorized and 403 insufficient_scope carry a details.hint that names the specific recovery action. The hint is enumerated so a client can branch on it without parsing the message:

Upstream failures

When a capability dispatch fails because the downstream provider is unavailable — Stripe is rate-limiting, the email gateway returned 5xx, the database connection pool is saturated — the error envelope uses code: "upstream_unavailable" with details.upstream naming the failing service and details.retryable set to true when the dispatcher believes a retry will succeed:
{
  "error": {
    "code": "upstream_unavailable",
    "message": "The payments provider is temporarily unreachable. Try again in a few seconds.",
    "details": {
      "upstream": "payments",
      "retryable": true,
      "request_id": "req_01H..."
    }
  }
}
upstream is enumerated: payments, email, storage, database, identity, dns. Use it to surface a targeted message to your user rather than a generic “something went wrong.”

Policy denials

A request that hit a policy rule rather than a scope check returns 403 policy_denied with a structured diagnostic naming the rule that fired. The diagnostic is intentionally minimal — it tells you that a rule blocked the request without revealing the rule’s predicate:
{
  "error": {
    "code": "policy_denied",
    "message": "Policy did not permit this action.",
    "details": {
      "rule_id": "rule_workspace_quota_exceeded",
      "resource": "pages",
      "action": "publish"
    }
  }
}
rule_id is stable per rule; the console exposes a human-readable description for each. Display it to operators rather than end users.

Retrying safely

Never retry a 4xx without changing the request — the outcome will not change. For 5xx and 429:
  • Respect Retry-After on 429 before attempting again
  • Apply exponential backoff with jitter on 5xx — start at 1 second, double each attempt, cap at a sensible ceiling
  • Attach an Idempotency-Key header on writes so retries do not produce duplicate side effects (see Idempotency)

Next

Idempotency

How Idempotency-Key makes write retries safe across a 24-hour window.