Developer · Last updated 18 May 2026 · 3 min read

Error codes

Every error response from the Tenant REST API uses the same shape: ``json { "error": "ERRORCODE", "message": "Human-readable description" } ` There are no code…

Every error response from the Tenant REST API uses the same shape:

{ "error": "ERROR_CODE", "message": "Human-readable description" }

There are no code, field, or trace_id sub-fields. The HTTP status code carries the category; the error string narrows it.

Error codes by status

400 — Bad Request

Code Meaning
BAD_REQUEST Validation failed — message names the specific field or rule
CONFLICTING_ALIASES Two field aliases provided with different values (e.g. procedure_count + procedures_count)
DISABLED Resource exists but is inactive — e.g. an inactive skill on api-tenant-skills-execute

401 — Unauthorized

Code Meaning
MISSING_AUTH No Authorization header or empty Bearer token
INVALID_KEY Bearer token didn't match any active API key

API keys don't expire on a schedule — they're valid until revoked. If you see INVALID_KEY for a key you know is yours, it's probably been revoked.

403 — Forbidden

Code Meaning
INSUFFICIENT_PERMISSIONS Key is valid but lacks the scope needed for the endpoint
NOT_ENTITLED Skill exists but isn't in your tenant's bundle allocations (api-tenant-skills-execute only)

Cross-tenant access also returns 403 — every endpoint enforces that the key's tenant matches the resource's tenant.

404 — Not Found

Code Meaning
NOT_FOUND Record doesn't exist, was deleted, or belongs to a different tenant (we don't leak existence across tenants)

405 — Method Not Allowed

Code Meaning
METHOD_NOT_ALLOWED Wrong HTTP method — message lists what's accepted

409 — Conflict

Code Meaning
CONFLICT Duplicate record. Most common on POST /api-tenant-users when the email already exists.

5xx — Server errors

Code Meaning
INTERNAL_ERROR Server-side error in the edge function — retry with backoff

5xx responses can also come from the Supabase / edge-runtime infrastructure layer (502 / 503 / 504) without a JSON body — treat all 5xx as transient and retry with exponential backoff.

How to handle errors in code

  • 400 — bug in your client. Don't retry, fix the request.
  • 401 / 403 — credentials or scope problem. Don't retry; investigate the key.
  • 404 — the resource isn't there. Don't retry blindly.
  • 409 — duplicate. Look up the existing record instead of retrying create.
  • 5xx — retry with exponential backoff (e.g. 1s → 5s → 30s, max ~3 attempts).

What we don't return

To keep error handling simple, the API deliberately doesn't return:

  • An expired_token / token_expired code (keys don't expire)
  • A version_conflict / optimistic-concurrency code (no ETag / If-Match handling today)
  • A still_processing code on sessions (session uploads are out-of-band; poll the list endpoint for state)
  • A 422 class of business-rule violations (validation errors land as 400)

If you need any of these semantics for an integration, get in touch (team@trainar.ai).

Webhook delivery errors (outbound)

If you're receiving webhook deliveries, your endpoint must return 2xx within 15 seconds. Anything else triggers TrainAR's retry policy — see Webhooks → Retries for the schedule.

Where to next