Error responses

The JSON:API error envelope every 4xx/5xx uses, the stable error codes, field-level errors and what to branch on.

Envelope#

{
  "errors": [
    {
      "status": "400",
      "code": "VALIDATION_FAILED",
      "title": "Validation Error",
      "detail": "amount must be greater than zero",
      "source": { "pointer": "/data/attributes/amount" },
      "meta": { "requestId": "abc12345" }
    }
  ]
}
MemberMeaning
statusHTTP status as a string
codestable UPPER_SNAKE_CASE identifier — branch on this
titleshort category
detailhuman-readable description of this failure; never empty
source.pointerJSON Pointer to the offending body field; source.parameter for query/path parameters
meta.requestIdcorrelate with audit logs, request logs and traces

Several problems in one request produce several entries (one per field).

Codes#

CodeStatusWhen
VALIDATION_FAILED400a request or field failed validation and no more specific code applies (a hook error without its own code, a malformed tree or batch body); meta may add field, constraint
nullable, type, pattern, minValue, maxValue, minLength, maxLength, enum, picklist, unique, immutable, systemMaintained400a field constraint failed — the code is the constraint’s name; one entry per field, source.pointer names it (nullable is a missing required value, systemMaintained a write to a field only the platform sets)
reference400a lookup value does not resolve; meta.field, meta.targetCollection, meta.value
INVALID_PAYLOAD400malformed JSON:API body
INVALID_QUERY400bad filter grammar or a client-caused SQL error (meta.sqlState)
MISSING_PARAMETER400a required query parameter is missing; source.parameter names it
INVALID_PARAMETER400a query or path parameter has the wrong type; source.parameter names it
INVALID_ROW_LIMIT400a list view rowLimit outside {10, 25, 50, 100}
INVALID_SINCE400GET /api/{collection}/_changes with a since that is not an ISO-8601 instant
SEMANTIC_SEARCH_INVALID400POST /api/{collection}/semantic-search without a query, or on a collection that is unknown or has no matching VECTOR field
INVALID_TYPE, INVALID_CHANGESET400/api/metadata/impact or /api/metadata/deployment-plan with an unknown metadata type or a malformed changeSet
PARSE_ERROR, EMPTY_BATCH, UNSUPPORTED_COLLECTION400POST /api/operations: unparseable atomic:operations, none given, or a system collection named
FIELD_NOT_EDITABLE400PATCH /api/me/profile with a field the profile does not let you change
RESERVED_COLLECTION_NAME400a collection name the platform API already uses (/api/<name>)
INVALID_OWNER_SCOPE, INVALID_OWNER_FIELD400a collection’s ownerScope or ownerField is not valid
OWNER_MISMATCH, OWNER_IMMUTABLE400an owner-scoped record created for someone else, or its owner changed
INVALID_PROPOSAL, PROPOSAL_ALREADY_APPLIED400POST /api/ai/proposals/{id}/apply on an unknown or already-applied proposal
UNAUTHORIZED401missing, expired or revoked credential
CALLER_UNRESOLVED401the credential is valid but does not resolve to a user in this tenant
FORBIDDEN403permitted credential, denied action (detail names action and collection), or a source IP outside the tenant’s allowlist
MASKED_FIELD_PREDICATE403filter/sort/group on a field masked for the caller (body deliberately uniform)
READ_ONLY_COLLECTION403a write to a read-only collection
FLOW_NOT_LAUNCHABLE, FLOW_STATE_OVERRIDE_FORBIDDEN403POST /api/flows/{id}/execute without MANAGE_WORKFLOWS on a flow members cannot launch, or with a state override
AI_DISABLED403AI features are not enabled for the tenant
NOT_FOUND404record or route not found (or not visible)
COLLECTION_NOT_FOUND404unknown collection
TENANT_NOT_FOUND404the tenant slug in the path does not resolve
METHOD_NOT_ALLOWED405the endpoint does not support the HTTP method
UNIQUE_VIOLATION409unique-constraint violation; one entry per field of the constraint
STALE_WRITE409stale If-Match: the record changed since you read it
REFERENCED_RECORD409delete of a record other records still reference
FLOW_INACTIVE409POST /api/flows/{id}/execute on an inactive flow (a test: true run by a MANAGE_WORKFLOWS holder is exempt)
FLOW_HAS_ACTIVE_EXECUTIONS409delete of a flow with running or waiting executions; meta.activeExecutions
DEFAULT_VIEW_EXISTS409a second default list view for the same collection and visibility; meta.existingId
USER_ANONYMIZED409a users update that moves an anonymized user to another status or gives them an email, or sets status: ANONYMIZED outside anonymize; meta.userId
ENVIRONMENT_NAME_CONFLICT, EMPTY_SANDBOX_REFRESH409a sandbox environment name already in use, or a refresh with nothing to copy
UNSUPPORTED_MEDIA_TYPE415the Content-Type is not one the endpoint accepts
VALIDATION_RULE_FAILED422a collection validation rule rejected the record; source.pointer names its error field
TOO_MANY_OPERATIONS, OPERATION_FAILED422POST /api/operations over the operation limit, or one operation failed (source.pointer is /atomic:operations/<n>) and the batch rolled back
RATE_LIMIT_EXCEEDED429tenant quota or per-user share used up; carries Retry-After and X-RateLimit-* headers — body below, details in Rate limits
TOO_MANY_REQUESTS429per-IP limit on the gateway or the portal sign-in endpoints; carries Retry-After; the body is {"error": {"status", "code", "message", "path"}}, not the envelope above
AI_RATE_LIMIT_EXCEEDED, TOKEN_LIMIT_EXCEEDED429the tenant’s AI request rate (Retry-After) or monthly AI token limit is used up
AI_PROVIDER_ERROR, TOKEN_BUDGET_EXCEEDED—in an AI chat response’s error.code (or a streamed error event): the model provider failed, or the tool loop hit its token budget
INTERNAL_ERROR500unexpected; meta.requestId is what to report
STORAGE_ERROR, JSONAPI_PARSE_ERROR, APPLY_FAILED500the database, the gateway’s response parsing, or applying an AI proposal failed; meta.requestId is what to report
TENANT_CONCURRENCY_LIMIT503too many concurrent requests for the tenant; retry after Retry-After

Hook-raised validation errors use VALIDATION_FAILED unless the hook defines a more specific code.

A rate-limited request (Retry-After: 42):

{
  "errors": [
    {
      "status": "429",
      "code": "RATE_LIMIT_EXCEEDED",
      "title": "RATE_LIMIT_EXCEEDED",
      "detail": "Rate limit exceeded. Retry after 42 seconds.",
      "meta": { "requestId": "3f2b9c1e-…" }
    }
  ]
}

CLI and SDK#

The CLI prints the same envelope as one JSON line on stderr and maps status to exit codes (Output contract). For RATE_LIMIT_EXCEEDED it adds error.hint (and a Hint: line in table output) telling you to wait the Retry-After seconds. @kelta/sdk throws typed errors — ValidationError, AuthenticationError, AuthorizationError, NotFoundError, ServerError, NetworkError — that expose the envelope (SDK).

Edit this page on GitHub