Skip to main content

Troubleshoot system and 5xx error codes

The generic system codes — INTERNAL_ERROR, BAD_GATEWAY, UPSTREAM_ERROR, UNPROCESSABLE_ENTITY, INVARIANT_VIOLATION, MISSING_TENANT, and TENANT_NOT_FOUND — surface on requests that failed before the caller’s domain logic could run. They read as opaque platform failures, but most split into two clear classes: transient (safe to retry) and deterministic (retrying changes nothing — fix the request or escalate). This page tells you which class a code belongs to and what to do about it.

Step 1 — read the retry-safety flag on the envelope

Before you classify anything, look at the error envelope you got back. Each response carries an error object with code, message, status, and optional details, plus a meta block with request_id and docs_url:
Two opt-in fields decide whether you retry:
  • details.retryable: when a transient class of failure is identified before the response is built, the envelope is stamped "retryable": true. That marks the response as designedly safe to retry — the platform has deliberately classified this failure as an availability blip, not a fault.
  • details.retry_after_seconds: when the envelope also names a wait window, the response carries it both in details and as a standard Retry-After header. Honour that window before retrying instead of retrying immediately.
If neither field is present, fall back to the default assumptions in the table below. In either case, keep the meta.request_id — it is the handle support uses to trace the failure if you escalate.
One well-known transient class, TENANT_SCHEMA_INCOMPLETE, has its own runbook at Troubleshoot TENANT_SCHEMA_INCOMPLETE (503). Check for that code before treating a 503 as generic.

Step 2 — classify the code

The table below lists the system codes as they appear in responses. The status field on a response is always authoritative for that response — where a code can surface under more than one status, the table names the value you most commonly see.
UNPROCESSABLE_ENTITY and VALIDATION_ERROR are both 422 determinants — match on the response code, not the message text. Either way, the fix is in your payload, not in a retry.

Step 3 — the decision checklist

Walk these steps in order on any of the codes above:
  1. Read the envelope. Capture error.code, error.status, and meta.request_id.
  2. Status page. For UPSTREAM_ERROR or BAD_GATEWAY, check status.orbit.devotel.io before you retry or escalate — an active incident answers every question.
  3. Retry or fix. Retry only when the envelope says retryable: true, or the operation is a read, or it accepts a fresh Idempotency-Key and you supply one. For a deterministic code (UNPROCESSABLE_ENTITY, MISSING_TENANT, TENANT_NOT_FOUND), fix the request and move on.
  4. Escalate. If a retryable check, a payload fix, and one retry all return the same code, or the code is INTERNAL_ERROR or INVARIANT_VIOLATION, open a support ticket.

Worked examples

A transient 503 stamped safe by the platform — the designed retryable envelope you should pass straight through your retry loop:
A deterministic 422 the platform will keep returning until the payload is fixed:
A 500 that should not be retried — escalate with meta.request_id:

Escalation

Open a support ticket when any of these holds:
  • The status page shows no incident, yet UPSTREAM_ERROR or BAD_GATEWAY persists past a few minutes of backoff.
  • The response says retryable: true and you have already retried within the stated window, but the same failure returns.
  • The code is INVARIANT_VIOLATION or INTERNAL_ERROR — both name the platform.
  • The code is MISSING_TENANT / TENANT_NOT_FOUND and a working key suddenly stops resolving its organization.
Include the meta.request_id, the payload body (minus any credentials), and whether the failure is reproducible or intermittent.

See also