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 status | Meaning | Example error value |
|---|---|---|
400 | Validation failure | invalid_request, invalid_pin, invalid_language |
401 | Missing, invalid, expired, or revoked token | unauthorized |
403 | Token scope is not full | insufficient_scope |
404 | Resource not owned by your agent | not_found |
409 | Conflict | roster_managed_by_payroll, duplicate_identity |
429 | Alert rate limit hit | rate_limited |
500 | Unexpected server error | internal_error |
Notable codes
insufficient_scope(403) — you used aread-scope token. Every REST endpoint needs afull-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}`);
}Updated about 1 hour ago
Did this page help you?

