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:

Nested — identity middleware, catch-all, every 5xx
{
  "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:

Flat, code first — connector routes
{
  "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:

Flat, message first — billing, key, and agent routes
{
  "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:

429 — nested envelope with a sibling field
{
  "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:

406-32000

Accept must list both application/json and text/event-stream. Sending only application/json fails here, which is why every sample sets both.

415-32000

Content-Type must be application/json.

400-32700

Parse error — malformed JSON, or a body that is not a valid JSON-RPC message.

400-32600

A 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

400no_active_org

The 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.

401missing_authorization

No 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.

401invalid_jwt

A 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.

401invalid_api_key

The API key is unknown, revoked, or expired. Replace it — do not retry.

402no_active_org

The 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.

402payment_required

The organization wallet crossed its billing threshold. Billable tools stop before execution; discovery and account reads still work. Topping up clears it.

402key_spend_limit_exceeded

This 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.

403org_suspended

Administrative suspension. Adding balance does not clear it — contact support.

403admin_required

A remote-connector write (register, refresh, or remove) was attempted by a non-admin member. Flat envelope.

403agent_suspended

The 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.

403agent_retired

The 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.

404endpoint_not_found

Unknown 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.

405Allow: POST

GET /mcp. The endpoint is POST-only — there is no SSE downstream. This response has no body and no error code: only the Allow header.

429rate_limit_exceeded

Too 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.

409multiple_accounts

DELETE /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.

503platform_db_unavailable

The 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.

500internal_error

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

HTTP 200 with a failed tool
{
  "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.

200Tool <name> not found

An 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.

200insufficient_balance

A 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.

200jwt_session_required

A 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.

200no_active_org

The 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.

200payment_required

The organization is on a billing hold, caught before the metered tool executed — the tool-level mirror of the 402 above.

200key_spend_limit_exceeded

The API key hit its monthly cap, caught before the metered tool executed.

200org_suspended

Administrative 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

SymptomFirst move
401 on every callCheck the header form, then whether the key was revoked.
429 in burstsBack 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 onlyTop up. Discovery still works, so the surface is intact.
403 org_suspendedSupport. No client-side fix exists.
404 on a tool call URLYou are calling a retired per-connector URL. Use POST /mcp.
Tool missing from tools/listCapability disabled, or the account is not connected for this caller.
isError with needs_reauthReauthorize the account in the dashboard.