Skip to Content
DocsErrors

Errors & troubleshooting

Every Valyd error is machine-readable: a stable code string plus an HTTP status, and every response carries an X-Request-Id header — quote it to support (never API keys or tokens). Read the status first (it tells you the category), then the code (the exact cause).

Contacting support

Include: the X-Request-Id header value, the session/event id if relevant, a timestamp, and the endpoint called. Never send API keys, tokens, or the person’s identity data — support will never ask for them.

1. Error shapes

Valyd uses two error shapes: the OIDC protocol endpoints speak standard OAuth 2.0 / OpenID Connect errors, and every other Valyd API uses the Valyd envelope.

OIDC protocol errors

Since 2026-09-18 the OIDC protocol endpoints return standard errors (RFC 6749 §5.2, RFC 6750 §3, RFC 7591 §3.2.2) — error is a string, there is no success/data wrapper, and responses carry Cache-Control: no-store:

Token (POST /api/auth/oidc/token), UserInfo (/api/auth/oidc/userinfo) and Dynamic Client Registration (POST /api/auth/oidc/register):

{ "error": "invalid_grant", "error_description": "Invalid or expired authorization code" }
Endpointerror values
Tokeninvalid_request, invalid_client (401; WWW-Authenticate: Basic if you used HTTP Basic), invalid_grant, invalid_scope, unsupported_grant_type
UserInfoinvalid_token (401, WWW-Authenticate: Bearer realm="valyd", error="invalid_token", …), insufficient_scope (403 — token lacks openid), invalid_request (token sent in both header and body)
Registrationinvalid_redirect_uri, invalid_client_metadata, invalid_token, access_denied

Authorization (/api/auth/oidc/authorize): once your client_id and redirect_uri are verified, every error is sent back to your redirect_uri (RFC 6749 §4.1.2.1 + RFC 9207):

https://yourapp.com/callback?error=access_denied&error_description=The+user+denied+the+request&state=YOUR_STATE&iss=https%3A%2F%2Fidp.valyd.id

error is one of invalid_request (e.g. missing PKCE code_challenge), unsupported_response_type, invalid_scope, invalid_target (RFC 8707 resource not allowed), access_denied (user pressed Cancel, or a private org app the user isn’t assigned to), login_required / consent_required (prompt=none couldn’t be satisfied), request_not_supported, request_uri_not_supported, or server_error. An unknown client_id or an unregistered redirect_uri is the only case that shows an HTML error page on Valyd instead of redirecting (redirecting there would be an open redirect).

Valyd envelope (all other APIs)

