Error Handling
Error Handling
How Litmus APIs report errors and what to do about them.
REST: HTTP status codes
Standard codes, used as you'd expect:
Code | Meaning | Common cause |
|---|---|---|
200 / 201 | Success | -- |
204 | No content | DELETE returned successfully |
400 | Validation error | Bad body, missing required field |
401 | Unauthorized | Token expired, wrong client_id/secret, wrong header for LEM |
403 | Forbidden | Token is valid but the user / client lacks permission |
404 | Not found | Wrong path prefix or unknown resource ID |
409 | Conflict | Resumable upload session already open (run DELETE first) |
5xx | Server error | Check edge / LEM logs |
Error body shape:
{
"error": "<short code>",
"message": "<human readable>"
}GraphQL: 200 with errors array
GraphQL endpoints (/devicehub/v2, /digitaltwins/v2, /cc/v2, /opcua/v2, /mqtt/gql) return HTTP 200 even on failure. Errors live in the body:
{
"data": null,
"errors": [
{
"message": "Driver not found: 12345",
"path": ["CreateDevice"],
"extensions": { "code": "NOT_FOUND" }
}
]
}Always check body.errors before reading body.data - a non-null errors array means the operation failed even though the HTTP status is 200.
Some GraphQL responses return partial data alongside errors (e.g. one entry in a list failed). The contract is: data is what succeeded; errors lists what didn't.
LEM async-task: per-subtask status
LEM async-task subtasks each carry a status:
Status | Meaning |
|---|---|
PENDING | Queued, not started |
IN_PROGRESS | Running |
SUCCESS | Done OK |
FAILED | Done with error; check errorMessage field |
A parent task can have some subtasks succeed and some fail. Inspect each subtask, don't aggregate.
Common 401 traps
Product | Header | Wrong header symptom |
|---|---|---|
LE | Authorization: Bearer <token> | 401 with "invalid token" |
LEM /api/v1/ | X-AuthToken: <token> | 401 - using Bearer returns "missing token" |
LEM /admin/v1/ | X-AuthToken: <token> | Same as above |
LEM /mpcs/ | Authorization: <token> (no Bearer prefix) | 401 if you add Bearer or use X-AuthToken |
LUNS | Authorization: Bearer <token> (Keycloak token) | 401 if you use the LE token instead of the UNS-issued one |
Retries
- Idempotent ops (GET, PUT, DELETE on a specific ID): safe to retry on 5xx and timeout.
- POST CreateX: not safe to retry blindly - may create duplicates. Either retry with a client-side deduplication key or verify the resource via List before retrying.
- Async-task launch: 5xx during step 3 = retry safe (no taskId returned). 5xx during polling = retry safe.
- Resumable uploads: if step 2 (PUT bytes) fails, run step 0 (DELETE) and start over - the partial bytes are discarded.