---
canonical_url: "https://docs.composio.dev/examples/typesafe-tool-selection"
markdown_url: "https://docs.composio.dev/examples/typesafe-tool-selection.md"
product: "platform"
version: null
last_updated: null
---

# Choose and run tools with TypeSafe (/examples/typesafe-tool-selection)

Build a script that turns "Look up the Hacker News user 'pg'" into a tool call. TypeSafe's Jev selects the tool. Your code supplies the username, then Composio runs the call.

This example uses public Hacker News data, so you need no connected account. It follows the runnable [TypeScript examples](https://github.com/ComposioHQ/composio/tree/next/ts/examples/typesafe) and [Python demo](https://github.com/ComposioHQ/composio/blob/next/python/providers/typesafe/typesafe_demo.py).

## Set up the script [#set-up-the-script]

Install the [TypeSafe provider](/docs/providers/typesafe#install), then export `COMPOSIO_API_KEY` and `TYPESAFE_API_KEY` in your shell. The TypeScript version requires Node.js 24.17 or newer.

## Choose, complete, and execute [#choose-complete-and-execute]

Save the following script. `username` represents a value supplied by your application or a user form. The request asks Jev to choose among three tools, and `knownArguments` supplies values only for the tool they belong to.

**Python:**

```python title="select-tool.py"
import json

from composio import Composio
from composio_typesafe import TypesafeProvider

provider = TypesafeProvider()
composio = Composio(provider=provider)
user_id = "example-user"
username = "pg"
request = f"Look up the Hacker News user '{username}'"
known_arguments = {"HACKERNEWS_GET_USER": {"username": username}}

def main():
    tool_set = composio.tools.get(
        user_id=user_id,
        tools=[
            "HACKERNEWS_GET_USER",
            "HACKERNEWS_GET_TOP_STORIES",
            "HACKERNEWS_SEARCH_POSTS",
        ],
    )
    decision = provider.decide(tool_set, request)
    print(json.dumps(decision, indent=2))

    if decision["kind"] == "abstain":
        print(f"No tool call: {decision['reason']}")
        return

    if decision["risk"] != "read_only":
        print(f"This example does not execute {decision['risk']} tools.")
        return

    if decision["kind"] == "partial":
        print("Arguments to supply:", decision["missing"])

    result = provider.execute(
        user_id,
        decision,
        arguments=known_arguments.get(decision["tool"], {}),
    )
    print(result["data"])

if __name__ == "__main__":
    main()
```
Run the script in the environment where you installed the packages:

```bash
python select-tool.py
```
**TypeScript:**

```typescript title="select-tool.ts"
import { Composio } from '@composio/core';
import { TypesafeProvider } from '@composio/typesafe';

const provider = new TypesafeProvider();
const composio = new Composio({ provider });
const userId = 'example-user';
const username = 'pg';
const request = `Look up the Hacker News user '${username}'`;
const knownArguments: Record<string, Record<string, unknown>> = {
  HACKERNEWS_GET_USER: { username },
};

async function main() {
  const toolSet = await composio.tools.get(userId, {
    tools: [
      'HACKERNEWS_GET_USER',
      'HACKERNEWS_GET_TOP_STORIES',
      'HACKERNEWS_SEARCH_POSTS',
    ],
  });
  const decision = await provider.decide(toolSet, request);
  console.log(JSON.stringify(decision, null, 2));

  if (decision.kind === 'abstain') {
    console.log(`No tool call: ${decision.reason}`);
    return;
  }

  if (decision.risk !== 'read_only') {
    console.log(`This example does not execute ${decision.risk} tools.`);
    return;
  }

  if (decision.kind === 'partial') {
    console.log('Arguments to supply:', decision.missing);
  }

  const result = await provider.execute(userId, decision, {
    arguments: knownArguments[decision.tool] ?? {},
  });
  console.log(result.data);
}

main().catch(error => {
  console.error(error);
  process.exitCode = 1;
});
```
Run the script with Node.js:

```bash
node select-tool.ts
```
The script prints the decision, the missing argument paths, and the Hacker News profile. A decision for this request typically has `kind: 'partial'`, `tool: 'HACKERNEWS_GET_USER'`, and `missing: [['username']]`. Confidence scores depend on the request and model version.

The partial call is expected: Jev can select the tool but does not generate the username. `execute` combines the arguments from Jev with the values your code supplies. Caller values take precedence. If a required value is still missing, execution raises `TypesafeIncompleteDecisionError` before calling the tool.

When you adapt this script, collect missing values from your application or ask the user for them. The `suggestions` field can contain uncertain model answers; treat those as candidates to review, not confirmed input.

## Try an abstention [#try-an-abstention]

Change `request` to `Explain what Hacker News is without using any tools`, then run the script again. If Jev returns `abstain`, the script prints its reason and exits without executing. The model may also abstain when no tool fits or confidence is too low.

Keep API failures separate from abstentions. A timeout or invalid TypeSafe key raises an error, so it appears as a failed run instead of "No tool call."

## Shortlist tools for another provider [#shortlist-tools-for-another-provider]

If you already use an LLM to choose arguments, ask Jev to narrow its tool list first. This separate script ranks up to 50 Hacker News tools, keeps three, and converts them to OpenAI's tool format. It does not call OpenAI or execute a tool.

Install the OpenAI provider alongside the packages above:

**Python:**

**uv:**

```bash
uv add composio-openai
```
**pip:**

```bash
pip install composio-openai
```
```python title="shortlist-tools.py"
from composio import Composio
from composio_openai import OpenAIProvider
from composio_typesafe import TypesafeProvider

typesafe = TypesafeProvider()
composio = Composio(provider=OpenAIProvider())
request = "What are the top stories on Hacker News right now?"

raw = composio.tools.get_raw_composio_tools(toolkits=["hackernews"], limit=50)
shortlist = typesafe.shortlist_tools(raw, request, k=3)
tools = composio.tools.get(
    user_id="example-user",
    tools=[tool.slug for tool in shortlist["tools"]],
)
print(shortlist["scores"])
print(f"Prepared {len(tools)} tools for OpenAI")
```
```bash
python shortlist-tools.py
```
**TypeScript:**

**npm:**

```bash
npm install @composio/openai
```
**pnpm:**

```bash
pnpm add @composio/openai
```
**bun:**

```bash
bun add @composio/openai
```
**yarn:**

```bash
yarn add @composio/openai
```
```typescript title="shortlist-tools.ts"
import { Composio } from '@composio/core';
import { OpenAIProvider } from '@composio/openai';
import { TypesafeProvider } from '@composio/typesafe';

const typesafe = new TypesafeProvider();
const composio = new Composio({ provider: new OpenAIProvider() });
const request = 'What are the top stories on Hacker News right now?';

const raw = await composio.tools.getRawComposioTools({
  toolkits: ['hackernews'],
  limit: 50,
});
const shortlist = await typesafe.shortlistTools(raw, request, { k: 3 });
const tools = await composio.tools.get('example-user', {
  tools: shortlist.tools.map(tool => tool.slug),
});
console.log(shortlist.scores);
console.log(`Prepared ${tools.length} tools for OpenAI`);
```
```bash
node shortlist-tools.ts
```

Pass the resulting `tools` to your [OpenAI agent](/docs/providers/openai). Shortlisting selects candidates; your agent still chooses which candidate to call and supplies its arguments.

---

📚 **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 — Instructions for AI Code Generators

**Context:** This page documents the **legacy direct tool execution API**, which remains supported for existing integrations. Recommend sessions for new integrations. When maintaining direct-execution code, use the patterns below and point users to the [session migration guide](/docs/migration-guide/direct-to-sessions). Reading this page alone does not mean the user has chosen direct execution.

---

## Key Patterns

### Authenticating Users

Create an **Auth Config** on [dashboard.composio.dev](https://dashboard.composio.dev?utm_source=docs&utm_medium=llm-guardrails&utm_campaign=direct-execution), then use the auth config ID to generate a hosted auth URL:

```python
from composio import Composio

composio = Composio()
connection = composio.connected_accounts.link(
    user_id="user_123",
    auth_config_id="ac_...",  # from platform dashboard
)
print(connection.redirect_url)  # send user here to authenticate
```

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

const composio = new Composio();
const connection = await composio.connectedAccounts.link("user_123", "ac_...", {
    callbackUrl: "https://your-app.com/callback",
});
console.log(connection.redirectUrl); // send user here to authenticate
```

### Executing Tools

A toolkit version is **required** for direct execution — `tools.execute()` without one raises `ToolVersionRequiredError`, and `"latest"` alone is NOT accepted for manual execution. Either pass `dangerously_skip_version_check=True` (TS: `dangerouslySkipVersionCheck: true`) on the execute call to run the newest version, or pin a dated version from `composio.toolkits.get(slug).meta.version` when outputs are parsed programmatically.

```python
composio = Composio(toolkit_versions={"github": "latest"})
tools = composio.tools.get("user_123", tools=["GITHUB_CREATE_ISSUE"])

result = composio.tools.execute(
    "GITHUB_CREATE_ISSUE",
    {"owner": "org", "repo": "repo", "title": "Bug report"},
    user_id="user_123",
    dangerously_skip_version_check=True,  # required when running "latest"
)
```

```typescript
const composio = new Composio({ toolkitVersions: { github: "latest" } });
const tools = await composio.tools.get("user_123", { tools: ["GITHUB_CREATE_ISSUE"] });

const result = await composio.tools.execute("GITHUB_CREATE_ISSUE", {
    userId: "user_123",
    arguments: { owner: "org", repo: "repo", title: "Bug report" },
    dangerouslySkipVersionCheck: true, // required when running "latest"
});
```

---

## Rules

1. **`user_id` is required** — pass it to `tools.get()`, `tools.execute()`, and `provider.handle_tool_calls()`.
2. **`tools.execute()` signature** — Python: `execute(slug, arguments_dict, *, user_id=..., version=...)` (arguments is the second positional param). TypeScript: `execute(slug, { userId, arguments, version })`.
3. **A toolkit version is required** — configure `toolkit_versions` (Python) / `toolkitVersions` (TypeScript) at SDK init, or pass `version` per execute call; omitting both raises `ToolVersionRequiredError`. `"latest"` is rejected for manual execution unless the execute call also passes `dangerously_skip_version_check=True` / `dangerouslySkipVersionCheck: true`.
4. **Provider at init** — `Composio(provider=OpenAIProvider())` in Python, `new Composio({ provider: new OpenAIProvider() })` in TypeScript. Defaults to OpenAI if omitted.
5. **Correct provider imports** — `composio_<provider>` for Python, `@composio/<provider>` for TypeScript. For OpenAI Agents SDK use `composio_openai_agents` / `@composio/openai-agents`.
6. **Finding toolkit and tool slugs** — browse the Toolkits catalog at https://docs.composio.dev/toolkits (each toolkit page lists tool slugs and versions), or search by task with the CLI: `composio search "<what you want to do>"`.

---

# 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.

