> For the complete documentation index, see [llms.txt](https://docs.brain.fi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.brain.fi/resources/errors.md).

# Errors

Every error Brain returns uses a single envelope: a stable `snake_case` `code`, a human-readable `message`, optional structured `details`, a `request_id` for cross-system correlation, and a `docs_url`. Codes are stable forever once shipped (format: `{domain}_{condition}`). The full registry lives in `shared/src/errors.ts`.

{% hint style="warning" %}
Brain **never** returns HTTP 200 with an error in the body. A non-2xx status always carries this envelope.
{% endhint %}

### Shape

```json
{
  "error": {
    "code": "policy_denied",
    "message": "Counterparty not in the approved allowlist",
    "details": { "counterparty_id": "cp_x", "policy_version": 3 },
    "request_id": "req_8f3a92...",
    "docs_url": "https://docs.brain.fi/resources/errors#policy_denied"
  }
}
```

In the SDK:

```typescript
try {
  await brain.pay("acme", { invoiceId: "inv_8231" });
} catch (err) {
  if (err instanceof BrainError) {
    console.log(err.code, err.message, err.requestId);
  }
}
```

### Auth (401) and Authorization (403)

| Code                       | Meaning                                                                               | Fix                                              |
| -------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------ |
| `auth_token_missing`       | No bearer token on a protected route                                                  | Send `Authorization: Bearer <token>`             |
| `auth_token_invalid`       | Token malformed or signature failed                                                   | Re-issue the token                               |
| `auth_token_expired`       | Access token past its expiry                                                          | Log in / re-sign and retry                       |
| `auth_invalid_key`         | API key malformed, revoked, or for the wrong environment                              | Check `.env`; sandbox and production keys differ |
| `auth_invalid_credentials` | Email/password login failed (also returned for an unknown email. No user enumeration) | Check the credentials                            |
| `auth_email_unverified`    | The owner's email has not been verified                                               | Complete `POST /v1/auth/verify-email`            |
| `auth_siwx_invalid`        | SIWX signature did not verify                                                         | Re-sign with the registered key                  |
| `auth_scope_insufficient`  | Token lacks the required scope                                                        | Re-issue with the right scope                    |

### Self-serve onboarding

| Code                    | Meaning                                                         | Fix                                     |
| ----------------------- | --------------------------------------------------------------- | --------------------------------------- |
| `signup_email_taken`    | An account with this email already exists (409)                 | Log in instead, or use another email    |
| `signup_token_invalid`  | The email-verification token is invalid, expired, or used (400) | Request a new verification token        |
| `wallet_already_linked` | The wallet is already linked to an account (409)                | Use a different wallet, or unlink first |

### Tenant

| Code                   | Meaning                                           | Fix                                                |
| ---------------------- | ------------------------------------------------- | -------------------------------------------------- |
| `tenant_not_found`     | The `tenant_id` doesn't exist in this environment | Verify the id; sandbox and production are separate |
| `tenant_suspended`     | The tenant is suspended (403)                     | Contact support                                    |
| `tenant_access_denied` | Authenticated, but not for this tenant (403)      | Check the token's tenant                           |

### Source and Raw

| Code                            | Meaning                                                                                                                                                                                                                                                         | Fix                                                                    |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `source_not_found`              | Source id doesn't match a connected source                                                                                                                                                                                                                      | List sources to find the right id                                      |
| `source_credential_invalid`     | Upstream-source credentials were rejected (401)                                                                                                                                                                                                                 | Reconnect the source                                                   |
| `raw_artifact_not_found`        | No raw artifact for that id (404)                                                                                                                                                                                                                               | Check the id                                                           |
| `raw_source_unsupported`        | Unknown `source_type` (400)                                                                                                                                                                                                                                     | Use a supported source type                                            |
| `raw_source_reserved`           | A high-trust provider `source_type` (`plaid`/`stripe`) was sent to the generic `/raw/ingest` route (403). Those may only arrive via the authenticated `/raw/webhooks/{provider}` path, so a generic caller can't mint high-trust evidence by choosing the label | Ingest via the provider webhook, or use `upload` / `agent_contributed` |
| `raw_webhook_signature_invalid` | A provider webhook's HMAC didn't verify (401)                                                                                                                                                                                                                   | Check the signing secret                                               |

### Policy

Policy **decisions** are `allow` / `confirm` / `reject` (returned on the decision, not as errors). These codes are the error conditions:

| Code                       | Meaning                                                | Fix                                    |
| -------------------------- | ------------------------------------------------------ | -------------------------------------- |
| `policy_not_found`         | No policy for the tenant (404)                         | Create + activate a policy             |
| `policy_not_active`        | The tenant has no active policy version (409)          | Activate a policy version              |
| `policy_denied`            | The action violates the active policy (422)            | Read `details` for which rule fired    |
| `policy_quorum_not_met`    | Required approver quorum not reached (409)             | Collect the remaining approvals        |
| `policy_version_mismatch`  | The decision was for a superseded policy version (409) | Re-evaluate against the active version |
| `policy_signature_invalid` | The signed policy attestation didn't verify (401)      | Re-sign the policy                     |

### Agent and scope

| Code                        | Meaning                                                | Fix                                          |
| --------------------------- | ------------------------------------------------------ | -------------------------------------------- |
| `agent_not_found`           | Agent id doesn't match a registered agent (404)        | List agents                                  |
| `agent_not_registered`      | Agent has no on-chain registration (401)               | Register in `BrainMCPAgentRegistry`          |
| `agent_inactive`            | Agent record is not `active` (409)                     | Reactivate / re-register                     |
| `agent_scope_hash_mismatch` | JWT `scope_hash` ≠ the on-chain hash (401)             | Re-sign with current scope (or it's revoked) |
| `scope_expired`             | The scope grant's window has passed (403)              | Renew the grant                              |
| `agent_proposal_duplicate`  | This run already produced an equivalent proposal (409) | Reuse the existing proposal                  |

### PaymentIntent / Action

| Code                           | Meaning                                                                                                                        | Fix                                                                                    |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
| `payment_intent_not_found`     | No PaymentIntent for that id (404)                                                                                             | Check the id                                                                           |
| `payment_intent_invalid_state` | The intent isn't in a state that allows this op (409)                                                                          | Read its current `status`                                                              |
| `payment_intent_gate_failed`   | The §6 pre-execution gate rejected the intent (409)                                                                            | Read `details` for the failed check                                                    |
| `obligation_not_found`         | The linked `obligation_id` doesn't resolve in the Ledger (404)                                                                 | Reference an existing obligation                                                       |
| `obligation_direction_invalid` | A new obligation-linked intent must target a known `payable` obligation; its direction is `null`/unknown or `receivable` (422) | Pay a payable (vendor) obligation; classify the counterparty so its direction resolves |
| `action_already_executed`      | Already settled (409)                                                                                                          | No retry needed                                                                        |
| `idempotency_key_reused`       | Same idempotency key, different request body (409)                                                                             | Generate a new key                                                                     |

### Pre-execution gate failures

When the §6 gate rejects an intent it surfaces as `payment_intent_gate_failed`, with `details` naming the failed check (e.g. behavior-hash pinned 1.5, balance, counterparty, approval, escrow-state binding 6.6). Standalone gate codes:

| Code                           | Meaning                                                |
| ------------------------------ | ------------------------------------------------------ |
| `gate_no_policy_decision`      | No PolicyDecision linked to the intent                 |
| `gate_policy_version_stale`    | The active policy superseded the one Policy evaluated  |
| `gate_counterparty_unverified` | Counterparty `verified_status` doesn't meet the policy |
| `gate_counterparty_sanctioned` | Counterparty is sanctioned per latest screening        |
| `gate_balance_insufficient`    | Source balance < amount at gate time                   |
| `gate_approval_incomplete`     | Required approver signatures missing or invalid        |
| `gate_session_key_invalid`     | On-chain session key expired or out-of-scope           |
| `gate_audit_chain_stale`       | Audit anchor too stale for the configured threshold    |

[**→ The pre-execution gate**](/protocol/the-pre-execution-gate.md)

### Validation (400)

| Code                     | Meaning                                                    |
| ------------------------ | ---------------------------------------------------------- |
| `request_body_invalid`   | Request body failed validation; `details` lists the issues |
| `request_params_invalid` | Path/query params failed validation                        |
| `validation_failed`      | Generic schema validation failure                          |
| `missing_required_field` | A required field is absent                                 |
| `invalid_cursor`         | The pagination cursor is malformed or expired              |

### Rate limiting and server

| Code               | Status | Meaning                                                          |
| ------------------ | ------ | ---------------------------------------------------------------- |
| `rate_limited`     | 429    | Per-minute limit for your tier; honour the `Retry-After` header  |
| `internal_error`   | 500    | Unexpected error; retry, and include the `request_id` in support |
| `upstream_timeout` | 504    | A downstream source timed out; retry with backoff                |
| `maintenance_mode` | 503    | Scheduled maintenance                                            |

### MCP-Specific JSON-RPC Codes

The MCP surface (`POST /v1/agents/mcp`) returns JSON-RPC error codes:

| Code     | Meaning                                                                                              |
| -------- | ---------------------------------------------------------------------------------------------------- |
| `-32001` | Auth token missing, invalid, or expired (covers `auth_token_missing/invalid/expired`)                |
| `-32002` | Scope insufficient. Also tenant mismatch (covers `auth_scope_insufficient` / `auth_tenant_mismatch`) |
| `-32003` | Agent not registered or inactive (`agent_not_registered`)                                            |
| `-32004` | Pre-execution gate failed. Covers every `gate_*` sub-code (`payment_intent_gate_failed`)             |
| `-32005` | Agent `scope_hash` does not match on-chain registration (`agent_scope_hash_mismatch`)                |
| `-32600` | Invalid request (standard JSON-RPC)                                                                  |
| `-32601` | Method not found                                                                                     |
| `-32602` | Invalid params                                                                                       |
| `-32603` | Internal error                                                                                       |

### Getting Help

Include the `request_id` from the error envelope when contacting support. Brain can resolve the exact request from it.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.brain.fi/resources/errors.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
