Apr 24, 2026
Latest updates and announcements
SDKs remove legacy automatic file handling config
The Composio SDKs no longer accept the legacy flags that previously controlled automatic file upload and download during tool execution. Automatic handling of file_uploadable fields stays off by default; you opt in with the explicit dangerously* option only.
Breaking change
Those legacy properties are removed from the public constructors and config types. Code that still passes them must migrate. Anyone who relied on the old default-on behavior (no file flag in the constructor) must now explicitly set the dangerously* flag to true to keep automatic path/URL file handling.
SDK versions (minor releases)
| Package | Previous | This release |
|---|---|---|
TypeScript @composio/core | v0.6.11 | v0.8.0 |
Python composio | v0.11.6 | v0.12.0 |
Other @composio/* npm packages that depend on core are published as patch bumps in the same release train (via the changesets updateInternalDependencies: "patch" setting) so they pick up the updated ComposioConfig surface — they have no public-API changes of their own.
Migration
Earlier SDK versions treated automatic file upload/download for file_uploadable tool fields as on by default (TypeScript: autoUploadDownloadFiles defaulted to true when unset; Python followed the same idea for auto_upload_download_files in prior releases). That default is now off. If you still want that behavior, you must explicitly set dangerouslyAllowAutoUploadDownloadFiles: true (TypeScript) or dangerously_allow_auto_upload_download_files=True (Python).
TypeScript — before (legacy):
// If you omitted autoUploadDownloadFiles, it used to default to true
new Composio({
apiKey: process.env.COMPOSIO_API_KEY!,
});
// Equivalent when you set it explicitly:
new Composio({
apiKey: process.env.COMPOSIO_API_KEY!,
autoUploadDownloadFiles: true,
});TypeScript — going forward (set true to match either legacy pattern above):
new Composio({
apiKey: process.env.COMPOSIO_API_KEY!,
dangerouslyAllowAutoUploadDownloadFiles: true,
});To leave automatic file handling off (new default), omit dangerouslyAllowAutoUploadDownloadFiles or set it to false.
Python — before (legacy):
# If you omitted auto_upload_download_files, it used to default to True (prior releases)
Composio(api_key="...")
# Equivalent when you set it explicitly:
Composio(api_key="...", auto_upload_download_files=True)Python — going forward (set True to match either legacy pattern above):
Composio(api_key="...", dangerously_allow_auto_upload_download_files=True)If you construct ToolRouterSession directly (unusual), the keyword argument is now dangerously_allow_auto_upload_download_files instead of auto_upload_download_files.
Local paths must live inside an allowlisted directory
Even with dangerouslyAllowAutoUploadDownloadFiles enabled, the SDK will only auto-upload local file paths that resolve inside an allowlisted directory. Paths outside the allowlist are rejected with ComposioFileUploadPathNotAllowed (TypeScript) / FileUploadPathNotAllowed (Python) before any file is read or sent.
This is a behavior change. Earlier SDK versions had no allowlist: any local path the legacy default-on flag saw was uploaded.
Default allowlist:
fileUploadDirs value | Effective allowlist |
|---|---|
omitted / undefined | [ <home>/.composio/temp ] (default staging dir) |
false | [] — all local paths rejected (URLs and File objects still work) |
[] | [] — same as false; explicit "no local paths" |
[<dir1>, <dir2>, …] | exactly those directories (replaces the default; ~ is expanded, paths are realpath-resolved, comparison is on a path-component boundary) |
URLs (http://… / https://…) and JavaScript File objects are not subject to the allowlist — only local string paths are.
TypeScript:
// Match the *broadest* legacy behavior: auto-upload on, allowlist widened to
// the directories your code actually reads from. Set this scope as tightly
// as you can — every path inside these dirs becomes uploadable to Composio.
new Composio({
apiKey: process.env.COMPOSIO_API_KEY!,
dangerouslyAllowAutoUploadDownloadFiles: true,
fileUploadDirs: ['/srv/uploads', '~/data/inbox'],
});
// Disallow local-path uploads entirely; the SDK will only stage URLs and
// File objects, and reject string paths outright.
new Composio({
apiKey: process.env.COMPOSIO_API_KEY!,
dangerouslyAllowAutoUploadDownloadFiles: true,
fileUploadDirs: false,
});Python:
# Equivalent of the TypeScript example above.
Composio(
api_key="...",
dangerously_allow_auto_upload_download_files=True,
file_upload_dirs=["/srv/uploads", "~/data/inbox"],
)
# Reject all local paths.
Composio(
api_key="...",
dangerously_allow_auto_upload_download_files=True,
file_upload_dirs=False,
)Manual staging (TypeScript only):
If you don't want to widen the allowlist, the TypeScript SDK lets you stage files yourself via composio.files.upload(...). That call bypasses the allowlist by design (the caller is expected to control the path), and the returned { name, mimetype, s3key } descriptor can be passed straight into tools.execute:
const staged = await composio.files.upload({
file: '/anywhere/on/disk/report.pdf',
toolSlug: 'GOOGLEDRIVE_UPLOAD_FILE',
toolkitSlug: 'googledrive',
});
await composio.tools.execute('GOOGLEDRIVE_UPLOAD_FILE', {
userId: 'user-1',
arguments: { file_to_upload: staged },
});The Python SDK does not currently expose a top-level manual-staging API; opt in with dangerously_allow_auto_upload_download_files=True and configure file_upload_dirs to cover the directories your code reads from.
What else changed
- TypeScript:
resolveAutoUploadDownloadFilesEnabledonly considersdangerouslyAllowAutoUploadDownloadFiles; the old deprecation warning for the legacy property is removed. - Python: the small
composio.utils.auto_upload_downloadhelper that merged legacy + new flags is removed;ToolsandToolRoutertake only the explicit opt-in boolean.
Documentation for executing tools and the TypeScript Composio reference is updated to describe defaults and the opt-in flag.
Link Auth Migration for Composio-Managed OAuth Connections
POST /api/v3/connected_accounts is being retired for Composio-managed OAuth connections. New organizations begin migrating on Friday, May 8, 2026, and all remaining organizations follow on Friday, July 3, 2026. Once migrated, affected requests receive 400 BadRequest with a message pointing at the replacement endpoint.
This does not affect custom auth configs (your own OAuth app) or non-OAuth schemes (API key, bearer token, basic auth, etc.) — those continue to work on POST /api/v3/connected_accounts unchanged. Only the specific combination of Composio-managed auth config + redirectable OAuth scheme (OAuth1, OAuth2, DCR_OAUTH) is moving.
Breaking Change (phased rollout)
If your integration calls POST /api/v3/connected_accounts for a Composio-managed OAuth1, OAuth2, or DCR_OAUTH auth config, it will start returning 400 BadRequest on the dates below. Migrate to POST /api/v3/connected_accounts/link before your organization's cutover.
- Friday, May 8, 2026 (00:00 UTC) — organizations created on or after this timestamp are blocked.
- Friday, July 3, 2026 (00:00 UTC) — all remaining organizations are blocked.
What's Changing
| Request | Before | After (once rollout reaches your org) |
|---|---|---|
POST /api/v3/connected_accounts, Composio-managed + OAuth1 / OAuth2 / DCR_OAUTH | Creates a connected account (redirect URL returned) | 400 BadRequest — use /link instead |
POST /api/v3/connected_accounts, custom auth config | Creates a connected account | Unchanged |
POST /api/v3/connected_accounts, API key / bearer / other non-OAuth | Creates a connected account | Unchanged |
POST /api/v3/connected_accounts/link | Creates a link session | Unchanged — the recommended path going forward |
Why
When a connection is initiated through a default (Composio-managed) auth config, a Composio-owned OAuth application is acting on behalf of your integration. We want the end user to explicitly understand and acknowledge, at the moment of connection, that they are granting a third-party application access to their account on the external service. That acknowledgement is enforced by the /link flow, which routes the user through a consent screen before the connection is created. The legacy POST /api/v3/connected_accounts path allowed that step to be bypassed when credentials were passed in directly, which this change closes.
Custom auth configs are unaffected because they are backed by your own OAuth application — the consent screen is served by your app, so you already own that experience. This change is scoped specifically to default auth configs on redirectable schemes, where the third-party relationship is with Composio rather than with the developer.
Migration
Before — legacy create (will be rejected for Composio-managed OAuth):
curl -X POST https://backend.composio.dev/api/v3/connected_accounts \
-H "Content-Type: application/json" \
-H "x-api-key: <YOUR_API_KEY>" \
-d '{
"auth_config": { "id": "ac_your_composio_managed_oauth_config" },
"connection": { "user_id": "your_end_user_id" }
}'After — link session (recommended, works for all schemes including non-OAuth and custom):
curl -X POST https://backend.composio.dev/api/v3/connected_accounts/link \
-H "Content-Type: application/json" \
-H "x-api-key: <YOUR_API_KEY>" \
-d '{
"auth_config_id": "ac_your_composio_managed_oauth_config",
"user_id": "your_end_user_id"
}'The response contains a redirect_url (valid for 10 minutes) that the end user opens to authorize the integration, plus a connected_account_id you can use to poll for status or associate with your own records.
Error Response (after rollout)
Requests that hit the retired combination receive:
{
"error": {
"code": "BadRequest",
"message": "Creating connections on this endpoint for Composio-managed OAuth auth configs is no longer supported. Use POST /api/v3/connected_accounts/link instead.",
"suggestedFix": "Call POST /api/v3/connected_accounts/link with the same auth_config_id and user_id to get a redirect URL for the end user."
}
}What to Do
- If you use Composio-managed OAuth auth configs (OAuth1, OAuth2, or DCR_OAUTH) via
POST /api/v3/connected_accounts: switch toPOST /api/v3/connected_accounts/linkbefore your org's cutover — Friday, May 8, 2026 for organizations created on or after that date, Friday, July 3, 2026 for all others. - If you already use
/link: no action required. - If you use custom OAuth apps (your own
client_id/client_secret) or non-OAuth auth (API key, bearer, etc.): no action required — the legacy endpoint continues to serve those cases.
Proxy execute now enforces same-domain endpoints
To prevent a connection's Authorization header from being forwarded to an unintended host, the proxy execute endpoint (POST /api/v3/tools/execute/proxy) now requires that the outbound endpoint URL share the same scheme and registrable domain (eTLD+1) as the connection's resolved base_url.
Cross-subdomain requests on the same registrable domain continue to work — for example, a Gmail connection with base https://gmail.googleapis.com can still call https://www.googleapis.com/.... Relative endpoints (/users/me/messages) are resolved against the connection's base_url as before and are unaffected.
Breaking Change
Existing proxy calls that pass an absolute endpoint URL whose registrable domain does not match the connection's base_url will now fail with 400 OriginMismatch instead of being forwarded. Calls that omit both connected_account_id and custom_connection_data will fail with 400 MissingAuthContext instead of being forwarded without auth.
Migration
If you currently pass an absolute URL that points at a different domain than your connection's base_url, switch to a relative endpoint (resolved against base_url) or an absolute URL under the same registrable domain.
Before — absolute URL on a different domain (will be rejected):
{
"endpoint": "https://api.someservice.com/v1/items",
"method": "GET",
"connected_account_id": "ca_..."
}After — relative endpoint (recommended):
{
"endpoint": "/v1/items",
"method": "GET",
"connected_account_id": "ca_..."
}Or — absolute URL on the same registrable domain as the connection's base_url:
{
"endpoint": "https://uploads.someservice.com/v1/items",
"method": "GET",
"connected_account_id": "ca_..."
}If your integration legitimately needs to call a different registrable domain for the same connection, reach out so we can add the additional host to the toolkit allowlist.
What changed
- Absolute
endpointURLs on a different registrable domain than the connection'sbase_urlare rejected with HTTP400 OriginMismatch. The upstream request is never made. - Proxy calls that provide neither
connected_account_idnorcustom_connection_datanow return HTTP400 MissingAuthContextinstead of being forwarded without auth.
Examples
Assume a connected account whose toolkit base_url is https://api.linear.app.
Allowed — relative endpoint:
curl -X POST https://backend.composio.dev/api/v3/tools/execute/proxy \
-H 'x-api-key: <YOUR_API_KEY>' \
-H 'Content-Type: application/json' \
-d '{
"endpoint": "/graphql",
"method": "POST",
"connected_account_id": "ca_..."
}'Allowed — same registrable domain (different subdomain):
{ "endpoint": "https://uploads.linear.app/...", "connected_account_id": "ca_..." }Rejected — different registrable domain:
{ "endpoint": "https://attacker.example/leak", "connected_account_id": "ca_..." }Response:
{
"error": {
"code": "OriginMismatch",
"message": "Endpoint host does not match the connection's base_url host."
}
}What to do
- If you call proxy execute with absolute URLs, make sure the host matches (or is a subdomain of) the connection's
base_url. - Prefer relative endpoints (
/path) — they are resolved againstbase_urland are not affected by this change. - If your integration legitimately needs to span multiple registrable domains for the same connection, reach out to us so we can add the additional host to the toolkit allowlist.