# Errors

# Errors

Every error the platform returns follows the same envelope and the same handful of HTTP status codes. This page is the canonical reference: the envelope shape, the status-to-code mapping, and a recovery matrix you can implement against.

For rate-limit specifics see [Rate limits](../rate-limits.md). For pagination-specific error cases see [Pagination and errors](../pagination-errors.md).

## Canonical error envelope

All errors use this exact shape:

```json
{
  "error": {
    "code": "invalid_input",
    "message": "Field 'task' is required.",
    "details": {
      "field": "task",
      "expected": "string"
    }
  }
}
```

Three fields:

- `code`: stable machine-readable identifier. Safe to branch on.
- `message`: human-readable description. Safe to log. Do not pattern-match against it programmatically; copy may change.
- `details`: optional JSON object with structured context. Shape depends on the code. Present when the server has something useful to attach (offending field, rate limit window, violated constraint).

The envelope is consistent across REST and MCP surfaces. MCP tool error responses wrap the same object as the result of the tool call so you can branch on `code` identically.

## HTTP status code mapping

| HTTP status | Common codes                                            | When it fires                                                                                                              |
| ----------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **400**     | `invalid_input`, `invalid_argument`, `validation_error` | The request body, query string, or tool input failed validation. Fix the payload and retry.                                |
| **401**     | `unauthenticated`, `invalid_token`, `expired_token`     | No credentials were presented, or the bearer token could not be parsed. Re-auth.                                           |
| **403**     | `forbidden`, `scope_denied`, `insufficient_role`        | The caller is authenticated but lacks the required scope, role, or tenant membership. Grant the scope or upgrade the role. |
| **404**     | `not_found`                                             | The resource does not exist, or it exists but belongs to another tenant. See "Security-sensitive errors" below.            |
| **409**     | `conflict`, `duplicate`, `version_conflict`             | The write would violate a uniqueness constraint or clobber a newer version of the resource. Read, merge, retry.            |
| **429**     | `rate_limited`                                          | Rate limit exceeded. Back off and retry; see [Rate limits](../rate-limits.md).                                             |
| **500**     | `internal`, `server_error`                              | Unexpected server-side failure. Retry with backoff; file an issue if persistent.                                           |
| **503**     | `service_unavailable`, `dependency_unavailable`         | An upstream dependency (database, Anthropic API, worker queue) is temporarily down. Retry with backoff.                    |

Status codes are the primary signal for your client-side dispatcher; `code` is the secondary signal for precise handling.

## Per-tool error codes

Some tools define richer codes on top of the standard set. They still use the canonical envelope; only the `code` value is more specific.

### `agents.start`

| Code                | Status | Meaning                                                                                                                                                                             |
| ------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `profile_not_found` | 400    | `profile_id` is not in the registered profile catalogue. Call `agents.profiles_list` to see valid ids.                                                                              |
| `invalid_input`     | 400    | Missing or malformed `task` or `input_params`. Details carry the field name.                                                                                                        |
| `enqueue_failed`    | 500    | The session row was created but the worker queue did not accept the job. Retry by calling `agents.start` again; the partially-created session can be inspected via `agents.status`. |

### `agents.resume`

| Code            | Status | Meaning                                                                                           |
| --------------- | ------ | ------------------------------------------------------------------------------------------------- |
| `invalid_input` | 400    | Resume payload did not match `pending_input_schema`. `details.validation_error` has the mismatch. |
| `invalid_state` | 409    | Session is not in `awaiting_input`. `details.current_status` shows where it actually is.          |
| `not_found`     | 404    | Session id unknown, or belongs to another tenant.                                                 |

### `artifacts.update`

| Code               | Status | Meaning                                                                                                                          |
| ------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `version_conflict` | 409    | The `expected_version` supplied does not match the current version. Read the artifact, merge, retry with the new version number. |
| `locked`           | 409    | Artifact is locked for editing by another caller. Retry after the lock expires.                                                  |

### `context_substrate.*`

| Code                  | Status | Meaning                                                                           |
| --------------------- | ------ | --------------------------------------------------------------------------------- |
| `substrate_not_found` | 404    | Substrate id unknown or pre-expiration.                                           |
| `query_too_broad`     | 400    | Query exceeded internal cost ceiling. Narrow the `filter` or reduce `max_tokens`. |

Check each tool's reference page under [API reference: tools](./tools/_index.md) for the authoritative list of codes it emits.

## Security-sensitive errors

Two behaviors are worth calling out because they can look like bugs:

1. **404 instead of 403 for cross-tenant access.** If you request an artifact, session, or webhook that exists but belongs to another business, the platform returns `404 not_found` rather than `403 forbidden`. This prevents probing, so a caller cannot distinguish "does not exist" from "exists but not yours". If you are confident the id is yours and you get a 404, check that your API key is bound to the correct business, not that the resource was deleted.
2. **Masked secrets in error details.** When a validation error touches a field that the platform classifies as sensitive (tokens, API keys, refresh credentials), `details` will show `[MASKED]` in place of the offending value along with a `<field>_masked: true` sentinel. The error message is still useful; the value is just never echoed back.

## Recovery matrix

The client behavior that is right for each code class:

| Code class                                              | Retry?             | Action                                                                                                                                      |
| ------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_input`, `invalid_argument`, `validation_error` | No                 | Fix the payload. The details field names the offending field. Do not retry the same request.                                                |
| `unauthenticated`, `invalid_token`, `expired_token`     | Yes, after re-auth | Run the auth flow to obtain a fresh credential, then retry. See [Authentication](../authentication.md).                                     |
| `forbidden`, `scope_denied`, `insufficient_role`        | No                 | The token is valid but lacks authority. Do not retry; surface to a human to grant the scope or role.                                        |
| `not_found`                                             | No (usually)       | Validate the id. For cross-tenant 404s, verify the API key is bound to the correct business.                                                |
| `conflict`, `duplicate`, `version_conflict`             | Yes, after merge   | Read the current state, reconcile, resubmit with the updated version number or dedupe key.                                                  |
| `rate_limited`                                          | Yes, with backoff  | Honor `Retry-After` or `details.retry_after_seconds`. Exponential backoff with jitter, capped at 60s. See [Rate limits](../rate-limits.md). |
| `internal`, `server_error`                              | Yes, with backoff  | Retry up to 3 times with backoff. If the correlation id repeats, file an issue.                                                             |
| `service_unavailable`, `dependency_unavailable`         | Yes, with backoff  | Same as `internal`. These indicate transient upstream problems.                                                                             |

## Correlation id

Every response carries an `X-Correlation-Id` header. When you surface errors to a user or log them for ops, capture the correlation id. On our side it joins the request to the audit log and any upstream traces. Support requests that include the correlation id resolve significantly faster.

## See also

- [Pagination and errors](../pagination-errors.md) for pagination-specific error conditions
- [Rate limits](../rate-limits.md) for the 429 contract and backoff guidance
- [Authentication](../authentication.md) for 401 and 403 handling
- [Agents guide: error handling](../agents.md#error-handling) for agent-session-specific errors
