Authentication

Connected Accounts

Markdown

If you're building an agent, we recommend using sessions instead. See Managing multiple connected accounts for how sessions handle account selection automatically.

Connected accounts are authenticated connections between your users and toolkits. After users authenticate (see Authenticating tools), you can manage these accounts throughout their lifecycle.

Devcaster automatically handles token refresh and credential management. This guide covers manual operations: listing, retrieving, refreshing, enabling, disabling, and deleting accounts.

List accounts

Retrieve all connected accounts with optional filters:

# List all accounts for a user
accounts = devcaster.connected_accounts.list(
    user_ids=[user_id]
)

# Filter by status
active_accounts = devcaster.connected_accounts.list(
    user_ids=[user_id],
    statuses=["ACTIVE"]
)
import { Devcaster } from '@devcaster/core';
const devcaster = new Devcaster({ apiKey: 'your_api_key' });
const userId = 'user_123';
// ---cut---
// List all accounts for a user
const accounts = await devcaster.connectedAccounts.list({
  userIds: [userId]
});

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

Get account details

Retrieve a connected account by ID:

account = devcaster.connected_accounts.get(connected_account_id)

print(f"Status: {account.status}")
print(f"Toolkit: {account.toolkit.slug}")
import { Devcaster } from '@devcaster/core';
const devcaster = new Devcaster({ apiKey: 'your_api_key' });
const connectedAccountId = 'ca_123';
// ---cut---
const account = await devcaster.connectedAccounts.get(connectedAccountId);

console.log('Status:', account.status);
console.log('Toolkit:', account.toolkit.slug);

Get account credentials

Get account credentials for use with your own tools:

# Get the connected account's authentication state
if account.state:
    # The state contains the auth scheme and credentials
    auth_scheme = account.state.auth_scheme
    credentials = account.state.val

    print(f"Auth scheme: {auth_scheme}")
    print(f"Credentials: {credentials}")
import { Devcaster } from '@devcaster/core';
const devcaster = new Devcaster({ apiKey: 'your_api_key' });
const account = await devcaster.connectedAccounts.get('ca_123');
// ---cut---
// Get the connected account's authentication state
if (account.state) {
  // The state contains the auth scheme and credentials
  const authScheme = account.state.authScheme;
  const credentials = account.state.val;

  console.log('Auth scheme:', authScheme);
  console.log('Credentials:', credentials);
}

Refresh credentials

Manually refresh credentials for a connected account:

try:
    refreshed = devcaster.connected_accounts.refresh(connected_account_id)
    print(f"Redirect URL: {refreshed.redirect_url}")

    # Wait for the connection to be established
    devcaster.connected_accounts.wait_for_connection(refreshed.id)
except Exception as e:
    print(f"Failed to refresh tokens: {e}")
import { Devcaster } from '@devcaster/core';
const devcaster = new Devcaster({ apiKey: 'your_api_key' });
const connectedAccountId = 'ca_123';
// ---cut---
try {
  const refreshed = await devcaster.connectedAccounts.refresh(connectedAccountId);
  console.log('Redirect URL:', refreshed.redirect_url);

  // Wait for the connection to be established
  await devcaster.connectedAccounts.waitForConnection(refreshed.id);
} catch (error) {
  console.error('Failed to refresh tokens:', error);
}

Enable and disable accounts

Change account status without deleting. Set to INACTIVE to pause access, or ACTIVE to restore. Useful for:

  • Pausing access during subscription lapses
  • Temporary disconnection
# Disable an account
disabled = devcaster.connected_accounts.disable(connected_account_id)
print(f"Account disabled status: {disabled.success}")

# Re-enable when needed
enabled = devcaster.connected_accounts.enable(connected_account_id)
print(f"Account enabled status: {enabled.success}")
import { Devcaster } from '@devcaster/core';
const devcaster = new Devcaster({ apiKey: 'your_api_key' });
const connectedAccountId = 'ca_123';
// ---cut---
// Disable an account
const disabled = await devcaster.connectedAccounts.disable(connectedAccountId);
console.log('Account disabled status:', disabled.success);

// Re-enable when needed
const enabled = await devcaster.connectedAccounts.enable(connectedAccountId);
console.log('Account enabled status:', enabled.success);

INACTIVE accounts cannot execute tools. Tool execution will fail until the status is changed.

Delete accounts

Permanently remove a connected account and revoke all credentials:

