---
canonical_url: "https://docs.composio.dev/docs/extending-sessions/local-tools-and-toolkits"
markdown_url: "https://docs.composio.dev/docs/extending-sessions/local-tools-and-toolkits.md"
product: "platform"
version: null
last_updated: null
---

# Local Tools and Toolkits (/docs/extending-sessions/local-tools-and-toolkits)

For a complete agent that combines an internal lookup with a public API, follow [Combine your own tools with Composio](/examples/custom-tools).

If you already have your own tool definitions, start with [Managed authentication for your own tools](/docs/authentication/managed-auth-for-custom-tools). You can call the authenticated proxy from your code without adopting the experimental session-extension API below.

Local tools let you define tools that run in-process alongside remote Composio tools within a session. You have three patterns:

* **Standalone tools**: internal app logic that doesn't need Composio auth (DB lookups, in-memory data, business rules).
* **Extension tools**: wrap a Composio toolkit's API with custom business logic via `extendsToolkit` / `extends_toolkit`, using `ctx.proxyExecute()` / `ctx.proxy_execute()` for authenticated requests.
* **Local toolkits**: group related standalone tools under a namespace.

Local tools were previously called custom tools. The SDK options keep their names: `customTools` / `custom_tools` and `customToolkits` / `custom_toolkits`.

### standalone

**Install**

**Python:**

**uv:**

```bash
uv add composio
```

**pip:**

```bash
pip install composio
```

**TypeScript:**

**npm:**

```bash
npm install @composio/core zod
```

**pnpm:**

```bash
pnpm add @composio/core zod
```

**bun:**

```bash
bun add @composio/core zod
```

**yarn:**

```bash
yarn add @composio/core zod
```

**Initialize the client**

{/* code-example: no-api reason="SDK client constructor; REST calls send the key in the x-api-key header instead" */}

**Python:**
```python
from composio import Composio

composio = Composio(api_key="your_api_key")
```

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

const composio = new Composio({ apiKey: "your_api_key" });
```

**Create the tool**

A standalone tool handles internal app logic that doesn't need Composio auth. `ctx.userId` identifies which user's session is running.

{/* code-example: no-api reason="local tool handlers are defined and run in-process in the SDK" */}

**Python:**
```python
from pydantic import BaseModel

from composio import Composio

composio = Composio(api_key="your_api_key")

class UserLookupInput(BaseModel):
    pass

PROFILES = {
    "user_1": {"name": "Alice Johnson", "email": "alice@myapp.com", "tier": "enterprise"},
    "user_2": {"name": "Bob Smith", "email": "bob@myapp.com", "tier": "free"},
}

@composio.experimental.tool()
def get_user_profile(input: UserLookupInput, ctx):
    """Retrieve the current user's profile from the internal directory."""
    profile = PROFILES.get(ctx.user_id)
    if not profile:
        raise ValueError(f'No profile found for user "{ctx.user_id}"')
    return profile
```

**TypeScript:**
```typescript
import { Composio, experimental_createTool } from "@composio/core";
import { z } from "zod/v3";

const profiles: Record<string, { name: string; email: string; tier: string }> = {
  "user_1": { name: "Alice Johnson", email: "alice@myapp.com", tier: "enterprise" },
  "user_2": { name: "Bob Smith", email: "bob@myapp.com", tier: "free" },
};

const getUserProfile = experimental_createTool("GET_USER_PROFILE", {
  name: "Get user profile",
  description: "Retrieve the current user's profile from the internal directory",
  inputParams: z.object({}),
  execute: async (_input, ctx) => {
    const profile = profiles[ctx.userId];
    if (!profile) throw new Error(`No profile found for user "${ctx.userId}"`);
    return profile;
  },
});
```

**Bind to a session**

Pass local tools via the `experimental` option. `session.tools()` returns both remote Composio tools and your local tools.

{/* code-example: no-api reason="binding custom tools needs the SDK to serialize their schemas and run their handlers locally" */}

**Python:**
```python
from composio import Composio

composio = Composio(api_key="your_api_key")

session = composio.create(
    user_id="user_1",
    experimental={
        "custom_tools": [get_user_profile],
    },
)

tools = session.tools()
```

**TypeScript:**
```typescript
import { Composio, experimental_createTool } from "@composio/core";
import { z } from "zod/v3";

declare const getUserProfile: ReturnType<typeof experimental_createTool>;
const composio = new Composio({ apiKey: "your_api_key" });

