Skip to main content

Error Handling

All API errors follow a consistent envelope structure, making it straightforward to detect failures and react programmatically.

Error Envelope

Every error response uses this format:

{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Human-readable description of what went wrong"
}
}
FieldTypeDescription
successbooleanAlways false for error responses
error.codestringMachine-readable error code (see reference below)
error.messagestringHuman-readable explanation of the error
error.detailsarrayOptional. On VALIDATION_ERROR, lists each invalid property and the constraints it failed

Error responses never include a data field.

Quick Reference

CodeHTTP StatusWhen it happens
VALIDATION_ERROR400Request body or query params fail field-level validation
INVALID_DATE_RANGE400Date range exceeds the endpoint maximum, end is before start, or a date is not in ISO format
UNAUTHORIZED401Missing or invalid JWT / API Key
TOKEN_EXPIRED401The JWT is valid but has expired (1 hour lifetime)
FORBIDDEN403Authenticated but insufficient permissions for this endpoint
NOT_FOUND404Resource ID doesn't exist or URL path is incorrect
RATE_LIMITED429Too many requests within the rate limit window
INTERNAL_ERROR500Unexpected server-side failure

Error Codes Reference

VALIDATION_ERROR — 400

Returned when the request body or query parameters fail field-level validation.

When it happens:

  • A required field is missing
  • A field has the wrong type (e.g., string instead of number)
  • A value is out of the allowed range (e.g., limit greater than 100)

How to resolve: Read the error.details array — it lists each invalid property and the constraints it failed, so you know exactly what to fix. A normalizing filter on the server guarantees that every field-validation failure comes back as VALIDATION_ERROR with this details array populated.

Example response:

{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "One or more fields failed validation.",
"details": [
{
"property": "limit",
"constraints": {
"max": "limit must not be greater than 100"
}
}
]
}
}

INVALID_DATE_RANGE — 400

Returned when a date range in the request is not acceptable.

When it happens:

  • The range between the start and end dates exceeds the maximum the endpoint allows
  • The end date is earlier than the start date
  • A date is not in ISO format (e.g., 2026-06-13 or 2026-06-13T10:30:00Z)

How to resolve: Shorten the range so it fits within the endpoint's limit, make sure the end date is on or after the start date, and send dates in ISO format.

Example response:

{
"success": false,
"error": {
"code": "INVALID_DATE_RANGE",
"message": "The selected date range is too wide. Choose a shorter period."
}
}

UNAUTHORIZED — 401

Returned when the request lacks valid authentication credentials.

When it happens:

  • The Authorization header is missing or malformed
  • The JWT token has expired (1 hour TTL)
  • The X-API-Key header is missing or invalid
  • The tenant header does not match the token's tenant
  • The API Key is inactive or outside its validity window

How to resolve: Re-authenticate via the Login endpoint to obtain a fresh token. If the API Key is rejected, verify its status and validity window with your account manager.

Example response:

{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Token has expired. Please re-authenticate."
}
}

FORBIDDEN — 403

Returned when the user is authenticated but does not have permission to access the requested resource.

When it happens:

  • The user's role does not include the required permission for this endpoint
  • The resource belongs to a scope the user cannot access

How to resolve: Contact your administrator to verify that the user account has the required permissions assigned. Each endpoint documents its required permission in the API reference.

Example response:

{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "Insufficient permissions to access this resource."
}
}

NOT_FOUND — 404

Returned when the requested resource does not exist.

When it happens:

  • The ID in the URL does not match any existing record
  • The resource was deleted
  • The URL path is incorrect

How to resolve: Verify that the resource ID is correct and that the resource has not been deleted. Double-check the endpoint URL.

Example response:

{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Device with ID 99999 not found"
}
}

RATE_LIMITED — 429

Returned when the request exceeds the allowed rate limit for the endpoint.

When it happens:

  • Too many requests sent within the rate limit window
  • Telemetry token bucket is exhausted

How to resolve: Wait until the time indicated by the Retry-After response header before retrying. See Rate Limits for details on limits per endpoint group.

Example response:

{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 12 seconds."
}
}

INTERNAL_ERROR — 500

Returned when an unexpected server-side error occurs.

When it happens:

  • An unhandled exception on the server
  • A downstream service is temporarily unavailable

How to resolve: Retry the request after a brief delay using the retry strategy below. If the error persists, contact support and include the X-Request-Id value from the response headers (see below).

Example response:

{
"success": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred. Please try again later."
}
}

X-Request-Id

Every API response includes an X-Request-Id header — a unique identifier generated by the server for that specific request. You don't need to send it; the server creates it automatically.

HTTP/1.1 500 Internal Server Error
X-Request-Id: req_a1b2c3d4e5f6

When contacting support about persistent errors, always include this value. It allows the team to trace the exact request through server logs.


Troubleshooting

I'm getting...Check first
400 VALIDATION_ERRORRead error.details — it lists each invalid field and the constraint it failed
400 INVALID_DATE_RANGEIs the range within the endpoint limit? Is the end on or after the start? Are dates in ISO format?
401 TOKEN_EXPIRED after working callsToken expired (1 h TTL) — re-authenticate via /apidev/v1/login
401 UNAUTHORIZED on first callIs X-API-Key present? Does tenant match the JWT's tenant?
403 on a valid endpointUser role lacks the required permission — check with your admin
404 with a correct IDResource may have been deleted, or the URL path has a typo
429 in a loopStop retrying — respect Retry-After header, implement backoff
500 onceRetry with backoff. Transient failures happen
500 repeatedlyStop retrying, contact support with X-Request-Id

Retry Strategy

For transient errors (429 and 500), implement an exponential backoff strategy:

  1. First retry: wait 1 second
  2. Second retry: wait 2 seconds
  3. Third retry: wait 4 seconds
  4. Fourth retry: wait 8 seconds
  5. Give up after 4 retries and log the error

For 429 responses, always prefer the Retry-After header value over your own backoff calculation — it gives the exact wait time needed.

async function requestWithRetry(fn, maxRetries = 4) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
const status = error.response?.status;

if (status === 429 || status === 500) {
if (attempt === maxRetries) throw error;

const retryAfter = error.response?.headers?.['retry-after'];
const delay = retryAfter
? parseInt(retryAfter, 10) * 1000
: Math.pow(2, attempt) * 1000;

await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}

throw error; // Non-retryable error
}
}
}