Skip to content

Error Handling

The DayZero API uses standard HTTP status codes and returns one consistent JSON envelope for every non-2xx response (schema ErrorResponse in the OpenAPI spec).

Error Response Format

json
{
  "error": "not_found",
  "message": "Invoice '01912345-abcd-7000-8000-000000000042' not found",
  "code": "NOT_FOUND_006",
  "request_id": "3f0e7c2a-4b1d-4a8e-9c2f-1d2e3f4a5b6c"
}
Field Description
error Machine-readable category: validation_error, unauthorized, forbidden, not_found, conflict, rate_limited, server_error, service_unavailable
message Human-readable explanation. May change between releases — don't branch on it
code Stable, specific code in the form CATEGORY_NNN (e.g. AUTH_010, NOT_FOUND_006). Branch on this
request_id Correlation ID for this request; also returned in the X-Request-ID response header. Quote it when contacting support
errors Only on 400/422 validation failures: a map of field name → message

request_id echoes your X-Request-ID request header when you send one; otherwise DayZero generates it.

Validation errors name the offending fields:

json
{
  "error": "validation_error",
  "message": "Validation failed: due_date: Input should be a valid date",
  "errors": {
    "due_date": "Input should be a valid date"
  },
  "code": "GEN_002",
  "request_id": "3f0e7c2a-4b1d-4a8e-9c2f-1d2e3f4a5b6c"
}

Send Accept: application/json on every request. A request whose Accept header prefers text/html (browser navigation) gets an HTML error page instead of JSON, so stating JSON removes the ambiguity.

Status Codes

Status error Typical code When
200 — — Request completed
201 — — Resource created
400 validation_error GEN_002, VALIDATION_* Request body or parameters failed schema validation, or a business-rule violation; errors names the fields
401 unauthorized AUTH_001, AUTH_008 (missing x-business-id), AUTH_010 (no access to that business) Missing, invalid, expired or revoked token, or wrong business
403 forbidden AUTH_002, AUTH_048 (token scope), AUTH_028 (IP not allowed), AUTH_070 (token limited to other businesses) Token valid but its scope, role, source IP or business limit doesn't permit the operation (e.g. a read_only token calling POST)
404 not_found NOT_FOUND_* Resource doesn't exist — or belongs to a different business than x-business-id
409 conflict CONFLICT_* Duplicate or state conflict (e.g. creating an invoice with a number another invoice already uses)
413 — BODY_TOO_LARGE Request body over the size limit. This response comes from a body-size guard, not the standard envelope: {"error": "Request body too large", "code": "BODY_TOO_LARGE"}
422 validation_error GEN_002 Same shape as 400. Used for a malformed cursor query parameter (see Pagination) and for OAuth callbacks; ordinary request validation failures are 400
429 rate_limited GEN_005, AUTH_058 (the token's own request budget) Too many requests — wait for Retry-After when present, otherwise back off exponentially, then retry the same request
500 server_error GEN_003 Unexpected error — retry idempotent requests with backoff; quote request_id to support
503 service_unavailable GEN_004 Temporary maintenance or dependency outage — retry with backoff

Retry guidance

  • Retry 429, 500, 503 with exponential backoff (start at 1–2 s, cap at ~30 s). On a 429 that carries Retry-After, wait that many seconds instead.
  • Don't retry 400, 401, 403, 404, 409, 413, 422 unchanged — the request itself is the problem. The one exception is VALIDATION_620 (409, "another request with this Idempotency-Key is still running"): wait for its Retry-After and resend.
  • Send an Idempotency-Key header on every POST, PUT and PATCH you might retry (see Idempotent retries). Without it, check whether the record already exists (list with the filter you would have used) before re-sending after a timeout or 5xx.

Idempotent retries

Add Idempotency-Key: <unique string> (1-255 characters; a UUID per logical operation works) to a POST, PUT or PATCH. The first request runs normally and its JSON answer is kept for 24 hours. Repeating the request with the same key, path, caller and body returns that answer with the same status and Idempotent-Replayed: true, and nothing runs a second time, so a retry after a timeout cannot double-post.

Situation Response
Repeat of a request that succeeded The original status and body, Idempotent-Replayed: true
Repeat while the first is still running 409, VALIDATION_620, Retry-After: 2: wait and send it again
Same key, different body or query string 400, VALIDATION_621: use a new key for a new request
Key empty or longer than 255 characters 400, VALIDATION_622

Only successful (2xx) JSON answers up to 256 KB are kept. A request that failed, returned a file or stream, or crashed releases its key, so retrying it runs it again. File uploads (multipart), webhooks and MCP calls are not covered; MCP tool calls are deduplicated by the server itself. A key belongs to the credentials that sent it: a rotated session token or a different API token starts a new key space.

Common Mistakes

Wrong business context: Getting 404 on a resource you know exists? Check that x-business-id matches the business that owns it.

Missing business header: 401 with code: "AUTH_008" means x-business-id was required and absent. Only GET /api/v1/businesses (and a handful of user-level endpoints) work without it.

Amounts in dollars instead of cents: All monetary values are integers in cents. $150.00 should be sent as 15000; 150.00 fails validation.

Expired token: Tokens with an expiration return 401 once expired. Generate a new one from Settings → Developers.

Read-only token on a write: A token created with the read_only scope gets 403 on every non-GET request.