Proxy execute

Markdown

Proxy execute lets you call any HTTP endpoint on a supported toolkit using a connected account. Composio injects the authentication (OAuth token, API key, basic auth, etc.) on the server side, so your code never handles raw credentials.

Use it when you need an endpoint that Composio's predefined tools do not cover, or when you want the flexibility of a raw HTTP call while keeping Composio as the source of truth for user auth.

Proxy execute is a form of direct tool execution. It bypasses session state, tool schemas, and modifiers. If you are building an agent, prefer sessions — use the proxy only for the specific API call that isn't available as a tool.

When to use proxy execute

1. Endpoints not covered by a predefined tool

You need a specific endpoint on a toolkit (for example, an unusual LinkedIn or Notion endpoint) that isn't exposed as a predefined Composio tool. Instead of extracting the raw OAuth token and making the call yourself, send the request through proxy execute and let Composio attach credentials.

# Fetch the authenticated user's LinkedIn profile
response = composio.tools.proxy(
    endpoint="/v2/userinfo",
    method="GET",
    connected_account_id="ca_linkedin_user_123",
)

print(response["data"])
// Fetch the authenticated user's LinkedIn profile
const { data } = await composio.tools.proxyExecute({
  endpoint: '/v2/userinfo',
  method: 'GET',
  connectedAccountId: 'ca_linkedin_user_123',
});

console.log(data);

2. Request shapes a predefined tool cannot express

AI workflows that talk to Gmail, Drive, Sheets, Outlook, or Teams often need request shapes beyond the pre-built actions — custom query parameters, partial field masks, advanced filters. Proxy execute gives you the full HTTP surface of the upstream API while keeping auth managed by Composio.

# Read a Google Sheet range with custom render options
response = composio.tools.proxy(
    endpoint="/v4/spreadsheets/1abc.../values/Sheet1!A1:D100",
    method="GET",
    connected_account_id="ca_googlesheets_user_123",
    parameters=[
        {"name": "valueRenderOption", "value": "UNFORMATTED_VALUE", "type": "query"},
        {"name": "dateTimeRenderOption", "value": "SERIAL_NUMBER", "type": "query"},
    ],
)

print(response["data"])
// Read a Google Sheet range with custom render options
const { data } = await composio.tools.proxyExecute({
  endpoint: '/v4/spreadsheets/1abc.../values/Sheet1!A1:D100',
  method: 'GET',
  connectedAccountId: 'ca_googlesheets_user_123',
  parameters: [
    { name: 'valueRenderOption', value: 'UNFORMATTED_VALUE', in: 'query' },
    { name: 'dateTimeRenderOption', value: 'SERIAL_NUMBER', in: 'query' },
  ],
});

console.log(data);

3. Terminal and CLI agents that would otherwise use raw tokens

Agents running in a terminal often fall back to curl calls with a hardcoded bearer token. Replacing that with a call to Composio's proxy endpoint keeps user credentials server-side — no tokens in shell history, env files, or process state.

curl --location 'https://backend.composio.dev/api/v3.1/tools/execute/proxy' \
  --header "x-api-key: $COMPOSIO_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
    "endpoint": "/user/repos",
    "method": "GET",
    "connected_account_id": "ca_github_user_123",
    "parameters": [
      { "name": "per_page", "value": "50", "type": "query" },
      { "name": "sort",     "value": "updated", "type": "query" }
    ]
  }'

Quick start

from composio import Composio

composio = Composio(api_key="your_api_key")

response = composio.tools.proxy(
    endpoint="/repos/composiohq/composio/issues/1",
    method="GET",
    connected_account_id="ca_github_user_123",
    parameters=[
        {"name": "Accept", "value": "application/vnd.github.v3+json", "type": "header"},
    ],
)

print(response["status"])
print(response["data"])
import { Composio } from '@composio/core';

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

const { status, data } = await composio.tools.proxyExecute({
  endpoint: '/repos/composiohq/composio/issues/1',
  method: 'GET',
  connectedAccountId: 'ca_github_user_123',
  parameters: [
    { name: 'Accept', value: 'application/vnd.github.v3+json', in: 'header' },
  ],
});

console.log(status);
console.log(data);

