Skip to main content

4. Response envelope and error format

4.1 Success​

{
"data": { "...": "..." },
"error": null,
"requestId": "<request-id>"
}

Responses carry Cache-Control: no-store and X-Request-Id. X-Request-Id is echoed when supplied (validated against ^[A-Za-z0-9._:-]{8,128}$; otherwise a UUID is generated).

4.2 Error​

{
"data": null,
"error": {
"code": "VALIDATION_ERROR",
"message": "Resource validation failed",
"fields": { "name": "Required" }
},
"requestId": "<request-id>"
}

fields is present only for validation errors. Keys are field paths; request is used when the path is empty.

4.3 Error codes​

Codes below are the values found in the code. Each endpoint section lists the codes it can return.

CodeTypical HTTP statusMeaning
UNAUTHENTICATED401No session. Also used for "fresh authentication required" in the finance wrapper.
MFA_REQUIRED401 or 403Account has no MFA enrolment where required. Status depends on the route (see endpoint sections).
FRESH_AUTH_REQUIRED401 or 403Session is older than the freshness window.
FORBIDDEN403Role, scope or policy does not permit the action.
ORIGIN_DENIED403Origin not in the allowed set.
IDEMPOTENCY_KEY_REQUIRED400Idempotency-Key header missing or malformed.
IDEMPOTENCY_CONFLICT409Same idempotency key used with different data, or a duplicate transaction reference.
VALIDATION_ERROR400, 422Request body or parameter failed validation. Status varies by route.
NOT_FOUND404Record missing, or outside the actor's scope. Scope-denied and missing records are indistinguishable.
INVALID_STATE409Record not in a state that allows the action.
STALE_STATE409Expected lifecycle value no longer matches (optimistic concurrency).
CONFLICT409Unique constraint violation on a generic create.
FEATURE_UNAVAILABLE501 or 503Resource is gated, reporting is disabled, or a dependency is unavailable.
PROVIDER_DISABLED503Webhook provider secret not configured.
NOT_READY503 (readiness) or 409 (report download)Dependency unavailable, or report not ready.
RATE_LIMITED429Contact-form limit.
LIMIT_REACHED429Report export concurrency or daily limit.
PAYLOAD_TOO_LARGE413Body exceeds the route's limit.
INVALID_CONTENT_TYPE400 or 415Body is not application/json.
INTERNAL_ERROR500Unhandled error. Message is generic by design.
WORKER_FAILED500Cron worker failed.
UNAUTHORIZED401Worker secret mismatch or missing.

Fields that appear on specific routes:

  • Finance, financing and application errors use a domain code in error.code, for example UNBALANCED_JOURNAL, ACCOUNT_INVALID, INVALID_STATE_TRANSITION, FINAL_TERMS_INVALID, ELIGIBILITY_FAILED. Those are listed per endpoint.
  • Better Auth errors follow Better Auth's own format.