# Error Catalog

Digital ID returns errors in this form:

```json
{
  "error": {
    "code": "invalid_input",
    "message": "The request input is invalid."
  }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `error` | object | Error details. Present on each non-2xx response. |
| `error.code` | string | Machine-readable error code, for example `invalid_input`. |
| `error.message` | string | Human-readable description. Do not parse this value. Use `error.code` instead. |

| HTTP status | Code | Meaning |
| --- | --- | --- |
| 400 | `invalid_input` | The request body or a parameter is invalid. |
| 401 | `unauthorized` | The API key or token is invalid. |
| 404 | `not_found` | The session, merchant, or exchange code was not found. |
| 409 | `invalid_state` | The requested action is not allowed at the current step. |
| 410 | `session_expired` | The verification session expired. This includes the end of the consent confirmation window. |
| 423 | `session_blocked` | Three liveness or face-match attempts failed. |
| 429 | `rate_limited` | The client sent too many requests. |

The `invalid_state`, `session_expired`, and `session_blocked` codes match the
conditions in [Verification Flow](/digital-id/concepts/verification-flow):

- `invalid_state`: the requested action is not allowed at the current step.
- `session_expired`: the verification session expired.
- `session_blocked`: three liveness or face-match attempts failed.

## Front-channel errors

The widget reports front-channel failures with `privy:error`:

```json
{
  "type": "privy:error",
  "payload": {
    "code": "session_expired"
  }
}
```

An unverified iframe host receives the event type without the payload.

The widget sends `privy:error` when the session is blocked after three failed
liveness or face-match attempts. It also sends `privy:error` with
`session_expired` when the consent confirmation window ends. The widget sends
`privy:cancel` when the user rejects the consent request.

## Camera errors

The WebView camera guard can reject a request before it accesses the camera:

- `camera_unsupported` when the OS or WebView does not support capture.
- `camera_missing_permission` when the host application lacks its required
  camera declaration.

User permission denial and hardware failures use standard browser
`DOMException` values: `NotAllowedError`, `NotFoundError`, or
`NotReadableError`.

## Recovery guide

Where the public API contract has no recovery mechanism, raise the case through
your agreed Privy support process.

| Scenario | Recovery |
| --- | --- |
| The create-session request times out, or no `session_url` arrives. | Create a new session. The retry can return a new `session_id`. The unused session expires automatically and is not charged. |
| The user closes the application during verification. | If the retained `session_url` has not expired, load it again. If not, create a new session. The user starts the flow again. |
| The session expired: `410 session_expired` or `privy:error`. | Create a new session. The user starts the flow again. You cannot extend or reactivate an expired session. |
| The user rejects the consent request: `privy:cancel`. | Create a new session if the user wants to try again. The user starts again from step 1. |
| The consent confirmation window ends: `privy:error` with `session_expired`. | Create a new session. The user starts again from step 1. |
| The exchange code is lost or expired: 404 or 410 on exchange. | The public API cannot issue the code again. If the server callback delivers the result, use it. If no verified result arrives through either channel, start a new session or contact Privy support. |
| Callback processing takes more than 60 seconds. | Privy can treat the delivery as timed out. Acknowledge promptly and process asynchronously. Use the exchange flow if you received the code. |
| The result arrives through both the exchange and the callback. | This is expected. Both channels deliver the same verified result. Process it one time, by `session_id`. |
| JWT verification fails. | Reject the result and do not use its data. Get the JWKS again and repeat the `kid`, `aud`, `exp`, and `iss` checks. If verification still fails, contact Privy support. Do not skip a check. |
| No verified result arrives on the backend. | There is no public status lookup by `session_id`. Treat the verification as incomplete. Start a new session, or raise the case through your agreed Privy support process. |
| The session is blocked: 423 after three failed liveness or face-match attempts. | The session cannot continue. Privy policy decides whether and when the user can try again with a new session. Raise the case through your agreed Privy support process. |
