SenseCrypt Docs
Reference

Error codes

The complete SenseCrypt error taxonomy — OAuth/OIDC, SAML, SCIM, and device-plane error codes, their HTTP statuses, the exact envelopes, and what each one means.

This page catalogs the errors SenseCrypt returns across its four protocol surfaces. Many authentication failures are deliberately opaque to prevent enumeration: treat auth failures as opaque, and branch only on the documented codes below — never on the exact wording of a message.

Error envelopes

SenseCrypt uses four distinct error shapes depending on the surface:

OAuth/OIDC protocol envelope — RFC 6749 §5.2, used by the OIDC and SAML protocol endpoints:

{ "error": "invalid_grant", "error_description": "Code invalid, expired, or already used" }

error_description is included only when it adds detail beyond the code.

Management / device envelope — used by the device plane and the management routes, and by a rate-limit 429 on every plane but SCIM, which answers a throttle in its own envelope below (with Retry-After, so a provisioning connector backs off correctly). Where a throttle is decided on Accept — the browser plane, and /authorize and /logout — a browser gets the rate-limited card and a machine caller gets this envelope. Clients branch on error (and kind where present) and must treat unknown values as generic failures:

{ "error": "unauthorized", "message": "unauthorized", "kind": "replayed_request" }

On a 422 the management envelope also carries a details[] array naming each refused field and the rule it broke — see Management API validation errors below.

SCIM envelope — the SCIM-mandated shape:

