---
title: Errors
description: Every Zaraftis v1 API error code, what it means, and what to do about it.
sidebar:
  label: Errors
  order: 3
---

Any route can fail with `401`, `403`, `429`, or `500`, besides the responses listed on its reference page. Every error has the same shape: branch on `error.code`, log `error.message`, and check `error.retryable` before you retry.

```json
{
  "success": false,
  "error": {
    "code": "validation_failed",
    "message": "validation failed",
    "retryable": false,
    "field_errors": [
      {
        "field": "body.prompts.0.text",
        "code": "required",
        "message": "Invalid input: expected string, received undefined"
      }
    ]
  },
  "request_id": "d3b0e28f-89a4-4c1c-a68b-11b0d5886f45"
}
```

Keep the `request_id`. Quote it when you report a problem.

## Errors and fixes

| Status | Code | Why it happens | What to do |
| ---: | --- | --- | --- |
| 400 | `bad_request` | The request cannot be read, for example the body is not valid JSON. | Fix the request and send again. |
| 401 | `unauthorized` | The `Authorization` header is missing, or the key is wrong, revoked, or expired. | Send `Authorization: Bearer <key>`. If the key was revoked, create a new one on the developer page. |
| 403 | `forbidden` | The key does not have the scope the route needs, such as `scans:write` to start a scan. | Create a key with the scope on the developer page. |
| 404 | `not_found` | The id is unknown, belongs to another workspace, or is outside the key's brand allowlist. All three get the same answer. | Check that the id came from this workspace and that the key's allowlist includes its brand. |
| 400 | `validation_failed` | The body or query does not match the route's schema. `field_errors` lists each bad field, for example `body.prompts.0.text`. | Fix the listed fields and send again. |
| 400 | `unsupported_selection` | A platform, country, or language in the request is not offered. | Read `GET /v1/capabilities` and pick from what it lists. |
| 400 | `invalid_page_token` | A page token was reused with a different filter or limit, changed, or expired. | Start the list again without a token. |
| 400 | `confirmation_required` | A destructive `DELETE` was sent without `{ "confirm": true }`. The message says what the delete will do. | Send the same request with `{ "confirm": true }` in the body. |
| 402 | `wallet_not_activated` | The workspace has no credits yet: no free trial and no credit pack. | Start the free trial or buy a credit pack in the dashboard. See [Credits and billing](/credits). |
| 402 | `insufficient_credits` | The wallet cannot cover the most this request can cost. | Add credits in the dashboard, or make the request smaller. |
| 402 | `payment_action_required` | Auto-reload needs the card holder to confirm a payment. | Confirm the payment in the dashboard, then retry. |
| 405 | `method_not_allowed` | The action needs a person in the dashboard, such as connecting a Google account. | Do it in the Zaraftis dashboard. |
| 409 | `conflict` | The resource is in a state that does not allow the request, or a brand with the same name already exists. | Read the resource again and check its state. For a name clash, use the existing brand. |
| 409 | `idempotency_conflict` | An `Idempotency-Key` was reused with a different request body. | Use a new key for a new request. Reuse a key only to retry the exact same request. |
| 409 | `duplicate_url` | A brand with this URL already exists in the workspace. `details` gives its `brand_id` and `name`. | Use the existing `brand_id`. |
| 422 | `incomplete_evidence` | A report or export needs a source record that is no longer available. `details.unavailable_source_ids` lists them. | Leave those sources out, or pick other ones. |
| 429 | `rate_limited` | Too many requests in the current window. | Wait for the seconds in `Retry-After`. Watch `X-RateLimit-Remaining` to slow down before you hit the limit. |
| 503 | `capacity_unavailable` | The workspace has too much work running at once (scans, or queries still owed). `details` gives the limit and the current count. | Retry after a running scan finishes. |
| 503 | `spending_temporarily_unavailable` | Paid work is paused on the wallet for now. Reads still work. | Retry later. |
| 503 | `reconciliation_pending` | The wallet is being checked. Paid work is blocked; reads still work. | Retry later. |
| 500 | `internal_error` | Something failed on our side. | Retry with backoff. If it keeps failing, report the `request_id`. |
| 501 | `not_implemented` | The option is not available yet, such as `creation_mode: "auto"` on a visibility setup. | Use the available option, such as `creation_mode: "manual"`. |

## When to retry

Retry only when `error.retryable` is `true`. When you retry a `POST`, send the same `Idempotency-Key` as the first attempt: the API then returns the first result and never starts or charges the work twice. Wait longer after each failed attempt, and honor `Retry-After` when it is present.
