---
title: Connected Accounts Management
impact: HIGH
description: Comprehensive guide to CRUD operations on connected accounts with emphasis on secure authentication flows
tags: [connected-accounts, authentication, oauth, crud, security]
---

# Connected Accounts Management

> **Using Sessions/Tool Router?** If you're using Tool Router Sessions, you can use `session.toolkits()` to view the auth configs and connected accounts being used by the Tool Router. You only need to use the methods below if you're managing connected accounts outside of Tool Router.
> When using sessions, only use authConfigs listed via session.toolkit(), or use session.authorize() for toolkits which does not have authConfigs

Connected accounts store authentication tokens for external services. Use the `connectedAccounts` API for CRUD operations.

## Create Connected Accounts

### Recommended: link() - Composio-Hosted Authentication

Use `link()` for most flows. Composio handles security, OAuth, and form rendering.

```typescript
const connectionRequest = await composio.connectedAccounts.link(
  'user_123',
  'auth_config_123',
  { callbackUrl: 'https://your-app.com/callback' }
);

// Redirect user to authentication page
window.location.href = connectionRequest.redirectUrl;

// IMPORTANT: if building web applications, look for `status=success&connectedAccountId=*******` query params in the callback url. Setup a dedicated route to handle the callback if opening via popus, to show success and failure states.

// Only use the below for simpler single user application/CLIs etc.
const account = await connectionRequest.waitForConnection();
```

While building popup-based authentication flows using Composio links, follow [Building Popup Connection UI](./app-auth-popup-ui.md).


**Why use link():**
- Handles OAuth security and form UI
- Works with 200+ services
- Whitelabel with your app name/logo (Project Settings on dashboard)
- No custom UI needed

### Advanced: initiate() - Custom Authentication UI

Only use when building custom auth interfaces:

```typescript
// API Key (custom form)
const connection = await composio.connectedAccounts.initiate(
  'user_123',
  'auth_config_456',
  {
    config: AuthScheme.ApiKey({ api_key: apiKey }),
  }
);

// OAuth with extra params (Zendesk, PostHog, etc.)
const connection = await composio.connectedAccounts.initiate(
  'user_123',
  'zendesk_config',
  {
    config: AuthScheme.OAuth2({ subdomain: "your_subdomain" })
  }
);
window.location.href = connection.redirectUrl;
```

**AuthScheme helpers:**
- `AuthScheme.OAuth2({ subdomain: 'example' })`
- `AuthScheme.ApiKey({ api_key: 'key123' })`
- `AuthScheme.Basic({ username: 'user', password: 'pass' })`
- `AuthScheme.BearerToken({ token: 'token123' })`

**Use initiate() only when:**
- Building custom authentication UI
- Handling credentials directly in backend
- OAuth requires extra parameters before redirect

## Read Connected Accounts

```typescript
// List all
const allAccounts = await composio.connectedAccounts.list();

// Filter by user
const userAccounts = await composio.connectedAccounts.list({
  userIds: ['user_123'],
});

// Filter by toolkit
const githubAccounts = await composio.connectedAccounts.list({
  toolkitSlugs: ['github'],
});

// Filter by status
const activeAccounts = await composio.connectedAccounts.list({
  statuses: ['ACTIVE']
});

// Filter by auth config
const configAccounts = await composio.connectedAccounts.list({
  authConfigIds: ['auth_config_123']
});

// Combine filters
const filtered = await composio.connectedAccounts.list({
  userIds: ['user_123'],
  toolkitSlugs: ['github', 'slack'],
  statuses: ['ACTIVE']
});

// Get specific account
const account = await composio.connectedAccounts.get('conn_abc123');
```

**Available filters:**
- `userIds` - Filter by user IDs
- `toolkitSlugs` - Filter by toolkit slugs
- `statuses` - Filter by connection statuses (see below for values)
- `authConfigIds` - Filter by auth config IDs
- `limit` - Results per page
- `cursor` - Pagination cursor
- `orderBy` - 'created_at' or 'updated_at'

## Update Connected Accounts

```typescript
// Enable/disable
await composio.connectedAccounts.enable('conn_abc123');
await composio.connectedAccounts.disable('conn_abc123');

// Refresh credentials (expired OAuth tokens)
await composio.connectedAccounts.refresh('conn_abc123');
```

## Delete Connected Accounts

```typescript
await composio.connectedAccounts.delete('conn_abc123');
```

**Warning:** Permanent deletion. User must re-authenticate.

## Wait for Connection Completion

For async OAuth flows:

```typescript
// Default timeout (60 seconds)
const account = await composio.connectedAccounts.waitForConnection('conn_123');

// Custom timeout (2 minutes)
const account = await composio.connectedAccounts.waitForConnection('conn_123', 120000);
```

**Errors:**
- `ComposioConnectedAccountNotFoundError` - Account doesn't exist
- `ConnectionRequestFailedError` - Connection failed/expired
- `ConnectionRequestTimeoutError` - Timeout exceeded

## Common Patterns

### OAuth Flow

```typescript
// Create connection
async function connectUser(userId, authConfigId) {
  const request = await composio.connectedAccounts.link(
    userId,
    authConfigId,
    { callbackUrl: 'https://app.com/callback' }
  );
  return { redirectUrl: request.redirectUrl };
}

// Handle callback
async function handleCallback(connectionId) {
  try {
    const account = await composio.connectedAccounts.waitForConnection(
      connectionId,
      180000
    );
    return { success: true, account };
  } catch (error) {
    if (error.name === 'ConnectionRequestTimeoutError') {
      return { error: 'Timeout. Please try again.' };
    }
    throw error;
  }
}
```

### Check Active Connections

```typescript
// Filter by status using statuses parameter
async function getUserActiveConnections(userId) {
  const accounts = await composio.connectedAccounts.list({
    userIds: [userId],
    statuses: ['ACTIVE']
  });
  return accounts.items;
}

// Check multiple statuses
async function getUserConnectionsByStatus(userId) {
  const accounts = await composio.connectedAccounts.list({
    userIds: [userId],
    statuses: ['ACTIVE', 'EXPIRED', 'FAILED']
  });
  return accounts.items;
}

async function isToolkitConnected(userId, toolkit) {
  const accounts = await composio.connectedAccounts.list({
    userIds: [userId],
    toolkitSlugs: [toolkit],
    statuses: ['ACTIVE']
  });
  return accounts.items.length > 0;
}
```

**Available statuses:**
- `INITIALIZING` - Connection being set up
- `INITIATED` - Connection initiated, awaiting completion
- `ACTIVE` - Connection active and ready to use
- `FAILED` - Connection failed
- `EXPIRED` - Credentials expired
- `INACTIVE` - Connection disabled

## Key Points

- **Prefer link()** - Security, UI, and whitelabeling handled
- **Store account IDs** - Save in your database, associate with users
- **Check status** - Verify ACTIVE before use, refresh on errors
- **Handle lifecycle** - Disable instead of delete when possible
