[Bug]: validate_credentials:true has no effect for API_KEY — invalid keys create an ACTIVE connected account
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:
- Create an auth config for Resend (
API_KEY, custom auth). - Connect an account with a deliberately invalid key (e.g.
re_invalid_00000000). - The account goes ACTIVE — an invalid key connects identically to a valid one.
- Execute
RESEND_SEND_EMAILon 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.
Source: ComposioHQ/composio