# Changelog - Apr 24, 2026

**Documentation:** https://docs.composio.dev/docs/changelog/2026/04/24

## SDKs remove legacy automatic file handling config

SDKs remove deprecated `autoUploadDownloadFiles` / `auto_upload_download_files`. Automatic file handling is off unless you set `dangerouslyAllowAutoUploadDownloadFiles` / `dangerously_allow_auto_upload_download_files`. Minor releases.

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):**

```typescript
// 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):

```typescript
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):**

```python
# 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):

```python
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:**

```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:**

```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`:

```typescript
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: `resolveAutoUploadDownloadFilesEnabled` only considers `dangerouslyAllowAutoUploadDownloadFiles`; the old deprecation warning for the legacy property is removed.
* Python: the small `composio.utils.auto_upload_download` helper that merged legacy + new flags is removed; `Tools` and `ToolRouter` take only the explicit opt-in boolean.

Documentation for [executing tools](/docs/tools-direct/executing-tools) and the TypeScript [Composio reference](/reference/sdk-reference/typescript/composio) is updated to describe defaults and the opt-in flag.

---

## Link Auth Migration for Composio-Managed OAuth Connections

POST /api/v3/connected_accounts will stop creating Composio-managed OAuth1/OAuth2/DCR_OAUTH connections. Callers must migrate to POST /api/v3/connected_accounts/link. Rollout begins Friday, May 8, 2026 for new organizations.

`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):

```bash
curl -X POST https://backend.composio.dev/api/v3/connected_accounts \
  -H "Content-Type: application/json" \
  -H "x-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):

```bash
curl -X POST https://backend.composio.dev/api/v3/connected_accounts/link \
  -H "Content-Type: application/json" \
  -H "x-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:

```json
{
  "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 to `POST /api/v3/connected_accounts/link` before 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

The /api/v3/tools/execute/proxy endpoint rejects requests whose endpoint URL is on a different registrable domain than the connected account's base URL.

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):

```json
{
  "endpoint": "https://api.someservice.com/v1/items",
  "method": "GET",
  "connected_account_id": "ca_..."
}
```

**After** — relative endpoint (recommended):

```json
{
  "endpoint": "/v1/items",
  "method": "GET",
  "connected_account_id": "ca_..."
}
```

**Or** — absolute URL on the same registrable domain as the connection's `base_url`:

```json
{
  "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 `endpoint` URLs on a different registrable domain than the connection's `base_url` are rejected with HTTP `400 OriginMismatch`. The upstream request is never made.
* Proxy calls that provide neither `connected_account_id` nor `custom_connection_data` now return HTTP `400 MissingAuthContext` instead of being forwarded without auth.

## Examples

Assume a connected account whose toolkit `base_url` is `https://api.linear.app`.

**Allowed — relative endpoint:**

```bash
curl -X POST https://backend.composio.dev/api/v3/tools/execute/proxy \
  -H 'x-api-key: ' \
  -H 'Content-Type: application/json' \
  -d '{
    "endpoint": "/graphql",
    "method": "POST",
    "connected_account_id": "ca_..."
  }'
```

**Allowed — same registrable domain (different subdomain):**

```json
{ "endpoint": "https://uploads.linear.app/...", "connected_account_id": "ca_..." }
```

**Rejected — different registrable domain:**

```json
{ "endpoint": "https://attacker.example/leak", "connected_account_id": "ca_..." }
```

Response:

```json
{
  "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 against `base_url` and 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.

---