Error Handling
Understand error response shapes, HTTP status codes, when to retry, and how to parse failures in your integration.
Always check HTTP status first
Error bodies are JSON with Content-Type: application/json. The exact fields vary by endpoint — use the status code to decide whether to refresh a token, fix input, or retry later.
Error response schema
There is no single global error envelope. Most error responses include one primary text field (message or error). Validation failures may add an errors object or array with per-field detail.
| Field | Type | Description |
|---|---|---|
| message | string | Primary human-readable error text (auth, login, orders, rate limits). |
| error | string | Alternate single-field error message (assets, team users, legal hold). |
| errors | object | array | Validation details — field map or Joi details array |
| success | boolean | false on auth middleware failures and some business-rule errors. |
Success responses are endpoint-specific (lists include pagination metadata; mutations may return message plus created IDs). This guide covers failure responses only.
HTTP status code catalogue
| Status | Title | When you see it | Retry |
|---|---|---|---|
| 200 | OK | Request succeeded. | — |
| 201 | Created | Resource created (e.g. legal hold ticket). | — |
| 400 | Bad Request | Malformed payload shape or business rule blocked the action. | Fix the request; do not retry unchanged. |
| 401 | Unauthorized | Missing, invalid, revoked, or expired token; invalid login credentials. | Refresh token or re-login, then retry once. |
| 404 | Not Found | Unknown route, order, asset, employee, or recovery ID. | Fix URL or ID; do not retry unchanged. |
| 422 | Validation Error | Required fields missing or field values invalid. | Fix payload; do not retry unchanged. |
| 429 | Too Many Requests | Rate limit exceeded (60 req/min per IP). | Backoff and retry (see Rate Limits). |
| 500 | Internal Server Error | Unexpected server failure. | Exponential backoff; contact support if persistent. |
Example payloads
401 — Missing Bearer token
Returned by the auth middleware when Authorization is absent.
{
"success": false,
"message": "Access denied: No token provided."
}401 — Invalid or expired JWT
{
"success": false,
"message": "Access denied: token Not Valid."
}401 — Revoked token
After calling DELETE /api/v1/auth/token, the access token is revoked and will be rejected for all subsequent requests until the user logs in again.
{
"success": false,
"message": "Access denied: token has been revoked."
}401 / 422 — Login failures
Missing credentials return 422; wrong credentials return 401.
{
"message": "client_id & client_secret are required"
}{
"message": "Invalid credentials"
}404 — Unknown route or resource
{
"message": "The requested API endpoint does not exist"
}{
"error": "Asset number not found in your company."
}422 — Validation (Joi details array)
Some endpoints return a Joi details array under errors.
{
"errors": [
{
"message": "\"assign_to_email\" must be a valid email",
"path": ["assign_to_email"],
"type": "string.email"
}
]
}422 — Validation (field map)
Bulk import and similar endpoints may return a map of field names to messages.
{
"errors": {
"email": "Email is required",
"firstname": "First name may only contain letters and spaces"
}
}400 — Business rule
{
"success": false,
"message": "Payload must be a non-empty array."
}429 — Rate limit
See also RateLimit-* response headers.
{
"message": "Too many requests. Please try again later."
}500 — Server error
{
"message": "An unexpected error occurred"
}Retry guidance
| Status | Action |
|---|---|
| 401 | Call refresh-token once. If still 401, re-login with client_id / client_secret. Do not retry in a tight loop. |
| 422 / 400 / 404 | Do not retry with the same payload. Fix validation errors or verify IDs and URLs. |
| 429 | Read RateLimit-Reset or use exponential backoff (1s → 2s → 4s, cap ~30s). See the Rate Limits guide. |
| 500 | Retry with exponential backoff (e.g. 3 attempts). If errors persist, contact Unduit support with timestamp and endpoint. |
- Treat POST mutations as non-idempotent unless the endpoint docs state otherwise — avoid blind retries that may duplicate resources.
- Log the HTTP status, response body, and request ID (if you add one client-side) for support tickets.
- Surface
messageorerrorto users; avoid exposing rawserverErrorvalues in production UIs.
Example: parsing errors in JavaScript
async function parseApiError(response) {
let body = {};
try {
body = await response.json();
} catch {
return { status: response.status, message: response.statusText };
}
const message =
body.message ??
body.error ??
(typeof body.errors === 'object' && !Array.isArray(body.errors)
? Object.values(body.errors).join('; ')
: Array.isArray(body.errors)
? body.errors.map((e) => e.message ?? e.msg ?? String(e)).join('; ')
: 'Request failed');
return { status: response.status, message, raw: body };
}
async function apiCall(url, options) {
const response = await fetch(url, options);
if (response.ok) {
return response.json();
}
const err = await parseApiError(response);
if (response.status === 401) {
// refresh token or re-login, then retry once
} else if (response.status === 429) {
// backoff using RateLimit-Reset header
} else if (response.status >= 500) {
// exponential backoff retry
}
throw err;
}