#4120·composio

[Bug]: validate_credentials:true has no effect for API_KEY — invalid keys create an ACTIVE connected account

Author: nikhilchowdhury-nixetCreated Aug 12, 2026Updated Sep 9, 2026
Labelssupport

SDK Language

Both — the behaviour is server-side. Reproducible directly against POST /api/v3.1/connected_accounts via raw HTTP, and observed identically through the hosted dashboard and @composio/core.

SDK Version

@composio/[email protected]; also reproduced via raw HTTP to the v3.1 API (no SDK).

Runtime Environment

Node on macOS; the reproduction is raw HTTP + the hosted dashboard.

Environment

Production Deployment

Describe the Bug

For static-credential schemes (API_KEY, BASIC, BEARER_TOKEN), an invalid credential creates a connected account with status: ACTIVE. The credential is never checked against the vendor at create time, and validate_credentials does not change this.

1. validate_credentials: true has no effect for API_KEY, contradicting its own typings.

ConnectedAccountCreateParams documents the flag as "[EXPERIMENTAL] Whether to validate the provided credentials, validates only for API Key Auth scheme" (@composio/client/resources/connected-accounts.d.mts). In practice, sending validate_credentials: true with a deliberately invalid API_KEY still returns 201 with status: ACTIVE. In our testing this held for 26/26 API_KEY toolkits tried with garbage keys. The flag is parsed (a non-boolean value 400s; an unknown field is silently ignored) — it simply has no effect on the outcome.

2. There is no credential-verification surface for static schemes.

A path scan of the v3.1 connected_accounts surface (create / get / list / patch / delete / revoke / refresh / status / link / complete_auth) exposes nothing that checks a static credential against the vendor. refresh(id, { validate_credentials }) returns 409 for API_KEY / BASIC / BEARER_TOKEN ("only redirectable schemes support /refresh"), so that path is unavailable too.

3. It can't be reliably worked around client-side either — we measured it.

Because there's no first-party check, we tried the obvious workaround: after create, probe the connection with a safe, read-only, argument-free call and look for a 401. Argument-bearing tools are unusable for this (a wrong argument returns its own 404 / 400, indistinguishable from an auth failure), and write tools obviously can't be called to test a key — so this reduces to the toolkit's get_current_user_endpoint, or an arg-free readOnlyHint tool.

We ran that probe against all 936 static-credential toolkits in the catalog with a deliberately invalid key. Only 444 (47%) returned a clean 401. For the rest, an invalid key is indistinguishable from a working one on any safe request:

Response to an invalid key Toolkits Usable as a verdict?
401 444 ✅ bad key
404 — endpoint routes before it authenticates 114
403 — authenticates, then hits an ACL 89 ❌ could be a valid but under-scoped key
200 — the endpoint is public 70 ❌ any key "works"
400 — the "get" actually requires an argument 50
401 on an OAuth-capable toolkit 45 ❌ may 401 a valid static key
unreachable — SSRF-blocked / non-routable base_url 86 ❌ can't call it
vendor 5xx / other 38

So ~53% of static toolkits can't be validated client-side by any safe request. Two of those buckets are Composio-side metadata issues worth flagging on their own: get_current_user_endpoint points at a public URL for some toolkits (e.g. jira/rest/api/3/serverInfo, serpapi/locations.json), so it 200s any key, and it is stale / 404s for a large chunk of the rest.

The practical effect for an end user: they connect with a wrong key, see "connected", and it only surfaces later at tools.execute as a real vendor 401. status: ACTIVE currently means "stored", not "valid" — surprising given the flag's documentation.

Steps to Reproduce

Dashboard:

  1. Create an auth config for Resend (API_KEY, custom auth).
  2. Connect an account with a deliberately invalid key (e.g. re_invalid_00000000).
  3. The account goes ACTIVE — an invalid key connects identically to a valid one.
  4. Execute RESEND_SEND_EMAIL on that connection → it returns a real 401.

Raw API:

POST /api/v3.1/connected_accounts
{
  "auth_config": { "id": "<api_key auth config>" },
  "connection": {
    "user_id": "u1",
    "state": { "authScheme": "API_KEY", "val": { "status": "ACTIVE", "api_key": "totally-invalid" } }
  },
  "validate_credentials": true
}
→ 201, connected_account.status = "ACTIVE"

Expected

With validate_credentials: true, an invalid API_KEY should fail the create (or land in a non-ACTIVE status), per the documented behaviour. More broadly, a first-party way to verify a static credential at connect would remove the need for the client-side probe above — since ~half of toolkits cannot be validated any other way without a real (mutating or argument-bearing) call. At minimum, either honour the flag for API_KEY or correct the typings so callers don't build on documented-but-absent behaviour.