Third-party accounts - Google Calendar, Slack, and the rest - that a workspace has connected through Connect, and acting on them.

Three scopes, escalating: `integrations:read` lists what is connected, `integrations:manage` connects and disconnects, `integrations:execute` performs actions.

```ts
const { integrations } = await connect.integrations.list(workspaceId);

// Connecting is a redirect the *user* completes - you cannot do it for them.
const { redirectUrl } = await connect.integrations.connect({ workspaceId, toolkit: "googlecalendar" });

await connect.integrations.execute({
  workspaceId,
  toolkit: "googlecalendar",
  action: "create_event",
  input: { summary: "Kickoff", start, end },
});
```

**`integrations:execute` reaches outside Connect entirely.** It acts on the customer's own Slack or calendar under their authorization - the blast radius is their account, not their workspace here. Scope keys accordingly, and expect the third party's own failures (revoked grants, rate limits, expired tokens) to surface as errors on these calls.

A connection belongs to the workspace, not to your application - another application they use may have connected it.

## Endpoints

### GET `/api/v1/application/integrations`

List a workspace's integrations

**Scope** `integrations:read` &nbsp;·&nbsp; **Rate limit** 30 requests / min per IP, 60 requests / min per API key

The Composio-hosted toolkits this workspace can see - only ones a platform admin has enabled in Connect's own Console - each with its connection status (`not_connected`, `pending`, `active`, `failed`, `disconnected`, `expired`).

Connections are workspace-shared, not per-user: one connection per `(workspace, toolkit)` covers every member.

`<IntegrationsModal>`/`useIntegrations()` in `@cosmoops/connect-sdk/client` render this for you, through your own bridge.

**Parameters**

| Name | In | Required | Notes |
| --- | --- | --- | --- |
| `workspaceId` | query | yes | The workspace to list integrations for. |

**Responses**

| Status | Meaning |
| --- | --- |
| `200` | Enabled toolkits and this workspace's connection status for each |
| `400` | validation_error - workspaceId missing |
| `403` | forbidden - key unknown or missing the integrations:read scope. key_revoked / key_expired if the key itself is dead |

**Response body**

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `toolkits` | `array` | yes |  |

### POST `/api/v1/application/integrations/connect`

Connect an integration

**Scope** `integrations:manage` &nbsp;·&nbsp; **Rate limit** 30 requests / min per key

Starts a Composio-hosted connection for one toolkit. Returns a `redirectUrl` - send the workspace's admin there to complete the provider's OAuth screen; Composio bounces them back to your own `returnUrl` once it finishes.

**The connection is not active yet when this returns.** Treat the toolkit as `pending` until a later `GET /integrations` read reports `active` - Connect only trusts its own webhook from Composio to make that call, never the redirect itself.

Reconnecting a disconnected/failed toolkit goes through this same endpoint - there is no separate "reconnect" call.

Only ever called through the SDK bridge on behalf of a signed-in workspace admin (see `createConnectHandler`'s fixed `POST /integrations/connect` route) - not meant for a bare server-to-server call with no workspace admin in the browser.

**Request body**

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `toolkitSlug` | `string` | yes |  |
| `scope` | `shared` \| `personal` | no |  |
| `returnUrl` | `uri` | yes |  |
| `connectionData` | `object` | no |  |

**Responses**

| Status | Meaning |
| --- | --- |
| `200` | Where to send the browser to complete the connection |
| `400` | validation_error - toolkit not enabled, or already actively connected |
| `403` | forbidden - key missing integrations:manage |

**Response body**

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `redirectUrl` | `string` | yes |  |
| `connectionId` | `string` | yes |  |

### POST `/api/v1/application/integrations/disconnect`

Disconnect an integration

**Scope** `integrations:manage` &nbsp;·&nbsp; **Rate limit** 30 requests / min per key

Ends this workspace's connection to one toolkit and deletes the underlying Composio connected account. Idempotent - disconnecting a toolkit that was never connected, or is already disconnected, is a success.

**Request body**

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `toolkitSlug` | `string` | yes |  |
| `scope` | `shared` \| `personal` | no |  |

**Responses**

| Status | Meaning |
| --- | --- |
| `200` | Disconnected |
| `403` | forbidden - key missing integrations:manage |

**Response body**

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `success` | `boolean` | yes |  |

### POST `/api/v1/application/integrations/actions/execute`

Execute an action against a connected integration

**Scope** `integrations:execute` &nbsp;·&nbsp; **Rate limit** 60 requests / min per key

Calls a Composio action (e.g. "list upcoming Google Calendar events") against this workspace's connected account for the named toolkit - Connect proxies the call using its own stored connection reference, so you never hold or see the underlying OAuth token.

`params` is passed through to Composio verbatim; its shape is defined by the toolkit's own action schema, which Connect does not model.

**Request body**

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `toolkitSlug` | `string` | yes |  |
| `actionSlug` | `string` | yes |  |
| `onBehalfOfUserId` | `any` | no |  |
| `params` | `object` | no |  |

**Responses**

| Status | Meaning |
| --- | --- |
| `200` | The action's result |
| `403` | forbidden - key missing integrations:execute |
| `404` | not_found - no active connection to this toolkit |

**Response body**

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `data` | `any` | yes |  |
| `successful` | `boolean` | yes |  |

### POST `/api/v1/application/integrations/proxy`

Call a connected integration's own API

**Scope** `integrations:execute` &nbsp;·&nbsp; **Rate limit** 60 requests / min per key

Sends one request to the provider's own API through this workspace's connected account, for an endpoint the toolkit ships no action for - Zoho Books has no "create sales order" action, for example. Connect attaches the credential; you never hold or see it.

`endpoint` is a path **relative to the provider's API base** (`/salesorders`), and nothing else: an absolute URL, a query string in the path, or a dot segment is refused, because whoever chooses the host chooses where the workspace's credential is sent. Put parameters in `query`. Headers cannot be set.

The response always comes back `200` once Connect has relayed the request. `status` is the provider's own HTTP status and `data` its body, so a provider-side refusal is read from those rather than from Connect's status code.

**Request body**

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `toolkitSlug` | `string` | yes |  |
| `onBehalfOfUserId` | `any` | no |  |
| `method` | `GET` \| `POST` \| `PUT` \| `PATCH` \| `DELETE` | yes |  |
| `endpoint` | `string` | yes |  |
| `query` | `object` | no |  |
| `body` | `any` | no |  |

**Responses**

| Status | Meaning |
| --- | --- |
| `200` | The provider's status and body |
| `400` | validation_error - an endpoint that is not a relative path, or an unknown method |
| `403` | forbidden - key missing integrations:execute |
| `404` | not_found - no active connection to this toolkit |

**Response body**

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `status` | `integer` | yes |  |
| `data` | `any` | yes |  |