Skip to content
ProductBlogOpen console
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.

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

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.

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.

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.

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.

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 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.