Design a consumer agent with Composio

A consumer agent serves individual end users. Each person signs in to your product, connects their own apps, and expects their connections and tool calls to stay isolated from everyone else.

Start with one Composio project per application environment. Map every signed-in user to a stable ID from your database, then create and reuse sessions for that ID.

from composio import Composio

composio = Composio()

# Use the immutable primary key from your application database.
app_user_id = "usr_01J9F4M2K8"
session = composio.sessions.create(
    user_id=app_user_id,
    toolkits=["gmail", "googlecalendar"],
)
tools = session.tools()
DecisionStart withChange it when
Project boundaryOne project for each environment, such as development and productionA separate product needs independent credentials, branding, or isolation
Composio user IDYour immutable application user IDNever change it for an existing user
Connected accountsPrivate to that userA deliberate shared-account workflow requires controlled sharing
Session lifecycleStore the session ID and reuse it for the conversationA new task needs a clean execution context
AuthenticationComposio managed authYou need your own branding, scopes, quotas, or provider app
Tool accessOnly the toolkits your feature needsThe product intentionally supports broader discovery

Keep identity stable

Use a database UUID or immutable primary key for user_id. Do not use an email address because it can change. Never use a shared value such as default in production, because different customers could inherit the same connection scope.

The same application user can connect several accounts for one toolkit, such as personal and work Gmail. Keep the same user_id and select the connected account when a session needs a specific one.

Reuse sessions for multi-turn work

Every call to create() makes a new session. Store session.session_id in Python or session.sessionId in TypeScript with your conversation, then restore it with composio.use(session_id). Reusing the session preserves its tool, authentication, and workbench context.

Create a new session for a different user or a genuinely separate task. Do not create a new session for every message in the same conversation.

Choose the connection experience

Use in-chat authentication when the agent can show a Connect Link and wait for the user. Use manual authentication when your product has its own integrations page or connection flow.

Managed auth is the quickest way to start. Create a custom auth config only when you need your own consent-screen branding, custom scopes, dedicated provider quotas, or a custom provider instance.

Limit the agent to its job

Restrict the session to the toolkits and tools your feature needs. This makes discovery more relevant and prevents unrelated actions from appearing in the agent's available surface. Ask for user confirmation before destructive or externally visible actions.

Run sessions in your organization's consumer project

Every Composio organization also has one consumer project: the project behind Composio Connect and the For You clients, where your members' own connections live. Sessions in it are created with a user API key and an explicit scope. Composio never mints or returns the consumer project's API key.

Resolve the consumer project once to learn its nano IDs, then build the SDK instance with the user API key and that scope. The SDK sends x-user-api-key, x-org-id, and x-project-id on every request, carries the scope into session clones, and exports it with the session's MCP config:

import os

from composio import Composio

composio = Composio(
    disable_api_key=True,
    user_api_key=os.environ["COMPOSIO_USER_API_KEY"],
    org_id="org_9f2k1x",
    project_id="proj_4b8m2q",
)

session = composio.sessions.create(user_id="user_123", toolkits=["gmail"], mcp=True)
# session.mcp.headers carries x-user-api-key, x-org-id, and x-project-id

orgId and projectId (org_id and project_id in Python) must be set together, and the SDK rejects a scope that disagrees with x-org-id or x-project-id entries in defaultHeaders. Without a scope, a user API key addresses the API default (the developer project).

Requires @composio/core0.19.1 (TypeScript) or composio0.22.1 (Python).

The nano IDs come from the consumer project resolve endpoint, which also provisions the project when it is absent. The same credential lists the toolkits a consumer user has already connected:

# Resolve the organization's consumer project
curl -X POST https://backend.composio.dev/api/v3.1/org/consumer/project/resolve \
  -H "x-user-api-key: $COMPOSIO_USER_API_KEY" \
  -H "x-org-id: $COMPOSIO_ORG_ID"
# {"project_id": "<uuid>", "project_nano_id": "proj_4b8m2q", "org_id": "org_9f2k1x",
#  "project_type": "CONSUMER", "config": {"consumer_experience_enabled": true, ...}}

# List the toolkits this consumer user has already connected
curl "https://backend.composio.dev/api/v3.1/org/consumer/connected_toolkits?user_id=user_123" \
  -H "x-user-api-key: $COMPOSIO_USER_API_KEY" \
  -H "x-org-id: $COMPOSIO_ORG_ID"
# {"toolkits": ["gmail"], "project_id": "<uuid>", "project_nano_id": "proj_4b8m2q", "user_id": "user_123"}

Use the nano IDs from the resolve response (org_id and project_nano_id), always together. A UUID in either header is a 400. A user with no connections yet gets an empty toolkits list with the project scope still filled in, so you can create their session before they connect anything. An organization without a consumer project gets null project identifiers from the listing, which never provisions one. Raw HTTP callers create the session with the same three headers on POST /api/v3.1/tool_router/session.

Before you launch

  • Verify two different users cannot see or use each other's connected accounts.
  • Test both the first-time connection flow and a returning user with an existing connection.
  • Store the Composio API key only on your server. A session created with a user API key exports that organization-wide key in its MCP config, so store that config as a secret too.
  • Persist session IDs for multi-turn conversations.
  • Test one safe, read-only call against a real connected account.
  • Inspect the resulting tool call in Logs.

Next