Errors
The one error envelope and the closed set of error codes.
On this page
Every refusal has the same shape, so error handling is one switch statement:
{ "code": "VALIDATION_ERROR", "title": "...", "description": "..." }Branch on code. The title and description are for people and may change. A VALIDATION_ERROR also carries fields, and an INSUFFICIENT_PERMISSION names the missing scope in permission.
Codes
| Code | Status | Meaning |
|---|---|---|
UNAUTHENTICATED | 401 | No key, or the key is invalid, revoked or expired. |
INSUFFICIENT_PERMISSION | 403 | The key lacks a scope (named in permission), or names a workspace outside its allowlist. |
NO_ORG | n/a | The key belongs to no business. |
WORKSPACE_REQUIRED | 400 | The key reaches several workspaces and the call has no X-WTN-Workspace. |
NOT_FOUND | 404 | Unknown id, or one in a workspace outside your key's reach (deliberately identical). |
VALIDATION_ERROR | 400 | The body failed validation; fields names each bad field. |
STATE_INVALID | 409 | The action is not valid in the current state (for example launching a campaign that is not ready; the description names the blocker). |
CONFLICT | 409 | An Idempotency-Key was reused with a different body, or is in flight. |
RATE_LIMITED | 429 | A quota is exhausted; Retry-After gives the wait in seconds. |
DB_UNAVAILABLE | 5xx | We could not process the request safely. Retry with the same Idempotency-Key. |
INTERNAL_ERROR | 5xx | Ours to fix. Send us the request id and timestamp. |
REQUEST_ERROR | 400, 413, 415 | Refused before a route: malformed JSON, body too large or unsupported content type. |
App and launch codes
| Code | Status | Meaning |
|---|---|---|
DEVICE_AUTH_INVALID | 401 | Heartbeat: the device is unknown or the refresh token is wrong, expired or revoked. Call register again. |
USER_HASH_INVALID | 401 | A userHash that does not verify, or a verified user addressed without one. |
INSTALL_BLOCKED | 403 | The workspace blocked this device. |
LAUNCH_SIGNATURE_REQUIRED | 403 | The campaign requires a signed launch and the visit had none. |
LAUNCH_TOKEN_INVALID | 403 | One answer for expired, forged, wrong-campaign, revoked-key and replayed tokens. Mint a fresh one. |
COLLECTION_PAUSED | 429 | The spend guard tripped; collection is paused. |
Retry only when it can help: RATE_LIMITED (after Retry-After), DB_UNAVAILABLE and INTERNAL_ERROR (with the same Idempotency-Key). Everything else needs a change to the request.