Managed authentication for your own tools

Use Composio to connect your users' accounts and authenticate API requests from your own tools. Your tool definitions and business logic stay in your application. Composio stores and refreshes credentials, sends requests to the provider, and returns the response. You don't need predefined Composio tools, MCP, or an LLM.

Connect an account and call its API

This example connects GitHub and reads the connected account's profile through GET /user. Install the SDK with pip install composio or npm install @composio/core. Set COMPOSIO_API_KEY in your backend environment. The API example also needs Bash and jq.

For a scoped project API key, enable Session management → Write only, Connected accounts → Read only, and Proxy execute (Legacy) → Write only. Default full-access project keys already include these permissions. Keep the key server-side.

The example uses org_42:user_7 as an illustrative connection owner. In your application, derive this value from the authenticated request after checking workspace membership. Never accept a model-supplied or browser-supplied owner ID as authorization. See B2B connection ownership for member-owned and workspace-owned connections.

This example uses a session to create the Connect Link. Subsequent API calls pass the owner and connected-account ID directly.

from composio import Composio

composio = Composio()

# Resolve this owner from your authenticated application session.
owner_id = "org_42:user_7"
session = composio.create(user_id=owner_id, toolkits=["github"])
connection = session.authorize("github")
print(f"Open this Connect Link: {connection.redirect_url}")
account = connection.wait_for_connection(60)


def read_github_profile(connected_account_id: str):
    return composio.tools.proxy(
        user_id=owner_id,
        connected_account_id=connected_account_id,
        endpoint="/user",
        method="GET",
    )


# Use the account returned by this owner's connection attempt.
response = read_github_profile(account.id)
print(response.status)
print(response.data)

Open the printed Connect Link and authorize GitHub within 60 seconds. The connection must become ACTIVE before the proxy request runs. During this wait, INACTIVE can still become ACTIVE, so keep polling until the deadline. Stop immediately on FAILED, EXPIRED, or REVOKED. The proxy returns the provider's response, not its OAuth token. No tool schemas are fetched and no model selects an action.

The proxy response contains the provider's response body in data, HTTP status in status, and response headers in headers.

The direct proxy call is independent of the session: pass both the owner (user_id in Python and REST, userId in TypeScript) and the exact returned connected-account ID. Projects with 2FA enabled check the owner against the connected account and may require it. Your own tool can call read_github_profile() or readGitHubProfile() from any framework, or you can make the equivalent REST request from your backend.

Reuse connections in your application

For onboarding or a settings page, send the Connect Link to the browser and persist the connection attempt server-side rather than holding an HTTP request open while the person signs in. When creating the Connect Link, pass your application's callback URL through callback_url in Python and REST, or callbackUrl in TypeScript. See Redirecting users after authentication for setup.

On callback, verify that the connected-account ID belongs to that attempt and the signed-in owner, then retrieve its current status. Callback query parameters aren't proof of ownership. See Identify the connected account.

Store the verified connected-account ID with your tenant/member mapping. On subsequent requests, authorize the caller and resolve that stored mapping before calling the proxy. Don't expose an endpoint that accepts arbitrary connected-account IDs from a browser or model.

If authorization fails or times out, don't call the provider. If a connection expires or is revoked, stop using it and ask its owner to reconnect. Subscribe to the connection expiration event to surface reconnect prompts before a request fails. Don't automatically retry side-effecting provider requests; see retry failed executions deliberately.

Choose your OAuth app separately

Keep Composio-managed OAuth apps in production when their consent branding, scopes, and shared provider quotas fit your product. Use a custom auth config when you need your own OAuth app, different scopes, dedicated quotas, or a custom provider instance. Composio still manages the resulting connected-account credentials in either case.

Understand the boundaries

Managed authentication is not token export

This is managed authentication for your own tools, not a token-export service. Connected-account reads redact credentials and aren't a supported way to retrieve access or refresh tokens. A custom OAuth app doesn't move token custody out of Composio Cloud. If your local vendor SDK requires a raw token, this proxy pattern doesn't supply one. Review token custody and deployment options instead of attempting to extract credentials from account state.

  • Provider scope: Use relative paths for the connected provider. Proxy Execute is not an arbitrary authenticated HTTP relay; absolute URLs must keep the connected provider's scheme and registrable domain.
  • Authorization: Proxy permission can allow operations beyond predefined tool schemas. Validate endpoints and inputs in your own tools, enforce your application's tenant and role checks, and request only the provider scopes you need.
  • Deployment: Private VPC and self-hosted deployments are Enterprise arrangements. Customer-managed keys require a separate deployment design with Composio. Confirm operator access, decryption, logs, and backups against your requirements.

Next