{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"], "status": "409", "scimType": "uniqueness", "detail": "a user with this userName or email already exists" }

Branded HTML page — the browser-facing endpoints have no RP to hand a machine envelope to, so they answer with a branded HTML card instead of JSON: the /authorize refusal page (400) and the logout confirmation, signed-out and bad-hint pages. The status-driven cards are narrower than that: they are served only on the browser plane — the paths under /v1/browser/ and /v1/link/ — and only when the request's Accept prefers HTML. There you get a uniform refusal card on a 401/403, an expired-link card on a 404, 405 or 422 (unbranded where the path matches no route at all), and a rate-limited card on a 429; there is no JSON body to parse, so branch on the HTTP status. Everywhere else a machine caller keeps the machine envelope, whatever the Accept header saysGET /v1/admin/oidc-apps with Accept: text/html and a stale session still answers 401 with {"error": "unauthorized", "message": …}, and a protocol path still answers the OAuth envelope. The endpoints a browser is sent to are the exception, each in its own way: /authorize's pre-redirect refusal is always the branded 400 page, while a rate-limit trip at /authorize or /logout — and every refusal of a SAML /sso request — answers a card only when Accept prefers HTML, and keeps the machine body otherwise (an RP backend, an SP backend and the conformance suites send */* or JSON, so they keep it). So parse the body on the management and protocol planes rather than branching on Accept.

A note on statuses: among OAuth errors, invalid_client is 401; every other OAuth error (invalid_grant, invalid_request, invalid_target, invalid_scope, unsupported_grant_type, unsupported_response_type, and the CIBA codes) is 400.


OAuth / OIDC errors

Token endpoint (POST /v1/idp/oidc/token)

CodeHTTPMeaning
unsupported_grant_type400grant_type isn't one of authorization_code, refresh_token, client_credentials, or CIBA.
invalid_grant400The authorization code is invalid, expired, or already used. This is deliberately opaque — a cross-tenant client and a failed access-gate check also collapse to this same message (see below).
invalid_grant400PKCE failure — a code_verifier is required but missing, fails the RFC 7636 format check, doesn't match the challenge, or was supplied when the session stored no challenge.
invalid_scope400A requested scope isn't granted to the client (re-checked here and on refresh).
invalid_grant400The authorization code is bound to a DPoP key (dpop_jkt, or a proof on the PAR push) and this request did not prove that same key. Checked before the code is consumed, so it cannot be burned by a party without the key.
invalid_request400The application registered tls_client_certificate_bound_access_tokens and the request carried no client certificate. The certificate is transport state, so this is invalid_request, not a DPoP error.
invalid_grant400A public (SPA) client's refresh token is bound to the key or certificate it was issued with, and another was presented.

client_credentials (M2M):

CodeHTTPMeaning
invalid_client401Missing client_id, unknown app, wrong secret, foreign tenant, or disabled app — all one opaque failure (Unknown client_id or bad client_secret).
unauthorized_client400The application is not opted in to this grant (allow_client_credentials is off — the default). Raised after client authentication, so the flag leaks nothing to an unauthenticated caller.
invalid_target400The requested audience/resource isn't the tenant's Management API identifier.

refresh_token:

CodeHTTPMeaning
invalid_grant400The refresh token is missing, unknown, revoked, expired, or a reuse was detected (which revokes the entire token family).
invalid_scope400The scope re-check on refresh failed.

CIBA (urn:openid:params:grant-type:ciba):

CodeHTTPMeaning
login_required400max_age was exceeded and reauthentication is required — the bound came from max_age at /bc-authorize and the approval is older. This grant only. The code flow decides max_age at /authorize (where the same code is the prompt=none refusal), never at the exchange, so do not wire a retry for it on the authorization_code table above.

The polling codes (authorization_pending, slow_down, expired_token, access_denied) are under CIBA token polling below.

Authorize endpoint (GET /v1/idp/oidc/authorize)

Before a redirect_uri is validated there is nobody to redirect to, so the endpoint answers the branded HTML refusal page described above — HTTP 400, no Location and no JSON body (the page itself names invalid_request). An unknown client_id, a client belonging to another tenant, and a redirect_uri that is not on the application's allow-list all produce the byte-identical page, so a caller cannot tell them apart or enumerate which client_ids exist. The codes below are the failures the endpoint reaches once the redirect_uri is known to be registered:

CodeHTTPMeaning
unsupported_response_type400A response_type the application is not registered for (applications are registered for code unless the implicit or hybrid types were enabled), or one SenseCrypt does not offer (a bare token).
invalid_request400Missing response_type (direct flow); an implicit request without nonce; response_mode=query with an implicit type, or a response_mode SenseCrypt does not offer. A missing redirect_uri is not here: with nothing registered to redirect to, it answers the branded 400 page above.
invalid_target400The client isn't authorized to request the resource audience.
invalid_request_uri400The PAR request_uri is malformed, empty, or already consumed/expired (the last is opaque).
invalid_scope400A requested scope isn't bound to the client.
invalid_request400PKCE required but no code_challenge, or an unsupported code_challenge_method (only S256 is accepted; plain is rejected).
request_not_supported400A request parameter (a JAR request object). This endpoint takes none — push signed request objects to /par. The object's contents are never read, not even to find a redirect target.
request_uri_not_supported400A request_uri that is not one SenseCrypt minted. Only the PAR URN form (urn:ietf:params:oauth:request_uri:…) is accepted.
invalid_request400The application is on the FAPI 2.0/CIBA profile or registered with require_signed_request_object, and this request was not pushed.

How these reach you. An authorization-endpoint error is delivered to a redirect_uri registered for your application — the one you pushed to /par, or the plain redirect_uri parameter on a direct request — as a redirect carrying error and error_description, so the status above is the code the redirect names, not a status your client sees. When no registered return address can be established from the request, there is no redirect at all: SenseCrypt answers its branded refusal page, which is always HTTP 400. Every error redirect also carries the RFC 9207 iss parameter — and if the request asked for a JARM response mode (jwt, query.jwt, fragment.jwt, form_post.jwt), the error arrives as a signed response JWT with error / error_description / state as its claims (and iss inside the JWT), not as plain query parameters. Parse the JWT before looking for an error.

PAR endpoint (POST /v1/idp/oidc/par)

Success is 201. Errors:

CodeHTTPMeaning
unsupported_response_type400A response_type the application is not registered for, or one SenseCrypt does not offer — the same rule as on /authorize (PAR no longer refuses every non-code type outright).
invalid_request400Unknown client for this issuer, a redirect_uri not on the allow-list, an implicit request without nonce, or a response_mode that is unsupported / query with an implicit type.
invalid_target400The client isn't authorized to request the resource audience.
invalid_scope400A requested scope isn't bound to the client.
invalid_request400PKCE gate (as on /authorize; not applied to an implicit push — there is no code to protect).
invalid_client401Client authentication failed (see Client authentication).
invalid_request400A request_uri was included — RFC 9126 §2.1 forbids it in a pushed request; this endpoint is what mints one.
invalid_request400The client is registered with require_signed_request_object and the push carried no request.
invalid_request400A dpop_jkt parameter and a DPoP proof were both present and named different keys.
invalid_request_object400The signed request object did not verify — a disallowed alg or typ, no usable registered key, a bad signature, an aud that isn't the issuer, a missing/out-of-window nbf or exp, a nested request/request_uri, or an iss/client_id that isn't the authenticated client. One fixed description for every cause — the reason is logged, not returned.

Introspection (POST /v1/idp/oidc/introspect)

Per RFC 7662, an unverifiable, expired, revoked, wrong-audience, or unknown token returns {"active": false} with HTTP 200 — never an error. True errors:

CodeHTTPMeaning
invalid_client401Client authentication failed.
invalid_client401Introspection requires a confidential client — a public/SPA client is rejected.
invalid_request400The token parameter is missing.

Revocation (POST /v1/idp/oidc/revoke)

Per RFC 7009, an invalid/unknown/already-revoked token returns an empty 200 (no validity oracle). But client authentication still gates the call:

CodeHTTPMeaning
invalid_client401Client authentication failed (cross-tenant client).
invalid_request400The token parameter is missing.

UserInfo (GET or POST /v1/idp/oidc/userinfo)

Failures use the OAuth/OIDC envelope ({"error": …, "error_description": …}), not the management one. A failure about the access token — none presented, or one that does not verify — is 401 with an RFC 6750 WWW-Authenticate challenge:

RequestWWW-Authenticate
A token presented with the Bearer scheme (or as the access_token form parameter)Bearer realm="userinfo", error="invalid_token", error_description="…"
A token presented with the DPoP scheme, or a DPoP-bound token presented as a bearer token (RFC 9449 §7.2)DPoP algs="ES256 PS256", error="…" — the code is invalid_token, invalid_dpop_proof, or use_dpop_nonce
No token at allboth challenges, neither carrying an error (RFC 6750 §3.1): Bearer realm="userinfo", DPoP algs="ES256 PS256" — the body's error is invalid_request

invalid_token covers: a bad/unverifiable access token, a token whose token_use isn't access, an expired or revoked token, a client-credentials token (there is no End-User behind it), and a sub that resolves to no active user. A use_dpop_nonce response also carries a DPoP-Nonce header to retry with. Branch on the body's error and the challenge scheme — never on error_description.

Two failures land before the token is read, and they are 400 invalid_request with no WWW-Authenticate header: an Authorization header whose scheme is neither Bearer nor DPoP (or one of those two schemes carrying an empty token), and a request that presents the token by both methods at once — the header and the form parameter, where RFC 6750 §2 allows exactly one. The endpoint is rate-limited per client IP as well, so a flood answers 429 — the one failure here that uses the management envelope.

RP-Initiated Logout (GET or POST /v1/idp/oidc/logout)

An id_token_hint is not required: without one the endpoint shows a confirmation page (HTTP 200) and, once confirmed, signs the user out on its own signed-out page. Failures are 400 HTML pages with error: "invalid_request", and nothing is signed out. Triggers: a hint that does not verify as one this issuer signed; a hint that is not an id_token — an access token or a logout token is refused even though it verifies against the same JWKS; a client_id that does not match the hint's aud; a hint whose application is no longer registered; a post_logout_redirect_uri with neither an id_token_hint nor a client_id to validate it against; or a client_id / post_logout_redirect_uri pair that is unknown or not on the client's dedicated post-logout allow-list (both answer with one identical page, so a client_id cannot be enumerated).

CIBA backchannel start (POST /v1/idp/oidc/bc-authorize)

CodeHTTPMeaning
invalid_client401Unknown client for this issuer.
unauthorized_client400The client isn't registered for CIBA.
invalid_request400scope must include openid; exactly one hint is required; a client_notification_token is required for ping/push delivery and, when supplied, must be a bearer token (RFC 6750 b64token — letters, digits, -._~+/, trailing =; no spaces or line breaks) of at most 1024 characters; or a signed authentication request (request) does not verify, is unsigned where one is required, or is presented a second time (its jti is single-use).
invalid_binding_message400binding_message is longer than 140 displayable characters (counted after Unicode NFC normalisation) or contains characters that cannot be displayed as plain text: control characters, line/paragraph separators, or bidirectional formatting controls. Unassigned code points (a newer emoji, say) are accepted.
invalid_target400The client isn't authorized to request the resource audience.
unknown_user_id400The hint didn't resolve to an eligible user. Deliberately the same whether the user is unknown or merely ungated.
invalid_scope400A requested scope isn't bound to the client.

invalid_binding_message is the CIBA Core §13 code — an over-length or non-displayable binding_message is not reported as invalid_request. Earlier releases capped the message at 512 characters and used invalid_request; see the changelog.

CIBA callback contract (ping and push)

For ping and push clients SenseCrypt POSTs to your registered notification endpoint with Authorization: Bearer <client_notification_token>. How your endpoint answers decides what happens next:

Your endpoint answersResult
2xxDelivered. A ping client then collects tokens at the token endpoint; a push client has its tokens (and the auth_req_id is spent).
3xxFinal, never retried, never followed. A redirect could carry the bearer to another host, so it is treated as a refusal.
4xxFinal, never retried (CIBA §10.2). For a push client the approved authorization is lost — the tokens had nowhere to go and push clients may not poll; start a new request. A ping client can still collect at the token endpoint.
5xx or a transport failureRetried, up to 5 attempts in total; after the fifth failure the notification is abandoned as above.

Failed requests are notified too: a ping client receives the same { "auth_req_id" } body and learns access_denied (or expired_token) at the token endpoint; a push client receives { "auth_req_id", "error": "access_denied" | "expired_token", "error_description" }. An approval that lands in the last seconds of the request window is still delivered (for a ping client the token endpoint then answers expired_token).

CIBA token polling (at POST /v1/idp/oidc/token)

These are returned in the RFC 6749 shape at HTTP 400. The first two are normal poll responses, not terminal failures:

CodeHTTPMeaning
authorization_pending400The user hasn't approved yet. Keep polling at the advertised interval.
slow_down400You're polling faster than the advertised interval. Back off.
expired_token400The auth_req_id has expired.
access_denied400The user denied the request.
unauthorized_client400A push-delivery client must not poll the token endpoint.
invalid_grant400Unknown or already-used auth_req_id.

Client authentication errors

Across /token, /par, /introspect, /revoke, and /bc-authorize, all client-authentication failures collapse to invalid_client (401) so the endpoint can't leak which side failed. This covers: unknown client_id, wrong secret, a malformed Authorization header, multiple auth mechanisms, an auth method the client isn't registered for, a public/SPA client presenting a credential, any client_assertion verification failure (client_secret_jwt / private_key_jwt), and every mutual TLS failure (tls_client_auth / self_signed_tls_client_auth): no certificate on the request, a chain that fails path validation against the registered anchors, a subject DN that doesn't match, a presented certificate that isn't the registered one, or one outside its validity window. A mutual TLS request must also carry client_id in the body — the certificate is the credential, but it is not how the client is located.

DPoP errors

DPoP failures have two shapes depending on which side of the issuer you are talking to. Every refusal carries one fixed description (The DPoP proof is invalid); the actual reason is logged, not returned.

CodeHTTPWhereMeaning
invalid_dpop_proof400/token, /par, /introspectThe proof failed a check: more than one DPoP header, a typ that isn't dpop+jwt, an alg outside dpop_signing_alg_values_supported, a missing or private-key-bearing jwk header, a bad signature, a missing jti/htm/htu/iat, an htm/htu that doesn't match the request, a stale or future iat, or a replayed jti.
invalid_dpop_proof401/userinfoThe same checks at a protected resource, plus an ath that doesn't match the presented access token, or no proof at all for a DPoP-bound token. Carries WWW-Authenticate: DPoP algs="ES256 PS256", error="invalid_dpop_proof", error_description="The DPoP proof is invalid".
use_dpop_nonce400/tokenA server nonce is required — every FAPI client's proof must carry one — or the one supplied is stale or not ours. The response carries a DPoP-Nonce header; retry with that value in the proof's nonce claim.
use_dpop_nonce400/par, /introspectA nonce is never required at these endpoints, but one that is present must be ours and fresh. Same DPoP-Nonce retry.
use_dpop_nonce401/userinfoAs at /token (required for a FAPI client), with both WWW-Authenticate: DPoP … and DPoP-Nonce.
invalid_token401/userinfoA DPoP-bound token presented with the Bearer scheme, an unbound token presented with the DPoP scheme, a proof from a key other than the token's cnf.jkt, a certificate-bound token whose certificate was not presented, or a cnf shape SenseCrypt cannot verify. Carries the WWW-Authenticate challenge for the scheme in play.

Treat use_dpop_nonce as a retry, not a failure: read the DPoP-Nonce header, put it in the next proof, and repeat the request once.


SAML errors

SAML protocol endpoints use the OAuth/OIDC envelope shape. Common codes:

CodeHTTPMeaning
unauthorized401Unknown SP entityID, IdP-initiated SSO not allowed for the SP, or a failed delivery gate — opaque, to prevent entityID enumeration.
invalid_request400Invalid SAMLRequest (parse failure), request-signature verification failure, an unregistered AssertionConsumerServiceURL, an unsatisfiable NameID policy (carrying the SAML status URN urn:oasis:names:tc:SAML:2.0:status:InvalidNameIDPolicy), or neither SAMLRequest nor sp present.
invalid_grant400The post-back session has no authorized subject.
not_found404The SAML session isn't ready or already delivered, or ?kid= names no published certificate.

Single Logout (SLO) is not implemented — there is no SLO endpoint or LogoutRequest handling, and no IdP-side SSO session to propagate a logout from. Request-signature verification and assertion encryption require RSA SP keys.


SCIM errors

SCIM uses the envelope shown above. The scimType field is present on 400-class errors; auth failures (401/403) carry only a detail message.

scimTypeHTTPWhen
invalidFilter400An unknown attribute path, unsupported operator, or malformed/over-long filter expression. Never a silent full-directory listing.
invalidValue400A missing required field on create (userName + a primary email), an illegal remove (of active, userName, or emails), leaving a user with no primary email, attributes and excludedAttributes supplied together, a bad date in a filter, or a malformed bulk request.
invalidSyntax400An unsupported PATCH op (not add/replace/remove).
invalidPath400An unsupported PATCH value-path, or a bulk path/id problem.
noTarget400A PATCH value-path remove/replace matched no value.
uniqueness409A userName/email collision (users) or a displayName collision (groups).
tooMany413More than 1000 operations in a bulk request.
tooLarge413A bulk payload over 1 MiB. (Note: tooLarge is a SenseCrypt extension, not a standard RFC 7644 scimType.)

Errors without a scimType:

HTTPWhen
401Missing or invalid bearer token — detail names the reason (e.g. Admin access token required).
403The token lacks the scim:read / scim:write capability for the method — detail is missing capability: scim:read (or scim:write).
404User/Group/Schema not found. (A console-managed group is a deliberate 404 to a SCIM caller — no leak.)
412A conditional write whose If-Match ETag doesn't match the current version (resource has changed).

A remove on members without a target is rejected as 400 invalidValue — it is never treated as a clear-all.

Supported filter operators: eq ne co sw ew gt ge lt le pr, plus and/or/not, parenthesized grouping, and emails[...] value paths. Multi-valued complex attributes support presence (pr) only.


Device-plane errors

Every phone-originated request is a signed request. Device errors use the management envelope; the mobile SDK branches on error and, for unauthorized, on the kind field. All of these are 401.

errorkindMeaning
signature_requiredOne or more of the required signed-request headers (X-Device-Id, X-Device-Key-Id, X-Timestamp, X-Signature) is missing.
unauthorizedbad_signed_headersA device/key id isn't a valid UUID, the timestamp isn't parseable, or the signature isn't valid base64.
unauthorizedclock_skewThe X-Timestamp is outside the allowed skew window. Recoverable — retry after an NTP sync.
unauthorizeddevice_key_revokedThe device key is unknown/revoked, or the user is suspended or deleted. Terminal for the SDK — wipe and re-enroll.
unauthorizedunsupported_key_algThe key's registered algorithm isn't verifiable by this build.
unauthorizedbad_signatureThe signature doesn't verify (against the current key or, during a rotation overlap, the previous key).
unauthorizedreplayed_requestThis exact signed request was already used — a verbatim replay.

Protocol version floor

CodeHTTPMeaning
upgrade_required426The X-Protocol-Version header is absent, non-integer, or below the supported floor. The body carries min_supported and current; the SDK maps this to a force-update state.

Device key rotation (/v1/device/keys/rotate/*)

These are 400 with a specific error code: no_keys_to_rotate, bad_nonce, no_rotation_in_progress, rotation_nonce_expired, rotation_nonce_mismatch, rotation_set_mismatch, bad_new_key_bytes, and bad_new_key_signature. Each message is self-describing.

PIN and registration

CodeHTTPMeaning
pin_invalid401Wrong PIN. The body carries attempts_left.
pin_locked403PIN attempts exhausted, or the code is already locked.
pin_expired410No active PIN, or the PIN's TTL has expired.
cooldown_active429A PIN-resend cooldown is in effect. Carries a Retry-After header.
token_invalid401A registration/signup token is unknown, consumed, or unverified.
token_expired410A registration/signup token is past its TTL.
email_exists409Self-signup on an already-enrolled email.

App attestation

When attestation is enforced, every attestation failure returns 403 with the machine error field forbidden and the message attestation_failed. The specific subreason (nonce mismatch, counter replay, integrity verdict, and so on) is intended for server logs; treat the client-visible failure as opaque and branch only on error: "forbidden" / message: "attestation_failed".


Management API validation errors

Every request body on the Management API (/v1/admin/*) is validated the same way, and every field-level refusal — a malformed value, a missing required field, an unknown or immutable field, or a registration rule such as the FAPI 2.0/CIBA rules on /v1/admin/oidc-apps — is answered with 422 in the management envelope plus a details[] array. The shape is the same on create (POST) and on update (PATCH/PUT): the same rule produces the same code on the same field whichever way it is reached.

{
  "error": "validation_error",
  "message": "Request validation failed",
  "details": [
    {
      "loc": ["body", "jwks"],
      "field": "jwks",
      "msg": "A FAPI application with CIBA signs every backchannel request; register its request-signing keys (jwks or jwks_uri) so they can be verified",
      "type": "fapi_ciba.requires_signing_keys",
      "code": "fapi_ciba.requires_signing_keys"
    }
  ]
}
  • error is always validation_error and message is always Request validation failed — branch on details[], never on the top-level message.
  • details[].field is the request-body field the entry is about. For a rule that spans several fields it is the first offending one (the loc is rewritten to ["body", <field>] too, so a client that only knows loc places it the same way).
  • details[].code is the stable machine code for the rule — either SenseCrypt's own (fapi.requires_pkce, fapi_ciba.requires_signing_keys, jwks.private_material, immutable_field, …) or the validator's generic one (missing, string_too_long, extra_forbidden, …). type repeats it. Branch on code; msg is the server's fallback wording and may change.
  • Secrets never round-trip: the submitted value is not echoed back in any entry.

Three codes apply to every body, not to one field's rule:

codeMeaning
extra_forbiddenThe body names a field this route does not have. Unknown fields are refused, not ignored — a typo'd key or a field that belongs to another route is a 422 naming it, never a silent 200 with nothing changed.
immutable_fieldThe field exists but is fixed when the resource is created — client_type, token_endpoint_auth_method or billing_model on an application, slug or fapi_profile on a tenant. Create a new resource to use another value; the request is refused before anything is read or written.
field_movedThe field exists but is set on another route now; msg names where. Today: op_session_ttl_seconds on PATCH /v1/admin/tenants/{id} — it moved to PATCH /v1/admin/issuer (see the changelog).

Application registration rule codes

These are the details[].code values the OIDC-application routes (POST / PATCH /v1/admin/oidc-apps) raise for the protocol rules — the security profile, client keys, mutual TLS, signed requests, CIBA and back-channel logout. Grouped by prefix; the same code is raised on create and on update.

fapi.* — the FAPI 2.0/CIBA profile

codeRefused because
fapi.requires_confidential_clientclient_type is not confidential.
fapi.requires_pkcerequire_pkce is not true.
fapi.requires_key_or_certificate_authtoken_endpoint_auth_method is not private_key_jwt, tls_client_auth or self_signed_tls_client_auth.
fapi.requires_sender_constrained_tokensNeither dpop_bound_access_tokens nor tls_client_certificate_bound_access_tokens is on.
fapi.forbids_rs256id_token_signed_response_alg is RS256.
fapi.requires_code_onlyresponse_types is not exactly ["code"].
fapi.requires_https_redirect_urisAn allowed_redirect_uris entry is http and not a loopback address. msg names the offending URI.
fapi.forbids_ciba_pushbackchannel_token_delivery_mode is push.
fapi.inherited_from_tenantThe tenant is a FAPI 2.0/CIBA tenant; its applications cannot set another profile or clear it.

fapi_ciba.* — a FAPI application with a CIBA delivery mode

codeRefused because
fapi_ciba.requires_signing_keysNo jwks / jwks_uri on file, so its signed backchannel requests could not be verified.
fapi_ciba.requires_certificate_bindingtls_client_certificate_bound_access_tokens is off.

jar.*signed request objects

codeRefused because
jar.requires_keysrequire_signed_request_object is on with no jwks / jwks_uri to verify against.

jwks.* — registered client keys

codeRefused because
jwks.requires_keyed_methodjwks / jwks_uri were sent for a method that registers none — only private_key_jwt and the two mutual TLS methods may carry them.
jwks.exactly_one_sourceA private_key_jwt application must register exactly one of jwks / jwks_uri.
jwks.one_sourceA mutual TLS application may register one of them, or neither — not both.
jwks.unreachableThe jwks_uri could not be fetched, or did not return a valid JWK Set, when it was saved. One constant sentence; the cause is logged.
jwks.invalidThe inline jwks is not a valid JWK Set.
jwks.too_largeLarger than 32 KB.
jwks.too_many_keysMore than 16 keys.
jwks.kid_requiredA key has no kid.
jwks.duplicate_kidTwo keys share a kid.
jwks.private_materialA key carries private members (d, p, q, dp, dq, qi, oth, k). Private material is never stored.
jwks.alg_mismatchA key's alg isn't allowed for its ktyES256/ES384 for EC, PS256/RS256 for RSA.

mtls.*mutual TLS

codeRefused because
mtls.listener_unavailableThis deployment runs no mutual TLS listener (its discovery publishes no mtls_endpoint_aliases), so neither mutual TLS client authentication nor tls_client_certificate_bound_access_tokens can be registered.
mtls.subject_dn_requiredtls_client_auth without tls_client_auth_subject_dn.
mtls.trust_anchors_requiredtls_client_auth without tls_client_auth_ca_pem.
mtls.subject_dn_invalidtls_client_auth_subject_dn is not parseable as an RFC 4514 distinguished name.
mtls.certificate_requiredself_signed_tls_client_auth without tls_client_certificate_pem.
mtls.certificate_not_applicabletls_client_certificate_pem sent for tls_client_auth.
mtls.pki_fields_not_applicabletls_client_auth_subject_dn / tls_client_auth_ca_pem sent for self_signed_tls_client_auth.
mtls.fields_require_mtls_methodAny mutual TLS registration field sent for a method that is not a mutual TLS one.

ciba.*CIBA registration

codeRefused because
ciba.requires_client_authCIBA is not available to a spa (public) client.
ciba.endpoint_requiredping/push delivery without backchannel_client_notification_endpoint.
ciba.user_code_unsupportedbackchannel_user_code_parameter was opted into. This issuer is passwordless and has no per-user code to verify, so the opt-in is refused rather than silently ignored.

backchannel_logout_initiators.*back-channel logout

codeRefused because
backchannel_logout_initiators.invalidA value outside the vocabulary (rp-logout, session-revoked, access-revoked, account-deleted, email-identifier-changed, session-expired).
backchannel_logout_initiators.requires_rp_logoutThe list does not contain rp-logout, which is always on. Refused rather than silently repaired.

response_types.*, redirect_uris.*, url.* — request shape

codeRefused because
response_types.invalidA response_types entry this issuer does not offer (a bare token, an unknown word).
redirect_uris.requiredallowed_redirect_uris is empty — at least one is required.
redirect_uris.invalidA allowed_redirect_uris or post_logout_redirect_uris entry is not a usable absolute URL. msg names the entry and the reason.
url.https_requiredA URL-shaped field is not an absolute https URL — jwks_uri, backchannel_client_notification_endpoint, backchannel_logout_uri, terms_url, privacy_url.
url.invalidterms_url / privacy_url is not a usable absolute URL.
url.private_hostA URL-shaped field's host is private, loopback, link-local or otherwise reserved. SenseCrypt calls these hosts server-side, so an internal target is never stored.

Other prefixes on the same routes follow the same shape and carry a self-describing msg: spa.* (what a public client may not change — PKCE, refresh rotation, certificate binding, credentials, keys), refresh.* (rotation needs a bounded family lifetime and a non-zero overlap), signup.* (allow_signup and signup_group_id travel together), cert.* and anchors.* (a pasted client certificate or trust-anchor bundle that is too large, unparseable, expired, not a CA, on too small a key, or not usable for TLS client authentication), metadata.* (client_metadata key, depth and size caps), list.* and string_too_long (list and string bounds), and field.replaced (a field superseded by another — msg names it).

Statuses that are not field refusals keep their own codes and shapes: a clash with an existing resource is 409 (slug_taken, tenant_name_exists), a missing capability 403, an unknown id in another tenant 404, and a billing block 402 payment_required. Earlier releases answered many of the application registration rules as 400 bad_request with a prose message; those are now 422 entries with a code — see the changelog.


Rate limiting (all surfaces)

Exceeding a rate limit returns 429 with error: "rate_limited", a Retry-After header, and a retry_after_seconds field in the body. See Rate limits for the full policy.


On this page