# Error handling (/docs/errors)

This page is about the operational decision. The status codes, error codes, and
envelopes themselves are listed once, in
[Errors & status codes](/docs/reference/errors).

## Read both layers \[#read-both-layers]

A CoreSpeed call can fail at two levels, and only one of them shows up in the
HTTP status:

* **Transport and identity** — HTTP `4xx` / `5xx`. The tool never ran. Routes
  answer with the nested envelope `{ "error": { "type", "code",
  "message" } }`; branch on `error.code` — see
  [Errors & status codes](/docs/reference/errors) for the few exceptions.
* **Tool execution** — HTTP `200` with `result.isError: true`. The request was
  authorized, then the tool refused or failed. The text is for the agent to
  read; `result.structuredContent.error.code` is the value to branch on.

Treating a `200` as success is the most common integration bug against this
surface. Branch on `isError` before interpreting a result. An unknown tool
name also answers `200`, with a JSON-RPC `error` instead of a `result`.

## Who fixes what \[#who-fixes-what]

| Failure                                                                         | Owner                             | Move                                                                                                                                      |
| ------------------------------------------------------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `401` on every call                                                             | The client                        | Fix the header form, or replace a revoked key.                                                                                            |
| `payment_required` — tool `isError` on `/mcp`, `402` on the media upload routes | An org admin                      | Top up. Discovery and account reads keep working meanwhile; memory calls wait with the other metered calls.                               |
| `key_spend_limit_exceeded` — tool `isError` on `/mcp`                           | An org admin                      | Raise the key's cap, or wait for the monthly reset.                                                                                       |
| `403 org_suspended` — on every organization-scoped route, `/mcp` included       | Support                           | No client-side or billing fix exists.                                                                                                     |
| `403 agent_suspended`                                                           | The agent's owner or an org admin | Resume the agent. The key is fine; do not rotate it.                                                                                      |
| `403 api_key_cannot_mint`                                                       | The client                        | A key does not mint keys; mint from the dashboard or a signed-in client.                                                                  |
| `403 member_principal_required`                                                 | The client                        | That act is a member's own (create an agent, shape an environment, the `/me` user actions); use a member's credential.                    |
| `404 endpoint_not_found`                                                        | The client                        | An unknown path, or a connector this environment does not offer. Tools are called on `POST /mcp`.                                         |
| `needs_reauth` on an account                                                    | The end user                      | Reauthorize in the dashboard.                                                                                                             |
| `ambiguous_account`                                                             | The agent                         | Pass `account` with one of the aliases in `structuredContent.error.aliases`.                                                              |
| `tool_not_granted`                                                              | The account's owner               | The account is shared with you for other tools only. Ask the owner to extend the share.                                                   |
| `tool_not_in_environment`                                                       | Whoever set up the environment    | This session's environment leaves the tool or the account out. Edit the environment from a session outside it, or use another URL or key. |
| `404 environment_not_found` / `403 environment_mismatch` on `/mcp/env/<name>`   | The client                        | The URL names no environment of yours, or the key is bound to a different one.                                                            |
| `not_configured`                                                                | Support                           | This environment lacks an app registration the connector needs; reconnecting does not help.                                               |
| Tool absent from `tools/list`                                                   | An org admin or the end user      | The capability is disabled, or the account is not connected for this caller.                                                              |

## Reauthorization is not a key problem \[#reauthorization-is-not-a-key-problem]

`needs_reauth` means the upstream OAuth grant can no longer be used. The
connector stays known and its tools stay visible — calls just fail until the
account is reauthorized.

Send the user to
[Dashboard → Connectors](https://app.corespeed.io/connectors). Do not rotate the
CoreSpeed key and do not rewrite client configuration: neither one is broken.

## Retry rules \[#retry-rules]

* `401`, `402`, `403`, `404` — do not retry. Nothing changes without an action by
  the client, an admin, or support.
* `429` — wait out `Retry-After` (or the top-level `retry_after`), then retry.
* `503` — transient; retry with backoff. CoreSpeed failed closed rather than
  guess.
* `500` — retry with backoff, and keep `error.request_id` for support.
* Tool-level `isError` — retry on `rate_limited` (after
  `retry_after_seconds`), `upstream_unreachable`, `token_refresh_failed`,
  `platform_db_unavailable`, `approval_unavailable`, or `internal` (once);
  media also says so with `retryable`. After `upstream_unreachable`,
  `upstream_response_invalid` or `cancelled` on a write, check whether the
  change landed before repeating it. A hold, a spend cap, `needs_reauth`,
  `tool_not_granted`, `tool_not_in_environment`, `api_key_cannot_mint`, or
  `member_principal_required` will not clear on retry.

## Spend caps are boundaries, not reservations \[#spend-caps-are-boundaries-not-reservations]

A cap is checked at the request boundary. An action already in flight can finish
above the remaining amount before its final cost is recorded — so a cap bounds
what starts, not what completes. See
[Billing & credits](/docs/billing).