const session = await composio.create("user_1", {
  experimental: {
    customTools: [getUserProfile],
  },
});

const tools = await session.tools();
```

### extension

**Install**

**Python:**

**uv:**

```bash
uv add composio
```

**pip:**

```bash
pip install composio
```

**TypeScript:**

**npm:**

```bash
npm install @composio/core zod
```

**pnpm:**

```bash
pnpm add @composio/core zod
```

**bun:**

```bash
bun add @composio/core zod
```

**yarn:**

```bash
yarn add @composio/core zod
```

**Initialize the client**

{/* code-example: no-api reason="SDK client constructor; REST calls send the key in the x-api-key header instead" */}

**Python:**
```python
from composio import Composio

composio = Composio(api_key="your_api_key")
```

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

const composio = new Composio({ apiKey: "your_api_key" });
```

**Create the tool**

An extension tool wraps a Composio toolkit's API with custom business logic. It inherits auth via `extendsToolkit` / `extends_toolkit`, so `ctx.proxyExecute()` / `ctx.proxy_execute()` handles credentials automatically.

Prefer relative `endpoint` values in proxy calls. They resolve against the toolkit base URL. Composio only accepts absolute URLs when they stay on the same scheme and registrable domain as that base URL.

{/* code-example: no-api reason="extension tool handlers are defined and run in-process in the SDK" */}

**Python:**
```python
import base64

from pydantic import BaseModel, Field

from composio import Composio

composio = Composio(api_key="your_api_key")

class PromoEmailInput(BaseModel):
    to: str = Field(description="Recipient email address")

@composio.experimental.tool(extends_toolkit="gmail")
def send_promo_email(input: PromoEmailInput, ctx):
    """Send the standard promotional email to a recipient."""
    subject = "You're invited to try MyApp Pro"
    body = (
        "Hi there,\n\n"
        "We'd love for you to try MyApp Pro — free for 14 days.\n\n"
        "Best,\nThe MyApp Team"
    )
    raw_msg = (
        f"To: {input.to}\r\n"
        f"Subject: {subject}\r\n"
        "Content-Type: text/plain; charset=UTF-8\r\n\r\n"
        f"{body}"
    )
    raw = base64.urlsafe_b64encode(raw_msg.encode()).decode().rstrip("=")

    res = ctx.proxy_execute(
        toolkit="gmail",
        endpoint="/gmail/v1/users/me/messages/send",
        method="POST",
        body={"raw": raw},
    )
    if res["result_type"] == "blocked":
        raise RuntimeError(res["message"])
    return {"status": res["status"], "to": input.to}
```

**TypeScript:**
```typescript
import { Composio, experimental_createTool } from "@composio/core";
import { z } from "zod/v3";

const sendPromoEmail = experimental_createTool("SEND_PROMO_EMAIL", {
  name: "Send promo email",
  description: "Send the standard promotional email to a recipient",
  extendsToolkit: "gmail",
  inputParams: z.object({
    to: z.string().describe("Recipient email address"),
  }),
  execute: async (input, ctx) => {
    const subject = "You're invited to try MyApp Pro";
    const body = "Hi there,\n\nWe'd love for you to try MyApp Pro — free for 14 days.\n\nBest,\nThe MyApp Team";
    const raw = btoa(`To: ${input.to}\r\nSubject: ${subject}\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n${body}`);

    const res = await ctx.proxyExecute({
      toolkit: "gmail",
      endpoint: "/gmail/v1/users/me/messages/send",
      method: "POST",
      body: { raw },
    });
    if (res.resultType === "blocked") {
      throw new Error(res.message);
    }
    return { status: res.status, to: input.to };
  },
});
```

**Bind to a session**

Pass local tools via the `experimental` option. Extension tools inherit auth from the toolkit specified in `extendsToolkit`.

{/* code-example: no-api reason="binding custom tools needs the SDK to serialize their schemas and run their handlers locally" */}

**Python:**
```python
from composio import Composio

composio = Composio(api_key="your_api_key")

session = composio.create(
    user_id="user_1",
    toolkits=["gmail"],
    experimental={
        "custom_tools": [send_promo_email],
    },
)

tools = session.tools()
```

**TypeScript:**
```typescript
import { Composio, experimental_createTool } from "@composio/core";
import { z } from "zod/v3";

declare const sendPromoEmail: ReturnType<typeof experimental_createTool>;
const composio = new Composio({ apiKey: "your_api_key" });

const session = await composio.create("user_1", {
  toolkits: ["gmail"],
  experimental: {
    customTools: [sendPromoEmail],
  },
});

const tools = await session.tools();
```

