Skip to content
Talk to our solutions team

Error codes

Every failure listed here is one a client can receive. Endpoint-by-endpoint request and response shapes live in the IAM API reference.

The service does not ship one error shape. Five are reachable, and which one you get depends on the surface and on how far the request got before it failed.

EnvelopeSurfaceBody
Code envelope/auth/*, /superadmin/impersonate, /superadmin/registry/*, /internal/session/*{"error":"<message>","code":"<slug>"}
Code envelope with details/rest/*, /anon/rest/*, /schema/*, /admin/user/*, /admin/role/*, /superadmin/tenant/*, DELETE /cache{"error":"<message>","code":"v2.<slug>","details":{…}}details omitted when empty
Message only/admin/data/*{"error":"<message>"}no code field at all
Rate-limit bodythe eight rate-limited /auth/* routes{"error":"rate_limited","profile":"<profile>","retry_after_seconds":N}error holds the slug, and there is no code field
Plain textany route where the credential or the tenant is rejected before a handler runsContent-Type: text/plain with a bare message — Unauthorized, invalid tenant, config client not initialised

Codes are not unique. The same slug carries different statuses on different routes, so (status, code) is the pair to switch on.

CodeStatus by surface
no_tenant400 from /auth/* handlers · 401 from the credential middleware · 403 as v2.no_tenant on the entity surface
unknown_realm400 on POST /auth/login · 401 on POST /auth/mfa/verify
exchange_failed401 on the OAuth callback · 500 on POST /internal/session/exchange
invalid_session401 for a WebAuthn ceremony session · 401 for a managed session
rotate_failed500 for refresh-token rotation · 500 for managed-session rotation
list_failed500 on GET /internal/session/list · 500 on GET /superadmin/registry/tenants
CodeStatusMeaningCause
bad_request400Body could not be decoded, or a required top-level field was absentMalformed JSON, empty body, or a body over the 1 MiB request cap
missing_fields400Body decoded, a required field was emptyThe message names the fields — identity and password are required, mfa_token and code are required, email, code, and new_password are required, and so on
missing_id400The :id path segment was emptyDELETE /auth/apikey/:id called with no id
no_tenant400No resolvable tenant on the requestThe four CPET headers were absent or incomplete. Message on login and refresh: tenant identity missing from request — set X-Tenant or equivalent headers
no_tenant401A credential was presented with no tenant contextBearer or ApiKey sent without CPET headers, so no per-tenant keyring can verify it
engine_unavailable500The tenant’s datastore could not be reachedDatabase down, wrong DSN, an unregistered product segment, or a tenant that was never provisioned. Message is always could not reach datastore
(plain text) invalid tenant400The CPET headers match no configured tenantThe middleware tries the full four-segment key, then the bare X-Tenant value, then refuses
(plain text) config client not initialised400The tenant middleware could not resolve the config clientDeployment wiring problem, not a client error

See Tenancy for how the four headers resolve.

CodeStatusMeaningCause
invalid_credentials401Identity or password was wrongDeliberately never says which. Wrong password, unknown identity, inactive identity and an identity row with no password hash all return this, and the miss path spends an equal argon2 budget so timing does not leak existence
bad_identity_type400identity_type names a column the realm’s entity does not declare as a login identifierThe message lists the declared identifiers
unknown_realm400The named realm does not exist in the tenant’s composed configTypo in realm, or the product’s realm config was never delivered to this CPET
provider_not_allowed403The realm exists but does not list password among its providersMessage: realm <name> does not allow password login. The realm provider gate applies to password login only
hook_denied403An auth-flow hook aborted the requestThe hook’s own error text is returned verbatim, so this message is not a stable string
login_abuse429A check_login_abuse hook denied the attemptDistinct from the built-in rate limiter. Only fires when a product registers such a hook
user_query_failed500The identity lookup query itself erroredEngine or SQL failure, or a missing identity table. Message: lookup failed
mfa_lookup_failed500Could not determine whether the user has MFA enabledEngine failure reading the factor table
sign_failed500The per-tenant signing key could not sign the claim setBad bring-your-own key material, or an unreachable key store
issue_failed500The refresh token could not be persistedWrite failure
update_failed500A new password could not be writtenWrite failure on POST /auth/password/reset/confirm
code_failed500Secure random code generation failedEntropy source failure on reset-request and magic-request
invalid_reset_token401The password-reset code is unknown, expired, or already usedOne code and one message for all three — no oracle
already_bootstrapped403POST /auth/bootstrap is closedThree causes, one message (bootstrap not available): no bootstrap token configured, a wrong token, or a superadmin already exists
superadmin_not_deployed503The control-plane scope is not deployed for this product and environmentNo reserved superadmin tenant, or its schema was never bootstrapped
policy_unavailable503The tenant’s session policy could not be resolvedThe service refuses rather than guessing stateless-versus-managed, which would silently downgrade a tenant
persist_failed500A required row could not be writtenMessages name the row: could not create superadmin, could not persist reset request, could not persist magic link

Full flow detail: Password login and Realms.

CodeStatusMeaningCause
invalid_mfa_token401The mfa_token is unusableFour messages behind one code: invalid MFA token (twice), MFA token expired, wrong token type (a full access token sent where an MFA-pending one is required)
invalid_code401The TOTP code did not validateWrong code, or client clock skew beyond the step. On disable the message is a valid TOTP code is required to disable MFA — a hijacked session alone cannot strip MFA
code_reused401The code was already consumed within its stepMessage: this code was already used; wait for the next one
mfa_not_enabled401Verification attempted for a user with no enabled factorStale client flow, or the factor was disabled between login and verify
unknown_realm401The realm on the MFA-pending token no longer existsRealm config changed between login and verification
already_enabled409TOTP is already enrolledMessage on setup: TOTP is already enabled; disable first to re-enrol
no_pending_setup400No enrolment is in progressenable called before setup, or the pending setup expired
lookup_failed500Could not read existing MFA stateMessage: could not check existing MFA setup
user_lookup_failed500Could not re-read the user row to build the post-MFA tokenEngine failure
persist_failed500The pending secret or the enabled factor could not be writtenMessages: could not persist MFA setup, could not enable TOTP

See Multi-factor authentication.

CodeStatusMeaningCause
invalid_magic401The code is unknown, expired, or already consumedThree call sites, one message — a second click on a one-shot link is indistinguishable from a wrong code
delete_failed500The one-shot link could not be consumed after a successful verifyA hard failure rather than a warning, because the link would otherwise stay replayable
persist_failed500The link row could not be writtenMessage: could not persist magic link

Every /auth/webauthn/* route answers 503 webauthn_disabled with the message WebAuthn not configured. The gate is the first statement in all four handlers, and the deployable binary never supplies a relying-party configuration. There is no operator-facing setting that changes this.

CodeStatusMeaning
webauthn_disabled503The only response the four passkey routes produce

The rest of the ceremony’s codes exist in the implementation but sit behind that gate. Background: Magic link, WebAuthn and passkeys.

Every /auth/oauth/:provider/* request answers 404 unknown_provider with the message OAuth provider not configured. The provider lookup runs before anything else, and the deployable binary registers no provider. Declaring type: oauth in a provider declaration wires nothing — nothing reads that field at request time.

CodeStatusMeaning
unknown_provider404The only response the OAuth routes produce

Background: OAuth and federated identity.

CodeStatusMeaningCause
auth_required401The route needs an authenticated principal and none was attachedNo Authorization header, or a scheme other than Bearer / ApiKey — both are prefix-matched, case-sensitive, one space
invalid_token401The token failed verification, was the wrong kind, or its identity is goneFour messages: token validation failed, MFA-pending tokens cannot be used for protected requests, invalid refresh token, identity no longer valid
token_expired401The token is outside its exp/nbf windowA token carrying no exp is rejected here too — absence is treated as forgery
tenant_mismatch401The token’s tenant claim names a different CPET than the request headersCross-tenant replay. There is no superadmin exception
token_reuse401An already-rotated refresh token was replayedThe whole rotation family descended from that login is revoked; the user must log in again
managed_session403The tenant runs managed sessions, where refresh tokens do not existExchange the opaque session reference instead
invalid_api_key401An ApiKey credential failed shape, secret or expiry validationOne opaque code for every cause. A missing or non-time expiry value counts as expired
apikey_required401POST /auth/token was called without Authorization: ApiKey <key>A Bearer does not satisfy it
apikey_invalid401The presented service key failed validationNever distinguishes unknown key from bad secret from expired key
not_a_service_key403The key is valid but carries no scope in its capabilitiesAn ordinary user key cannot be exchanged for a service token
scope_exceeds_authority403Requested key capabilities exceed the issuer’s ownA CPET scope the issuer cannot confer, a role it does not hold, or a superadmin marker from a non-superadmin issuer
generate_failed500Key material generation failedEntropy source failure
rotate_failed500Refresh-token rotation failedWrite failure retiring the old token or issuing the new one
lookup_failed500The refresh-token read failedMessage: refresh-token lookup failed
mint_failed500The service token could not be signedSame causes as sign_failed
jwks_failed500The tenant’s key set could not be assembledThe tenant’s signing-key table is unreachable, or key material is corrupt

See Tokens and API keys and service tokens. Errors a relying service sees when verifying a token are in Verifying tokens in another service.

CodeStatusMeaningCause
invalid_session401The session reference is invalid, expired, or revokedOne status for all three. Message: session invalid or expired
unknown_session404No session with that control-plane idPOST /internal/session/rotate returns 404 for the condition /exchange returns 401 for
session_limit409The tenant’s concurrent-session limit is reached and its policy is rejectSet the policy to evict the oldest session instead
session_service_unavailable500The tenant resolves to managed sessions but no session service is wired into the processA deployment fault. The service refuses rather than silently downgrading the tenant to a stateless token
session_failed500A session row could not be createdMessages: could not create session, could not open an impersonation session
exchange_failed500The exchange failed for a reason other than an invalid sessionStore or mint failure. Message: could not exchange session
revoke_failed500The session could not be revokedStore failure
rotate_failed500Session rotation failedStore failure
list_failed500Session enumeration failedStore failure

See Sessions.

CodeStatusMeaningCause
v2.requires_admin403The caller holds neither admin nor superadminRole matching is case-insensitive; superadmin supersedes admin
v2.requires_superadmin403The caller lacks superadminStrict — admin does not suffice. The marker is derived from the reserved realm at mint time, so a tenant identity cannot forge it
unauthorized401No verified identity on an impersonation requestCalled without a valid control-plane bearer
not_an_operator403The caller’s token is not the control plane’sMessage: impersonation is a control-plane operation
role_required403The operator holds neither impersonate-readonly nor impersonateNeither role is implied by any other. A read-write request requires the strictly more privileged one
not_granted403The operator has no grant over the target tenantReturned identically when the tenant does not exist, and when the target is outside the operator’s own plane, so the endpoint is not a tenant-existence oracle. Message: you do not manage that tenant
impersonation_not_allowed403The target user is not opted in to impersonationPer-user consent. An operator cannot write that field, so cannot self-authorise
reason_required400The mandatory reason field was emptyNon-optional: it is the only record of why an operator entered an account
bad_tenant400The target tenant is not a full four-segment keyMessage: tenant must be a full customer:product:env:tenant key
no_such_user404No active user with that id in the target tenantWrong id, or a soft-deleted or inactive user
tenant_unavailable503The target tenant’s datastore could not be reachedTarget tenant database down or unprovisioned
lookup_failed500The target user could not be readMessage: could not read the target user

See Authorization model, Superadmin and Impersonation.

Tenant registry and provisioning, under /superadmin/registry/*.

CodeStatusMeaningCause
reserved_or_invalid_name400The tenant slug is empty, is not a valid four-segment key, or uses a reserved scope namesuperadmin and shared are reserved in every segment
superadmin_not_deployed503The control plane is not deployed for this product and environmentThe registry lives in the plane; without it there is nothing to write to
create_failed500The registry row could not be writtenMessage: could not register tenant
list_failed500The registry query failedMessage: could not list tenants
provision_failed500Provisioning failed for an unclassified reasonThe underlying error text is returned verbatim. Errors whose text names a missing registration, an invalid CPET, a reserved scope, or a required field are reclassified to 400 bad_request instead

The tenant-scoped and cross-tenant data plane, /admin/data/* and /superadmin/tenant/:tenant/*, uses the message-only envelope: {"error":"<message>"}, with no code field at all. Status carries the whole classification — 401 for a missing tenant identity, 403 for a missing tenant key in context, 400 for a missing path or query parameter, 404 for an unknown plan, 500 for a failed ledger read or lock operation. The role gates in front of those routes still answer with the code envelope (v2.requires_admin, v2.requires_superadmin).

Cross-tenant route errors:

CodeStatusMeaning
v2.missing_tenant400The :tenant path parameter was empty
v2.invalid_tenant400The :tenant slot could not be composed into a valid key against the caller’s own customer, product and environment
v2.list_tenants_failed500Tenant inventory enumeration failed
v2.invalidate_failed500Tenant cache invalidation failed

Eight routes carry a built-in per-tenant, per-caller-IP token bucket: login, MFA verify, password reset request and confirm, magic request and verify, API-key issue, and service-token exchange. A denial is 429 with Retry-After, X-RateLimit-Limit and X-RateLimit-Remaining headers, and this body:

{"error":"rate_limited","profile":"auth_login","retry_after_seconds":2}
ProfileRoutesCapacityRefill
auth_loginPOST /auth/login300.5/s
auth_mfaPOST /auth/mfa/verify300.5/s
auth_resetPOST /auth/password/reset/request, /confirm150.25/s
auth_magicPOST /auth/magic/request, /verify150.25/s
auth_tokenPOST /auth/token601/s
auth_apikeyPOST /auth/apikey150.25/s

The entity surface has no limiter installed at all — its 429 path is unreachable in this service.

/rest/*, /anon/rest/*, /admin/user/*, /admin/role/* and /schema/* come from the shared entity-surface library and prefix every code with v2..

CodeStatusMeaning
v2.no_tenant403No tenant key in the request context — note the status differs from the auth surface
v2.bad_path400The :entity path segment was empty
v2.entity_not_found404No entity by that name in the composed schema
v2.bad_json400The body was not valid JSON
v2.missing_id400An update without an id in the path must carry the primary key in the body
v2.id_mismatch422The primary key in the body differs from the one in the path; the key is immutable
v2.row_not_found404No row with that id
v2.query_failed403 · 404 · 422 · 500A read failed or was refused — see the classification note below
v2.create_failed403 · 404 · 422 · 500A create failed or was refused
v2.update_failed403 · 404 · 422 · 500An update failed or was refused
v2.delete_failed403 · 404 · 422 · 500A delete failed or was refused
v2.restore_failed403 · 404 · 422 · 500A restore of a soft-deleted row failed or was refused
v2.search_query_failed403 · 404 · 422 · 500A structured search failed or was refused
v2.engine_unavailable500The per-tenant engine could not be resolved; the underlying error text is returned verbatim
v2.search_body_parse_failed400The search body was not decodable
v2.search_text_not_supported501Free-text search needs a search backend this service does not wire — send a structured filter
v2.search_dsl_not_supported501The legacy filter-as-string form is not interpreted — send filter as a JSON object
v2.empty_envelope400The bulk operations envelope was empty
v2.body_read400The bulk request body could not be read
v2.bulk_failed422The bulk transaction rolled back; per-operation results are in details.operations, each failed one tagged v2.op_failed
v2.bad_expression400The ?expression= response-shaping expression failed to compile
v2.expression_runtime500The expression compiled but failed at evaluation
v2.missing_entity400The entity path segment on a schema or export route was empty
v2.export_render_failed500A schema export (GraphQL SDL, OpenAPI, JSON Schema, Zod, CUE) could not be rendered
v2.cache_clear_failed500DELETE /cache could not drop the tenant’s cached resources
v2.file_storage_unconfigured501Multipart entity create needs a file-storage backend this service does not wire
v2.file_downloader_unconfigured501File download needs a downloader this service does not wire
v2.multipart_parse400The multipart body failed to parse
v2.multipart_empty400The multipart form was empty
v2.multipart_bad_payload400The JSON part of the multipart body was invalid
v2.download_needs_id400?download=true requires a row id in the path
v2.missing_field400?field= was absent on a download
v2.field_not_found404?field= names no field on the entity
v2.field_not_file400?field= names a field that is not a file
v2.field_empty404The named field resolved to an empty value
v2.field_unexpected_shape500The named field holds something other than a string reference
v2.download_failed500The downloader errored
  • v2.authz_denied — emitted by an authorizer; the entity surface authorizes through entity access: rules instead, which deny with the codes above.
  • v2.rate_limited — rate limiting is applied at the auth routes listed earlier, not on the entity surface.
  • Codes from the GraphQL, scripted-endpoint, action, state-flow, query-hash and bulk import/export surfaces. Those are library surfaces this service does not mount.