> **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 40+ agents.
> Features: full-stack-auth, agent-auth, mcp-auth, modular-sso, modular-scim.
> [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# Manage connected accounts

Check status, list, delete, and update credentials for connected accounts across all connector auth types.
A **connected account** is the per-user record that holds a user's credentials and tracks their authorization state for a specific connection. Scalekit creates one automatically when a user completes authentication.

## Account states

| State | Meaning |
|---|---|
| `ACTIVE` | Credentials valid, ready for tool calls |
| `EXPIRED` | Access token expired and needs refresh or re-authentication |
| `PENDING_AUTH` | User hasn't completed authentication, or re-authentication is in progress |
| `PENDING_VERIFICATION` | OAuth complete; user identity verification still required before activation |
| `DISCONNECTED` | Account was manually disconnected |

See [Troubleshoot connection errors](/agentkit/authentication/troubleshooting/#connected-account-status) for what to do in each state.

## Check account status

Use `get_or_create_connected_account` as the safe default when a user may be connecting for the first time. Use `get_connected_account` only when you know the account already exists and you need to inspect or return its stored auth details.

  ### Python

```python
response = actions.get_or_create_connected_account(
    connection_name="gmail",
    identifier="user_123"
)
connected_account = response.connected_account
print(f"Status: {connected_account.status}")
```

  ### Node.js

```typescript
const response = await actions.getOrCreateConnectedAccount({
  connectionName: 'gmail',
  identifier: 'user_123',
});

console.log('Status:', response.connectedAccount?.status);
```

## Handle inactive accounts

When a connected account isn't `ACTIVE`, generate a new authorization link and send it to the user.

The link opens a **Hosted Page**, a Scalekit-hosted UI that adapts automatically based on the connection's auth type:

- **OAuth connectors**: presents the provider's OAuth consent screen
- **API key, basic auth, or other connectors**: presents a form to collect the required credentials

Your code is the same regardless of connector type. Scalekit determines the right flow based on the connection configuration.

  ### Python

```python
if connected_account.status != "ACTIVE":
    link_response = actions.get_authorization_link(
        connection_name="gmail",
        identifier="user_123"
    )
    # Redirect or send link_response.link to the user
```

  ### Node.js

```typescript

if (connectedAccount?.status !== ConnectorStatus.ACTIVE) {
  const linkResponse = await actions.getAuthorizationLink({
    connectionName: 'gmail',
    identifier: 'user_123',
  });
  // Redirect or send linkResponse.link to the user
}
```

> tip: Customize hosted pages
>
> By default, hosted pages use Scalekit's branding. You can configure your own logo, colors, and custom domain so the pages look like part of your product. See [Custom domain](/agentkit/advanced/custom-domain/).

## Detect when re-authentication is needed

A connected account can leave the `ACTIVE` state on its own, with no action from you or the user. When that happens, the next tool call fails until the user re-authorizes. To catch it early, subscribe to the `connected_account.status_updated` webhook instead of waiting for a failed call.

### Common causes

OAuth connected accounts most often move to `EXPIRED` for reasons outside Scalekit's control:

- **The provider revoked the refresh token.** A password change, an admin-initiated token revocation, or a provider security policy invalidates the refresh token, so Scalekit can no longer obtain new access tokens.
- **The refresh token expired.** Providers cap refresh-token lifetimes (for example, 30 or 180 days), and the expiry is rarely surfaced in advance.
- **No refresh token was issued.** When the connection's scopes don't request offline access, the provider returns only a short-lived access token and no refresh token to renew it.
- **The provider hit a per-user token limit.** Some providers keep only a fixed number of refresh tokens per user and app, and silently drop the oldest ones when a user reconnects repeatedly.

The first two cases require the user to re-authenticate; there is no server-side workaround. The last two are configuration issues you fix on the connection by requesting offline access scopes.

### Subscribe to status changes

The `connected_account.status_updated` event fires on every status transition and carries both the new and previous status:

```json title="connected_account.status_updated"
{
  "spec_version": "1",
  "id": "evt_101652975398683158",
  "type": "connected_account.status_updated",
  "occurred_at": "2025-12-02T06:31:34.895815554Z",
  "environment_id": "env_88640229614813449",
  "object": "ConnectedAccount",
  "data": {
    "id": "ca_133400349586228019",
    "identifier": "john@acmecorp.com",
    "connection_id": "conn_133400101014995480",
    "connection_name": "gmail",
    "provider": "GMAIL",
    "authorization_type": "OAUTH",
    "status": "EXPIRED",
    "old_status": "ACTIVE"
  }
}
```

Because the event covers every transition, filter on the change you care about. To alert users only when an active account needs re-authorization, act on `old_status` `ACTIVE` moving to `status` `EXPIRED`:

```js
// The event fires for all transitions (for example, PENDING_AUTH to ACTIVE).
// Filter to the one that requires user action, or you will notify on noise.
if (event.data.old_status === 'ACTIVE' && event.data.status === 'EXPIRED') {
  // Generate a fresh authorization link and notify the user
}
```

When you receive this event, [generate a new authorization link](#handle-inactive-accounts) and prompt the user to reconnect. See the full payload for the [`connected_account.status_updated` event](/apis/#webhook/connectedaccountstatusupdated) in the API reference.

> note: Verify webhook signatures
>
> Scalekit signs every webhook. Verify the signature before you trust a payload, so a forged request cannot trigger a re-authorization prompt for the wrong user. See [Verify webhook signatures](/guides/webhooks-best-practices/#verify-webhook-signatures).

## List connected accounts

> note: Node.js only
>
> List and delete operations are currently available in the Node.js SDK. Use the [Scalekit dashboard](https://app.scalekit.com) or REST API for Python.

```typescript
const listResponse = await actions.listConnectedAccounts({
  connectionName: 'gmail',
});
console.log('Connected accounts:', listResponse);
```

## Delete a connected account

Deleting a connected account removes the user's credentials and authorization state. The user must re-authenticate to reconnect.

```typescript
await actions.deleteConnectedAccount({
  connectionName: 'gmail',
  identifier: 'user_123',
});
```

## Update OAuth scopes

Scopes apply to OAuth connectors only. For non-OAuth connectors (API key, basic auth, and similar), generate a new authorization link and the hosted page will collect updated credentials.

To request additional OAuth scopes from an existing connected account:

1. Update the connection's scopes in **AgentKit** > **Connections** > **Edit**.
2. Generate a new authorization link for the user.
3. The user completes the OAuth consent screen, approving the updated scopes.
4. Scalekit updates the connected account with the new token set.


---

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