Errors

Every error is a JSON object with a stable code and a human-readable message.

Every error response from the REST API has the same shape:

{ "error": "<code>", "message": "<human-readable>" }

The error field is a stable, machine-readable code — branch your logic on it. The message is a human-readable explanation that may change.

Status codes

HTTP statusMeaningExample error value
400Validation failureinvalid_request, invalid_pin, invalid_language
401Missing, invalid, expired, or revoked tokenunauthorized
403Token scope is not fullinsufficient_scope
404Resource not owned by your agentnot_found
409Conflictroster_managed_by_payroll, duplicate_identity
429Alert rate limit hitrate_limited
500Unexpected server errorinternal_error

Notable codes

  • insufficient_scope (403) — you used a read-scope token. Every REST endpoint needs a full-scope token. See Authentication.
  • not_found (404) — the resource isn't owned by your agent. Your token only ever sees your own agent's data.
  • roster_managed_by_payroll (409) — your roster is synced to a payroll provider, so creating, editing, and deleting users is blocked. See Payroll-managed rosters.
  • duplicate_identity (409) — a user create or rename collides with an existing active user (same first name, last name, and PIN).
  • rate_limited (429) — the shared alert rate limit was hit. See Rate limits.

Handling errors

Check the HTTP status first, then read error for the specific case:

const res = await fetch(url, options);
if (!res.ok) {
  const { error, message } = await res.json();
  if (error === "roster_managed_by_payroll") {
    // fall back to reading the roster instead of writing
  }
  throw new Error(`${res.status} ${error}: ${message}`);
}

Did this page help you?