Errors
Error Envelope
Section titled “Error Envelope”All errors use the same envelope shape. This is a validation failure on POST /tasks with no name:
{ "error": { "type": "validation_error", "code": "VALIDATION_ERROR", "message": "Action input validation failed", "details": { "service": "TaskService", "method": "createOrFromTemplate", "issues": [{ "path": ["name"], "message": "…" }] } }, "meta": { "requestId": "3f1c9a52-…" }}| Field | Type | Description |
|---|---|---|
error.type | string | Error category, fixed by the HTTP status (see table below). Branch on this. |
error.code | string | The specific error, in UPPER_SNAKE_CASE, for example NOT_FOUND, FORBIDDEN, VALIDATION_ERROR, CONFLICT |
error.message | string | Human-readable description. Wording can change; do not parse it. |
error.details | object, optional | Extra context when there is any. Validation errors list each failing field in details.issues (each issue has a path and a message). Omitted when empty. |
meta.requestId | string | Unique ID for the request, also sent as the X-Request-Id header. Include this when contacting support. |
When an operation rejects a request with a known business reason, the message is rewritten into a sentence that says what to change, and the original machine key is kept in details.key. Some of these are also re-typed to 400 validation_error.
Error Types
Section titled “Error Types”| Type | Status | Typical code | Retry? | Notes |
|---|---|---|---|---|
validation_error | 400 | VALIDATION_ERROR | No | Fix the request. Check error.details.issues for the failing fields. |
authentication_error | 401 | UNAUTHORIZED | No | Credential missing, invalid, revoked, expired, or sent to the wrong region or environment. Check your Authorization header. |
authorization_error | 403 | FORBIDDEN | No | Credential is valid but lacks a permission (Missing permission: …). Create a key with the needed scopes. An OAuth token gets code TWO_FACTOR_REQUIRED when the organization requires two-factor authentication and the user hasn’t enrolled. |
not_found_error | 404 | NOT_FOUND | No | The resource does not exist, belongs to another organization, or sits on a property outside your scope. Unknown routes also return 404. |
conflict_error | 409 | CONFLICT | Maybe | State conflict, for example an idempotent request with the same key still in flight. Read error.message before retrying. |
business_rule_error | 422 | PERMANENT_MUTATION_ERROR | No | The request is well-formed but can’t be applied, for example an Idempotency-Key reused for a different request. Read error.message. |
rate_limit_error | 429 | RATE_LIMITED | Yes | Wait for the Retry-After header (seconds). See Rate Limits. |
internal_error | 500 | INTERNAL_ERROR | Yes | Server error. The message is usually Internal server error; some 500s carry their own message and code, such as NON_RETRYABLE, which must not be retried. Otherwise retry with backoff; if it persists, contact support with meta.requestId. |
provider_error | 502 | CONNECTOR_ERROR | Depends | A connected provider (a PMS, a smart lock vendor) failed or refused. The provider’s own response is withheld; error.message says whether a retry can help. See Provider refusals. |
service_unavailable | 503 | SERVICE_UNAVAILABLE | Yes | Temporarily unavailable. Honor Retry-After when present. |
A status the API doesn’t map to one of these types is reported as internal_error.
Retrying Requests
Section titled “Retrying Requests”- 429 rate_limit_error: Honor the
Retry-Afterheader (whole seconds). See Rate Limits for details. - 500, 502 and 503: Retry with exponential backoff (for example start at 1s, double each attempt, cap at 60s), honoring
Retry-Afterwhen the response has one. GET requests are safe to retry. For POST requests, send anIdempotency-Keyso a retry can’t apply the operation twice (see Idempotency). - All other errors: Do not retry without changing the request.
Provider refusals
Section titled “Provider refusals”Device commands (lockDevice, unlockDevice, setDeviceTemperature, setDeviceMode) reach a real device through its provider. If the provider answers with a refusal, the API returns 502 provider_error. Sending the same command again unchanged will be refused again. When you sent an Idempotency-Key, a retry with that key replays the refusal without contacting the provider. Check the device in SuiteOp, then send a new Idempotency-Key if you still want the command attempted.
A command SuiteOp refuses itself, before any provider is contacted — an archived device, remote control switched off, an operation the provider doesn’t offer — is not a 502. It answers 403, 400 or 409 with a message that says why; see Devices.