Login / account APIs (/api/auth/... outside /api/auth/oidc/*), the Verify API (/api/v2/...), the hosted-flow, verifications, agent and internal endpoints keep the envelope:

{ "success": false, "data": [], "error": { "code": "invalid_token", "message": "Session expired. Please login again." } }

Verification API (/api/v2/...) errors carry the code in error with check context where relevant.

2. What the HTTP status means

StatusCategoryTypical causes
400Bad requestMissing/malformed parameter, invalid scope, expired code
401Not authenticatedWrong client_id/secret, missing/expired token, bad API key
402Payment requiredWallet balance / quota exhausted — top up in the console
403Not allowedMissing scope, app not permitted for this user, consent declined
404Not foundWrong workflow/session/resource id, or it belongs to another project
409ConflictDuplicate — e.g. the member already exists on your roster
410GoneRemoved legacy endpoint (TPSSO) — migrate to /api/auth/oidc/*
422Rejected inputImage/selfie quality, face rescan required, validation failure
429Rate limitedToo many requests — back off and retry after the window
5xxOur sideRetry with backoff; if it persists, contact support

3. Complete code catalog

Generated from the API source — 177 codes. Do not edit by hand; run node scripts/gen-error-catalog.mjs.

CodeHTTPMeaning / fix
access_denied403, redirectThe user declined, or the app is not permitted for this account (OIDC: sent to your redirect_uri as ?error=access_denied).
account_dob_unavailable422This Valyd account has no verified date of birth.
account_not_found404No Valyd account for this valyd_id
activation_failed500, 503Activation failed.
already_exists409E2E encryption is already active for this device
already_paired409This device is already paired and has E2E keys
app_default_protected422The default app cannot be deleted. Set another app as default first.
back_image_not_found404Back image not found
back_required400, 422Please also capture the BACK of your
cache_clear_failed400Failed to clear caches:
callback_not_allowed422callback must exactly match an active, approved HTTPS webhook destination for this project.
challenge_expired400The liveness instruction expired — please try again.
challenge_required400This device must complete challenge flow before login. Call POST /api/auth/face/challenge, then send c, d, s, and face_image.
client_not_found404Client not found
code_expired410The one-time code has expired
consent_requiredredirectprompt=none but this app has no prior grant for these scopes — redirect again without prompt=none.
database_error500Failed to store device keys
decrypt_failed500Could not open the managed payload
deletion_failed500Could not delete your account. Please try again.
denied403User denied the request
device_already_paired409This device is already paired
device_key_unknown410This browser’s device key is no longer registered. Please register this device again.
device_locked403This device is linked with another account.
device_mismatch400device_id does not match
device_not_found404Device not registered or not linked to a user
device_revoked401This device has been removed. Please sign in again.
dob_unreadable—
document_not_authentic—
document_type_required422Please select your document type first.
duplicate_image422This is the same photo you used for the front. Please flip the card over and capture the BACK.
e2e_already_initialized409This account already has encryption set up on another device. Pair or recover this device to get the same keys.
endpoint_not_found—
endpoint_removed410You are calling a removed legacy TPSSO endpoint — migrate to /api/auth/oidc/*.
engine_unreachable503Verification engine did not respond. Please retry.
enroll_failed422, 502Could not enrol the face
expired410Request expired
face_feature_extraction_failed400Could not read your face clearly. Please try again.
face_match_failed500Face match failed
face_match_unavailable502Could not verify your selfie right now. Please try again.
face_mismatch403Face did not match
face_not_enrolled409No face is enrolled on this account
face_not_matched403, 422Face does not match logged-in user
face_not_verified403Face was not verified for this tracking ID
face_reenroll_required—
face_required409A face check is required to release identity data
face_rescan_required422We could not confidently recognize you. Please rescan your face.
failed_to_extract_id_portrait_feature—
feature_failed502Feature extraction failed
feature_size_mismatch—
fields_required422The engine could not read a name and date of birth — provide fields.full_name and fields.dob (YYYY-MM-DD) to approve.
forbidden403Invalid internal auth
frames_required400The demo needs a 3-8 frame live burst from your camera.
front_image_id_required400front_image_id is required
front_image_not_found404Front image not found
front_not_readable—
idempotency_in_progress409A request with this Idempotency-Key is still being processed. Retry shortly.
idempotency_key_reused422This Idempotency-Key was already used with a different request body.
identity_locked403Your identity is verified. Only your email and phone number can be changed. Contact support if something else is wrong.
image_not_found404Image not found
image_too_large413That photo is too large to upload. Please use a smaller image (under 20 MB).
insufficient_scope403The access token lacks a required scope (openid is required for OIDC resource calls).
invalid_activation400, 404This activation link is invalid or has expired.
invalid_api_key401Invalid verification API key.
invalid_audience—
invalid_challenge403Invalid or expired challenge, or signature verification failed
invalid_challenge_result403Invalid challenge result
invalid_client401Check client_id/client_secret and that the app is active in the Developer Portal.
invalid_client_metadata400client_name must be between 1 and 120 characters
invalid_config500AGENT_API_KEY not configured
invalid_date400Invalid expire_at format
invalid_document_type400document_type must be one of:
invalid_feature400Invalid face vector
invalid_frames400Live verification needs at least 3 frames; send one selfie for single-click mode.
invalid_grant400, 401Code/refresh token expired, already used, or issued to another client — restart the flow.
invalid_idp_response—
invalid_image400, 422A selfie is required.
invalid_recovery_phrases403Invalid recovery phrases
invalid_redirect_uri400redirect_uris (non-empty array) is required
invalid_request400, 401, 404, 422, redirectA required parameter is missing or malformed — compare against the reference.
invalid_scope400, redirectEnable the scope for your app in the Developer Portal before requesting it.
invalid_session400Session has no user_ref (pollus_id)
invalid_state409Share request already decided
invalid_status400Invalid status
invalid_targetredirectThe requested resource is not allowed
invalid_token401, 403Token missing/expired — refresh it or sign the user in again.
kyc_already_completed409Your identity is already verified. To verify again, request a re-verification from support or your administrator.
kyc_processing409Your document is still being verified — this usually takes a few moments.
kyc_required400Complete ID verification before verifying the license.
kyc_under_review409Your previous submission is being reviewed by our team. We’ll notify you when it’s done.
legacy_ocr_failed—
license_not_found400, 404CPR license not found
limit_reached422Vault item limit reached
liveness_required400Complete the liveness check before enrolling your face.
liveness_unavailable502Could not check your selfie right now. Please try again.
login_required400, redirectprompt=none (or id_token_hint) could not be satisfied silently — redirect again without prompt=none.
logo_invalid422Logo must be an image data URL.
logo_too_large422Logo image is too large. Please use a smaller file.
misconfigured500Invalid signing key
misconfigured_oidc_endpoints—
missing_dob400A date of birth is required (provide dob or run id-verification first).
missing_document400A selfie is required for face-match.
missing_parameter400Provide valyd_id or vendor_data.
no_containers—
no_existing_e2e400User has no existing E2E keys. Use bootstrap/complete for first device setup.
no_face422No usable face detected in the selfie
no_face_in_first_image—
no_face_in_second_image—
no_face_on_file422This account has no enrolled face — recovery cannot verify it
no_face_pair—
no_images—
no_images_provided400At least one image (front or back) is required
no_reusable_record400No reusable verification found — please verify fully.
no_verification404This app has no verification set up yet. Open Verification in the dev console once to provision it.
not_found404User not found
not_linked400Sign in with Valyd before reusing your identity.
not_managed400This session is not bound to a Valyd account.
otp_expired410This OTP has expired
otp_invalid400Invalid OTP
otp_not_found404Invalid OTP
pairing_already_exchanged409Tokens have already been issued for this pairing
pairing_already_fulfilled409This pairing request has already been fulfilled
pairing_expired410Pairing request has expired
pairing_incomplete400Pairing request is missing Device B public key. Device B must provide its public key first.
pairing_not_found404Pairing request not found
pairing_not_fulfilled400Pairing is not yet complete. Status:
portrait_invalid—
portrait_not_found—
project_not_found404Verification project not found
protected_account403This is a Valyd service account and cannot be deleted.
rate_limited429Back off and retry after the window resets.
recovery_phrases_not_set404Recovery phrases not set for this user
registration_failed500Registration failed. Please try again.
request_not_supportedredirectThe request (JWT request object) parameter is not supported — send plain query/form parameters.
request_uri_not_supportedredirectThe request_uri parameter is not supported — send plain query/form parameters.
required_face_checks_incomplete409Approval requires passed ID verification, liveness, and face match checks.
requires_login401User must authenticate first
reuse_not_available400Your Valyd account is no longer verified — please complete the full verification.
review_not_pending409Only an IN_REVIEW session can be manually decided.
sdk_compare_failed—
server_error500Unexpected error
session_closed409This verification session is already closed.
session_expired410Verification session has expired
session_not_found—Wrong or expired session id.
sso_error500Sign-in failed. Please try again.
sso_exchange_failed401Could not complete sign-in with Valyd.
sso_no_identity422Valyd did not return any account identity.
sso_not_configured503Valyd SSO is not configured on this server.
stored_reference_invalid—
stored_reference_invalid_type—
tamper_detected409payload_hash does not match the server-signed attestation
token_error500Could not issue tokens
token_issuance_failed500Token issuance failed
token_missing401Session token is required.
too_many_attempts429Too many failed face checks
too_many_frames400At most 8 frames are accepted.
tracking_already_used403Tracking ID has already been used
tracking_expired403Tracking ID has expired
tracking_not_found404Tracking ID not found or invalid
unauthenticated401Account not found.
unauthorized401, 403Not authorized.
unauthorized_client403This app is not active.
unauthorized_domain403Unauthorized domain
unknown_band—
unsupported_grant_type400Only authorization_code and refresh_token are supported
unsupported_response_typeredirectOnly response_type=code is supported
user_deleted410This device was linked to a deleted account. Please clear your local data and register again.
user_has_no_face_reference—
user_not_found400, 404User not found
userinfo_failed—
valyd_account_mismatch403This verification belongs to a different Valyd account. Sign in as that account to continue.
valyd_login_required401Sign in to your Valyd account to continue this verification.
verification_soft_locked423Verification is temporarily locked. Please contact support.
wc_error400WC API error
workflow_not_found404The workflow id does not belong to this project — copy it from the portal.
wrapped_ku_recovery_not_set404WrappedKuRecovery not set for this user
wrong_endpoint400This endpoint expects image IDs, not file uploads. Use the file upload endpoint instead.
wrong_side422That looks like the FRONT of your ID. Please capture the BACK of the card.

4. Troubleshooting the common integration mistakes

State mismatch on the callback. The state on your callback must equal the one you stored before redirecting (the button stores it in the valyd_oidc_state cookie; the SDK transaction carries it). If it differs, the login is forged or expired — restart the flow. Never skip this check.

invalid_grant on token exchange. Authorization codes are single-use and expire in ~2 minutes, and are bound to your client, redirect_uri, and PKCE code_challenge. Exchange immediately, exactly once, with the same redirect_uri you authorized with and the matching code_verifier. Replaying a used code also revokes every token it already produced.

error=invalid_request on the callback. Usually a missing PKCE code_challenge (or code_challenge_method=plain). PKCE S256 is required for every client — use the SDK’s createAuthorizationRequest() or send code_challenge + code_challenge_method=S256.

error=access_denied on the callback. The user pressed Cancel on the consent screen, or the app is a private organization app and the user isn’t assigned to it. Show a friendly “sign-in was cancelled” message and offer to retry.

invalid_client with both Basic and body credentials. Authenticate with one method: client_secret_basic (HTTP Basic) or client_secret_post (client_id + client_secret in the form body). Sending both returns invalid_request.

Redirect URI mismatch. URIs are matched exactly — scheme, host, port, and path. Register every environment’s callback in the Developer Portal.

insufficient_scope on userinfo. OIDC resource calls require the openid scope in the token (HTTP 403, {"error":"insufficient_scope",...} plus a WWW-Authenticate: Bearer header). The SDK and button add it automatically; raw integrations must include it in scope.

Wrong environment host. Each environment has its own IdP host (this documentation’s is idp.valyd.id). A token from one environment never works on another — and the Sign-in button targets whichever host served its script.

Refresh suddenly failing. Refresh tokens rotate on every use. If you replay an old one, every refresh token for that user+client is revoked (theft protection) — persist the newest token atomically, then re-login once.

Last updated on