---
cuppy_hub: salesforce
cuppy_path: reference/auth/oauth-and-session-errors.md
cuppy_section: docs
cuppy_doc: Y4B5-0VA7
cuppy_doc_uuid: 714ee28a-c6a0-4b4f-9d9c-27fdb58cf5bd
cuppy_permalink: https://cuppy.io/salesforce/d/Y4B5-0VA7
cuppy_version: 1
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/salesforce/docs/reference/auth/oauth-and-session-errors
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: c2f21608aa620f2fee981c57fafe7b0e0b6c4e174d25f8e38857d5b5b4d50ab8
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Salesforce OAuth and session errors

Token endpoint errors arrive as `{"error": "<code>", "error_description": "<text>"}`. **Branch on `error`, then read `error_description`.** `invalid_grant` alone covers more than ten causes. Login History in Setup shows the server-side reason for each failed attempt.

## invalid_grant

| `error_description` | Cause → fix | Check |
|---|---|---|
| `authentication failure` | The user behind the request can't sign in through this app: deactivated, wrong or locked user, or the OAuth username-password flow is blocked (Login History: "OAuth Username-Password Flow Disabled"). Move to client credentials or JWT | Setup → Login History for the user |
| `user hasn't approved this consumer` | JWT bearer: the user isn't pre-authorized. Set **Permitted Users = Admin approved users are pre-authorized** and add the user's profile or permission set to the app. Also check that you didn't send a sandbox username to production | The ECA's Policies tab |
| `audience is invalid` | The JWT `aud` claim doesn't match the authorization server: `https://login.salesforce.com`, `https://test.salesforce.com` or the My Domain. The CLI tries several and can be forced with `SF_AUDIENCE_URL` | The URL you signed into `aud` |
| `IP restricted or invalid login hours` | Outside the profile's IP ranges or login hours. JWT and SAML bearer flows always enforce IP restrictions; the app's IP Relaxation policy decides the others. Not retryable: alert on it | The profile's Login IP Ranges and Hours |
| `expired access/refresh token` | The refresh token was revoked, expired under the app's refresh token policy, or rotated. Re-authorize | The app's refresh token policy |
| (client credentials) | Salesforce: "For the client credentials flow, the request isn't supported for the host domain." The token request went to `login.salesforce.com` instead of the **My Domain**; or the flow isn't enabled; or there's no Run As user (field reports quote "no client credentials user enabled") | The URL you POST to; the ECA's Policies tab |
| (PKCE) | The `code_verifier` doesn't match the `code_challenge` (a malformed challenge gives `invalid_request` instead) | Your client's PKCE code |

## Other codes

| `error` | Meaning → fix |
|---|---|
| `invalid_client_id` | Client identifier is invalid: wrong consumer key, or another org's key (after a sandbox refresh, re-read it) |
| `invalid_client` | Wrong or rotated client secret. With "app must be installed into org" (the user sees OAUTH_APPROVAL_ERROR_GENERIC), the app isn't installed and the user lacks the approval permission |
| `invalid_app_access` | An admin hasn't approved this user for the app: add the profile or permission set |
| `inactive_user` / `inactive_org` | The user was deactivated, or the org is locked, closed or suspended |
| `invalid_scope` | A scope the app doesn't have; for the Agent API, check `chatbot_api` and `sfap_api` |
| `invalid_request` | Malformed: not HTTPS, wrong method, bad PKCE parameters, or a JWT/SAML bearer flow without the `refresh_token` scope |
| `redirect_uri_mismatch` / `redirect_uri_missing` | The callback isn't exactly the registered one (scheme, host, port, trailing slash), or wasn't sent |
| `rate_limit_exceeded` | Too many login attempts: a client requesting tokens in a loop. Cache tokens and reuse them |
| `server_error` | Authorization requests exceed the hourly limit |
| `access_denied` | The user clicked Deny |

**Refresh tokens:**
- With rotation on, every refresh returns a new refresh token, and **reusing an old one invalidates the current one**.
- Concurrent refreshes with the same token fail intermittently (Login History: "Token request is already being processed").
- Serialize refreshes per connection and keep only the latest token.

## API calls after a token

| Response | Cause → fix |
|---|---|
| **401 `INVALID_SESSION_ID`** | The access token expired, was revoked, or was sent to the wrong host. Use the `instance_url` from the token response; refresh once and retry once. jsforce refreshes automatically when a refresh token or `refreshFn` is configured |
| 403 `API_DISABLED_FOR_ORG` | The edition or the user lacks API access ("API Enabled"), or the user is UI-only |
| 403 `REQUEST_LIMIT_EXCEEDED` | The org's API request limit is used up: family 10 shows how to read it |

## Salesforce CLI messages

| Message | Meaning |
|---|---|
| `Error authenticating with JWT config due to: …` | The token endpoint rejected the assertion: read the `invalid_grant` description above |
| `Error authenticating with the refresh token due to: …` | "The stored refresh token is no longer valid (it may have expired, been revoked, or been rotated)": run the login again |
| `Client ID is required for JWT authentication.` | Pass `--client-id` to `sf org login jwt` |

Two kinds of text: Salesforce's OAuth errors page gives each code's *cause* in prose (for example "Client identifier is invalid.", "User hasn’t approved the connected app.", "For the refresh token flow, the refresh or access token is expired."). The `error_description` values a client actually receives (`authentication failure`, `user hasn't approved this consumer`, `expired access/refresh token`) are quoted from vendor troubleshooting guides and field reports. Match on `error` and treat descriptions as hints.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 376 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/salesforce/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
