# Errors and troubleshooting

This page catalogs the error responses of the Truora Pass integration endpoints and the most common integration mistakes with their fixes.

## The two error formats

The Truora Pass API uses two error envelopes, depending on the endpoint and grant:

**Truora Pass error envelope** — used by the authorization flow and by the `authorization_code` grant on `POST /v1/oauth2/token`:

| Field | Type | Description |
|---|---|---|
| `code` | integer | HTTP status code |
| `status` | string | HTTP status text |
| `message` | string | Human-readable description |
| `error_code` | string (optional) | Machine-readable error identifier |
| `fields` | array (optional) | Field-level validation details |

**RFC 6749 error envelope** — used by the `refresh_token` grant, the CIBA grant on `POST /v1/oauth2/token`, and `POST /v1/oauth2/bc-authorize`:

```json
{
  "error": "invalid_grant",
  "error_description": "refresh token already used"
}
```

## Authorization errors (authorize redirect)

When the [Authorization Code flow](/truora-pass/authorization_code_flow/) fails after the request is accepted, Truora Pass redirects the browser back to your `redirect_uri` with `error` and `error_description` query parameters instead of `code`. With the [embedded SDK](/truora-pass/embedded_sdk/), the same information arrives through the `onError` callback as `{error, error_description, state}`.

Some failures are rejected before any redirect can happen and are shown to the user inside Truora Pass instead — your application only observes that the callback never fires:

| Condition | What happens |
|---|---|
| `redirect_uri` does not exactly match a registered URI | Request rejected in Truora Pass; no redirect to your application |
| A requested scope is not registered for your application | Request rejected in Truora Pass |
| `response_type` is not `code` | Request rejected in Truora Pass |
| `response_mode` is not empty, `query`, or `web_message` | Request rejected in Truora Pass |
| `response_mode=web_message` without web_message enabled, or the embedding origin is not allow-listed | Request rejected in Truora Pass |
| Step-up conditions: `acr_not_satisfied`, `session_too_old`, `email_domain_not_allowed` | Handled inside the Truora Pass UI; your application sees only the eventual callback success or error |

## `POST /v1/oauth2/token` — `authorization_code` grant

Send `Content-Type: application/json`. Errors use the Truora Pass envelope:

| HTTP status | `error_code` | Message |
|---|---|---|
| 401 | `bad_credentials` | Invalid client credentials |
| 400 | `validation_error` | Invalid or expired authorization code, or invalid client/redirect_uri |

## `POST /v1/oauth2/token` — `refresh_token` grant

Send `Content-Type: application/json`. Errors use the RFC 6749 envelope:

| HTTP status | `error` | When |
|---|---|---|
| 401 | `invalid_client` | Client authentication failed |
| 400 | `invalid_grant` | The refresh token is invalid or expired |
| 400 | `invalid_grant` — "refresh token was not issued to this client" | The token belongs to a different client |
| 400 | `invalid_grant` — "refresh token already used" | Reuse detected — the entire token family is revoked |
| 400 | `invalid_grant` | The refresh token has expired, or was invalidated by a concurrent refresh ("invalid or already used") |
| 400 | `invalid_scope` | The requested `scope` is not a subset of the originally granted scopes |

See [Refresh tokens](/truora-pass/refresh_tokens/) for rotation and reuse-detection behavior.

## `POST /v1/oauth2/token` — CIBA grant (polling)

Send `Content-Type: application/x-www-form-urlencoded` with `grant_type=urn:openid:params:grant-type:ciba`. Errors use the RFC 6749 envelope; all are HTTP 400 unless noted:

| `error` | When | What to do |
|---|---|---|
| `authorization_pending` | The user has not decided yet | Keep polling, waiting `interval` seconds between attempts |
| `access_denied` | The user declined the request | Stop — the `auth_req_id` is consumed |
| `expired_token` | The `auth_req_id` is unknown or expired, the request has expired, or it has already been redeemed | Start a new backchannel request |
| `invalid_grant` | The `auth_req_id` was issued to a different client | Check your `client_id` |
| `unsupported_grant_type` | The `grant_type` value is wrong | Use `urn:openid:params:grant-type:ciba` |
| `unauthorized_client` | Your application is not allowed to use the CIBA grant | Ask your Truora contact to enable it |
| `invalid_client` (HTTP 401) | Client authentication failed | Check `client_id` / `client_secret` |

## `POST /v1/oauth2/bc-authorize`

Errors use the RFC 6749 envelope:

| HTTP status | `error` | When |
|---|---|---|
| 400 | `invalid_request` | A sensitive scope (`documents`, `background`) was requested without `binding_message` |
| 400 | `invalid_request` — "the resolved user has no verified phone for delivery" | The user exists but has no verified phone to receive the approval link |
| 400 | `unknown_user_id` | The `login_hint` does not resolve to a Truora Pass user (it must be an email) |
| 503 | `notifier_unavailable` | The approval message could not be delivered — retry later |

See the [CIBA flow](/truora-pass/ciba_flow/) for the full request reference.

## `GET /v1/oauth2/userinfo`

| HTTP status | Error | When |
|---|---|---|
| 401 | Missing or invalid Bearer token | No `Authorization` header, or the access token is invalid or expired |
| 401 | `insufficient_scope` — "Invalid token: missing scopes" | The token carries no usable scopes |
| 404 | `user_not_found` | The user behind the token no longer exists |

## Troubleshooting common mistakes

1. **`redirect_uri` mismatch.** The `redirect_uri` in the authorization request must match a registered URI **exactly** (scheme, host, port, path), and the `redirect_uri` sent to the token endpoint must match the one used in the authorization request. Register every URI you use — see [Registering your application](/truora-pass/registering_your_application/).

2. **Expired or re-used authorization code.** Authorization codes live for **10 minutes** and are **single-use**. Exchange the code immediately after receiving it, and never retry a token exchange with the same code — a second attempt returns `validation_error`. If the code expired, restart the authorization flow.

3. **Scope not registered.** Every scope in your authorization request must be registered for your application. Remove unregistered scopes, or add them yourself by editing your application in the Truora Pass Dashboard — see [Registering your application](/truora-pass/registering_your_application/) and the [Scopes reference](/truora-pass/scopes_reference/).

4. **Wrong `Content-Type` on `/token`.** The token endpoint dispatches by `Content-Type`: use `application/json` for the `authorization_code` and `refresh_token` grants, and `application/x-www-form-urlencoded` for the CIBA grant. Mixing them up makes the request land on the wrong grant handler.

5. **Polling faster than `interval`.** In the CIBA flow, wait the `interval` (in seconds) returned by `bc-authorize` between polls of the token endpoint — polling faster does not speed up approval.

6. **Refresh token reuse.** Refresh tokens rotate on every use: the old one is consumed and a new one is returned. Presenting an already-used refresh token is treated as reuse, and the **entire token family is revoked** — your application must send the user through the authorization flow again. Always persist the newest `refresh_token` from each response. See [Refresh tokens](/truora-pass/refresh_tokens/).

7. **web_message origin not allow-listed.** The [embedded SDK](/truora-pass/embedded_sdk/) requires your application to be registered with `allows_web_message` enabled and the embedding page's origin included in `allowed_web_message_origins`. If either is missing, Truora Pass rejects the `response_mode=web_message` request.
