Manual auth management

Manual authentication lets you connect users to toolkits outside the chat flow. Reach for it when you want to:

  • Pre-authenticate users before they start chatting.
  • Build a custom connections UI in your app.

Authorize a toolkit

Call session.authorize() to generate a Connect Link URL, redirect the user, and wait for them to finish:

session = composio.create(user_id="user_123")

connection_request = session.authorize("gmail")

print(connection_request.redirect_url)
# https://connect.composio.dev/link/ln_abc123

connected_account = connection_request.wait_for_connection(60000)
print(f"Connected: {connected_account.id}")

Redirect the user to the redirectUrl. After they authenticate, they'll return to your callback URL. The connection request polls until the user completes authentication (default timeout: 60 seconds).

If the user closes the Connect Link without completing auth, the connection remains in INITIATED status until it expires.

Reusing an active connection

In TypeScript, call session.ensureConnected("gmail") to reuse the session's active connection. If none exists, it starts an authorization flow and waits for the connection to become active. The optional timeout sets how long to wait in milliseconds (default: 60000).

The result includes toolkit, wasConnected, and connectedAccount. wasConnected is true when an active connection already exists. Toolkits that don't require authentication also return wasConnected: true, with no connectedAccount.

Use session.authorize() and waitForConnection() when you need to display or redirect to the Connect Link. ensureConnected() waits internally and doesn't return the redirect URL. Calling authorize() always starts a new authorization flow, even if an active connection exists.

Redirecting users after authentication

Pass a callbackUrl to control where users land after authenticating. You can include query parameters to carry context through the flow, for example to identify which userID or session triggered the connection.

connection_request = session.authorize(
    "gmail",
    callback_url="https://your-app.com/callback?user_id=user_123&source=onboarding"
)

print(connection_request.redirect_url)

After authentication, Composio redirects the user to your callback URL with the following parameters appended, while preserving your existing ones:

ParameterDescription
statussuccess or failed
connected_account_idThe ID of the newly created connected account
https://your-app.com/callback?user_id=user_123&source=onboarding&status=success&connected_account_id=ca_abc123

Identify the connected account

After authentication, use the account returned by wait_for_connection() or waitForConnection() to identify which provider account connected. If you use a callback instead, verify its connected_account_id against the connection attempt stored on your server and the signed-in application user before retrieving it. Callback query parameters alone aren't proof of ownership.

These identifiers serve different purposes:

ValueMeaning
Application user IDYour application's user, such as user_123. This doesn't identify their Gmail or GitHub account.
Connected account IDOne connection in Composio, such as ca_abc123. Use it to select the account for a profile request.
AliasA label you assign, such as work-gmail. It doesn't prove which account connected.
Provider identityThe email address, username, or ID returned by the provider for the authenticated account.

For a "Connected as…" label, read state.val.displayName when available. For additional profile fields, get the toolkit's current-user endpoint and call it through Composio's authenticated proxy with the same connected account ID:

# Continue after connected_account = connection_request.wait_for_connection().
account_id = connected_account.id
state = connected_account.model_dump().get("state") or {}
display_name = state.get("val", {}).get("displayName")
print(f"Connected as {display_name}" if display_name else "Account connected")

toolkit = composio.toolkits.get(connected_account.toolkit.slug)
endpoint = toolkit.get_current_user_endpoint
method = toolkit.get_current_user_endpoint_method
if not endpoint or method not in ("GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"):
    raise ValueError("This toolkit doesn't publish a supported current-user endpoint")

response = composio.tools.proxy(
    endpoint=endpoint,
    method=method,
    connected_account_id=account_id,
)
if response["status"] != 200:
    raise RuntimeError("Could not retrieve the connected account's profile")
profile = response["data"]  # Parse the fields documented by this provider.

Toolkit metadata describes where to request a profile; it doesn't contain the account owner's details. Endpoint availability, required scopes, and response fields vary by provider. If the metadata is absent, use a documented profile endpoint or tool for that provider. A GitHub username or display label isn't a verified email address.

For an application policy, validate the provider's response before giving an agent access to the account. Follow Restrict Gmail connections to an email domain for a complete example that checks the mailbox address and disables rejected connections. Return only the fields your UI needs, not the account's authentication state.

Check connection status

Use session.toolkits() to see all toolkits in the session and their connection status:

toolkits = session.toolkits()

for toolkit in toolkits.items:
    status = toolkit.connection.connected_account.id if toolkit.connection.is_active else "Not connected"
    print(f"{toolkit.name}: {status}")

Disabling in-chat auth

By default, sessions include the COMPOSIO_MANAGE_CONNECTIONS meta-tool that prompts users to authenticate during chat. To turn it off and handle auth entirely in your own UI, set manage_connections to False:

session = composio.create(
    user_id="user_123",
    manage_connections=False,
)

Putting it together

A common pattern is to verify all required connections before starting the agent:

from composio import Composio

composio = Composio(api_key="your-api-key")

required_toolkits = ["gmail", "github"]

session = composio.create(
    user_id="user_123",
    manage_connections=False,  # Disable in-chat auth prompts
)

toolkits = session.toolkits()

connected = {t.slug for t in toolkits.items if t.connection.is_active}
pending = [slug for slug in required_toolkits if slug not in connected]

print(f"Connected: {connected}")
print(f"Pending: {pending}")

for slug in pending:
    connection_request = session.authorize(slug)
    print(f"Connect {slug}: {connection_request.redirect_url}")
    connection_request.wait_for_connection()

print("All toolkits connected!")

Next

Managing multiple connected accounts

Let a user connect work and personal accounts for the same toolkit, then pick which one runs