Snoze Developers
REST API

Errors

The error envelope, stable codes, and how to retry safely.

Every non-2xx response carries the same flat JSON envelope: a stable machine-readable error code, a human-readable message, the HTTP status, and a _tag naming the specific failure:

{
  "_tag": "ApiUnauthorized",
  "error": "unauthorized",
  "message": "Invalid API key.",
  "status": 401
}

Write code against error (or status), never against message — messages can be reworded at any time. _tag is more specific than error and is stable too, but new tags appear as the API grows.

Common codes

HTTPerrorMeaning
400bad_requestThe request was well-formed JSON but failed validation — bad query param, unknown field key, wrong value shape.
401unauthorizedMissing, malformed, expired, or revoked API key — or a session where a key is required.
403forbiddenThe key is valid but lacks a required scope or item grant.
404not_foundThe resource doesn't exist — or the key can't see it.
409conflictThe write collided with current state (e.g. a taken slug or replayed idempotency key with a different body).
410goneThe resource existed but is permanently gone.
413payload_too_largeUpload or body exceeded its size limit.
429rate_limitedToo many requests — see Rate limits.
500persistence_failed / internal_server_errorSomething failed on our side.

not_found deliberately covers both "doesn't exist" and "not visible to this key", so probing for ids reveals nothing.

Retrying

  • rate_limited and 5xx are retryable. Back off exponentially and honor Retry-After when present.
  • Mutations should always send an Idempotency-Key header, so a retried request that already succeeded returns the original result instead of duplicating work.
  • conflict means re-read the resource, reapply your change, and try again — retrying the identical request will keep failing.
  • Everything else (unauthorized, forbidden, bad_request) is a bug in the request; retrying without changes won't help.

On this page