Error codes
Errors arrive in two places: as standard HTTP responses for transport-level failures, and as a typed error field on the tool result for application-level failures. Both are documented below with resolution paths.
Two error layers
Transport-layer errors arrive as a non-200 HTTP response with a JSON body like {"error":"Unauthorized"}. They mean the request never reached a tool handler.
Tool-result errors arrive as a 200 HTTP response with a JSON-RPC tools/call result whose content[0].text deserializes to { ok: false, error: "<code>", ... } (or in the action-engine case, a McpActionResult with status: "error"). They mean the tool ran but couldn't complete.
Transport-layer errors
| Code | HTTP | What it means | How to fix |
|---|---|---|---|
unauthorized | HTTP 401 | Missing or invalid bearer token. | Set the `Authorization: Bearer <token>` header on every request. The token is issued by the operator dashboard once available; in beta it is the shared secret stored in AWS Secrets Manager under `mcpBearerToken`. |
quota_exceeded | HTTP 429 | Per-minute, daily call, or daily inference budget exceeded. | Respect the `Retry-After` header (seconds). Per-minute window: 20 calls. Daily window: 1000 calls. Daily inference budget applies. See Rate limits. |
Example: 401
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{"error":"Unauthorized"}Example: 429
HTTP/1.1 429 Too Many Requests
Retry-After: 47
Content-Type: application/json
{"error":"Too Many Requests"}Retry-After is in seconds. For minute-window violations it bounds the wait until your oldest call falls out of the rolling 60s window; for daily-window violations it's the seconds until midnight UTC.
Tool-result errors
| Code | What it means | How to fix |
|---|---|---|
invalid_arguments | Tool input failed Zod validation. | Check the field name, type, and required-ness in the tool reference. Empty strings on `min(1)` fields are the most common cause. |
trust_insufficient | The current autonomy level for this (action class × counterparty) is too low for direct execution. | Result will carry `status: "pending_approval"` and a `decision_id`. Wait for the user to approve via `action.approve` (Coming July 20, 2026) or surface the approval in your own UI. |
action_engine_error | The action engine refused to enqueue the decision (downstream failure, transient). | Retry with exponential backoff. If it persists, inspect the ledger via the operator dashboard. |
decision_expired | The approval window for a pending decision has elapsed. | Re-issue the original action call. Default approval window is 24 hours. |
already_decided | The decision was already approved or rejected. | Inspect the ledger to see the resolution. The decision is idempotent once resolved. |
invalid_decision_id | No pending decision matches the supplied id. | Confirm the id came back from a recent `pending_approval` response and was not truncated. |
feature_not_available | The tool is registered but the underlying capability is not wired in this build. | Currently affects `entity.resolve` and `entity.relationships` (consolidation pipeline pending). See the per-tool status pill. |
not_implemented | The tool name is reserved but not yet exposed on this server. | See the changelog for the planned ship date. Public surface is locked to the allow-list. |
signature_invalid | HMAC verification failed on a dispatch callback (`trigger.complete` / `trigger.reverse`). | Confirm you are signing with the latest `webhook_secret` for the handler (rotate via `handler.rotate_secret` if you suspect a mismatch). The canonical signing string is documented in Dispatch. Replays are accepted within a 5-minute window; outside that window the signature is rejected. |
handler_disabled | The registered handler is disabled (manual disable, repeated failed deliveries, or exceeded DLQ threshold). | Re-enable via `handler.update` after fixing the underlying issue. Inspect recent delivery history with `trigger.history` and replay failed triggers with `trigger.replay` once the handler is healthy. |
idempotency_conflict | Same `Idempotency-Key` seen with a different request body. | Idempotency keys must be unique per logical operation. If you intended a retry, ensure the body is byte-identical to the original. If you intended a new operation, generate a fresh key. |
namespace_owned | action_classes.register rejected because the namespace is owned by a different client_id within the same tenant. | Choose a different namespace, or have the namespace-owning client_id register on your behalf. Namespace ownership is per-tenant and locked at first registration. Per ADR-0022 §1. |
too_many_classes | action_classes.register over per-tenant quota. | Deregister unused classes via `action_classes.deregister` when you are over quota. See Rate limits. |
not_found | The requested resource (action_id, decision_id, counterparty_id, etc.) was not found. | Confirm the id came from a recent tool response and was not truncated. Tenant-scoped — cross-tenant ids never resolve. |
write_failed | The tool could not persist the requested write (ledger, episode correction, etc.). | Retry once. If it persists, check tenant context, encryption key availability, and operator logs. |
Example: pending_approval (not an error, but a non-success outcome)
{
"content": [
{
"type": "text",
"text": "{\"status\":\"pending_approval\",\"decision_id\":\"dec_01HXP9\",\"message\":\"Trust insufficient for direct execution; routed to approval queue.\"}"
}
]
}pending_approval is the engine's designed response for actions that need user consent, not an exception. Treat it as a third lane alongside success and error.