---
title: Errors
description: "Every delegated-access error carries a machine reason. Branch on it: reconfirm, reconnect, respect the user's choice, contact ZevAuth support, fix the integration, or retry later."
---

Every delegated-access error carries a **reason**, a stable machine word
from one vocabulary:

- **In a JSON body** (token, revoke, authorize-url): `reason` and the same
  value as `error_reason`, next to `error` and `error_description`.
- **On a redirect** to your callback: `error_reason`, next to `error`,
  `error_description` and `state`.

`error` and `error_description` keep their usual meaning. New reasons may be
added; treat one you do not know by its `error`. Most support questions are
answered on [Common scenarios](/delegated-access/scenarios/).

## What to do, by reason

| Do this | Reasons |
| --- | --- |
| **Reconfirm**: authorize with `prompt=reconfirm` | `high_risk_expired` |
| **Reconnect**: send the user through authorize again | `grant_revoked`, `refresh_reused`, `refresh_revoked`, `refresh_expired`, `refresh_invalid`, `code_invalid`, `code_used`, `code_expired`, `no_scopes`, `test_grant_expired` |
| **Respect the user's choice**: do not retry automatically | `user_denied`, `account_mismatch`, `account_unavailable`, `not_a_test_member` |
| **Contact ZevAuth support**; do not retry | `unknown_partner`, `approval_pending`, `approval_suspended`, `app_suspended`, `partner_suspended`, `tenant_suspended`, `tenant_unverified`, `grant_suspended`, `scope_not_approved`, `high_risk_scope_not_approved`, `scope_not_grantable`, `scope_suspended`, `test_apps_not_allowed` |
| **Fix your integration** | `missing_param`, `redirect_uri_mismatch`, `unsupported_response_type`, `missing_state`, `invalid_state`, `missing_pkce`, `pkce_method_unsupported`, `invalid_pkce_challenge`, `pkce_mismatch`, `redirect_mismatch`, `client_mismatch`, `invalid_prompt`, `invalid_login_hint`, `scope_missing`, `scope_unknown`, `scope_not_for_test_apps`, `tenant_not_live`, `test_grant_limit`, `grant_not_found`, `invalid_request` |
| **Retry later**, with backoff | `rate_limited`, `product_unavailable`, `server_error` |

A few deserve a sentence:

- **`account_mismatch`**: you sent a `login_hint`, and the user did not
  continue with that ZevID account, or a connection came back for another
  account and we revoked it. Tell the user to continue with the ZevID account
  they use with you. Never attach the connection to a different user. See
  [The flow](/delegated-access/flow/#tell-zevid-who-you-expect).
- **`not_a_test_member`**: someone outside your team tried your test
  environment. See [Test environments](/delegated-access/testing/).
- **`test_grant_expired`**: a test connection reached the end of its life.
  Reconnect to keep testing.
- **`test_grant_limit`**: your test environment already has 25 connections.
  Disconnect one, or wait for one to end.
- **`client_mismatch`**: a code or refresh token from your test environment
  was used with your live key, or the reverse. They never cross.

## Authorize

Until we know the `redirect_uri` is yours, there is nowhere safe to send the
user. So two cases show a page; everything else redirects back to your
callback:

```text
https://app.example.com/zev/callback
  ?error=invalid_scope
  &error_description=The+permission+zevcloud.services.read+is+paused+for+this+app.
  &error_reason=scope_suspended
  &state=...
```

Check `state` before acting on an error, exactly as you do on success.

| Shown as a page | What to do |
| --- | --- |
| `resource` or `redirect_uri` missing | Fix the link your app builds. |
| `redirect_uri` not registered for this product | Register it exactly in the console under **Delegated access**. |

A missing or invalid key is refused before either, with a JSON `401`.

## Token and revoke

Errors follow RFC 6749, with the HTTP status:

```json
{
  "error": "invalid_grant",
  "error_description": "The user's confirmation for this connection's high-risk permissions expired. Send the user through authorize with prompt=reconfirm.",
  "reason": "high_risk_expired"
}
```

| Status | `error` | Usual reasons |
| --- | --- | --- |
| 400 | `invalid_request` | A required field is missing. |
| 400 | `unsupported_grant_type` | `grant_type` is `authorization_code` or `refresh_token`. |
| 400 | `invalid_grant` | See the table above. |
| 401 | | Missing, invalid or revoked API key. Token and revoke need your **secret** key. |
| 403 | | A configuration problem on ZevAuth's side, not yours. Retry later; contact support if it persists. |
| 404 | `grant_not_found` | Revoke only: the connection is not yours, or the id is wrong. |
| 429 | `rate_limited` | Wait for `Retry-After` seconds. See [Rate limits](/delegated-access/rate-limits/). |
| 503 | `temporarily_unavailable` | ZevID could not be reached. Retry with backoff. |

> **Warning:**
> `pkce_mismatch`, `redirect_mismatch` and `client_mismatch` are bugs in your
> integration, not in the user's choice. Sending the user round again fails the
> same way. Fix the integration first.

## Calls to the product

Each product defines its own errors, and its guide is the source of truth for
them. For ZevCloud (for example `grant_expired`, `team_suspended`,
`github_access_required`, `project_limit_reached`), see
[ZevCloud's error table](https://docs.zevcloud.net/guide/connected-apps#errors).
ZevCloud's `grant_expired` is the same ending we report as
`test_grant_expired`. Two matter for every partner:

| Status | Code | What to do |
| --- | --- | --- |
| 403 | `high_risk_confirmation_required` | Send the user through `prompt=reconfirm`. |
| 403 | `scope_paused` | The permission is paused. Do not retry. |
| 401 | | The access token is expired, stale or revoked. Refresh once; if that fails with `invalid_grant`, branch on its `reason`. |