# Delete a connected account
devcaster.connected_accounts.delete(connected_account_id)
print("Account deleted successfully")
import { Devcaster } from '@devcaster/core';
const devcaster = new Devcaster({ apiKey: 'your_api_key' });
const connectedAccountId = 'ca_123';
// ---cut---
// Delete a connected account
await devcaster.connectedAccounts.delete(connectedAccountId);
console.log('Account deleted successfully');

Deletion is permanent. Users must re-authenticate to reconnect.

Credential masking

By default, sensitive fields in connected account responses are partially masked for security. This affects fields like access_token, refresh_token, api_key, bearer_token, password, and other secrets.

Instead of returning full values, the API returns the first 4 characters followed by ...:

{
  "access_token": "gho_...",
  "refresh_token": "ghr_...",
  "api_key": "sk-l..."
}

Values shorter than 4 characters are replaced with REDACTED.

This applies to the Get Connected Account and List Connected Accounts endpoints.

Disabling masking

If you need full credential values (e.g., to use tokens in your own API calls), disable masking via either:

  1. Dashboard: Go to Settings → Project Settings → Project Configuration and turn off "Mask Connected Account Secrets"
  2. API:
curl -X PATCH https://backend.devcaster.dev/api/v3/org/project/config \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"mask_secret_keys_in_connected_account": false}'

Disabling masking exposes full credentials in API responses. Only disable this if your application needs direct access to tokens and you have appropriate security measures in place.

Multiple accounts

Users can connect multiple accounts for the same toolkit (e.g., personal and work Gmail).

Use link() for creating accounts, as it provides hosted authentication and allows multiple accounts by default. See Connect Link authentication.

# First account
try:
    first_account = devcaster.connected_accounts.initiate(
        user_id=user_id,
        auth_config_id=auth_config_id
    )
    print(f"First account redirect URL: {first_account.redirect_url}")
    connected_first_account = first_account.wait_for_connection()
    print(f"First account status: {connected_first_account.status}")
except Exception as e:
    print(f"Error initiating first account: {e}")

# Second account - must explicitly allow multiple
try:
    second_account = devcaster.connected_accounts.initiate(
        user_id=user_id,
        auth_config_id=auth_config_id,
        allow_multiple=True  # Required for additional accounts
    )
    print(f"Second account redirect URL: {second_account.redirect_url}")
    connected_second_account = second_account.wait_for_connection()
    print(f"Second account status: {connected_second_account.status}")
except Exception as e:
    print(f"Error initiating second account: {e}")
import { Devcaster } from '@devcaster/core';
const devcaster = new Devcaster({ apiKey: 'your_api_key' });
const userId = 'user_123';
const authConfigId = 'ac_123';
// ---cut---
// First account
try {
  const firstAccount = await devcaster.connectedAccounts.initiate(
    userId,
    authConfigId
  );
  console.log('First account redirect URL:', firstAccount.redirectUrl);
  const connectedFirstAccount = await firstAccount.waitForConnection();
  console.log('First account status:', connectedFirstAccount.status);
} catch (error) {
  console.error('Error initiating first account:', error);
}

// Second account - must explicitly allow multiple
try {
  const secondAccount = await devcaster.connectedAccounts.initiate(
    userId,
    authConfigId,
    {
      allowMultiple: true  // Required for additional accounts
    }
  );
  console.log('Second account redirect URL:', secondAccount.redirectUrl);
  const connectedSecondAccount = await secondAccount.waitForConnection();
  console.log('Second account status:', connectedSecondAccount.status);
} catch (error) {
  console.error('Error initiating second account:', error);
}

Execute with a specific account

When you have multiple accounts, specify which one to use with connected_account_id:

# Execute tool with a specific connected account
result = devcaster.tools.execute(
    "GMAIL_GET_PROFILE",
    user_id=user_id,
    connected_account_id=connected_account_id,  # Specify which account to use
    version="20251111_00",
    arguments={}
)
print(f"Tool executed: {result}")
import { Devcaster } from '@devcaster/core';
const devcaster = new Devcaster({ apiKey: 'your_api_key' });
const userId = 'user_123';
const connectedAccountId = 'ca_123';
// ---cut---
// Execute tool with a specific connected account
const result = await devcaster.tools.execute('GMAIL_GET_PROFILE', {
  userId: userId,
  connectedAccountId: connectedAccountId,  // Specify which account to use
  version: '20251111_00',
  arguments: {}
});
console.log('Tool executed:', result);