> **Building with AI coding agents?** Install the authstack plugin with one command. This equips your agent with accurate Scalekit implementation patterns.
>
> **Recommended**:
> ```bash
> npx @scalekit-inc/cli setup
> ```
>
> Global:
> ```bash
> npm install -g @scalekit-inc/cli
> scalekit setup
> ```
>
> Supports Claude Code, Cursor, GitHub Copilot, Codex + skills for other Agent Skills-compatible agents.
> Skills: integrate-agentkit, implement-saaskit, add-mcp-oauth, implement-sso, implement-scim.
> [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# Troubleshoot connection and OAuth errors

Fix AgentKit connection errors: account statuses, failed_to_exchange_token, session_not_found, redirect_uri_mismatch, and Google, Microsoft or Slack consent.
When a user can't connect an app, or a connected account stops working, the cause is almost always one of three things: the account's status, an error from Scalekit's connection error page, or a rule the provider enforces on your OAuth app. Check the status first, then find the error text below. Each error is its own heading, so you can link straight to it.

For errors from tool calls, the API proxy or a Virtual MCP server, see the **Common problems** on [Use built-in tools](/agentkit/tools/scalekit-optimized-tools/#common-problems), [Call any API](/agentkit/tools/custom-tools/#common-problems) and [Virtual MCP servers](/agentkit/mcp/overview/).

## Start with diagnostics

The connected account's status tells you whether the user never finished authorizing, still needs identity verification, has an expired token, or disconnected.

  **Python**

  ```python
  account = actions.get_connected_account(
      identifier="user_123",
      connection_name="github-connect",
  ).connected_account

  print(account.status)  # ACTIVE, EXPIRED, PENDING_AUTH, PENDING_VERIFICATION or DISCONNECTED
  # Scopes the user granted (OAuth connections only)
  print((account.authorization_details or {}).get("oauth_token", {}).get("scopes"))
  ```

  **Node.js**

  ```typescript
  import { ConnectorStatus } from '@scalekit-sdk/node';

  const { connectedAccount } = await scalekit.actions.getConnectedAccount({
    identifier: 'user_123',
    connectionName: 'github-connect',
  });

  // The status name, such as ACTIVE or PENDING_AUTH
  console.log(connectedAccount && ConnectorStatus[connectedAccount.status]);
  // Scopes the user granted (OAuth connections only)
  const details = connectedAccount?.authorizationDetails?.details;
  console.log(details?.case === 'oauthToken' ? details.value.scopes : []);
  ```

  **cURL**

  ```bash
  curl -sS -G "$SCALEKIT_ENVIRONMENT_URL/api/v1/connected_accounts/details" \
    -H "Authorization: Bearer $TOKEN" \
    --data-urlencode "connector=github-connect" \
    --data-urlencode "identifier=user_123" |
    jq '.connected_account | {status, scopes: .authorization_details.oauth_token.scopes}'
  ```

If the status is `ACTIVE` but a tool call fails, run a read-only tool such as `github_user_get_authenticated`. If that works, the connection is fine and the problem is in the call: see [Use built-in tools](/agentkit/tools/scalekit-optimized-tools/#common-problems).

To learn about status changes without polling, subscribe to the `connected_account.status_updated` and `connected_account.token_refresh_failed` webhooks. See [Listen for account events](/agentkit/account-events/).

## Connected account status

| Status | Meaning | Fix |
|--------|---------|-----|
| `ACTIVE` | Credentials are valid | Nothing to fix. Tool calls work. |
| `PENDING_AUTH` | The user hasn't finished authorizing | [Send an authorization link](#pending_auth) |
| `PENDING_VERIFICATION` | Authorization succeeded, identity verification hasn't | [Finish user verification](#pending_verification) |
| `EXPIRED` | The token expired or was revoked, and Scalekit couldn't refresh it | [Send a new authorization link](#expired) |
| `DISCONNECTED` | The account was disconnected in Scalekit | [Send a new authorization link](#disconnected) |

### `PENDING_AUTH`

The user hasn't finished authorizing, or you started re-authorization and they haven't completed it. Create an authorization link and send it through your app, for example as an in-app prompt or on a settings page.

  **Python**

  ```python
  if account.status == "PENDING_AUTH":
      link = actions.get_authorization_link(
          connection_name="github-connect",
          identifier="user_123",
      )
      print(link.link)
  ```

  **Node.js**

  ```typescript
  import { ConnectorStatus } from '@scalekit-sdk/node';

  if (connectedAccount?.status === ConnectorStatus.PENDING_AUTH) {
    const { link } = await scalekit.actions.getAuthorizationLink({
      connectionName: 'github-connect',
      identifier: 'user_123',
    });
    console.log(link);
  }
  ```

The status changes to `ACTIVE` when the user finishes, or to `PENDING_VERIFICATION` if your environment verifies users.

### `PENDING_VERIFICATION`

The user authorized the app, but Scalekit is waiting for your app to confirm the user's identity before it activates the account. Complete the verification step described in [Verify user identity](/agentkit/user-verification/). After verification succeeds, the status is `ACTIVE`.

### `EXPIRED`

The access token expired and Scalekit couldn't refresh it, for example because the user removed your app's access at the provider, such as in their Google Account's third-party connections. Scalekit refreshes tokens on its own, so there's no refresh call to make: send the user a new authorization link, as for [`PENDING_AUTH`](#pending_auth).

If the account expires again soon after every re-authorization, the connection never received a refresh token. Providers issue one only when the connection asks for offline access. Add the provider's offline-access scope, such as `offline_access`, in [Configure scopes](/agentkit/connections/#configure-scopes), then have the user connect again. [Common causes](/agentkit/connected-accounts/#common-causes) lists the other reasons accounts expire.

### `DISCONNECTED`

The account was disconnected in Scalekit, from the dashboard or by a disconnect call from your app. Its credentials are cleared, so only re-authorization fixes it. Send a new authorization link, and until the user reconnects, show the state in your UI and stop scheduling tool calls for that account.

## Errors on Scalekit's connection page

When authorization fails, Scalekit sends the user to its connection error page. The page shows a title, and its **Debug info** panel shows the `error` code, the `error_description` and an **Auth Request ID**. Find the title or the code below.

> tip: Keep the Auth Request ID
>
> The Auth Request ID lets Scalekit support find the failed attempt in the logs. **Copy error details** on the page copies it with the error and description.

### Authorization failed (`failed_to_exchange_token`)

The user approved access, but Scalekit couldn't finish the connection. Read the `error_description` to find which step failed:

- **The provider rejected the token exchange.** The description holds the provider's error, such as `invalid_client` or `invalid_grant`. Check the client ID and secret on the connection: see [Invalid client or client authentication failed](#invalid-client-or-client-authentication-failed). An authorization code is valid once and briefly, so a user who waited a long time on the consent screen should start again.

The page also suggests organization-level causes. The user's admin may have blocked your app with an access policy, the app may lack permission in the organization's settings, or the user's account may not have the rights to authorize it. The [provider errors](#provider-errors) below cover how Google, Microsoft and Slack report these.

### Session expired or not available (`session_not_found`)

Your environment's user verification mode is **Scalekit users only**, and the person authorizing isn't signed in to the Scalekit dashboard. Scalekit uses the dashboard session to confirm who the user is, so without one it can't activate the account.

- If the user is on your team, have them sign in to the Scalekit dashboard in the same browser and start the flow again.
- If the user is a customer, they don't have a Scalekit login. Switch to **Custom user verifier** in **AgentKit** > **Settings** > **User Verification**, and verify users in your own app. See [Verify user identity](/agentkit/user-verification/).

This isn't a timeout: retrying without signing in fails the same way.

### Account mismatch (`access_denied`)

The flow was refused. Two things cause it:

- **The user declined consent** at the provider, or the provider denied the request. The provider sends `access_denied` back and Scalekit shows it with this title. Offer the user a way to start again.
- **Scalekit's identity check failed** in **Scalekit users only** mode. The user is signed in to a different Scalekit workspace, or, for a flow started from the dashboard's playground, as a different user from the one who started it. Sign in with the expected account and start again.

### Invalid request (`invalid_request`)

The request reached Scalekit without a required parameter, or with one it couldn't read, such as a missing `auth_request_id` or `state`. This happens when a link is truncated, edited or reused. Create a new authorization link and use it unchanged. If the provider returned `invalid_request`, the authorization request didn't meet its rules: check the connection's scopes and settings.

### Something went wrong (`server_error`)

An unexpected error, in Scalekit or at the provider. Start the flow again after a few minutes, and check the [Scalekit status page](https://status.scalekit.com) and the provider's status page. If it persists, contact support with the Auth Request ID.

Two `error_description` values point to a specific step:

- **`user_verify_url not configured for verification redirect`.** Your environment uses **Custom user verifier**, but the authorization link was created without a `user_verify_url`. Pass one when you create the link, or switch modes. See [Verify user identity](/agentkit/user-verification/#common-scenarios).
- **`Error executing post auth hooks`.** Scalekit stored the tokens but a step after authorization failed. Start the flow again. If it fails again, contact support with the Auth Request ID.

### Other codes from the provider

Scalekit passes the provider's own OAuth error through to the page, where it shows as **Unable to complete connection**. The codes are defined in [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1):

| Code | Meaning | What to do |
|------|---------|------------|
| `unauthorized_client` | The provider doesn't allow this OAuth app to use this flow | Check the app's settings in the provider's console |
| `invalid_scope` | A requested scope is unknown or not allowed for this app | Fix the scopes on the connection, then retry |
| `unsupported_response_type` | The provider doesn't support the authorization code flow for this app | Check the app type in the provider's console |
| `temporarily_unavailable` | The provider is overloaded or down for maintenance | Retry after a few minutes |

## OAuth app configuration errors

These come from the OAuth app a connection uses, whether it's Scalekit's or [one you registered](/agentkit/advanced/bring-your-own-oauth/).

### `redirect_uri_mismatch`

The redirect URI registered in the provider's OAuth app doesn't match the one Scalekit sends. Providers require an exact match.

1. In the Scalekit dashboard, open **AgentKit** > **Connections** and select the connection.
2. Copy the **Redirect URI**.
3. In the provider's developer console, paste it into the OAuth app's allowed redirect URIs.
4. Save, and start the connection flow again.

Most mismatches are a trailing slash (`/callback/` and `/callback`), `http` instead of `https`, or a missing port number in local development.

### Invalid client or client authentication failed

The client ID or secret on the connection doesn't match the provider's OAuth app. Providers report it as `invalid_client`, inside a `failed_to_exchange_token` error.

1. Open **AgentKit** > **Connections** and select the connection.
2. Compare the **Client ID** and **Client Secret** with the OAuth app in the provider's console.
3. If the secret was rotated or expired, create a new one in the provider's console and paste it into the connection.
4. Start the connection flow again.

### `invalid_state`

Scalekit checks the OAuth `state` parameter to protect against cross-site request forgery, and fails the flow with `invalid_state` when it's missing or doesn't match. The connection error page shows it as **Unable to complete connection**. Finish the flow in the same browser it started in, allow cookies, and start again from a fresh authorization link rather than a bookmarked or back-button page.

### Authorization succeeds but tools fail on a missing scope

The user's token doesn't include a scope a tool needs. The app returns an error such as Slack's `missing_scope`, or a `403`. Add the scope in [Configure scopes](/agentkit/connections/#configure-scopes), then have the user authorize again: existing tokens don't gain new scopes.

### The connection works but access tokens come back empty

A connected account has an ID, such as `ca_...`, but its access and refresh token fields are empty. That's expected: API responses don't include provider tokens by default, which keeps them out of your app. Call tools with `execute_tool` or the [API proxy](/agentkit/tools/custom-tools/), which add the user's token for you. To read the tokens themselves, contact [support](mailto:support@scalekit.com) to enable it.

## Provider errors

Each provider sets its own rules for OAuth apps. These are the common ones, with links to the provider's documentation.

### It works for your account but fails for your customers

A provider OAuth app often starts in a development or testing state that only its creator, or a short list of test users, can authorize. The connection works while you test it with your own account, and fails when a customer connects. Listing tools also works for you, because your own account already authorized the app. This applies to OAuth apps you registered and to connections that use Scalekit's shared credentials.

To fix it, finish the provider's requirements for external use, publish the app, and connect again from an account outside your organization. [Publish your own OAuth apps](/agentkit/advanced/launch-checklist/#publish-your-own-oauth-apps) in the launch checklist lists the steps. For example:

- **Google** limits apps in the **Testing** publishing status to 100 test users that you list on the OAuth consent screen. Publish the app to production, and complete verification if it requests sensitive or restricted scopes. See [Google's publishing status guide](https://support.google.com/cloud/answer/15549945).
- **Airtable** returns "This OAuth application cannot be used outside of development" until the integration has a privacy policy URL, a terms of service URL and a support email. See [Airtable's OAuth integration guide](https://airtable.com/developers/web/guides/oauth-integrations).
- **ZoomInfo** offers partner apps for serving customers across organizations. Partner apps go through ZoomInfo's review before customers can install them. See [ZoomInfo partner apps](https://docs.gtm.ai/docs/partner-app).

### Google: unverified app screen or `admin_policy_enforced`

Google shows an unverified app screen when an app that hasn't completed verification requests sensitive or restricted scopes, and caps such an app at 100 new users. Complete [Google's app verification](https://support.google.com/cloud/answer/7454865), or request fewer sensitive scopes.

`admin_policy_enforced` means the user's Google Workspace administrator doesn't allow the app to access one or more requested scopes. The administrator can [mark the app as trusted](https://knowledge.workspace.google.com/admin/apps/control-which-apps-access-google-workspace-data) by its OAuth client ID in the Admin console's API controls. See [Google's OAuth error reference](https://developers.google.com/identity/protocols/oauth2/web-server).

> note: Testing apps get short-lived refresh tokens
>
> Google issues refresh tokens that expire in 7 days to apps with an external user type in **Testing** status, unless they request only name, email and profile scopes. Accounts on such an app expire weekly until you publish it. See [Google's OAuth 2.0 guide](https://developers.google.com/identity/protocols/oauth2).

### Microsoft: `AADSTS65001` or "Need admin approval"

`AADSTS65001` means the user or an administrator hasn't consented to the permissions the app requests. When the tenant doesn't allow users to consent, the user sees **Need admin approval**, and the error can be `AADSTS90094` (administrator consent is required).

1. Open the app registration in the Microsoft Entra admin center.
2. Check that its API permissions match the connection's scopes.
3. Have a tenant administrator select **Grant admin consent**.
4. Start the connection flow again.

See Microsoft's [consent troubleshooting guide](https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/app-integration/troubleshoot-consent-issues) and [error code reference](https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes).

### Microsoft: `AADSTS50020`

The account the user signed in with doesn't exist in the tenant the app expects, for example a personal account or an account from another organization signing in to a single-tenant app. Have the user sign in with their work or school account in that tenant, have an administrator add them as an external user, or make the app registration multi-tenant. See Microsoft's [error code reference](https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes).

### Slack: the workspace requires app approval

When a Slack workspace only allows pre-approved apps, a member who tries to install your app submits a request instead, and the connection can't finish until an administrator approves it. Ask a workspace administrator to approve the app, or test in a workspace where you manage app settings. See [Slack's app approval guide](https://slack.com/help/articles/222386767-Manage-app-approval-for-your-workspace).

### The provider rate limits your requests

The provider rejected calls because of its rate limit or quota, usually with a `429`. Retry with exponential backoff, and cache read results where you can. With [your own OAuth credentials](/agentkit/advanced/bring-your-own-oauth/), your app gets its own quota at providers that set quotas per OAuth app.

## Get help

Open **AgentKit** > **Connected Accounts** in the Scalekit dashboard to see the account's status and tool call logs. When you contact [support@scalekit.com](mailto:support@scalekit.com), include:

- The Auth Request ID from the connection error page, if there was one
- The connected account ID, or the user's identifier and the connection name
- The full error text and when it happened
- The steps that reproduce it

## Next

  - [Manage connected accounts](/agentkit/connected-accounts/): Check an account's status and ask users to reconnect.
  - [Listen for account events](/agentkit/account-events/): Get a webhook when an account expires or is disconnected.

## API reference

The endpoints this page's code calls, with every field and the SDK method for each:

- `GET` [Get a connected account](https://docs.scalekit.com/agentkit/reference/connected-accounts/get-a-connected-account.md)
- `GET` [Get a connected account's credentials](https://docs.scalekit.com/agentkit/reference/connected-accounts/get-connected-account-credentials.md)
- `POST` [Get an authorization link](https://docs.scalekit.com/agentkit/reference/authorization/get-an-authorization-link.md)


---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
