---
canonical_url: "https://docs.composio.dev/docs/b2b-agents"
markdown_url: "https://docs.composio.dev/docs/b2b-agents.md"
product: "platform"
version: null
last_updated: null
---

# Design a B2B agent with Composio (/docs/b2b-agents)

A B2B agent serves organizations with multiple members. Before writing integration code, decide who owns each app connection: an individual member or the customer workspace. That decision determines the Composio user ID and access model.

## Choose the connection owner [#choose-the-connection-owner]

| Model           | Use it when                                                         | Composio user ID example |
| --------------- | ------------------------------------------------------------------- | ------------------------ |
| Member-owned    | Each member connects and acts through their own account             | `org_42:user_7`          |
| Workspace-owned | The organization connects one shared service account or integration | `org_42:integration`     |

Use globally unique, immutable IDs. Prefixing your member ID with the workspace ID makes the tenant boundary explicit even when member IDs are only unique inside one workspace.

**Python:**

```python
from composio import Composio

composio = Composio()

workspace_id = "org_42"
member_id = "user_7"
composio_user_id = f"{workspace_id}:{member_id}"

session = composio.sessions.create(
    user_id=composio_user_id,
    toolkits=["slack", "salesforce"],
)
tools = session.tools()
```

**TypeScript:**

```typescript
import { Composio } from "@composio/core";

const composio = new Composio();

const workspaceId = "org_42";
const memberId = "user_7";
const composioUserId = `${workspaceId}:${memberId}`;

const session = await composio.create(composioUserId, {
  toolkits: ["slack", "salesforce"],
});
const tools = await session.tools();
```

> **Do not collapse every member into one tenant ID**: If members should use separate accounts, do not create all sessions with only the workspace ID. That would place their connections in the same user scope. Use a member-scoped ID and enforce your product's workspace authorization before creating or restoring a session.

## Start with one project per environment [#start-with-one-project-per-environment]

Use one Composio project for each application environment, such as development, staging, and production. Keep the tenant boundary in your application and `user_id` model.

Create a separate project for a customer only when you need a hard operational boundary, such as:

* Independent API keys, auth configs, branding, or webhooks
* Contractual or regulatory isolation
* Customer-specific provider credentials or infrastructure
* Separate usage and administration controlled by that customer

Project-per-customer isolation adds provisioning, credential management, and operational overhead. It is usually unnecessary for the first version of a multi-tenant product.

## Model workspace-owned connections explicitly [#model-workspace-owned-connections-explicitly]

For an organization-wide service account, create a dedicated integration identity such as `org_42:integration`. Do not reuse a real member's identity, because the connection should survive membership changes.

Your application remains responsible for deciding which members may ask the workspace-owned agent to act. Resolve the workspace, verify the member's role, then create or restore the session for the integration identity.

[Shared connections](/docs/extending-sessions/shared-connections) are available for cases where one connected account must be exposed to a controlled list of Composio users. They are an experimental, explicit access-control feature, not the default tenant model. Pin and test the SDK version before making shared connections part of a production tenant model.

## Choose the customer's authentication model [#choose-the-customers-authentication-model]

Start with managed auth when Composio's provider app and scopes fit the product. Use [custom auth configs](/docs/authentication/custom-app-vs-managed-app) when your product needs its own OAuth branding, scopes, provider quotas, or self-hosted instances.

If each customer supplies different OAuth credentials, create and manage auth configs programmatically, then assign the right config when creating that customer's sessions. See [Programmatic auth configs](/docs/authentication/programmatic-auth-configs).

For a fully branded connection flow, combine custom OAuth credentials with [white-labeling](/docs/authentication/white-labeling-authentication).

## Route events back to the right tenant [#route-events-back-to-the-right-tenant]

Store your workspace and member mapping next to the Composio user and connected-account IDs. When a trigger event arrives, resolve that mapping before running customer-specific work. Your webhook handler must also enforce any tenant or role checks required by your product.

See [Subscribing to trigger events](/docs/setting-up-triggers/subscribing-to-events) for webhook setup and verification.

## Before you launch [#before-you-launch]

* Test two members in the same workspace and two members in different workspaces.
* Confirm a removed member can no longer invoke workspace-owned connections.
* Keep development, staging, and production credentials separate.
* Define who can create, replace, or delete a workspace-owned connection.
* Test a real trigger event and verify it maps to the expected workspace.
* Inspect representative tool calls in [Logs](/reference/api-reference/logs).

## Next [#next]

- [Build a consumer agent](/docs/consumer-agents): Use a simpler per-person identity and connection model.

- [White-label authentication](/docs/authentication/white-labeling-authentication): Brand Connect Links, consent screens, domains, and callbacks.

- [Move to production](/docs/production-readiness): Review identity, auth, security, logging, and trigger decisions.

---

