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/org,
GET /connectors/org/:slug, POST /connectors/org/:slug/refresh, and
DELETE /connectors/org/:slug — 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.
Three envelopes
Identity, standing, and unknown-endpoint failures use a nested envelope —
error is an object:
{
"error": {
"type": "authentication_error",
"message": "Authorization header or x-api-key is required",
"code": "missing_authorization"
}
}Connector-route business failures use a flat envelope in which error
is the code and the prose sits in message:
{
"error": "multiple_accounts",
"message": "Multiple accounts connected for notion; use manage accounts_remove with a specific alias."
}The account routes the dashboard drives — /checkout, /customers, /plans,
/api-keys, /agents, and /me — use a second flat envelope in which
error is the message and the code sits beside it — upper snake case,
with key_spend_limit_exceeded as the one lowercase exception:
{
"error": "This API key has reached its monthly spend limit",
"code": "key_spend_limit_exceeded"
}Its codes include INVALID_REQUEST (400), CUSTOMER_NOT_FOUND,
WALLET_NOT_FOUND, and PLAN_NOT_FOUND (404), ALREADY_PRO, ALREADY_FREE,
and CUSTOMER_ALREADY_EXISTS (409), INSUFFICIENT_BALANCE and
key_spend_limit_exceeded (402), and WALLET_BLOCKED and MEMBERSHIP_REVOKED
(403). Any 5xx on these routes is masked to the nested internal_error above,
so a client never sees upstream detail in this shape.
A handler that assumes error is always an object — or always a code — misses
one of these. Branch on typeof error, then on which sibling is present.
Rate limiting is 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.
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 402 form of the condition above: the caller authenticated but no
organization resolved. Still answered by the billing routes the dashboard
drives — POST /checkout/subscribe, POST /checkout/topup, and the
/customers/me/* billing-profile and payment-method routes.
It is not an /mcp status: /mcp mounts no HTTP wallet gate, so a
metered tools/call answers 200 with isError instead — see the
tool-result codes below. The 402 the retired LLM proxy (/v1/*) used to
return from its fail-closed wallet check went away with that surface on
2026-07-30.
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.
agent_suspendedThe credential belongs to an agent that is suspended. The key itself is fine
— resume the agent (its owner or an org admin, via manage__agents_update or
the dashboard) instead of rotating anything. Nested envelope.
agent_retiredThe agent was retired and its keys are being revoked; once the revocation
lands the same key answers 401 invalid_api_key. Nothing restores a retired
agent — mint a new one.
endpoint_not_foundUnknown path, a connector id that is unknown or not offered on this
environment (the two are deliberately indistinguishable — see
Get connector), 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.
The standing codes differ by surface. On /mcp the aggregator's pre-flight
check answers payment_required and org_suspended as tool results (below).
The billing routes answer the same conditions as INSUFFICIENT_BALANCE and
WALLET_BLOCKED in the message-first flat envelope. Media generation adds
its own pre-flight: insufficient_balance when the wallet cannot cover the
model's estimated hold, and billing_authorization_failed (retryable) when
the authorization itself could not be recorded.
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 — or arguments that fail the tool's
inputSchema (Input validation error: Invalid arguments for tool <name>: …). The MCP SDK reports both as a tool result with isError: true, so a
typo in params.name never surfaces in the HTTP status and never arrives as
a JSON-RPC error object either.
insufficient_balanceA media__generate or media__understand call whose estimated hold the
wallet cannot cover. Top up, or pick a cheaper model. Its sibling
billing_authorization_failed means the hold could not be recorded at all
and is retryable.
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.
no_active_orgThe sign-in has no workspace attached yet — typically browser MCP sign-in by a user who has never opened the dashboard. Open the dashboard once to finish setup, then sign in again from the MCP client so the fresh token carries the workspace.
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. |