# Error codes

Every error code the platform emits, what triggers it, and what to do about it.

Source: https://docs.omazy.ai/reference/platform/error-codes/

When something fails, the payload carries a stable `code`. That code is the part
you should branch on.

Messages get reworded by whoever is improving the wording that week. HTTP
statuses are coarse: a 403 could be four genuinely different problems with four
genuinely different fixes. The code is the only part that is both specific and
promised not to change. Treat the message as something to show a person and the
code as something to write an `if` about.

```json
{
  "success": false,
  "error": { "code": "WORKSPACE_SUSPENDED", "message": "workspace is suspended" }
}
```

## Authentication and permission

You are not who you need to be, or you are but you still may not do that.

| Code | Status | Trigger | What to do |
|---|---|---|---|
| `UNAUTHORIZED` | 401 | No token, or the token was rejected | Sign in again. `omazy auth login` |
| `FORBIDDEN` | 403 | Authenticated, but the role lacks the permission | Ask an owner to widen the role |
| `INSUFFICIENT_SCOPE` | 403 | The token is valid but was not granted this scope | Re-authorise with broader consent |
| `ACCOUNT_REQUIRED` | 403 | The action needs a completed account | Finish account setup, usually an email |
| `LEGAL_HOLD` | 403 | A legal hold blocks this change | Not overridable. Talk to whoever set it |
| `WORKSPACE_SUSPENDED` | 423 | The workspace is suspended | An owner resolves billing |

The distinction between `UNAUTHORIZED` and `FORBIDDEN` is the one people get
backwards. `UNAUTHORIZED` means we do not know who you are. `FORBIDDEN` means we
know exactly who you are and the answer is still no. Retrying the login fixes
the first and does nothing at all for the second.

## Plan and limits

Nothing is broken. You have simply asked for more than the current plan or the
current minute allows.

| Code | Status | Trigger | What to do |
|---|---|---|---|
| `PAYMENT_REQUIRED` | 402 | Payment is needed to continue | Check Billing |
| `UPGRADE_REQUIRED` | 403 | The feature exists on a higher plan | Upgrade, or use the included alternative |
| `RATE_LIMIT_EXCEEDED` | 429 | Too many requests | Honour `Retry-After`, back off exponentially |
| `COMPUTE_BUDGET_EXCEEDED` | 429 | Refresh rate or compute budget exceeded | Poll less often |
| `PAYLOAD_TOO_LARGE` | 413 | The body exceeded the size limit | Send less, or upload and reference it |
| `MODEL_NOT_ALLOWED` | | The requested model is blocked by a guard | Pick a permitted model, or change the guard |

On `RATE_LIMIT_EXCEEDED`, add jitter to your backoff. Retrying immediately from
every worker at once turns a rate limit into an outage, and the limit was the
thing trying to prevent that.

## Capability not configured

The feature exists. It has not been switched on here.

| Code | Status | Trigger |
|---|---|---|
| `NOT_CONFIGURED` | 501 | The thing exists but is not set up for this app |
| `NOT_IMPLEMENTED` | 501 | The operation is not available on this deployment |
| `STORAGE_DISABLED` | 503 | Object storage is not configured |
| `SEARCH_DISABLED` | 503 | The search backend is not configured |
| `ANALYTICS_DISABLED` | 503 | The analytics store is not configured |
| `INGEST_DISABLED` | 503 | Event ingest is switched off |
| `INSPECTOR_UNAVAILABLE` | 503 | The queue inspector is not configured |
| `CIPHER_UNAVAILABLE` | | Credential encryption is not configured |

These are the friendliest failures on this page, because the fix is a setting
rather than a rewrite. `CIPHER_UNAVAILABLE` is the one to escalate rather than
work around: it means the server cannot encrypt a credential you were about to
give it, and a stored secret is not something to improvise.

## Validation and conflict

The request was understood and refused on its merits.

| Code | Status | Trigger | What to do |
|---|---|---|---|
| `BAD_REQUEST` | 400 | Malformed or missing parameters | Read the message, fix the request |
| `UNPROCESSABLE_ENTITY` | 422 | Well-formed but not valid here | Usually an id that belongs to another app |
| `FORM_VALIDATION` | 422 | One or more fields failed validation | Field details are in the payload |
| `UNSUPPORTED_MEDIA_TYPE` | 415 | Wrong content type | Check `Content-Type` |
| `NOT_FOUND` | 404 | No such object, or not visible to you | Check the id, and check the scope |
| `CONFLICT` | 409 | The change collides with current state | Re-fetch and retry |
| `CHANNEL_EXISTS` | 409 | That channel is already connected | Edit the existing one |
| `HANDLE_TAKEN` | | The handle is in use | Pick another |
| `HANDLE_RESERVED` | | The handle is reserved | Pick another |
| `LAST_OWNER` | | Removing or demoting the only owner | Promote someone first |

`NOT_FOUND` deserves suspicion. It is also what you get when the object exists
but sits in a different workspace or app than the one your request was scoped
to. Before hunting for a deleted record, check that you are pointed where you
think you are. `omazy --debug` will tell you in one line.

`LAST_OWNER` exists because a workspace with no owner cannot be recovered by
anyone inside it. The rule is not being difficult, it is the only thing standing
between you and a locked room with the key inside.

## Upstream and internal

Not your request.

| Code | Status | Trigger | What to do |
|---|---|---|---|
| `UPSTREAM_REJECTED` | 502 | A provider refused the call | Check provider health and credentials |
| `UPSTREAM_UNAVAILABLE` | 503 | A provider could not be reached | Retry with backoff |
| `INTERNAL_ERROR` | 500 | Something failed on our side | Retry once, then send us the request id |

For `INTERNAL_ERROR`, capture the request id. It is the difference between us
finding the exact failure in seconds and asking you to describe what happened.

## The CLI explains some of these for you

The CLI attaches a suggested remedy to the codes it recognises, so a failed
command tells you what to do rather than only what went wrong.

It covers 18 of the codes above. It also still carries four remedies for codes
this platform does not emit (`AUTH_TOKEN_EXPIRED`, `AUTH_TOKEN_REPLAY`,
`BUSINESS_NOT_AUTHORIZED`, `OCC_CONFLICT`). They are harmless, and they are
listed here so nobody spends an afternoon looking for where `OCC_CONFLICT` comes
from. The answer is that it does not.
