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 three 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, UserInfo, and management routes. Clients branch on error (and kind where present) and must treat unknown values as generic failures:
{ "error": "unauthorized", "message": "unauthorized", "kind": "replayed_request" }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" }A note on statuses: among OAuth errors,
invalid_clientis401; every other OAuth error (invalid_grant,invalid_request,invalid_target,invalid_scope,unsupported_grant_type,unsupported_response_type, and the CIBA codes) is400.
OAuth / OIDC errors
Token endpoint (POST /v1/idp/oidc/token)
| Code | HTTP | Meaning |
|---|---|---|
unsupported_grant_type | 400 | grant_type isn't one of authorization_code, refresh_token, client_credentials, or CIBA. |
invalid_grant | 400 | The 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_grant | 400 | PKCE 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_scope | 400 | A requested scope isn't granted to the client (re-checked here and on refresh). |
login_required | 400 | max_age was exceeded and reauthentication is required. |
client_credentials (M2M):
| Code | HTTP | Meaning |
|---|---|---|
invalid_client | 401 | Missing client_id, unknown app, wrong secret, foreign tenant, or disabled app — all one opaque failure (unknown client_id or bad client_secret). |
invalid_target | 400 | The requested audience/resource isn't the tenant's Management API identifier. |
refresh_token:
| Code | HTTP | Meaning |
|---|---|---|
invalid_grant | 400 | The refresh token is missing, unknown, revoked, expired, or a reuse was detected (which revokes the entire token family). |
invalid_scope | 400 | The scope re-check on refresh failed. |
Authorize endpoint (GET /v1/idp/oidc/authorize)
| Code | HTTP | Meaning |
|---|---|---|
unauthorized | 401 | Unknown client, cross-tenant client, or a redirect_uri not on the allow-list — deliberately identical to prevent client enumeration. (This uses the management-envelope unauthorized, not an OAuth code.) |
unsupported_response_type | 400 | response_type isn't code. |
invalid_request | 400 | Missing response_type or redirect_uri (direct flow). |
invalid_target | 400 | The client isn't authorized to request the resource audience. |
invalid_request_uri | 400 | The PAR request_uri is malformed, empty, or already consumed/expired (the last is opaque). |
invalid_scope | 400 | A requested scope isn't bound to the client. |
invalid_request | 400 | PKCE required but no code_challenge, or an unsupported code_challenge_method (only S256 is accepted; plain is rejected). |
PAR endpoint (POST /v1/idp/oidc/par)
Success is 201. Errors:
| Code | HTTP | Meaning |
|---|---|---|
unsupported_response_type | 400 | Only response_type=code is supported. |
invalid_request | 400 | Unknown client for this issuer, or a redirect_uri not on the allow-list. |
invalid_target | 400 | The client isn't authorized to request the resource audience. |
invalid_scope | 400 | A requested scope isn't bound to the client. |
invalid_request | 400 | PKCE gate (as on /authorize). |
invalid_client | 401 | Client authentication failed (see Client authentication). |
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:
| Code | HTTP | Meaning |
|---|---|---|
invalid_client | 401 | Client authentication failed. |
invalid_client | 401 | Introspection requires a confidential client — a public/SPA client is rejected. |
invalid_request | 400 | The 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:
| Code | HTTP | Meaning |
|---|---|---|
invalid_client | 401 | Client authentication failed (cross-tenant client). |
invalid_request | 400 | The token parameter is missing. |
UserInfo (GET /v1/idp/oidc/userinfo)
All failures are 401 and use the management envelope ({"error": "unauthorized", ...}, with a reason field) — note this differs from the RFC 6749 shape used elsewhere, and no WWW-Authenticate header is sent. Triggers include: no bearer token, a bad/invalid access token, a token whose token_use isn't access, a revoked token, or a sub that resolves to no active user.
RP-Initiated Logout (GET /v1/idp/oidc/logout)
All failures are 400 with error: "invalid_request". An id_token_hint is required (there's no server-side SSO cookie). Other triggers: an invalid hint, a client_id that doesn't match the hint's aud, an unresolvable client, or a post_logout_redirect_uri not on the client's dedicated allow-list.
CIBA backchannel start (POST /v1/idp/oidc/bc-authorize)
| Code | HTTP | Meaning |
|---|---|---|
invalid_client | 401 | Unknown client for this issuer. |
unauthorized_client | 400 | The client isn't registered for CIBA. |
invalid_request | 400 | scope must include openid; exactly one hint is required; binding_message must not exceed 512 characters; or a client_notification_token is required for ping/push delivery. |
invalid_target | 400 | The client isn't authorized to request the resource audience. |
unknown_user_id | 400 | The hint didn't resolve to an eligible user. Deliberately the same whether the user is unknown or merely ungated. |
invalid_scope | 400 | A requested scope isn't bound to the client. |
There is no
invalid_binding_messagecode — an over-length binding message isinvalid_request.
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:
| Code | HTTP | Meaning |
|---|---|---|
authorization_pending | 400 | The user hasn't approved yet. Keep polling at the advertised interval. |
slow_down | 400 | You're polling faster than the advertised interval. Back off. |
expired_token | 400 | The auth_req_id has expired. |
access_denied | 400 | The user denied the request. |
unauthorized_client | 400 | A push-delivery client must not poll the token endpoint. |
invalid_grant | 400 | Unknown 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, and any client_assertion verification failure (client_secret_jwt / private_key_jwt).
SAML errors
SAML protocol endpoints use the OAuth/OIDC envelope shape. Common codes:
| Code | HTTP | Meaning |
|---|---|---|
unauthorized | 401 | Unknown SP entityID, IdP-initiated SSO not allowed for the SP, or a failed delivery gate — opaque, to prevent entityID enumeration. |
invalid_request | 400 | Invalid 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_grant | 400 | The post-back session has no authorized subject. |
not_found | 404 | The 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
LogoutRequesthandling, 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.
scimType | HTTP | When |
|---|---|---|
invalidFilter | 400 | An unknown attribute path, unsupported operator, or malformed/over-long filter expression. Never a silent full-directory listing. |
invalidValue | 400 | A 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. |
invalidSyntax | 400 | An unsupported PATCH op (not add/replace/remove). |
invalidPath | 400 | An unsupported PATCH value-path, or a bulk path/id problem. |
noTarget | 400 | A PATCH value-path remove/replace matched no value. |
uniqueness | 409 | A userName/email collision (users) or a displayName collision (groups). |
tooMany | 413 | More than 1000 operations in a bulk request. |
tooLarge | 413 | A bulk payload over 1 MiB. (Note: tooLarge is a SenseCrypt extension, not a standard RFC 7644 scimType.) |
Errors without a scimType:
| HTTP | When |
|---|---|
| 401 | Missing or invalid bearer token — detail names the reason (e.g. admin access token required). |
| 403 | The token lacks the scim:read / scim:write capability for the method — detail is missing capability: scim:read (or scim:write). |
| 404 | User/Group/Schema not found. (A console-managed group is a deliberate 404 to a SCIM caller — no leak.) |
| 412 | A conditional write whose If-Match ETag doesn't match the current version (resource has changed). |
A
removeonmemberswithout a target is rejected as400 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.
error | kind | Meaning |
|---|---|---|
signature_required | — | One or more of the required signed-request headers (X-Device-Id, X-Device-Key-Id, X-Timestamp, X-Signature) is missing. |
unauthorized | bad_signed_headers | A device/key id isn't a valid UUID, the timestamp isn't parseable, or the signature isn't valid base64. |
unauthorized | clock_skew | The X-Timestamp is outside the allowed skew window. Recoverable — retry after an NTP sync. |
unauthorized | device_key_revoked | The device key is unknown/revoked, or the user is suspended or deleted. Terminal for the SDK — wipe and re-enroll. |
unauthorized | unsupported_key_alg | The key's registered algorithm isn't verifiable by this build. |
unauthorized | bad_signature | The signature doesn't verify (against the current key or, during a rotation overlap, the previous key). |
unauthorized | replayed_request | This exact signed request was already used — a verbatim replay. |
Protocol version floor
| Code | HTTP | Meaning |
|---|---|---|
upgrade_required | 426 | The 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
| Code | HTTP | Meaning |
|---|---|---|
pin_invalid | 401 | Wrong PIN. The body carries attempts_left. |
pin_locked | 403 | PIN attempts exhausted, or the code is already locked. |
pin_expired | 410 | No active PIN, or the PIN's TTL has expired. |
cooldown_active | 429 | A PIN-resend cooldown is in effect. Carries a Retry-After header. |
token_invalid | 401 | A registration/signup token is unknown, consumed, or unverified. |
token_expired | 410 | A registration/signup token is past its TTL. |
email_exists | 409 | Self-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".
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.
Related
- Rate limits — the throttling policy and the
429shape. - Troubleshooting — mapping common errors to fixes.
- Glossary — terms used above.
Reference
Reference material for SenseCrypt — the error-code taxonomy, the rate-limit policy, token validation, CORS, a glossary, and a troubleshooting guide.
Rate limits
SenseCrypt rate-limit policy for the protocol endpoints you integrate against — the per-action windows and ceilings you need to design retries, the 429 response shape with Retry-After, how the client IP is determined, and how to handle limits.