### toolkit

**Install**

**Python:**

**uv:**

```bash
uv add composio
```

**pip:**

```bash
pip install composio
```

**TypeScript:**

**npm:**

```bash
npm install @composio/core zod
```

**pnpm:**

```bash
pnpm add @composio/core zod
```

**bun:**

```bash
bun add @composio/core zod
```

**yarn:**

```bash
yarn add @composio/core zod
```

**Initialize the client**

{/* code-example: no-api reason="SDK client constructor; REST calls send the key in the x-api-key header instead" */}

**Python:**
```python
from composio import Composio

composio = Composio(api_key="your_api_key")
```

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

const composio = new Composio({ apiKey: "your_api_key" });
```

**Create the toolkit**

A local toolkit groups related standalone tools under a namespace. Tools inside a toolkit cannot use `extendsToolkit`.

{/* code-example: no-api reason="local toolkits and their tool handlers are defined in-process in the SDK" */}

**Python:**
```python
import typing as t

from pydantic import BaseModel, Field

from composio import Composio

composio = Composio(api_key="your_api_key")

user_management = composio.experimental.Toolkit(
    slug="USER_MANAGEMENT",
    name="User scoping",
    description="Manage user roles and permissions",
)

class AssignRoleInput(BaseModel):
    user_id: str = Field(description="Target user ID")
    role: t.Literal["admin", "editor", "viewer"] = Field(description="Role to assign")

@user_management.tool()
def assign_role(input: AssignRoleInput, ctx):
    """Assign a role to a user in the internal system."""
    return {"user_id": input.user_id, "role": input.role, "assigned": True}
```

**TypeScript:**
```typescript
import {
  Composio,
  experimental_createTool,
  experimental_createToolkit,
} from "@composio/core";
import { z } from "zod/v3";

const userManagement = experimental_createToolkit("USER_MANAGEMENT", {
  name: "User scoping",
  description: "Manage user roles and permissions",
  tools: [
    experimental_createTool("ASSIGN_ROLE", {
      name: "Assign role",
      description: "Assign a role to a user in the internal system",
      inputParams: z.object({
        user_id: z.string().describe("Target user ID"),
        role: z.enum(["admin", "editor", "viewer"]).describe("Role to assign"),
      }),
      execute: async ({ user_id, role }) => ({ user_id, role, assigned: true }),
    }),
  ],
});
```

**Bind to a session**

Pass local toolkits via the `experimental` option. `session.tools()` returns both remote Composio tools and your local toolkit's tools.

{/* code-example: no-api reason="binding custom toolkits needs the SDK to serialize their schemas and run their handlers locally" */}

**Python:**
```python
from composio import Composio

composio = Composio(api_key="your_api_key")

session = composio.create(
    user_id="user_1",
    experimental={
        "custom_toolkits": [user_management],
    },
)

tools = session.tools()
```

**TypeScript:**
```typescript
import { Composio, experimental_createToolkit } from "@composio/core";
import { z } from "zod/v3";

declare const userManagement: ReturnType<typeof experimental_createToolkit>;
const composio = new Composio({ apiKey: "your_api_key" });

const session = await composio.create("user_1", {
  experimental: {
    customToolkits: [userManagement],
  },
});

const tools = await session.tools();
```

## Preloading local tools [#preloading-local-tools]

Local tools are searchable by default. Set `preload: true` / `preload=True` on a
local tool when it should be returned directly from `session.tools()`. Toolkit
preload applies to all tools in that toolkit; set `preload: false` /
`preload=False` on one tool to opt it out.

{/* code-example: no-api reason="preload is a property of SDK-defined local tools whose handlers run in-process" */}

**Python:**
```python
from pydantic import BaseModel, Field

from composio import Composio
from composio_openai_agents import OpenAIAgentsProvider

composio = Composio(
    api_key="your_api_key",
    provider=OpenAIAgentsProvider(),
)

class ReplyGuideInput(BaseModel):
    topic: str = Field(description="Email topic")

@composio.experimental.tool(preload=True)
def get_reply_style_guide(input: ReplyGuideInput, ctx):
    """Return the team's email reply style guide."""
    return {"topic": input.topic, "tone": "concise and helpful"}

session = composio.create(
    user_id="user_1",
    experimental={
        "custom_tools": [get_reply_style_guide],
    },
)

