Errors
Most error responses use a structured error envelope, so your automation can branch on typed codes rather than parsing prose. A few do not; see Responses without the envelope. This page covers the envelope shape, common HTTP statuses, typed error codes, and how to handle them well.
On this page: the envelope, common statuses, responses without the envelope, error codes, handling errors well.
The envelope
{
"error": {
"code": "FIELD_VALIDATION_ERROR",
"message": "Human-readable description of what went wrong",
"details": { "errors": [ { "field": "name", "error": "must be 1 to 300 characters", "value": "" } ] },
"timestamp": "2026-05-31T10:00:00Z"
}
}
code and message are always present; timestamp records when the error occurred. Some codes add a structured details object (see below). Branch on error.code, not on the human-readable message.
Common statuses
| HTTP | Meaning | What to do |
|---|---|---|
| 400 | Request failed validation | Inspect error.code and details, fix the offending field, retry |
| 401 | Missing or expired token | Fetch a new token (see Authentication) |
| 403 | Token valid but lacks permission | Use a Super User service account for writes |
| 404 | Resource or configuration version not found | Verify the ID and configurationVersion |
| 409 | Conflict (for example, publishing an empty draft, a concurrent rule edit, or another publish in progress) | Inspect error.code: CONFLICT is often safe to ignore, PUBLISH_IN_PROGRESS is safe to retry after a short wait |
| 413 | Request body or uploaded file too large | Reduce the payload; see System limits. Branch on the status: an oversized request body gets the gateway's body, not this envelope |
| 422 | Well-formed request the server cannot act on | Inspect the message and adjust the request |
| 429 | Tenant rate limit exceeded | Wait 60 seconds, then retry every 15 seconds. The body is the gateway's, not this envelope; pace calls with the RateLimit-Remaining header. See System limits |
| 500 | Server error, or a response too large to return (RESPONSE_TOO_LARGE) | Retry with backoff; for RESPONSE_TOO_LARGE, request a smaller limit and continue with the cursor. If the error persists, contact support |
| 501 | Operation not supported for the objects you named | Do not retry; inspect error.code |
Responses without the envelope
| Response | Body |
|---|---|
401 | The authentication layer's own response |
413 for an oversized request body | The API gateway's body |
429 | The API gateway's body: { "_error": { "code", "message", "_request_id" } } |
Branch on the HTTP status first, then on error.code when the body carries the envelope.
Error codes
The error.code is a typed discriminator. Some codes add a structured details object you can act on programmatically:
error.code | HTTP | details |
|---|---|---|
VALIDATION_ERROR | 400 | none (generic validation failure) |
FIELD_VALIDATION_ERROR | 400 | details.errors[], each with field, error, optional index and value |
CONTIGUITY_VIOLATION | 400 | details.sectionId, details.ruleIds (rules of a section not contiguous) |
INCOMPLETE_ARRAY | 400 | details.missingRules, details.missingSections (positions array omits existing items) |
NOT_FOUND | 404 | none |
CONFIGURATION_VERSION_NOT_FOUND | 404 | none (the configurationVersion you asked for does not exist) |
FIELD_REFERENCE_NOT_FOUND | 404 | details.errors[] (a referenced rule, section, or anchor ID does not exist) |
FORBIDDEN | 403 | none |
UNSUPPORTED_OPERATION | 501 | none |
PARTIAL_PUBLISH_POLICY_UNSUPPORTED | 501 | details.errors[] (a partial publish named a rule or section) |
PARTIAL_PUBLISH_TYPE_UNSUPPORTED | 501 | details.errors[] (a partial publish named an object type it does not support) |
CONFLICT | 409 | none (the request conflicts with the current state of the resource) |
PUBLISH_IN_PROGRESS | 409 | none (another publish or draft reset holds the draft, so nothing was published) |
REQUEST_ENTITY_TOO_LARGE | 413 | none (an uploaded file exceeds its size limit) |
UNPROCESSABLE_ENTITY | 422 | none (well-formed request the server cannot act on) |
INTERNAL_ERROR | 500 | none |
RESPONSE_TOO_LARGE | 500 | none (the response exceeds the service limit; request a smaller page) |
PROVIDER_IN_USE | 409 | details naming what still references the cloud storage provider |
The bulk device and user actions add their own codes: DEVICES_NOT_FOUND, USERS_NOT_FOUND, INVALID_DEVICE_ID, INVALID_USER_ID, INVALID_STATUS_TRANSITION, UNEXPECTED_DEVICE_STATUS, CANNOT_DELETE_SUSPENDED_DEVICE. A request with more device or user IDs than an action accepts returns VALIDATION_ERROR; see Devices and Users for the limits.
The code vocabulary is open: handle unknown codes by falling back to the HTTP status.
Handling errors well
- Treat
4xxas actionable,5xxas retryable. For400, readerror.codeand, for field errors,details.errors[].field; for401, refresh the token; for403, check the service account's role. For500, retry with exponential backoff. - Retry
429, not other4xx. A429clears on its own: wait, then retry slowly. Do not retry501: the operation is not available for that tenant or those objects. - Branch on
error.codeand HTTP status, not onmessage. The human-readable text can change.
Every endpoint returns this envelope for its own errors (401 and 429 excepted), including devices, users, user groups, device groups, cloud storage and User requests. The details object is populated on the policy and object endpoints; elsewhere the response carries error.code and error.message only.
