> ## Documentation Index
> Fetch the complete documentation index at: https://docs.busha.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Every OAuth2 and API error, what causes it, and how to fix it.

## Error envelopes

Errors come in two shapes depending on which surface returned them.

**OAuth2 protocol endpoints** (`/oauth2/auth`, `/oauth2/token`, `/oauth2/revoke`) return RFC 6749 §5.2 shape:

```json theme={null}
{
  "error": "invalid_grant",
  "error_description": "code has already been used"
}
```

**Busha resource API** errors return:

```json theme={null}
{
  "status": "error",
  "message": "Insufficient scope",
  "error": {
    "name": "insufficient_scope",
    "message": "this endpoint requires balances:read"
  }
}
```

`error_description` is human-readable diagnostic text — do not surface it verbatim to end users.

## OAuth2 errors

| Error                         | Surface                | Cause                                                                                                        | Fix                                                                                                                                                                       |
| ----------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_request`             | All OAuth2 endpoints   | Malformed request — missing parameter, bad encoding, wrong content type.                                     | Validate your request shape against the [Endpoints reference](./endpoints).                                                                                               |
| `invalid_client`              | `/oauth2/token`        | Wrong `client_id`, wrong `client_secret`, or wrong authentication method.                                    | Use HTTP Basic auth with URL-encoded `client_id:client_secret`.                                                                                                           |
| `invalid_grant`               | `/oauth2/token`        | Code reused, code expired (10 min window), PKCE mismatch, or refresh token already rotated (family revoked). | For codes: make your callback handler idempotent — deduplicate on `state` or the code itself. For refresh tokens: treat as a hard disconnect and prompt re-authorization. |
| `invalid_scope`               | `/oauth2/auth`         | Scope not in the [catalog](./scopes), or not in your `approved_scopes`.                                      | Compare your requested scopes against the catalog and your approved set on the dashboard.                                                                                 |
| `unauthorized_client`         | `/oauth2/auth`         | Client status is `pending`, `rejected`, or `suspended`.                                                      | Check your app status on the dashboard.                                                                                                                                   |
| `access_denied`               | Callback (`/callback`) | User clicked Cancel on the consent screen.                                                                   | Render a "continue without connecting" path in your UI.                                                                                                                   |
| `redirect_uri does not match` | OAuth2 endpoints       | `redirect_uri` is not byte-identical to a registered URI.                                                    | Compare bytes against the dashboard value. Check trailing slash, scheme, port, path casing.                                                                               |

## Resource API errors

| HTTP status        | Error name           | Cause                                                            | Fix                                                                                   |
| ------------------ | -------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `401 Unauthorized` | —                    | Access token expired, revoked, or signature verification failed. | Refresh the access token. Force a JWKS refetch if the `kid` is unknown.               |
| `403 Forbidden`    | `insufficient_scope` | The token does not include the scope required by this endpoint.  | Inspect the `scp` claim in the access token. Re-authorize with the broader scope set. |

## Common gotchas

**`invalid_grant` on code exchange**
Token exchange is not idempotent — authorization codes are single-use. If your callback handler runs twice (user double-clicks, browser retry), the second exchange returns `invalid_grant`. Deduplicate on the `state` value or the `code` itself in your callback handler.

**`invalid_grant` on refresh**
A refresh token can only be used once. If you replay a previously-used refresh token, Busha revokes the entire token family — all tokens for that user are immediately invalidated. Handle any `invalid_grant` on a refresh as a hard disconnect and send the user through the authorization flow again.

**Access token still works after refresh token revocation**
Revoking a refresh token prevents new tokens from being minted, but does not invalidate an already-issued access token. JWTs self-expire at `exp` (\~1 hour). This is by design. If you need instant invalidation, layer a server-side allowlist on critical endpoints.

**`openid` scope not shown on consent screen**
`openid` and `offline_access` are protocol scopes that Busha hides from the consent UI. If you received an `id_token` and `refresh_token`, they were granted successfully.