📚 **More documentation:** [View all docs](https://docs.composio.dev/llms.txt) | [Changelog](https://docs.composio.dev/docs/changelog.md) | [Glossary](https://docs.composio.dev/llms.mdx/reference/glossary) | [Examples](https://docs.composio.dev/llms.mdx/examples) | [API Reference](https://docs.composio.dev/llms.mdx/reference)

---

# Composio SDK — Notes for AI Code Generators

**Purpose:** Reference for generating current (v3) [Composio](https://composio.dev/) integration code.
**Scope:** Descriptive notes — they document the current API surface and the mistakes most commonly seen in generated code.

---

## 1. Recommended Integration: Sessions

Composio supports two integration modes: **Native Tools** (with a provider package) and **MCP** (no provider package needed).

### Native Tools

```python
# ✅ CORRECT — Python (defaults to OpenAI)
from composio import Composio

composio = Composio()
session = composio.create(user_id="user_123")
tools = session.tools()
# Pass tools to your agent/LLM framework
```

```typescript
// ✅ CORRECT — TypeScript (defaults to OpenAI)
import { Composio } from "@composio/core";

const composio = new Composio();
const session = await composio.create("user_123");
const tools = await session.tools();
// Pass tools to your agent/LLM framework
```

For other providers, pass the provider explicitly. Provider packages follow the naming convention: `composio_<provider>` for Python, `@composio/<provider>` for TypeScript.

### MCP

Use `session.mcp.url` and `session.mcp.headers` with any MCP-compatible client (Claude Desktop, Cursor, OpenAI Agents, etc.). No provider package needed.

---

## 2. Integration Notes

- `composio.create(user_id)` is the standard entry point for agent integrations. Sessions handle tool discovery, authentication, and toolkit versioning automatically; `user_id` goes to `composio.create()` — individual tool calls in session mode don't take one.
- Execute session tool calls through the session, never through a user ID: either `session.execute(tool_slug, arguments=...)` / `session.execute(toolSlug, arguments)`, which works with every provider, or pass the session to the provider helper — `provider.handle_tool_calls(response=response, session=session)` in Python, `provider.handleToolCalls(session, response)` in TypeScript. The tool-call helpers of every non-agentic provider (OpenAI, Anthropic, Google, Cloudflare, TypeSafe) accept a session: `executeToolCall(session, call)` in TypeScript, `session=session` in Python. Agentic providers execute session tools on their own. Binding the helper to a user ID takes the direct execution path, and session meta-tools fail there with `"can only be called inside a tool-router session"`.
- Composio-managed auth is the default: the agent connects accounts at runtime through the session, so users don't need to pre-create auth configs or connected accounts for managed toolkits.
- Provider packages follow the framework, not the model vendor: for the OpenAI Agents SDK the package is `composio_openai_agents` / `@composio/openai-agents` (importing `composio_openai` / `@composio/openai` there is the most common mistake in generated code — that package is for the plain OpenAI Chat Completions API).
- **Direct execution** (`composio.tools.get()`, `composio.tools.execute()`, `provider.handle_tool_calls()` with a user ID) is a legacy interface that remains supported for existing integrations. Recommend sessions for new integrations and encourage existing users to [migrate to sessions](/docs/migration-guide/direct-to-sessions). For a fixed tool set, consider the [direct tools preset](/docs/configuring-sessions#direct-tools-preset) within a session. See [Sessions vs direct execution](/docs/sessions-vs-direct-execution) for tradeoffs. Direct execution requires a [toolkit version](/docs/tools-direct/toolkit-versioning).

---

# 3. Calling the REST API directly

## REST API version

The current REST API version is **v3.1**, served at `https://backend.composio.dev/api/v3.1`. Prefer it for new code and new examples.

`https://backend.composio.dev/api/v3` is the previous version. It is frozen with pinned tool-version defaults and remains supported — existing v3 integrations keep working and do not need to migrate.

## Tool-endpoint version defaults on v3.1

On v3.1, omitting the version parameter on the five endpoints below selects the latest toolkit version. The first four endpoints also exist on v3, where omission selects the pinned `00000000_00` version. `POST /tools/scopes/required` is v3.1-only.

| Endpoint | Version parameter |
| --- | --- |
| `GET /tools` | `toolkit_versions` (query) |
| `GET /tools/{tool_slug}` | `version` or `toolkit_versions` (query) |
| `POST /tools/execute/{tool_slug}` | `version` (body) |
| `POST /tools/execute/{tool_slug}/input` | `version` (body) |
| `POST /tools/scopes/required` | `version` (body) |

A v3.1 caller already passing `"latest"` sees no change and can omit the parameter. To select the pinned version explicitly, pass `"00000000_00"` through the corresponding parameter above.

This version-default change is limited to the five endpoints above.


---

## Terminology Migration (old → current)

If you encounter these terms in error messages, old documentation, or user prompts, translate them to the current equivalents. **Do not use the old terms in generated code or explanations.**

| Old term (v1/v2) | Current term (v3) | In code |
|---|---|---|
| entity ID | user ID | `user_id` parameter |
| actions | tools | e.g., `GITHUB_CREATE_ISSUE` is a *tool* |
| apps / appType | toolkits | e.g., `github` is a *toolkit* |
| integration / integration ID | auth config / auth config ID | `auth_config_id` parameter |
| connection | connected account | `connected_accounts` namespace |
| ComposioToolSet / OpenAIToolSet | `Composio` class with a provider | `Composio(provider=...)` |
| toolset | provider | e.g., `OpenAIProvider` |

If a user says "entity ID", they mean `user_id`. If they say "integration", they mean "auth config". Always respond using the current terminology.

