Errors & status codes
The single source for CoreSpeed HTTP status codes, error codes, error envelopes, and MCP isError results.
Every status code and error code returned by the endpoints in this reference is listed here. Other pages link to this one instead of repeating the table, so this is the copy to trust if you find a disagreement.
The remote-connector management routes — POST /connectors/remote and
POST /connectors/:slug/refresh — are not part of this reference yet, so their
codes (slug_taken, oauth_discovery_failed, oauth_registration_failed,
cooldown, snapshot_failed) are not listed below.
Two envelopes
Identity, billing, and unknown-endpoint failures use a nested envelope:
{
"error": {
"type": "authentication_error",
"message": "Authorization header or x-api-key is required",
"code": "missing_authorization"
}
}Connector-route business failures use a flat envelope. Parse both:
{
"error": "multiple_accounts",
"message": "Multiple accounts connected for notion; use manage accounts_remove with a specific alias."
}Rate limiting adds a third shape: the nested envelope plus a sibling
retry_after, alongside the Retry-After header. Read the header or the
sibling field — not error.retry_after, which does not exist:
{
"error": {
"type": "rate_limit_error",
"message": "Rate limit exceeded. Retry after 60s.",
"code": "rate_limit_exceeded"
},
"retry_after": 60
}Transport errors on /mcp
Identity and rate limiting run first: POST /mcp mounts requireIdentity()
and rateLimit() ahead of the transport, so an unauthenticated caller gets the
nested 401 and a throttled one gets 429 — the envelopes above — no matter
what the request body or headers look like.
Only once the caller is identified does the Streamable HTTP transport validate
the envelope itself. Those failures answer with a bare JSON-RPC error object —
{ "jsonrpc": "2.0", "error": { code, message }, "id": null } — not either
envelope above:
-32000Accept must list both application/json and text/event-stream. Sending
only application/json fails here, which is why every sample sets both.
-32000Content-Type must be application/json.
-32700Parse error — malformed JSON, or a body that is not a valid JSON-RPC message.
-32600A JSON-RPC batch (array body). CoreSpeed rejects arrays ahead of the transport so one rate-limit slot cannot carry an unbounded number of tool calls.
HTTP status codes
no_active_orgThe credential is valid but resolves to no active organization, on a connector
or identity route. Flat envelope. Pick or create an organization in the
dashboard. The same condition answers 402 on billable routes — see below.
missing_authorizationNo Authorization and no x-api-key header. On /mcp the response also
carries a WWW-Authenticate header pointing at the protected-resource
metadata, which is how OAuth-capable clients start browser sign-in.
invalid_jwtA bearer JWT failed verification. Which token to renew depends on which one
you sent: a user session JWT is refreshed against the session issuer, while an
MCP OAuth token on /mcp has its own issuer and lifecycle and is renewed by
re-running the client's browser sign-in — refreshing the session does nothing
for it. Both failures answer with this one code.
invalid_api_keyThe API key is unknown, revoked, or expired. Replace it — do not retry.
no_active_orgThe same condition as the 400 above, reached on a billable HTTP route —
the LLM proxy. The wallet check fails closed rather than run unbilled, so the
status is 402 and the body is the nested envelope with
type: "payment_required". /mcp does not mount this check; a metered
tools/call answers 200 with isError instead — see the tool-result codes
below.
payment_requiredThe organization wallet crossed its billing threshold. Billable tools stop before execution; discovery and account reads still work. Topping up clears it.
key_spend_limit_exceededThis API key reached its monthly spend cap. Raise the cap or wait for the reset. Caps are request-boundary guardrails, so a call already in flight can finish above the remaining amount.
org_suspendedAdministrative suspension. Adding balance does not clear it — contact support.
admin_requiredA remote-connector write (register, refresh, or remove) was attempted by a non-admin member. Flat envelope.
endpoint_not_foundUnknown path, an unknown connector id, or a retired per-connector /
per-engine MCP URL. Call the unified POST /mcp.
Allow: POSTGET /mcp. The endpoint is POST-only — there is no SSE downstream. This
response has no body and no error code: only the Allow header.
rate_limit_exceededToo many requests in the current 60-second window. Two independent dimensions
are metered: per API key, and per user for session callers — so one runaway key
is throttled without affecting the rest of the organization. Wait out
Retry-After (advisory, matches the window) before retrying; the body repeats
it as a top-level retry_after.
multiple_accountsDELETE /connectors/:id matched more than one account. Remove one directly
via DELETE /connectors/:id/accounts/:accountId, or use
manage__accounts_remove with an alias. Flat envelope.
platform_db_unavailableThe API-key authority (the platform database) was unreachable on a cache miss. The gateway fails closed rather than allowing an unverified key, so this is a transient infrastructure state, not a credential problem — retry with backoff.
internal_errorUnhandled gateway error. Retry with backoff and keep the request id for support.
Earlier releases used insufficient_balance and wallet_blocked. Current
clients should branch on payment_required and org_suspended.
MCP tool errors
A tool failure is not an HTTP failure. The transport answers 200 and the
JSON-RPC result carries isError: true with the detail serialized as text:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\"error\":{\"type\":\"forbidden\",\"message\":\"This operation requires a human session, not an API key.\",\"code\":\"jwt_session_required\"}}"
}
],
"isError": true
}
}Always check result.isError in addition to the HTTP status.
Tool <name> not foundAn unknown or disabled tool name. The MCP SDK catches it and answers with an
isError result, so a typo in params.name never surfaces in the HTTP status
— and neither do schema violations on params.arguments.
jwt_session_requiredA JWT-gated manage__* tool (API keys, connected accounts, whoami) was
called with an API key. Manage those from the dashboard or a user-session
client. The remote-connector tools (manage__remote_*) are the exception and
accept API keys.
payment_requiredThe organization is on a billing hold, caught before the metered tool
executed — the tool-level mirror of the 402 above.
key_spend_limit_exceededThe API key hit its monthly cap, caught before the metered tool executed.
org_suspendedAdministrative suspension, caught before the metered tool executed — the
tool-level mirror of the 403 above.
Other expected tool-level failures carry the upstream provider's own message rather than a CoreSpeed code:
- a connector account that moved to
needs_reauth— send the user to Dashboard → Connectors to reauthorize, and do not rotate the CoreSpeed key: the broken credential is the provider's OAuth grant, not yours; - an upstream provider API rejecting the requested operation;
- arguments that violate the tool's
inputSchema; - a capability that is no longer present in this caller's
tools/list.
Recovery at a glance
| Symptom | First move |
|---|---|
401 on every call | Check the header form, then whether the key was revoked. |
429 in bursts | Back off for Retry-After. The window follows the credential, one or the other: a session call is limited per user, an API-key call per key. A runaway key is throttled on its own without consuming its owner's session window. |
402 on billable tools only | Top up. Discovery still works, so the surface is intact. |
403 org_suspended | Support. No client-side fix exists. |
404 on a tool call URL | You are calling a retired per-connector URL. Use POST /mcp. |
Tool missing from tools/list | Capability disabled, or the account is not connected for this caller. |
isError with needs_reauth | Reauthorize the account in the dashboard. |