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:

Nested — identity, billing, catch-all
{
  "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:

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

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. The same condition answers 402 on billable routes — see below.

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

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.

404endpoint_not_found

Unknown path, an unknown connector id, 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.

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:

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

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.

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.