# Errors reference

Every error code the Platform API and the CLI can return, with its HTTP status and what to do about it.

Errors are JSON with a machine-readable `error` code and usually a human-readable `message`.
Clients must tolerate a code-only response from the access gate.

```json
{
  "error": "validation_error",
  "message": "idempotency_key exceeds 255 bytes"
}
```

Handle by status first, then refine by code. The status tells you whether to retry; the code tells
you what to change.

## By status

| Status | Retry? | Typical codes |
|---|---|---|
| `400` | No, fix the request | `bad_request`, `validation_error`, `invalid_request`, `invalid_json`, `missing_tenant`, `invalid_tenant`, `unknown_tenant`, `workspace_not_selected`, `bad_limit`, `bad_path`, `missing_path` |
| `401` | With a new token | `unauthorized` |
| `403` | No, change the grant or operation | `forbidden`, `direct_operator_required` |
| `404` | No, check ids and capabilities | `not_found`, `no_active_bundle` |
| `405` | No | `method_not_allowed` |
| `409` | Read the resource, then decide | `conflict`, `work_has_live_run`, `upload_in_progress`, `invalid_revision`, `not_rejected`, `fact_schema_mismatch` |
| `412` | No, create a replacement | Idempotent create against a terminal session |
| `422` | No, fix the request | `validation_error`, `unprocessable`, `over_budget`, `incomparable`, `empty_dataset_version` |
| `429` | Yes, with backoff and jitter | Rate limited |
| `502` | Bounded retry | `upstream_error`, `registry_unavailable`, `account_service_unavailable` |
| `503` | Bounded retry | `unavailable` |
| `500` | Bounded retry, then report | `internal_error`, `lookup_failed`, `read_failed`, `list_failed`, `validate_failed`, `audit_unavailable` |

## By code

| Code | Meaning | What to do |
|---|---|---|
| `bad_request` | The request could not be understood. | Fix the request shape. |
| `validation_error` | A field failed validation; `message` names it. | Correct the field. Sent as `400` for request shape and `422` for semantic checks such as a manifest rule. |
| `invalid_request` | The combination of fields is not allowed, such as `work.subject` with `work.work_id`. | Send one or the other. |
| `invalid_json` | The body is not valid JSON. | Fix the encoding. |
| `missing_tenant`, `invalid_tenant`, `unknown_tenant` | No tenant resolved, or the header named one the token does not grant. | Send the workspace headers from `GET /v1/me/workspaces`. |
| `workspace_not_selected` | The CLI has no selected workspace for this context. | `tanomis workspace use ORG --tenant TENANT`. |
| `workspace_not_armed` | The selected workspace is not usable yet, typically a tenant still being provisioned. | Wait for the tenant to be `ready`. |
| `unauthorized` | Missing, expired, or invalid access token. | Obtain a new token. |
| `forbidden` | The caller cannot perform this operation in the selected workspace. | Ask an owner for the capability or grant. |
| `direct_operator_required` | The operation is reserved for a Tanomis operator acting directly. | Contact your administrator. |
| `not_found` | The resource is absent in the selected tenant, or an optional capability is unavailable here. | Check the id, the workspace, and the capability. |
| `no_active_bundle` | The tenant has no active policy bundle for this decision. | Contact your administrator. |
| `conflict` | A lifecycle or idempotency conflict: another run is active, the key matched a different body, or the session cannot admit work. | Read the resource, then retry or create anew. |
| `work_has_live_run` | The settlement work already has a live run. | Wait for it to finish. |
| `upload_in_progress` | The file's bytes are still being transferred. | Wait, then retry. |
| `invalid_revision` | The policy revision you referenced is not current. | Reload and resubmit. |
| `not_rejected` | The transition needs a rejected version and this one is not. | Check `tanomis agent statuses`. |
| `over_budget` | The evaluation run would exceed its example budget. | Lower `--max-examples`. |
| `incomparable` | The two experiments do not share a dataset version. | Compare experiments over the same dataset version. |
| `empty_dataset_version` | The dataset version has no examples. | Populate it before running. |
| `unavailable` | The capability or a dependency is temporarily unavailable. | Retry with backoff. |
| `upstream_error`, `registry_unavailable`, `account_service_unavailable` | A platform dependency failed. | Retry boundedly; report if it persists. |
| `internal_error` and other `*_failed` codes | An unexpected platform error. | Retry boundedly, then contact support with the request id. |

## CLI

The CLI prints the server's code and message as returned and exits non-zero. It forms no
authorization opinion of its own: a refusal you see at the terminal is the server's, so a rule
change never leaves a stale client refusing correct calls. Local refusals are limited to missing
arguments and to irreversible commands that require `--yes`.

## Where to go next

- [Platform API overview](/api#errors-and-retries): the retry contract per status.
- [Sessions and runs](/concepts/sessions-and-runs): the conflicts you can hit on create and run.