tools = session.tools()
print([tool.name for tool in tools])
# LOCAL_GET_REPLY_STYLE_GUIDE
# COMPOSIO_SEARCH_TOOLS
# ... other default meta tools
```

**TypeScript:**
```typescript
import { Composio, experimental_createTool } from "@composio/core";
import { OpenAIAgentsProvider } from "@composio/openai-agents";
import { z } from "zod/v3";

const composio = new Composio({
  apiKey: "your_api_key",
  provider: new OpenAIAgentsProvider(),
});

const replyGuide = experimental_createTool("GET_REPLY_STYLE_GUIDE", {
  name: "Get reply style guide",
  description: "Return the team's email reply style guide",
  preload: true,
  inputParams: z.object({
    topic: z.string().describe("Email topic"),
  }),
  execute: async ({ topic }) => ({ topic, tone: "concise and helpful" }),
});

const session = await composio.create("user_1", {
  experimental: {
    customTools: [replyGuide],
  },
});

const tools = await session.tools();
console.log(tools.map((tool) => tool.name));
// LOCAL_GET_REPLY_STYLE_GUIDE
// COMPOSIO_SEARCH_TOOLS
// ... other default meta tools
```

## Meta tools integration [#meta-tools-integration]

Local tools work automatically with Composio's meta tools:

| Meta tool                     | Behavior                                                                                                    |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `COMPOSIO_SEARCH_TOOLS`       | Includes local tools in search results, with slight priority for tools that don't require auth              |
| `COMPOSIO_GET_TOOL_SCHEMAS`   | Returns schemas for local tools alongside remote tools                                                      |
| `COMPOSIO_MULTI_EXECUTE_TOOL` | Runs local tools in-process while remote tools go to the backend, merging results transparently             |
| `COMPOSIO_MANAGE_CONNECTIONS` | Handles auth for extension tools. If a tool extends `gmail`, the agent can prompt the user to connect Gmail |

> Local tools are not supported in the sandbox.

## Context object (`ctx`) [#context-object-ctx]

Every local tool's `execute` function receives `(input, ctx)`. Use `ctx` to access the current user, make authenticated API requests, or call other Composio tools.

**Python:**
| Property / Method                                                        | Description                                                  |
| ------------------------------------------------------------------------ | ------------------------------------------------------------ |
| `ctx.user_id`                                                            | The userID for the current session                           |
| `ctx.proxy_execute(toolkit, endpoint, method, body=None, parameters=[])` | Make an authenticated HTTP request via Composio's auth layer |
| `ctx.execute(tool_slug, arguments)`                                      | Execute any Composio native tool from within your local tool |

**TypeScript:**
| Property / Method                                                     | Description                                                  |
| --------------------------------------------------------------------- | ------------------------------------------------------------ |
| `ctx.userId`                                                          | The userID for the current session                           |
| `ctx.proxyExecute({ toolkit, endpoint, method, body?, parameters? })` | Make an authenticated HTTP request via Composio's auth layer |
| `ctx.execute(toolSlug, args)`                                         | Execute any Composio native tool from within your local tool |

See the full API in the SDK reference: [TypeScript](/reference/sdk-reference/typescript/session-context-impl) | [Python](/reference/sdk-reference/python/session-context-impl)

## Verifying registration [#verifying-registration]

Use these methods to list registered tools and toolkits. Slugs include their final `LOCAL_` prefix, and toolkit-scoped tools also include the toolkit slug.

{/* code-example: no-api reason="custom_tools() and custom_toolkits() read the SDK session's local registry" */}

**Python:**
```python
custom_tools = session.custom_tools()
custom_toolkits = session.custom_toolkits()
```

**TypeScript:**
```typescript
import { Composio } from "@composio/core";
const composio = new Composio({ apiKey: "your_api_key" });
const session = await composio.create("user_1");
const customTools = session.customTools();
const customToolkits = session.customToolkits();
```

## Reusing a session with local tools [#reusing-a-session-with-local-tools]

When [reusing a session](/docs/how-composio-works#how-sessions-behave) via `composio.use()`, you can attach local tools at the same time:

{/* code-example: no-api reason="attaching local tools on use() needs the SDK to run their handlers locally" */}

**Python:**
```python
from composio import Composio

composio = Composio(api_key="your_api_key")

session = composio.use(
    "session_id",
    custom_tools=[get_user_profile],
)
```

**TypeScript:**
```typescript
import { Composio, experimental_createTool } from "@composio/core";
import { z } from "zod/v3";