The endpoint can be an absolute URL (https://api.example.com/v1/resource) or a relative path (/v1/resource). Relative paths are resolved against the toolkit's default base URL — only use the absolute form when calling a host that isn't the toolkit's standard API (for example, a regional Salesforce or Zendesk domain).

Proxy execute rejects cross-domain requests. The endpoint must resolve to the same domain as the connected account's toolkit (for example, a GitHub connection can only call api.github.com paths). Pointing the proxy at an arbitrary third-party host will fail — this is an intentional security boundary, not a quota, so it cannot be bypassed by adjusting the request.

Parameters

ParameterRequiredTypeDescription
endpointYesstringAbsolute URL or path relative to the toolkit's base URL.
methodYes"GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD"HTTP verb.
connected_account_id / connectedAccountIdNostringWhich connected account to use for auth. Defaults to the most recent active account for the project.
bodyNoobjectJSON request body. Used with POST, PUT, and PATCH.
parametersNoArray<{ name, value, type }>Extra headers or query parameters. type is "header" or "query" (Python) / in is "header" or "query" (TypeScript).
binary_bodyNo{ url } | { base64, content_type? }Binary upload payload. Either a URL to fetch or base64-encoded bytes (up to 4 MB).

The full schema is documented in the POST /api/v3.1/tools/execute/proxy reference.

Response shape

{
  "data": { /* parsed JSON body returned by the upstream API */ },
  "status": 200,
  "headers": { "content-type": "application/json", "...": "..." },
  "binary_data": {
    "url": "https://...",
    "content_type": "application/pdf",
    "size": 12345,
    "expires_at": "2026-01-01T00:00:00Z"
  }
}

binary_data is only present when the upstream API returns a binary response (PDFs, images, etc.). See Binary data support for details.

Common patterns

GET with query parameters

response = composio.tools.proxy(
    endpoint="/search/issues",
    method="GET",
    connected_account_id="ca_github_user_123",
    parameters=[
        {"name": "q", "value": "is:open label:bug repo:composiohq/composio", "type": "query"},
        {"name": "per_page", "value": "20", "type": "query"},
    ],
)
const { data } = await composio.tools.proxyExecute({
  endpoint: '/search/issues',
  method: 'GET',
  connectedAccountId: 'ca_github_user_123',
  parameters: [
    { name: 'q', value: 'is:open label:bug repo:composiohq/composio', in: 'query' },
    { name: 'per_page', value: '20', in: 'query' },
  ],
});

POST with a JSON body

response = composio.tools.proxy(
    endpoint="/repos/composiohq/composio/issues",
    method="POST",
    connected_account_id="ca_github_user_123",
    body={
        "title": "Found a bug",
        "body": "Steps to reproduce: ...",
        "labels": ["bug"],
    },
)
const { data } = await composio.tools.proxyExecute({
  endpoint: '/repos/composiohq/composio/issues',
  method: 'POST',
  connectedAccountId: 'ca_github_user_123',
  body: {
    title: 'Found a bug',
    body: 'Steps to reproduce: ...',
    labels: ['bug'],
  },
});

Custom headers

Add vendor-specific headers (API versions, accept types, correlation IDs) via parameters with type: "header".

response = composio.tools.proxy(
    endpoint="/crm/v3/objects/contacts",
    method="GET",
    connected_account_id="ca_hubspot_user_123",
    parameters=[
        {"name": "Accept", "value": "application/json", "type": "header"},
        {"name": "X-Request-Id", "value": "req_abc123", "type": "header"},
        {"name": "limit", "value": "100", "type": "query"},
    ],
)
const { data } = await composio.tools.proxyExecute({
  endpoint: '/crm/v3/objects/contacts',
  method: 'GET',
  connectedAccountId: 'ca_hubspot_user_123',
  parameters: [
    { name: 'Accept', value: 'application/json', in: 'header' },
    { name: 'X-Request-Id', value: 'req_abc123', in: 'header' },
    { name: 'limit', value: '100', in: 'query' },
  ],
});

Do not set the Authorization header yourself — Composio injects the correct one based on the connected account's auth scheme. Setting it manually will override Composio's credential and usually produces a 401.

Uploading a file

Use binary_body to upload binary content. You can point at a URL that Composio fetches server-side, or inline the bytes as base64.

curl --location 'https://backend.composio.dev/api/v3.1/tools/execute/proxy' \
  --header "x-api-key: $COMPOSIO_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
    "endpoint": "/upload",
    "method": "POST",
    "connected_account_id": "ca_abc123",
    "binary_body": { "url": "https://example.com/report.pdf" }
  }'

binary_body posts the file as the request body with a single Content-Type header. It does not build a multipart/form-data payload, so APIs that require multipart uploads (for example, Twitter / X media upload, Slack files.upload, some Google upload endpoints) are not supported today. For those, wrap the call in a custom tool that constructs the multipart body yourself.

See the binary data changelog entry for the full upload and download flow.

Error handling

Proxy execute forwards the upstream response verbatim — status, headers, and data reflect what the toolkit API returned. Check status and branch on the common failures.

StatusTypical causeHow to resolve
400 Bad RequestMalformed endpoint path, invalid body, or unsupported method.Check the upstream API docs for the expected request shape; proxy execute does not validate upstream schemas.
401 UnauthorizedThe connected account's token expired, was revoked, or is for the wrong project.Re-authenticate the user, or import fresh credentials. Verify the connected_account_id belongs to the caller's project.
403 ForbiddenThe user's OAuth scopes or API key permissions do not cover this endpoint.Update the auth config scopes and have the user re-consent. For API keys, regenerate with the required permissions.
429 Too Many RequestsUpstream rate limit (GitHub, Google, etc.).Honor the Retry-After header from the response, back off exponentially, and batch requests where possible. Composio does not retry automatically.