Basic Hono Server

View as markdown

With Composio's managed authentication and tool calling, it's easy to build AI agents that interact with the real world while reducing boilerplate for setup and authentication management. This cookbook will guide you through building and serving agents using Composio, OpenAI, and Hono.js.

Prerequisites

  • Node.js 18.x or higher
  • npm or yarn package manager
  • Composio API key
  • OpenAI API key
  • Basic knowledge of OAuth
  • Understanding of building HTTP services (preferably using Hono.js)

Building an AI agent that can interact with gmail service

First, let's start with building a simple AI agent embedded with tools from Composio that lets the agent interact with the gmail service.

import { class OpenAI
API Client for interfacing with the OpenAI API.
OpenAI
} from 'openai';
import { class Composio<TProvider extends BaseComposioProvider<unknown, unknown, unknown> = OpenAIProvider>
This is the core class for Composio. It is used to initialize the Composio SDK and provide a global configuration.
Composio
} from '@composio/core';
import { class OpenAIProviderOpenAIProvider } from '@composio/openai'; export async function function runGmailAgent(composioClient: Composio<OpenAIProvider>, openaiClient: OpenAI, userId: string, prompt: string): Promise<any[]>runGmailAgent( composioClient: Composio<OpenAIProvider>composioClient: class Composio<TProvider extends BaseComposioProvider<unknown, unknown, unknown> = OpenAIProvider>
This is the core class for Composio. It is used to initialize the Composio SDK and provide a global configuration.
Composio
<class OpenAIProviderOpenAIProvider>,
openaiClient: OpenAIopenaiClient: class OpenAI
API Client for interfacing with the OpenAI API.
OpenAI
,
userId: stringuserId: string, // Composio uses the User ID to store and access user-level authentication tokens. prompt: stringprompt: string, ): interface Promise<T>
Represents the completion of an asynchronous operation
Promise
<any[]> {
// Step 1: Fetch the necessary Gmail tools list with Composio const const tools: OpenAiToolCollectiontools = await composioClient: Composio<OpenAIProvider>composioClient.Composio<OpenAIProvider>.tools: Tools<unknown, unknown, OpenAIProvider>
List, retrieve, and execute tools
tools
.Tools<unknown, unknown, OpenAIProvider>.get<OpenAIProvider>(userId: string, filters: ToolListParams, options?: ToolOptions | undefined): Promise<OpenAiToolCollection> (+1 overload)
Get a list of tools from Composio based on filters. This method fetches the tools from the Composio API and wraps them using the provider.
@paramuserId - The user id to get the tools for@paramfilters - The filters to apply when fetching tools@paramoptions - Optional provider options including modifiers@returnsThe wrapped tools collection@example```typescript // Get tools from the GitHub toolkit const tools = await composio.tools.get('default', { toolkits: ['github'], limit: 10 }); // Get tools with search const searchTools = await composio.tools.get('default', { search: 'user', limit: 10 }); // Get a specific tool by slug const hackerNewsUserTool = await composio.tools.get('default', 'HACKERNEWS_GET_USER'); // Get a tool with schema modifications const tool = await composio.tools.get('default', 'GITHUB_GET_REPOS', { modifySchema: (toolSlug, toolkitSlug, schema) => { // Customize the tool schema return {...schema, description: 'Custom description'}; } }); ```
get
(
userId: stringuserId, { tools: string[]tools: [ "GMAIL_FETCH_EMAILS", "GMAIL_SEND_EMAIL", "GMAIL_CREATE_EMAIL_DRAFT" ] } ); // Step 2: Use OpenAI to generate a response based on the prompt and available tools const
const response: OpenAI.Chat.Completions.ChatCompletion & {
    _request_id?: string | null;
}
response
= await openaiClient: OpenAIopenaiClient.OpenAI.chat: OpenAI.Chatchat.Chat.completions: OpenAI.Chat.Completionscompletions.Completions.create(body: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming, options?: RequestOptions): APIPromise<OpenAI.Chat.Completions.ChatCompletion> (+2 overloads)
**Starting a new project?** We recommend trying [Responses](https://platform.openai.com/docs/api-reference/responses) to take advantage of the latest OpenAI platform features. Compare [Chat Completions with Responses](https://platform.openai.com/docs/guides/responses-vs-chat-completions?api-mode=responses). --- Creates a model response for the given chat conversation. Learn more in the [text generation](https://platform.openai.com/docs/guides/text-generation), [vision](https://platform.openai.com/docs/guides/vision), and [audio](https://platform.openai.com/docs/guides/audio) guides. Parameter support can differ depending on the model used to generate the response, particularly for newer reasoning models. Parameters that are only supported for reasoning models are noted below. For the current state of unsupported parameters in reasoning models, [refer to the reasoning guide](https://platform.openai.com/docs/guides/reasoning).
@example```ts const chatCompletion = await client.chat.completions.create( { messages: [{ content: 'string', role: 'developer' }], model: 'gpt-4o', }, ); ```
create
({
ChatCompletionCreateParamsBase.model: (string & {}) | ChatModel
Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a wide range of models with different capabilities, performance characteristics, and price points. Refer to the [model guide](https://platform.openai.com/docs/models) to browse and compare available models.
model
: "gpt-4.1",
ChatCompletionCreateParamsBase.tools?: OpenAI.Chat.Completions.ChatCompletionTool[] | undefined
A list of tools the model may call. You can provide either [custom tools](https://platform.openai.com/docs/guides/function-calling#custom-tools) or [function tools](https://platform.openai.com/docs/guides/function-calling).
tools
,
ChatCompletionCreateParamsBase.messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[]
A list of messages comprising the conversation so far. Depending on the [model](https://platform.openai.com/docs/models) you use, different message types (modalities) are supported, like [text](https://platform.openai.com/docs/guides/text-generation), [images](https://platform.openai.com/docs/guides/vision), and [audio](https://platform.openai.com/docs/guides/audio).
messages
: [{ ChatCompletionUserMessageParam.role: "user"
The role of the messages author, in this case `user`.
role
: "user", ChatCompletionUserMessageParam.content: string | OpenAI.Chat.Completions.ChatCompletionContentPart[]
The contents of the user message.
content
: prompt: stringprompt }],
}); // Step 3: Handle tool calls with Composio and return the result const const result: ChatCompletionToolMessageParam[]result = await composioClient: Composio<OpenAIProvider>composioClient.Composio<OpenAIProvider>.provider: OpenAIProvider
The tool provider instance used for wrapping tools in framework-specific formats
provider
.OpenAIProvider.handleToolCalls(userId: string, chatCompletion: ChatCompletion, options?: ExecuteToolFnOptions, modifiers?: ExecuteToolModifiers): Promise<ChatCompletionToolMessageParam[]>
Handles tool calls from OpenAI's chat completion response. This method processes tool calls from an OpenAI chat completion response, executes each tool call, and returns the results.
@paramuserId - The user ID for authentication and tracking@paramchatCompletion - The chat completion response from OpenAI@paramoptions - Optional execution options@parammodifiers - Optional execution modifiers@returnsArray of tool execution results as JSON strings@example```typescript // Handle tool calls from a chat completion response const chatCompletion = { choices: [ { message: { tool_calls: [ { id: 'call_abc123', type: 'function', function: { name: 'SEARCH_TOOL', arguments: '{"query":"composio documentation"}' } } ] } } ] }; const results = await provider.handleToolCalls( 'user123', chatCompletion, { connectedAccountId: 'conn_xyz456' } ); console.log(results); // Array of tool execution results ```
handleToolCalls
(
userId: stringuserId,
const response: OpenAI.Chat.Completions.ChatCompletion & {
    _request_id?: string | null;
}
response
); return const result: ChatCompletionToolMessageParam[]result; }

This is a simple agent without state management and agentic loop implementation, so the agent can't perform complicated tasks. If you want to understand how composio can be used with agentic loops, check other cookbooks with more agentic frameworks.

To invoke this agent, authenticate your users with Composio's managed authentication service.

Authenticating users

To authenticate your users with Composio you need an authentication config for the given app. In this case you need one for gmail.

To create an authentication config for gmail you need client_id and client_secret from your Google OAuth Console. Once you have the credentials, use the following piece of code to set up authentication for gmail.

import { class Composio<TProvider extends BaseComposioProvider<unknown, unknown, unknown> = OpenAIProvider>
This is the core class for Composio. It is used to initialize the Composio SDK and provide a global configuration.
Composio
} from '@composio/core';
import { class OpenAIProviderOpenAIProvider } from '@composio/openai'; export async function
function createAuthConfig(composioClient: Composio<OpenAIProvider>): Promise<{
    toolkit: string;
    authScheme: string;
    id: string;
    isComposioManaged: boolean;
}>
createAuthConfig
(composioClient: Composio<OpenAIProvider>composioClient: class Composio<TProvider extends BaseComposioProvider<unknown, unknown, unknown> = OpenAIProvider>
This is the core class for Composio. It is used to initialize the Composio SDK and provide a global configuration.
Composio
<class OpenAIProviderOpenAIProvider>) {
/** * Create a auth config for the gmail toolkit. */ const const clientId: string | undefined
Create a auth config for the gmail toolkit.
clientId
= var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnv
The `process.env` property returns an object containing the user environment. See [`environ(7)`](http://man7.org/linux/man-pages/man7/environ.7.html). An example of this object looks like: ```js { TERM: 'xterm-256color', SHELL: '/usr/local/bin/bash', USER: 'maciej', PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin', PWD: '/Users/maciej', EDITOR: 'vim', SHLVL: '1', HOME: '/Users/maciej', LOGNAME: 'maciej', _: '/usr/local/bin/node' } ``` It is possible to modify this object, but such modifications will not be reflected outside the Node.js process, or (unless explicitly requested) to other `Worker` threads. In other words, the following example would not work: ```bash node -e 'process.env.foo = "bar"' &#x26;&#x26; echo $foo ``` While the following will: ```js import { env } from 'node:process'; env.foo = 'bar'; console.log(env.foo); ``` Assigning a property on `process.env` will implicitly convert the value to a string. **This behavior is deprecated.** Future versions of Node.js may throw an error when the value is not a string, number, or boolean. ```js import { env } from 'node:process'; env.test = null; console.log(env.test); // => 'null' env.test = undefined; console.log(env.test); // => 'undefined' ``` Use `delete` to delete a property from `process.env`. ```js import { env } from 'node:process'; env.TEST = 1; delete env.TEST; console.log(env.TEST); // => undefined ``` On Windows operating systems, environment variables are case-insensitive. ```js import { env } from 'node:process'; env.TEST = 1; console.log(env.test); // => 1 ``` Unless explicitly specified when creating a `Worker` instance, each `Worker` thread has its own copy of `process.env`, based on its parent thread's `process.env`, or whatever was specified as the `env` option to the `Worker` constructor. Changes to `process.env` will not be visible across `Worker` threads, and only the main thread can make changes that are visible to the operating system or to native add-ons. On Windows, a copy of `process.env` on a `Worker` instance operates in a case-sensitive manner unlike the main thread.
@sincev0.1.27
env
.string | undefinedGMAIL_CLIENT_ID;
const const clientSecret: string | undefinedclientSecret = var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnv
The `process.env` property returns an object containing the user environment. See [`environ(7)`](http://man7.org/linux/man-pages/man7/environ.7.html). An example of this object looks like: ```js { TERM: 'xterm-256color', SHELL: '/usr/local/bin/bash', USER: 'maciej', PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin', PWD: '/Users/maciej', EDITOR: 'vim', SHLVL: '1', HOME: '/Users/maciej', LOGNAME: 'maciej', _: '/usr/local/bin/node' } ``` It is possible to modify this object, but such modifications will not be reflected outside the Node.js process, or (unless explicitly requested) to other `Worker` threads. In other words, the following example would not work: ```bash node -e 'process.env.foo = "bar"' &#x26;&#x26; echo $foo ``` While the following will: ```js import { env } from 'node:process'; env.foo = 'bar'; console.log(env.foo); ``` Assigning a property on `process.env` will implicitly convert the value to a string. **This behavior is deprecated.** Future versions of Node.js may throw an error when the value is not a string, number, or boolean. ```js import { env } from 'node:process'; env.test = null; console.log(env.test); // => 'null' env.test = undefined; console.log(env.test); // => 'undefined' ``` Use `delete` to delete a property from `process.env`. ```js import { env } from 'node:process'; env.TEST = 1; delete env.TEST; console.log(env.TEST); // => undefined ``` On Windows operating systems, environment variables are case-insensitive. ```js import { env } from 'node:process'; env.TEST = 1; console.log(env.test); // => 1 ``` Unless explicitly specified when creating a `Worker` instance, each `Worker` thread has its own copy of `process.env`, based on its parent thread's `process.env`, or whatever was specified as the `env` option to the `Worker` constructor. Changes to `process.env` will not be visible across `Worker` threads, and only the main thread can make changes that are visible to the operating system or to native add-ons. On Windows, a copy of `process.env` on a `Worker` instance operates in a case-sensitive manner unlike the main thread.
@sincev0.1.27
env
.string | undefinedGMAIL_CLIENT_SECRET;
if (!const clientId: string | undefined
Create a auth config for the gmail toolkit.
clientId
|| !const clientSecret: string | undefinedclientSecret) {
throw new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
("GMAIL_CLIENT_ID and GMAIL_CLIENT_SECRET must be set");
} return composioClient: Composio<OpenAIProvider>composioClient.Composio<OpenAIProvider>.authConfigs: AuthConfigs
Manage authentication configurations for toolkits
authConfigs
.AuthConfigs.create(toolkit: string, options?: CreateAuthConfigParams): Promise<CreateAuthConfigResponse>
Create a new auth config
@paramtoolkit - Unique identifier of the toolkit@paramoptions - Options for creating a new auth config@returnsCreated auth config@exampleconst authConfig = await authConfigs.create('my-toolkit', { type: AuthConfigTypes.CUSTOM, name: 'My Custom Auth Config', authScheme: AuthSchemeTypes.API_KEY, credentials: { apiKey: '1234567890', }, });@linkhttps://docs.composio.dev/reference/auth-configs/create-auth-config
create
(
"GMAIL", { "name": "default_gmail_auth_config", "type": "use_custom_auth", "authScheme": "OAUTH2", "credentials": { "clientId": const clientId: string
Create a auth config for the gmail toolkit.
clientId
,
"clientSecret": const clientSecret: stringclientSecret, }, }, ); }

This will create a Gmail authentication config to authenticate your app's users. Ideally, create one authentication object per project, so check for an existing auth config before creating a new one.

export async function 
function fetchAuthConfig(composioClient: Composio<OpenAIProvider>): Promise<{
    name: string;
    status: "ENABLED" | "DISABLED";
    toolkit: {
        slug: string;
        logo: string;
    };
    id: string;
    noOfConnections: number;
    uuid: string;
    credentials?: Record<string, unknown> | undefined;
    authScheme?: "OAUTH1" | "OAUTH2" | "API_KEY" | "BASIC" | "BEARER_TOKEN" | "BILLCOM_AUTH" | "GOOGLE_SERVICE_ACCOUNT" | "NO_AUTH" | "BASIC_WITH_JWT" | "CALCOM_AUTH" | "SERVICE_ACCOUNT" | "SAML" | "DCR_OAUTH" | undefined;
    toolAccessConfig?: {
        toolsForConnectedAccountCreation?: string[] | undefined;
        toolsAvailableForExecution?: string[] | undefined;
    } | undefined;
    ... 5 more ...;
    lastUpdatedAt?: string | undefined;
} | null>
fetchAuthConfig
(composioClient: Composio<OpenAIProvider>composioClient: class Composio<TProvider extends BaseComposioProvider<unknown, unknown, unknown> = OpenAIProvider>
This is the core class for Composio. It is used to initialize the Composio SDK and provide a global configuration.
Composio
<class OpenAIProviderOpenAIProvider>) {
/** * Fetch the auth config for a given user id. */ const
const authConfigs: {
    items: {
        name: string;
        status: "ENABLED" | "DISABLED";
        toolkit: {
            slug: string;
            logo: string;
        };
        id: string;
        noOfConnections: number;
        uuid: string;
        credentials?: Record<string, unknown> | undefined;
        authScheme?: "OAUTH1" | "OAUTH2" | "API_KEY" | "BASIC" | "BEARER_TOKEN" | "BILLCOM_AUTH" | "GOOGLE_SERVICE_ACCOUNT" | "NO_AUTH" | "BASIC_WITH_JWT" | "CALCOM_AUTH" | "SERVICE_ACCOUNT" | "SAML" | "DCR_OAUTH" | undefined;
        toolAccessConfig?: {
            toolsForConnectedAccountCreation?: string[] | undefined;
            toolsAvailableForExecution?: string[] | undefined;
        } | undefined;
        isComposioManaged?: boolean | undefined;
        expectedInputFields?: unknown[] | undefined;
        restrictToFollowingTools?: string[] | undefined;
        createdBy?: string | undefined;
        createdAt?: string | undefined;
        lastUpdatedAt?: string | undefined;
    }[];
    nextCursor: string | null;
    totalPages: number;
}
Fetch the auth config for a given user id.
authConfigs
= await composioClient: Composio<OpenAIProvider>composioClient.Composio<OpenAIProvider>.authConfigs: AuthConfigs
Manage authentication configurations for toolkits
authConfigs
.AuthConfigs.list(query?: AuthConfigListParams): Promise<AuthConfigListResponse>
Lists authentication configurations based on provided filter criteria. This method retrieves auth configs from the Composio API, transforms them to the SDK format, and supports filtering by various parameters.
@paramquery - Optional query parameters for filtering auth configs@returnsA paginated list of auth configurations@throws{ValidationError} If the query parameters or response fail validation@example```typescript // List all auth configs const allConfigs = await composio.authConfigs.list(); // List auth configs for a specific toolkit const githubConfigs = await composio.authConfigs.list({ toolkit: 'github' }); // List Composio-managed auth configs const managedConfigs = await composio.authConfigs.list({ isComposioManaged: true }); ```
list
();
for (const
const authConfig: {
    name: string;
    status: "ENABLED" | "DISABLED";
    toolkit: {
        slug: string;
        logo: string;
    };
    id: string;
    noOfConnections: number;
    uuid: string;
    credentials?: Record<string, unknown> | undefined;
    authScheme?: "OAUTH1" | "OAUTH2" | "API_KEY" | "BASIC" | "BEARER_TOKEN" | "BILLCOM_AUTH" | "GOOGLE_SERVICE_ACCOUNT" | "NO_AUTH" | "BASIC_WITH_JWT" | "CALCOM_AUTH" | "SERVICE_ACCOUNT" | "SAML" | "DCR_OAUTH" | undefined;
    toolAccessConfig?: {
        toolsForConnectedAccountCreation?: string[] | undefined;
        toolsAvailableForExecution?: string[] | undefined;
    } | undefined;
    ... 5 more ...;
    lastUpdatedAt?: string | undefined;
}
authConfig
of
const authConfigs: {
    items: {
        name: string;
        status: "ENABLED" | "DISABLED";
        toolkit: {
            slug: string;
            logo: string;
        };
        id: string;
        noOfConnections: number;
        uuid: string;
        credentials?: Record<string, unknown> | undefined;
        authScheme?: "OAUTH1" | "OAUTH2" | "API_KEY" | "BASIC" | "BEARER_TOKEN" | "BILLCOM_AUTH" | "GOOGLE_SERVICE_ACCOUNT" | "NO_AUTH" | "BASIC_WITH_JWT" | "CALCOM_AUTH" | "SERVICE_ACCOUNT" | "SAML" | "DCR_OAUTH" | undefined;
        toolAccessConfig?: {
            toolsForConnectedAccountCreation?: string[] | undefined;
            toolsAvailableForExecution?: string[] | undefined;
        } | undefined;
        isComposioManaged?: boolean | undefined;
        expectedInputFields?: unknown[] | undefined;
        restrictToFollowingTools?: string[] | undefined;
        createdBy?: string | undefined;
        createdAt?: string | undefined;
        lastUpdatedAt?: string | undefined;
    }[];
    nextCursor: string | null;
    totalPages: number;
}
Fetch the auth config for a given user id.
authConfigs
.
items: {
    name: string;
    status: "ENABLED" | "DISABLED";
    toolkit: {
        slug: string;
        logo: string;
    };
    id: string;
    noOfConnections: number;
    uuid: string;
    credentials?: Record<string, unknown> | undefined;
    authScheme?: "OAUTH1" | "OAUTH2" | "API_KEY" | "BASIC" | "BEARER_TOKEN" | "BILLCOM_AUTH" | "GOOGLE_SERVICE_ACCOUNT" | "NO_AUTH" | "BASIC_WITH_JWT" | "CALCOM_AUTH" | "SERVICE_ACCOUNT" | "SAML" | "DCR_OAUTH" | undefined;
    toolAccessConfig?: {
        toolsForConnectedAccountCreation?: string[] | undefined;
        toolsAvailableForExecution?: string[] | undefined;
    } | undefined;
    ... 5 more ...;
    lastUpdatedAt?: string | undefined;
}[]
items
) {
if (
const authConfig: {
    name: string;
    status: "ENABLED" | "DISABLED";
    toolkit: {
        slug: string;
        logo: string;
    };
    id: string;
    noOfConnections: number;
    uuid: string;
    credentials?: Record<string, unknown> | undefined;
    authScheme?: "OAUTH1" | "OAUTH2" | "API_KEY" | "BASIC" | "BEARER_TOKEN" | "BILLCOM_AUTH" | "GOOGLE_SERVICE_ACCOUNT" | "NO_AUTH" | "BASIC_WITH_JWT" | "CALCOM_AUTH" | "SERVICE_ACCOUNT" | "SAML" | "DCR_OAUTH" | undefined;
    toolAccessConfig?: {
        toolsForConnectedAccountCreation?: string[] | undefined;
        toolsAvailableForExecution?: string[] | undefined;
    } | undefined;
    ... 5 more ...;
    lastUpdatedAt?: string | undefined;
}
authConfig
.
toolkit: {
    slug: string;
    logo: string;
}
toolkit
.slug: stringslug === "gmail") {
return
const authConfig: {
    name: string;
    status: "ENABLED" | "DISABLED";
    toolkit: {
        slug: string;
        logo: string;
    };
    id: string;
    noOfConnections: number;
    uuid: string;
    credentials?: Record<string, unknown> | undefined;
    authScheme?: "OAUTH1" | "OAUTH2" | "API_KEY" | "BASIC" | "BEARER_TOKEN" | "BILLCOM_AUTH" | "GOOGLE_SERVICE_ACCOUNT" | "NO_AUTH" | "BASIC_WITH_JWT" | "CALCOM_AUTH" | "SERVICE_ACCOUNT" | "SAML" | "DCR_OAUTH" | undefined;
    toolAccessConfig?: {
        toolsForConnectedAccountCreation?: string[] | undefined;
        toolsAvailableForExecution?: string[] | undefined;
    } | undefined;
    ... 5 more ...;
    lastUpdatedAt?: string | undefined;
}
authConfig
;
} } return null; }

Composio platform provides composio managed authentication for some apps to fast-track your development, gmail being one of them. You can use these default auth configs for development, but for production, always use your own oauth app configuration.

Once you have authentication management in place, we can start with connecting your users to your gmail app. Let's implement a function to connect users to your gmail app via composio.

// Function to initiate a connected account
export async function function createConnection(composioClient: Composio<OpenAIProvider>, userId: string): Promise<ConnectionRequest>createConnection(composioClient: Composio<OpenAIProvider>composioClient: class Composio<TProvider extends BaseComposioProvider<unknown, unknown, unknown> = OpenAIProvider>
This is the core class for Composio. It is used to initialize the Composio SDK and provide a global configuration.
Composio
<class OpenAIProviderOpenAIProvider>, userId: stringuserId: string) {
/** * Create a connection for a given user id and auth config id. */ // Fetch or create the auth config for the gmail toolkit let
let authConfig: {
    id: string;
} | null
Create a connection for a given user id and auth config id.
authConfig
= await
function fetchAuthConfig(client: Composio<OpenAIProvider>): Promise<{
    id: string;
} | null>
fetchAuthConfig
(composioClient: Composio<OpenAIProvider>composioClient);
if (!
let authConfig: {
    id: string;
} | null
Create a connection for a given user id and auth config id.
authConfig
) {
let authConfig: {
    id: string;
} | null
Create a connection for a given user id and auth config id.
authConfig
= await
function createAuthConfig(client: Composio<OpenAIProvider>): Promise<{
    id: string;
}>
createAuthConfig
(composioClient: Composio<OpenAIProvider>composioClient);
} // Create a connection for the user return composioClient: Composio<OpenAIProvider>composioClient.Composio<OpenAIProvider>.connectedAccounts: ConnectedAccounts
Manage authenticated connections
connectedAccounts
.ConnectedAccounts.initiate(userId: string, authConfigId: string, options?: CreateConnectedAccountOptions): Promise<ConnectionRequest>
Compound function to create a new connected account. This function creates a new connected account and returns a connection request. Users can then wait for the connection to be established using the `waitForConnection` method.
@paramuserId - User ID of the connected account@paramauthConfigId - Auth config ID of the connected account@paramoptions - Options for creating a new connected account@returnsConnection request object@example```typescript // For OAuth2 authentication const connectionRequest = await composio.connectedAccounts.initiate( 'user_123', 'auth_config_123', { callbackUrl: 'https://your-app.com/callback', config: AuthScheme.OAuth2({ access_token: 'your_access_token', token_type: 'Bearer' }) } ); // For API Key authentication const connectionRequest = await composio.connectedAccounts.initiate( 'user_123', 'auth_config_123', { config: AuthScheme.ApiKey({ api_key: 'your_api_key' }) } ); // For Basic authentication const connectionRequest = await composio.connectedAccounts.initiate( 'user_123', 'auth_config_123', { config: AuthScheme.Basic({ username: 'your_username', password: 'your_password' }) } ); ```@linkhttps://docs.composio.dev/reference/connected-accounts/create-connected-account
initiate
(
userId: stringuserId,
let authConfig: {
    id: string;
}
Create a connection for a given user id and auth config id.
authConfig
.id: stringid,
); } // Setup Hono const const app: Hono<BlankEnv, BlankSchema, "/">app = new new Hono<BlankEnv, BlankSchema, "/">(options?: HonoOptions<BlankEnv> | undefined): Hono<BlankEnv, BlankSchema, "/">
Creates an instance of the Hono class.
@paramoptions - Optional configuration options for the Hono instance.
Hono
();
// Connection initiation endpoint const app: Hono<BlankEnv, BlankSchema, "/">app.
Hono<BlankEnv, BlankSchema, "/", "/">.post: HandlerInterface
<"/connection/create", "/connection/create", Promise<JSONRespondReturn<{
    connection_id: string;
    redirect_url: string | null | undefined;
}, ContentfulStatusCode>>, BlankInput, BlankEnv>(path: "/connection/create", handler: H<BlankEnv, "/connection/create", BlankInput, Promise<JSONRespondReturn<{
    connection_id: string;
    redirect_url: string | null | undefined;
}, ContentfulStatusCode>>>) => Hono<...> (+22 overloads)
post
("/connection/create", async (c: Context<BlankEnv, "/connection/create", BlankInput>c) => {
/** * Create a connection for a given user id. */ // For demonstration, using a default user_id. Replace with real user logic in production. const const userId: "default"
Create a connection for a given user id.
userId
= "default";
// Create a new connection for the user const const connectionRequest: ConnectionRequestconnectionRequest = await function createConnection(composioClient: Composio<OpenAIProvider>, userId: string): Promise<ConnectionRequest>createConnection(const composioClient: Composio<OpenAIProvider>composioClient, const userId: "default"
Create a connection for a given user id.
userId
);
return c: Context<BlankEnv, "/connection/create", BlankInput>c.
Context<BlankEnv, "/connection/create", BlankInput>.json: JSONRespond
<{
    connection_id: string;
    redirect_url: string | null | undefined;
}, ContentfulStatusCode>(object: {
    connection_id: string;
    redirect_url: string | null | undefined;
}, status?: ContentfulStatusCode | undefined, headers?: HeaderRecord) => JSONRespondReturn<{
    connection_id: string;
    redirect_url: string | null | undefined;
}, ContentfulStatusCode> (+1 overload)
json
({
"connection_id": const connectionRequest: ConnectionRequestconnectionRequest.ConnectionRequestState.id: stringid, "redirect_url": const connectionRequest: ConnectionRequestconnectionRequest.ConnectionRequestState.redirectUrl?: string | null | undefinedredirectUrl, }); });

Now, you can make a request to this endpoint on your client app, and your user will get a URL which they can use to authenticate.

Set Up Hono service

We will use Hono.js to build an HTTP service that authenticates your users and lets them interact with your agent. This guide will provide best practices for using composio client in production environments.

Setup dependencies

Hono allows dependency injection patterns to simplify the usage of SDK clients that must be singletons. We recommend using composio SDK client as singleton.

import { class Composio<TProvider extends BaseComposioProvider<unknown, unknown, unknown> = OpenAIProvider>
This is the core class for Composio. It is used to initialize the Composio SDK and provide a global configuration.
Composio
} from '@composio/core';
import { class OpenAIProviderOpenAIProvider } from '@composio/openai'; import { class OpenAI
API Client for interfacing with the OpenAI API.
OpenAI
} from 'openai';
let let _composioClient: Composio<OpenAIProvider> | null_composioClient: class Composio<TProvider extends BaseComposioProvider<unknown, unknown, unknown> = OpenAIProvider>
This is the core class for Composio. It is used to initialize the Composio SDK and provide a global configuration.
Composio
<class OpenAIProviderOpenAIProvider> | null = null;
export function function provideComposioClient(): Composio<OpenAIProvider>provideComposioClient(): class Composio<TProvider extends BaseComposioProvider<unknown, unknown, unknown> = OpenAIProvider>
This is the core class for Composio. It is used to initialize the Composio SDK and provide a global configuration.
Composio
<class OpenAIProviderOpenAIProvider> {
/** * Provide a Composio client. */ if (let _composioClient: Composio<OpenAIProvider> | null_composioClient === null) { let _composioClient: Composio<OpenAIProvider> | null_composioClient = new new Composio<OpenAIProvider>(config?: ComposioConfig<OpenAIProvider> | undefined): Composio<OpenAIProvider>
Creates a new instance of the Composio SDK. The constructor initializes the SDK with the provided configuration options, sets up the API client, and initializes all core models (tools, toolkits, etc.).
@paramconfig - Configuration options for the Composio SDK@paramconfig.apiKey - The API key for authenticating with the Composio API@paramconfig.baseURL - The base URL for the Composio API (defaults to production URL)@paramconfig.allowTracking - Whether to allow anonymous usage analytics@paramconfig.provider - The provider to use for this Composio instance (defaults to OpenAIProvider)@example```typescript // Initialize with default configuration const composio = new Composio(); // Initialize with custom API key and base URL const composio = new Composio({ apiKey: 'your-api-key', baseURL: 'https://api.composio.dev' }); // Initialize with custom provider const composio = new Composio({ apiKey: 'your-api-key', provider: new CustomProvider() }); ```
Composio
({
provider?: OpenAIProvider | undefined
The tool provider to use for this Composio instance.
@examplenew OpenAIProvider()
provider
: new new OpenAIProvider(): OpenAIProvider
Creates a new instance of the OpenAIProvider. This is the default provider for the Composio SDK and is automatically available without additional installation.
@example```typescript // The OpenAIProvider is used by default when initializing Composio const composio = new Composio({ apiKey: 'your-api-key' }); // You can also explicitly specify it const composio = new Composio({ apiKey: 'your-api-key', provider: new OpenAIProvider() }); ```
OpenAIProvider
()
}); } return let _composioClient: Composio<OpenAIProvider>_composioClient; } // A Composio client dependency. export type type ComposioClient = Composio<OpenAIProvider>ComposioClient = class Composio<TProvider extends BaseComposioProvider<unknown, unknown, unknown> = OpenAIProvider>
This is the core class for Composio. It is used to initialize the Composio SDK and provide a global configuration.
Composio
<class OpenAIProviderOpenAIProvider>;

Check config/composio.ts module for more details.

Invoke agent via Hono

When invoking an agent, make sure you validate the user_id.

export function function checkConnectedAccountExists(composioClient: Composio<OpenAIProvider>, userId: string): Promise<boolean>checkConnectedAccountExists(
    composioClient: Composio<OpenAIProvider>composioClient: class Composio<TProvider extends BaseComposioProvider<unknown, unknown, unknown> = OpenAIProvider>
This is the core class for Composio. It is used to initialize the Composio SDK and provide a global configuration.
Composio
<class OpenAIProviderOpenAIProvider>,
userId: stringuserId: string, ): interface Promise<T>
Represents the completion of an asynchronous operation
Promise
<boolean> {
/** * Check if a connected account exists for a given user id. */ // Fetch all connected accounts for the user return composioClient: Composio<OpenAIProvider>composioClient.Composio<OpenAIProvider>.connectedAccounts: ConnectedAccounts
Manage authenticated connections
connectedAccounts
.ConnectedAccounts.list(query?: ConnectedAccountListParams): Promise<ConnectedAccountListResponse>
Lists all connected accounts based on provided filter criteria. This method retrieves connected accounts from the Composio API with optional filtering.
@paramquery - Optional query parameters for filtering connected accounts@returnsA paginated list of connected accounts@throws{ValidationError} If the query fails validation against the expected schema@example```typescript // List all connected accounts const allAccounts = await composio.connectedAccounts.list(); // List accounts for a specific user const userAccounts = await composio.connectedAccounts.list({ userIds: ['user123'] }); // List accounts for a specific toolkit const githubAccounts = await composio.connectedAccounts.list({ toolkitSlugs: ['github'] }); ```
list
({ userIds?: string[] | null | undefineduserIds: [userId: stringuserId], toolkitSlugs?: string[] | null | undefinedtoolkitSlugs: ["GMAIL"] }).
Promise<{ items: { id: string; authConfig: TypeOf<ZodObject<{ id: ZodString; isComposioManaged: ZodBoolean; isDisabled: ZodBoolean; }, "strip", ZodTypeAny, { ...; }, { ...; }>>; data?: Record<string, unknown>; params?: Record<string, unknown>; status: ConnectedAccountStatusEnum; statusReason: string | null; toolkit: { slug: string; }; state?: TypeOf<ZodDiscriminatedUnion<...>>; testRequestEndpoint?: string; isDisabled: boolean; createdAt: string; updatedAt: string; }[]; totalPages: number; nextCursor?: string | null | undefined; }>.then<boolean, boolean>(onfulfilled?: ((value: {
    items: {
        id: string;
        authConfig: TypeOf<ZodObject<{
            id: ZodString;
            isComposioManaged: ZodBoolean;
            isDisabled: ZodBoolean;
        }, "strip", ZodTypeAny, {
            id: string;
            isComposioManaged: boolean;
            isDisabled: boolean;
        }, {
            id: string;
            isComposioManaged: boolean;
            isDisabled: boolean;
        }>>;
        data?: Record<string, unknown>;
        params?: Record<string, unknown>;
        status: ConnectedAccountStatusEnum;
        statusReason: string | null;
        toolkit: {
            slug: string;
        };
        state?: TypeOf<ZodDiscriminatedUnion<"authScheme", [ZodObject<{
            authScheme: ZodLiteral<"OAUTH1">;
            val: ZodDiscriminatedUnion<"status", [ZodObject<{
                subdomain: ZodOptional<ZodString>;
                "your-domain": ZodOptional<ZodString>;
                region: ZodOptional<ZodString>;
                shop: ZodOptional<ZodString>;
                account_url: ZodOptional<ZodString>;
                COMPANYDOMAIN: ZodOptional<ZodString>;
                extension: ZodOptional<ZodString>;
                form_api_base_url: ZodOptional<ZodString>;
                instanceEndpoint: ZodOptional<ZodString>;
                api_url: ZodOptional<ZodString>;
                borneo_dashboard_url: ZodOptional<ZodString>;
                proxy_username: ZodOptional<ZodString>;
                proxy_password: ZodOptional<ZodString>;
                domain: ZodOptional<ZodString>;
                version: ZodOptional<ZodString>;
                dc: ZodOptional<ZodString>;
                site_name: ZodOptional<ZodString>;
                instanceName: ZodOptional<ZodString>;
                account_id: ZodOptional<ZodString>;
                your_server: ZodOptional<ZodString>;
                server_location: ZodOptional<ZodString>;
                base_url: ZodOptional<ZodString>;
                api_key: ZodOptional<ZodString>;
                generic_api_key: ZodOptional<ZodString>;
                bearer_token: ZodOptional<ZodString>;
                basic_encoded: ZodOptional<ZodString>;
                long_redirect_url: ZodOptional<ZodBoolean>;
                state_prefix: ZodOptional<ZodString>;
                registration_access_token: ZodOptional<ZodString>;
                registration_client_uri: ZodOptional<ZodString>;
                composio_link_redirect_url: ZodOptional<ZodString>;
            } & {
                status: ZodLiteral<"INITIALIZING">;
            }, "strip", ZodUnknown, objectOutputType<{
                subdomain: ZodOptional<ZodString>;
                "your-domain": ZodOptional<ZodString>;
                region: ZodOptional<ZodString>;
                shop: ZodOptional<ZodString>;
                account_url: ZodOptional<ZodString>;
                COMPANYDOMAIN: ZodOptional<ZodString>;
                extension: ZodOptional<ZodString>;
                form_api_base_url: ZodOptional<ZodString>;
                instanceEndpoint: ZodOptional<ZodString>;
                api_url: ZodOptional<ZodString>;
                borneo_dashboard_url: ZodOptional<ZodString>;
                proxy_username: ZodOptional<ZodString>;
                proxy_password: ZodOptional<ZodString>;
                domain: ZodOptional<ZodString>;
                version: ZodOptional<ZodString>;
                dc: ZodOptional<ZodString>;
                site_name: ZodOptional<ZodString>;
                instanceName: ZodOptional<ZodString>;
                account_id: ZodOptional<ZodString>;
                your_server: ZodOptional<ZodString>;
                server_location: ZodOptional<ZodString>;
                base_url: ZodOptional<ZodString>;
                api_key: ZodOptional<ZodString>;
                generic_api_key: ZodOptional<ZodString>;
                bearer_token: ZodOptional<ZodString>;
                basic_encoded: ZodOptional<ZodString>;
                long_redirect_url: ZodOptional<ZodBoolean>;
                state_prefix: ZodOptional<ZodString>;
                registration_access_token: ZodOptional<ZodString>;
                registration_client_uri: ZodOptional<ZodString>;
                composio_link_redirect_url: ZodOptional<ZodString>;
            } & {
                status: ZodLiteral<"INITIALIZING">;
            }, ZodUnknown, "strip">, objectInputType<{
                subdomain: ZodOptional<ZodString>;
                "your-domain": ZodOptional<ZodString>;
                region: ZodOptional<ZodString>;
                shop: ZodOptional<ZodString>;
                account_url: ZodOptional<ZodString>;
                COMPANYDOMAIN: ZodOptional<ZodString>;
                extension: ZodOptional<ZodString>;
                form_api_base_url: ZodOptional<ZodString>;
                instanceEndpoint: ZodOptional<ZodString>;
                api_url: ZodOptional ...
Attaches callbacks for the resolution and/or rejection of the Promise.
@paramonfulfilled The callback to execute when the Promise is resolved.@paramonrejected The callback to execute when the Promise is rejected.@returnsA Promise for the completion of which ever callback is executed.
then
(
connectedAccounts: {
    items: {
        id: string;
        authConfig: TypeOf<ZodObject<{
            id: ZodString;
            isComposioManaged: ZodBoolean;
            isDisabled: ZodBoolean;
        }, "strip", ZodTypeAny, {
            id: string;
            isComposioManaged: boolean;
            isDisabled: boolean;
        }, {
            id: string;
            isComposioManaged: boolean;
            isDisabled: boolean;
        }>>;
        data?: Record<string, unknown>;
        params?: Record<string, unknown>;
        status: ConnectedAccountStatusEnum;
        statusReason: string | null;
        toolkit: {
            slug: string;
        };
        state?: TypeOf<ZodDiscriminatedUnion<"authScheme", [ZodObject<{
            authScheme: ZodLiteral<"OAUTH1">;
            val: ZodDiscriminatedUnion<"status", [ZodObject<{
                subdomain: ZodOptional<ZodString>;
                "your-domain": ZodOptional<ZodString>;
                region: ZodOptional<ZodString>;
                shop: ZodOptional<ZodString>;
                account_url: ZodOptional<ZodString>;
                COMPANYDOMAIN: ZodOptional<ZodString>;
                extension: ZodOptional<ZodString>;
                form_api_base_url: ZodOptional<ZodString>;
                instanceEndpoint: ZodOptional<ZodString>;
                api_url: ZodOptional<ZodString>;
                borneo_dashboard_url: ZodOptional<ZodString>;
                proxy_username: ZodOptional<ZodString>;
                proxy_password: ZodOptional<ZodString>;
                domain: ZodOptional<ZodString>;
                version: ZodOptional<ZodString>;
                dc: ZodOptional<ZodString>;
                site_name: ZodOptional<ZodString>;
                instanceName: ZodOptional<ZodString>;
                account_id: ZodOptional<ZodString>;
                your_server: ZodOptional<ZodString>;
                server_location: ZodOptional<ZodString>;
                base_url: ZodOptional<ZodString>;
                api_key: ZodOptional<ZodString>;
                generic_api_key: ZodOptional<ZodString>;
                bearer_token: ZodOptional<ZodString>;
                basic_encoded: ZodOptional<ZodString>;
                long_redirect_url: ZodOptional<ZodBoolean>;
                state_prefix: ZodOptional<ZodString>;
                registration_access_token: ZodOptional<ZodString>;
                registration_client_uri: ZodOptional<ZodString>;
                composio_link_redirect_url: ZodOptional<ZodString>;
            } & {
                status: ZodLiteral<"INITIALIZING">;
            }, "strip", ZodUnknown, objectOutputType<{
                subdomain: ZodOptional<ZodString>;
                "your-domain": ZodOptional<ZodString>;
                region: ZodOptional<ZodString>;
                shop: ZodOptional<ZodString>;
                account_url: ZodOptional<ZodString>;
                COMPANYDOMAIN: ZodOptional<ZodString>;
                extension: ZodOptional<ZodString>;
                form_api_base_url: ZodOptional<ZodString>;
                instanceEndpoint: ZodOptional<ZodString>;
                api_url: ZodOptional<ZodString>;
                borneo_dashboard_url: ZodOptional<ZodString>;
                proxy_username: ZodOptional<ZodString>;
                proxy_password: ZodOptional<ZodString>;
                domain: ZodOptional<ZodString>;
                version: ZodOptional<ZodString>;
                dc: ZodOptional<ZodString>;
                site_name: ZodOptional<ZodString>;
                instanceName: ZodOptional<ZodString>;
                account_id: ZodOptional<ZodString>;
                your_server: ZodOptional<ZodString>;
                server_location: ZodOptional<ZodString>;
                base_url: ZodOptional<ZodString>;
                api_key: ZodOptional<ZodString>;
                generic_api_key: ZodOptional<ZodString>;
                bearer_token: ZodOptional<ZodString>;
                basic_encoded: ZodOptional<ZodString>;
                long_redirect_url: ZodOptional<ZodBoolean>;
                state_prefix: ZodOptional<ZodString>;
                registration_access_token: ZodOptional<ZodString>;
                registration_client_uri: ZodOptional<ZodString>;
                composio_link_redirect_url: ZodOptional<ZodString>;
            } & {
                status: ZodLiteral<"INITIALIZING">;
            }, ZodUnknown, "strip">, objectInputType<{
                subdomain: ZodOptional<ZodString>;
                "your-domain": ZodOptional<ZodString>;
                region: ZodOptional<ZodString>;
                shop: ZodOptional<ZodString>;
                account_url: ZodOptional<ZodString>;
                COMPANYDOMAIN: ZodOptional<ZodString>;
                extension: ZodOptional<ZodString>;
                form_api_base_url: ZodOptional<ZodString>;
                instanceEndpoint: ZodOptional<ZodString>;
                api_url: ZodOptional<ZodString>;
                borneo_dashboard_url ...
connectedAccounts
=> {
// Check if there's an active connected account for (const
const account: {
    id: string;
    authConfig: TypeOf<ZodObject<{
        id: ZodString;
        isComposioManaged: ZodBoolean;
        isDisabled: ZodBoolean;
    }, "strip", ZodTypeAny, {
        id: string;
        isComposioManaged: boolean;
        isDisabled: boolean;
    }, {
        id: string;
        isComposioManaged: boolean;
        isDisabled: boolean;
    }>>;
    data?: Record<string, unknown>;
    params?: Record<string, unknown>;
    status: ConnectedAccountStatusEnum;
    ... 6 more ...;
    updatedAt: string;
}
account
of
connectedAccounts: {
    items: {
        id: string;
        authConfig: TypeOf<ZodObject<{
            id: ZodString;
            isComposioManaged: ZodBoolean;
            isDisabled: ZodBoolean;
        }, "strip", ZodTypeAny, {
            id: string;
            isComposioManaged: boolean;
            isDisabled: boolean;
        }, {
            id: string;
            isComposioManaged: boolean;
            isDisabled: boolean;
        }>>;
        data?: Record<string, unknown>;
        params?: Record<string, unknown>;
        status: ConnectedAccountStatusEnum;
        statusReason: string | null;
        toolkit: {
            slug: string;
        };
        state?: TypeOf<ZodDiscriminatedUnion<"authScheme", [ZodObject<{
            authScheme: ZodLiteral<"OAUTH1">;
            val: ZodDiscriminatedUnion<"status", [ZodObject<{
                subdomain: ZodOptional<ZodString>;
                "your-domain": ZodOptional<ZodString>;
                region: ZodOptional<ZodString>;
                shop: ZodOptional<ZodString>;
                account_url: ZodOptional<ZodString>;
                COMPANYDOMAIN: ZodOptional<ZodString>;
                extension: ZodOptional<ZodString>;
                form_api_base_url: ZodOptional<ZodString>;
                instanceEndpoint: ZodOptional<ZodString>;
                api_url: ZodOptional<ZodString>;
                borneo_dashboard_url: ZodOptional<ZodString>;
                proxy_username: ZodOptional<ZodString>;
                proxy_password: ZodOptional<ZodString>;
                domain: ZodOptional<ZodString>;
                version: ZodOptional<ZodString>;
                dc: ZodOptional<ZodString>;
                site_name: ZodOptional<ZodString>;
                instanceName: ZodOptional<ZodString>;
                account_id: ZodOptional<ZodString>;
                your_server: ZodOptional<ZodString>;
                server_location: ZodOptional<ZodString>;
                base_url: ZodOptional<ZodString>;
                api_key: ZodOptional<ZodString>;
                generic_api_key: ZodOptional<ZodString>;
                bearer_token: ZodOptional<ZodString>;
                basic_encoded: ZodOptional<ZodString>;
                long_redirect_url: ZodOptional<ZodBoolean>;
                state_prefix: ZodOptional<ZodString>;
                registration_access_token: ZodOptional<ZodString>;
                registration_client_uri: ZodOptional<ZodString>;
                composio_link_redirect_url: ZodOptional<ZodString>;
            } & {
                status: ZodLiteral<"INITIALIZING">;
            }, "strip", ZodUnknown, objectOutputType<{
                subdomain: ZodOptional<ZodString>;
                "your-domain": ZodOptional<ZodString>;
                region: ZodOptional<ZodString>;
                shop: ZodOptional<ZodString>;
                account_url: ZodOptional<ZodString>;
                COMPANYDOMAIN: ZodOptional<ZodString>;
                extension: ZodOptional<ZodString>;
                form_api_base_url: ZodOptional<ZodString>;
                instanceEndpoint: ZodOptional<ZodString>;
                api_url: ZodOptional<ZodString>;
                borneo_dashboard_url: ZodOptional<ZodString>;
                proxy_username: ZodOptional<ZodString>;
                proxy_password: ZodOptional<ZodString>;
                domain: ZodOptional<ZodString>;
                version: ZodOptional<ZodString>;
                dc: ZodOptional<ZodString>;
                site_name: ZodOptional<ZodString>;
                instanceName: ZodOptional<ZodString>;
                account_id: ZodOptional<ZodString>;
                your_server: ZodOptional<ZodString>;
                server_location: ZodOptional<ZodString>;
                base_url: ZodOptional<ZodString>;
                api_key: ZodOptional<ZodString>;
                generic_api_key: ZodOptional<ZodString>;
                bearer_token: ZodOptional<ZodString>;
                basic_encoded: ZodOptional<ZodString>;
                long_redirect_url: ZodOptional<ZodBoolean>;
                state_prefix: ZodOptional<ZodString>;
                registration_access_token: ZodOptional<ZodString>;
                registration_client_uri: ZodOptional<ZodString>;
                composio_link_redirect_url: ZodOptional<ZodString>;
            } & {
                status: ZodLiteral<"INITIALIZING">;
            }, ZodUnknown, "strip">, objectInputType<{
                subdomain: ZodOptional<ZodString>;
                "your-domain": ZodOptional<ZodString>;
                region: ZodOptional<ZodString>;
                shop: ZodOptional<ZodString>;
                account_url: ZodOptional<ZodString>;
                COMPANYDOMAIN: ZodOptional<ZodString>;
                extension: ZodOptional<ZodString>;
                form_api_base_url: ZodOptional<ZodString>;
                instanceEndpoint: ZodOptional<ZodString>;
                api_url: ZodOptional<ZodString>;
                borneo_dashboard_url ...
connectedAccounts
.
items: {
    id: string;
    authConfig: TypeOf<ZodObject<{
        id: ZodString;
        isComposioManaged: ZodBoolean;
        isDisabled: ZodBoolean;
    }, "strip", ZodTypeAny, {
        id: string;
        isComposioManaged: boolean;
        isDisabled: boolean;
    }, {
        id: string;
        isComposioManaged: boolean;
        isDisabled: boolean;
    }>>;
    data?: Record<string, unknown>;
    params?: Record<string, unknown>;
    status: ConnectedAccountStatusEnum;
    ... 6 more ...;
    updatedAt: string;
}[]
items
) {
if (
const account: {
    id: string;
    authConfig: TypeOf<ZodObject<{
        id: ZodString;
        isComposioManaged: ZodBoolean;
        isDisabled: ZodBoolean;
    }, "strip", ZodTypeAny, {
        id: string;
        isComposioManaged: boolean;
        isDisabled: boolean;
    }, {
        id: string;
        isComposioManaged: boolean;
        isDisabled: boolean;
    }>>;
    data?: Record<string, unknown>;
    params?: Record<string, unknown>;
    status: ConnectedAccountStatusEnum;
    ... 6 more ...;
    updatedAt: string;
}
account
.status: "INITIALIZING" | "INITIATED" | "ACTIVE" | "FAILED" | "EXPIRED" | "INACTIVE"status === "ACTIVE") {
return true; } // Ideally you should not have inactive accounts, but if you do, delete them. var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v24.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v24.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(`[warning] inactive account ${
const account: {
    id: string;
    authConfig: TypeOf<ZodObject<{
        id: ZodString;
        isComposioManaged: ZodBoolean;
        isDisabled: ZodBoolean;
    }, "strip", ZodTypeAny, {
        id: string;
        isComposioManaged: boolean;
        isDisabled: boolean;
    }, {
        id: string;
        isComposioManaged: boolean;
        isDisabled: boolean;
    }>>;
    data?: Record<string, unknown>;
    params?: Record<string, unknown>;
    status: ConnectedAccountStatusEnum;
    ... 6 more ...;
    updatedAt: string;
}
account
.id: stringid} found for user id: ${userId: stringuserId}`);
} return false; }); } export async function function validateUserId(userId: string, composioClient: Composio<OpenAIProvider>): Promise<string>validateUserId(userId: stringuserId: string, composioClient: Composio<OpenAIProvider>composioClient: class Composio<TProvider extends BaseComposioProvider<unknown, unknown, unknown> = OpenAIProvider>
This is the core class for Composio. It is used to initialize the Composio SDK and provide a global configuration.
Composio
<class OpenAIProviderOpenAIProvider>): interface Promise<T>
Represents the completion of an asynchronous operation
Promise
<string> {
/** * Validate the user id, if no connected account is found, create a new connection. */ if (await function checkConnectedAccountExists(composioClient: Composio<OpenAIProvider>, userId: string): Promise<boolean>checkConnectedAccountExists(composioClient: Composio<OpenAIProvider>composioClient, userId: stringuserId)) { return userId: stringuserId; } throw new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
("No connected account found for the user id");
} // Endpoint: Run the Gmail agent for a given user id and prompt const app: Hono<BlankEnv, BlankSchema, "/">app.
Hono<BlankEnv, BlankSchema, "/", "/">.post: HandlerInterface
<"/agent", "/agent", Promise<JSONRespondReturn<any[], ContentfulStatusCode>>, BlankInput, BlankEnv>(path: "/agent", handler: H<BlankEnv, "/agent", BlankInput, Promise<JSONRespondReturn<any[], ContentfulStatusCode>>>) => Hono<BlankEnv, {
    "/agent": {
        $post: {
            input: {};
            output: any[];
            outputFormat: "json";
            status: ContentfulStatusCode;
        };
    };
}, "/", "/agent"> (+22 overloads)
post
("/agent", async (c: Context<BlankEnv, "/agent", BlankInput>c) => {
/** * Run the Gmail agent for a given user id and prompt. */ const const request: any
Run the Gmail agent for a given user id and prompt.
request
= await c: Context<BlankEnv, "/agent", BlankInput>c.Context<BlankEnv, "/agent", BlankInput>.req: HonoRequest<"/agent", unknown>
`.req` is the instance of {@link HonoRequest } .
req
.HonoRequest<"/agent", unknown>.json<any>(): Promise<any>
`.json()` can parse Request body of type `application/json`
@see{@link https://hono.dev/docs/api/request#json}@example```ts app.post('/entry', async (c) => { const body = await c.req.json() }) ```
json
();
// For demonstration, using a default user_id. Replace with real user logic in production. const const userId: "default"userId = "default"; // Validate the user id before proceeding await function validateUserId(userId: string, composioClient: Composio<OpenAIProvider>): Promise<string>validateUserId(const userId: "default"userId, const composioClient: Composio<OpenAIProvider>composioClient); // Run the Gmail agent using Composio and OpenAI const const result: any[]result = await function runGmailAgent(c: Composio<OpenAIProvider>, o: OpenAI, u: string, p: string): Promise<any[]>runGmailAgent( const composioClient: Composio<OpenAIProvider>composioClient, const openaiClient: OpenAIopenaiClient, const userId: "default"userId, const request: any
Run the Gmail agent for a given user id and prompt.
request
.prompt,
); return c: Context<BlankEnv, "/agent", BlankInput>c.
Context<BlankEnv, "/agent", BlankInput>.json: JSONRespond
<any[], ContentfulStatusCode>(object: any[], status?: ContentfulStatusCode | undefined, headers?: HeaderRecord) => JSONRespondReturn<any[], ContentfulStatusCode> (+1 overload)
json
(const result: any[]result);
});

Check src/api.ts module for service implementation

Putting everything together

So far, we have created an agent with ability to interact with gmail using the composio SDK, functions to manage connected accounts for users and a Hono service. Now let's run the service.

Before proceeding, check the code for utility endpoints not discussed in the cookbook

  1. Clone the repository

    git clone git@github.com:composiohq/composio-hono
    cd composio-hono/
  2. Setup environment

    cp .env.example .env

    Fill the api keys

    COMPOSIO_API_KEY=
    OPENAI_API_KEY=

    Install dependencies

    npm install
  3. Run the HTTP server

    npm run dev

Testing the API with curl

Assuming the server is running locally on http://localhost:8000.

Check if a connection exists

curl -X POST http://localhost:8000/connection/exists

Create a connection

Note: The body fields are required by the API schema, but are ignored internally in this example service.

curl -X POST http://localhost:8000/connection/create \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "default",
    "auth_config_id": "AUTH_CONFIG_ID_FOR_GMAIL_FROM_THE_COMPOSIO_DASHBOARD"
  }'

Response includes connection_id and redirect_url. Complete the OAuth flow at the redirect_url.

Check connection status

Use the connection_id returned from the create step.

curl -X POST http://localhost:8000/connection/status \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "default",
    "connection_id": "CONNECTION_ID_FROM_CREATE_RESPONSE"
  }'

Run the Gmail agent

Requires an active connected account for the default user.

curl -X POST http://localhost:8000/agent \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "default",
    "prompt": "Summarize my latest unread emails from the last 24 hours."
  }'

Fetch emails (direct action)

curl -X POST http://localhost:8000/actions/fetch_emails \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "default",
    "limit": 5
  }'

These examples are intended solely for testing purposes.

Using Composio for managed auth and tools

Composio reduces boilerplate for building AI agents that access and use various apps. In this cookbook, to build Gmail integration without Composio, you would have to write code to

  • manage Gmail OAuth app
  • manage user connections
  • tools for your agents to interact with Gmail

Using Composio simplifies all of the above to a few lines of code as shown in the cookbook.

Best practices

🎯 Effective Prompts:

  • Be specific: "Send email to john@company.com about tomorrow's 2pm meeting" works better than "send email"
  • Include context: "Reply to Sarah's email about the budget with our approval"
  • Use natural language: The agent understands conversational requests

🔑 User Management:

  • Use unique, consistent user_id values for each person
  • Each user maintains their own Gmail connection
  • User IDs can be email addresses, usernames, or any unique identifier

Troubleshooting

Connection Issues:

  • Ensure your .env file has valid COMPOSIO_API_KEY and OPENAI_API_KEY
  • Check if the user has completed Gmail authorization.
  • Verify the user_id matches exactly between requests

API Errors:

  • Check the server logs for detailed error messages
  • Ensure request payloads match the expected format
  • Visit /docs endpoint for API schema validation

Gmail API Limits:

  • Gmail has rate limits; the agent will handle these gracefully
  • For high-volume usage, consider implementing request queuing