> **Building with AI coding agents?** Install the authstack plugin with one command. This equips your agent with accurate Scalekit implementation patterns.
>
> **Recommended** (the `-y` flags skip prompts, so the command runs without an interactive terminal):
> ```bash
> npx -y @scalekit-inc/cli setup -y
> ```
>
> Global:
> ```bash
> npm install -g @scalekit-inc/cli
> scalekit setup -y
> ```
>
> Supports Claude Code, Cursor, GitHub Copilot, Codex + skills for other Agent Skills-compatible agents in `~/.agents/skills`.
> 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
  import os

  from dotenv import load_dotenv
  from scalekit import ScalekitClient

  load_dotenv()

  scalekit_client = ScalekitClient(
      env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"],
      client_id=os.environ["SCALEKIT_CLIENT_ID"],
      client_secret=os.environ["SCALEKIT_CLIENT_SECRET"],
  )
  actions = scalekit_client.actions

  account = actions.get_connected_account_details(
      identifier="user_123",
      connection_name="github-connect",
  ).connected_account

  print(account.status)  # ACTIVE, EXPIRED, PENDING_AUTH, PENDING_VERIFICATION or DISCONNECTED
  ```

  **Node.js**

  ```typescript
  import 'dotenv/config';
  import { ConnectorStatus, ScalekitClient } from '@scalekit-sdk/node';

  const scalekit = new ScalekitClient(
    process.env.SCALEKIT_ENVIRONMENT_URL!,
    process.env.SCALEKIT_CLIENT_ID!,
    process.env.SCALEKIT_CLIENT_SECRET!,
  );

  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]);
  ```

  **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'
  ```

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, or the app rejected a stored API key or other credential | [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.

### 401 or `REAUTHENTICATION_NEEDED`: the user must authorize again

A tool call returns HTTP 401 with `error_code` `TOOL_ERROR` and `tool_error_code` `REAUTHENTICATION_NEEDED`, or `UNAUTHENTICATED` while the connected account is `EXPIRED`. Retrying with a new Scalekit access token doesn't help. What it means depends on how the connection authenticates:

- **OAuth.** When the app rejects the user's access token, Scalekit refreshes the token and retries the call once. You see this error when the refresh failed, usually because the user revoked your app's access or the refresh token expired. The account is now `EXPIRED`.
- **API key, bearer token or basic auth.** There's no token to refresh, so a single 401 from the app marks the account `EXPIRED`. The app rejected the stored credential, for example because it was deleted, rotated or lacks a permission the tool needs: some apps answer a missing permission with 401 rather than 403. Check the credential at the provider before the user enters it again.

In both cases, send the user a new authorization link, as for [`PENDING_AUTH`](#pending_auth): for API key and basic auth connections, the link collects the new credential. Pause tool calls for that account until its status is `ACTIVE` again. [Authorize a user](/agentkit/tools/authorize/) shows the full flow, and [Errors and rate limits](/agentkit/reference/errors/) lists every error code.

If the refresh succeeds but the app still rejects the call, the token is valid and lacks a permission: see [`PERMISSION_DENIED`](#authorization-succeeds-but-tools-fail-on-a-missing-scope).

## 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`.

Some apps answer a missing scope with a 401 instead. For an OAuth account, Scalekit refreshes the token and retries, and when the retry is rejected too, the call returns HTTP 403 with `tool_error_code` `PERMISSION_DENIED` and the message "The connected account does not have permission to perform this action." The account stays `ACTIVE`, because its token works.

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: "Access blocked" or "This app is blocked"

Google stops the user on its sign-in screen before they can grant access:

- **"Access blocked: … has not completed the Google verification process"**, with `Error 403: access_denied`. The OAuth app is in **Testing** status and the user isn't on its list of test users. Add them as a test user on the OAuth consent screen, or publish the app.
- **"This app is blocked"**. Google blocked the app from requesting the sensitive scopes the connection asks for, usually because the app isn't verified for them. A shared OAuth app can be blocked too. Use [your own OAuth app](/agentkit/advanced/bring-your-own-oauth/), and complete [Google's app verification](https://support.google.com/cloud/answer/7454865) for the scopes you request.

### 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.

## Custom and MCP connector errors

These come from [custom connectors](/agentkit/bring-your-own-connector/create-connector/), including MCP servers you add yourself.

### MCP accounts need re-authorizing within hours

Users connect without problems, but within hours their connected accounts need re-authorizing, with `REAUTHENTICATION_NEEDED` on the next tool call. Scalekit couldn't refresh the token. The usual cause is that the server's token endpoint expects the client credentials in a different place from where Scalekit sends them.

1. Find the methods the server accepts in `token_endpoint_auth_methods_supported` in its OAuth metadata, usually at `/.well-known/oauth-authorization-server`.
2. Open **AgentKit** > **Connections**, select the connection, and set **Token Endpoint Auth Method** to match, for example **Request Body (Post params)** for `client_secret_post`. See [Match the server's token endpoint auth method](/agentkit/bring-your-own-connector/create-connector/#match-the-servers-token-endpoint-auth-method).
3. Have each affected user authorize again.

If the method already matches, check whether the server issues refresh tokens at all. Some MCP servers issue only short-lived access tokens, so their users must authorize again when each token expires.

### "No tools found for this account. Try reauthorizing the account."

The dashboard shows this on a connection's **Tools** tab when Scalekit has no tools for the selected connected account: it couldn't list the MCP server's tools for that account. Scalekit lists them when the user connects, and the server rejected the request or returned no tools. Common causes:

- **The credentials don't work at the server.** The user entered the wrong API key or token, or the connector's auth type doesn't match what the server expects, such as **Bearer token** for a server that takes an API key.
- **The OAuth client was rejected.** The server doesn't support Dynamic Client Registration, so the connection needs the client ID and secret of [your own OAuth app](/agentkit/advanced/bring-your-own-oauth/), with the connection's redirect URI registered in that app. Don't enter a client ID copied from the provider's own MCP client.
- **The server blocked Scalekit.** A self-hosted or restricted server may need to allow Scalekit's [outbound IP addresses](/reference/outbound-ip-addresses/) and the connection's redirect URI.

Fix the cause, then have the user authorize again. With [log streaming](/agentkit/log-streaming/), each failure also arrives as a `connected_account.tool_discovery_failed` event, with the server's error.

## 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 |