declare const getUserProfile: ReturnType<typeof experimental_createTool>;
const composio = new Composio({ apiKey: "your_api_key" });

const session = await composio.use("session_id", {
  customTools: [getUserProfile],
});
```

## Programmatic execution [#programmatic-execution]

Use `session.execute()` to run local tools directly, outside of an agent loop. Local tools execute in-process; remote tools are sent to the backend automatically.

{/* code-example: no-api reason="local tools execute in-process in the SDK, not on the backend" */}

**Python:**
```python
result = session.execute("GET_USER_PROFILE")
```

**TypeScript:**
```typescript
import { Composio } from "@composio/core";
const composio = new Composio({ apiKey: "your_api_key" });
const session = await composio.create("user_1");
const result = await session.execute("GET_USER_PROFILE");
```

## Best practices [#best-practices]

### Naming and descriptions [#naming-and-descriptions]

The agent relies on your tool's name and description to decide when to call it. Be specific: "Send weekly promo email" is better than "Send email". Include what the tool does, when to use it, and what it returns.

In TypeScript, use uppercase slugs like `SEND_PROMO_EMAIL`. In Python, slugs are inferred from the function name, so `snake_case` produces clean defaults. You can also pass `slug` and `name` explicitly.

### Accessing authenticated APIs [#accessing-authenticated-apis]

If your tool calls an API that requires user credentials (Gmail, GitHub, and so on), set `extendsToolkit` / `extends_toolkit` to the toolkit name. Composio handles authentication automatically, and the agent can prompt users to connect their account when needed.

### Defining inputs in Python [#defining-inputs-in-python]

Your tool's first parameter must be a Pydantic `BaseModel`. The field descriptions become what the agent sees as the input schema, and the function's docstring becomes the tool description. You can override this by passing `description` explicitly.

### Tool names get prefixed [#tool-names-get-prefixed]

Slugs exposed to the agent are automatically prefixed with `LOCAL_` and the toolkit name (if applicable):

* `GET_USER_PROFILE` becomes `LOCAL_GET_USER_PROFILE`
* `ASSIGN_ROLE` in `USER_MANAGEMENT` becomes `LOCAL_USER_MANAGEMENT_ASSIGN_ROLE`

Your slugs cannot start with `LOCAL_`. This prefix is reserved.

Use the final `LOCAL_*` slug when you call a local tool directly. The original slug still works
when it identifies exactly one local tool. If two local toolkits both define `SEARCH`, call
`LOCAL_CATALOG_SEARCH` or `LOCAL_SUPPORT_SEARCH`. Calling `SEARCH` directly raises an error with the
qualified slugs instead of choosing one toolkit silently.

## Next [#next]

- [Configuring sessions](/docs/configuring-sessions): Filter toolkits and tools, set auth configs, and read tools with session.tools()

---

📚 **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

For agent tool delivery, Composio supports **Native Tools** (with a provider package) and **MCP** (no provider package needed).

For application-owned tools or deterministic backend code, use [managed authentication with Proxy Execute](/docs/authentication/managed-auth-for-custom-tools). A session can provide a user-scoped runtime context without an LLM or MCP. Composio authenticates provider requests server-side; connected-account reads redact credentials and aren't a supported token-export workflow.

### Native Tools

#### Python

```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

```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
```

#### API

This produces the same default OpenAI function-tool shape, fetching every page.

```bash
set -euo pipefail
SESSION=$(curl --fail-with-body -s -X POST https://backend.composio.dev/api/v3.1/tool_router/session \
  -H "x-api-key: $COMPOSIO_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"user_123"}')
SESSION_ID=$(echo "$SESSION" | jq -r '.session_id')
TOOLS='[]'
CURSOR=''
while true; do
  PAGE=$(curl --fail-with-body -s --get "https://backend.composio.dev/api/v3.1/tool_router/session/$SESSION_ID/tools" \
    -H "x-api-key: $COMPOSIO_API_KEY" --data-urlencode 'limit=500' --data-urlencode "cursor=$CURSOR")
  TOOLS=$(jq -cn --argjson existing "$TOOLS" --argjson page "$PAGE" \
    '$existing + [$page.items[] | {type:"function",function:{name:.slug,description:.description,parameters:.input_parameters}}]')
  CURSOR=$(echo "$PAGE" | jq -r '.next_cursor // empty')
  [ -n "$CURSOR" ] || break
done
printf '%s\n' "$TOOLS"
```

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.

