Error shape
The body of every non-2xx response is a single error object. The HTTP status code determines the broad class (4xx vs 5xx); the type field narrows it; code is the precise reason.
{
"error": {
"type": "validation_error",
"code": "missing_field",
"message": "Request body is missing required field 'email'.",
"field": "email",
"request_id": "req_01HZSCC3D4E5F6G7H8J9K0L1M2"
}
}HTTP status codes
- 200OK — success.
- 201Created — a new resource was inserted.
- 204No content — successful with no body.
- 400Bad request — see `error.message` for the field.
- 401Unauthenticated — missing or invalid Bearer key.
- 403Forbidden — authenticated, but the key lacks the required scope.
- 404Not found — the resource doesn't exist or isn't visible to your key.
- 409Conflict — uniqueness or version conflict (e.g. dedupe collision).
- 422Unprocessable — validation failure with structured detail.
- 429Rate limit exceeded — see `Retry-After`.
- 500Server error — safe to retry with an idempotency key.
- 503Outbox is back-pressured. Retry with backoff.
Error types
authentication_error— bad / missing / revoked key.permission_error— wrong scope, or row not visible to the key.validation_error— malformed body, bad enum, missing field. Carriesfield.not_found— the path-id doesn’t resolve.conflict— uniqueness violation, optimistic-lock failure.rate_limit_error— see theRate limitspage.provider_error— Gmail/Twilio/Meta returned an error we couldn’t recover from.internal_error— unexpected; please report.
Request id
Every response — success or failure — carries an X-Request-Id header. The same id is echoed in the error body and stamped on every audit-log row the request touched, so a quick search in /audit?request_id=... shows you everything that happened.