---
title: "Error codes"
description: "Broker, MCP, OAuth, provider and inbox errors with status, retryability and the next action, including the v1.2 RP trust codes."
image: "https://docs.agentregistrar.dev/og/docs-default.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.agentregistrar.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Error codes

Errors come from five layers, and each has its own envelope. Read the layer
first, then the code. `error_description` and `message` are for humans and may
change; `error`, `code` and `retryable` are the control-flow fields.

| Layer | Where it appears | Shape |
| :--- | :--- | :--- |
| Discovery validation | The conformance/attestation validator, not an agent call | Exit code plus `errors`, `warnings`, `conformance`, `attestation` |
| MCP transport | `POST /mcp` before a tool runs | HTTP status plus a JSON-RPC `error` object |
| MCP tools | Inside a tool result | HTTP `200` with `isError: true` and the tool error envelope |
| Provider handshake | The provider's `agent_signup` response | `error`, optional `error_description`, optional `retryable` |
| Broker APIs and OAuth | `/api/broker/*`, `/api/oauth/*` | HTTP status plus `{error, error_description, code}` where a client branches |

The tool error envelope, returned as text content with `isError: true`:

```json
{
  "error": {
    "code": "rp_unverified",
    "message": "The relying party is not verified and the human owner's policy requires verification.",
    "retryable": false,
    "side_effects": {
      "acceptance_recorded": false,
      "acceptance_created": false,
      "ledger_email_sent_to_agent": false,
      "ledger_email_forwarded_to_human": false,
      "rp_verification_recorded": false,
      "rp_verification_reused": false,
      "assertion_minted": false
    },
    "next_step": "Ask the human owner to change the RP policy or use a verified provider. Nothing was minted.",
    "docs": "agentregistrar://docs/error-recovery"
  }
}
```

`side_effects` reports what the failed call actually did. It is the difference
between "retry safely" and "retry and duplicate a legal event", so read it before
retrying anything with a side effect.

## Discovery and verification

The validator is a producer tool, not an agent-facing error surface. It reports
two verdicts and an exit code:

| Exit | Meaning | Next action |
| :--- | :--- | :--- |
| `0` | The document was fetched, parsed, and is conformant; no required verification failed. Warnings do not change the code. | Publish. `attestation.status` may still be `unverified`, `expired`, or `invalid` without `--require-attestation`. |
| `1` | A conformance error (including unparseable JSON or a malformed `rp_attestation` field), or a failed attestation under `--require-attestation`. | Read `errors` and fix the document or credential. |
| `2` | The run could not obtain what it needed: document read/fetch failure, or JWKS unavailable under `--require-attestation`. `conformance` is not evaluated. | Fix hosting, the URL, the timeout, or the JWKS, then re-run. |

A malformed `rp_attestation` field is a conformance error with
`attestation.status: "not_checked"`, never `invalid`. An absent credential is
`unverified`, never invalid. See the [validator
guide](/agent-identity/v1/validator/).

## MCP transport and gate errors

These arrive with a non-`200` HTTP status from `POST /mcp`, before any tool runs.
The JSON-RPC `id` is `null`.

| Code | HTTP | Retryable | Meaning | Next action |
| :--- | :-: | :-: | :--- | :--- |
| `-32001` | `401` | no | Bearer token missing, invalid, expired or revoked. The response carries `WWW-Authenticate` with `resource_metadata` and `scope`. | Re-run the harness OAuth step, then retry. Do not retry the same token. |
| `-32003` | `403` | no | Human owner suspended, or the Master Agent Delegation agreement is unsigned. | The human fixes the account in the portal. Retrying cannot succeed. |
| `-32000` | `406` | no | The request did not accept `text/event-stream` (a `GET` without an SSE `Accept` header). | Use `POST` with `Accept: application/json, text/event-stream`. |

The three codes are broker implementation details, not part of
`agent-identity-v1`. The stable part is the HTTP status and the
`WWW-Authenticate` challenge.

## MCP tool errors

A tool failure is a successful JSON-RPC call: HTTP `200`, a result object with
`isError: true`, and the envelope above. Every tool uses the same code set.
`retryable` and `next_step` are part of the envelope; the table repeats them for
lookup.

| Code | Tools | Retryable | Meaning | Next action |
| :--- | :--- | :-: | :--- | :--- |
| `human_suspended` | identity tools | no | Human owner account is suspended. | The human reactivates the account in the portal. |
| `human_email_unverified` | identity tools | no | Human owner email is not verified. | The human verifies the email, then retry. |
| `master_tos_unsigned` | identity tools | no | The Master Agent Delegation agreement is unsigned. | The human completes onboarding and signs it. |
| `agent_revoked` | all tools | no | The agent is revoked or operator-suspended. `identity_record_signup` still records historical proofs for a revoked agent. | The human reviews a revocation in the portal. An operator suspension is operator-only to restore. Do not retry or create a replacement silently. |
| `scope_not_granted` | identity and inbox tools | no | The agent lacks `tos:accept`, `inbox:read`, `inbox:send`, or one entry of `requested_scopes`. | Check `identity_whoami`; request only delegated scopes. |
| `tos_text_hash_mismatch` | `identity_signup`, `identity_prove` | no | `accepted_tos_text` does not hash to the published/accepted hash. No side effect occurred. | Re-fetch `terms.text`, recompute, send the exact text. |
| `tos_hash_mismatch` | `identity_signup`, `identity_prove` | no | The accepted terms hash does not match the discovery document. | Hard stop: re-discover, re-read, re-accept. |
| `discovery_unreachable` | `identity_signup`, `identity_prove` | yes | The discovery document could not be fetched. Nothing was minted. | Retry the same call; the broker refetches. |
| `discovery_invalid` | `identity_signup`, `identity_prove` | no | The discovery document failed validation or its signup endpoint is not fetchable. | Ask the provider to fix discovery. |
| `protocol_unsupported` | `identity_signup`, `identity_prove` | no | `protocol` is not `agent-identity-v1`. | Stop; the service is not supported. |
| `issuer_not_allowed` | `identity_signup`, `identity_prove` | no | The broker is not in the provider's `issuer_allowlist`. | Stop; this broker is not accepted by the provider. |
| `discovery_source_not_allowed` | `identity_signup`, `identity_prove`, `identity_record_signup` | no | A forbidden or mixed discovery/attestation source was supplied, or a `require` policy rejected `agent_supplied_discovery`. | Use `identity_signup` with `service`, or supply exactly one allowed source. |
| `rp_unverified` | `identity_signup`, `identity_prove` | no | No attestation, and policy is `require`. | Ask the human owner to change policy or use a verified provider. |
| `rp_attestation_invalid` | `identity_signup`, `identity_prove`, `identity_record_signup` | no | The attestation failed parse, signature, claims, origin, audience, or key binding, or the exact historical attestation is unavailable. | Use the exact attestation; never substitute a newer one. |
| `rp_attestation_expired` | `identity_signup`, `identity_prove` | no | The attestation is past `exp` plus 60 seconds. | Use a current attestation or a non-gated policy. |
| `rp_broker_untrusted` | `identity_signup`, `identity_prove`, `identity_record_signup` | no | The attestation `iss` is outside the effective trust list. No JWKS fetch was made. | Ask the human owner to review the broker trust list. |
| `rp_broker_unavailable` | `identity_signup`, `identity_prove`, `identity_record_signup` | yes | A transient broker metadata/JWKS outage. Nothing was written. | Retry later. Do not downgrade to unverified. |
| `rp_suspended` | `identity_signup`, `identity_prove` | no | The attestation or local provider status is suspended. | Stop; the provider is suspended. |
| `rp_blocked` | `identity_signup`, `identity_prove` | no | The human policy is `block`. | The human changes the policy if intended. |
| `ledger_email_failed` | `identity_signup`, `identity_prove` | yes | The acceptance is recorded; the ledger email dispatch did not finish. | Retry the same call. Only mail is retried; no new acceptance. |
| `signup_token_invalid` | `identity_record_signup` | no | The receipt failed signature, `typ`, audience, claim, historical-window, or conflict checks, or it is a legacy broker-signed token. | Obtain an RP-signed v1.2 receipt and the exact historical attestation. Do not retry the rejected token. |
| `signup_token_not_linked` | `identity_record_signup` | no | The receipt references an acceptance this broker has not recorded for that agent/audience. | Call `identity_signup` or `identity_prove` first. |
| `signup_token_ownership_mismatch` | `identity_record_signup` | no | The receipt belongs to another agent or human owner. | Stop. Use the receipt issued for this authenticated agent. |
| `message_not_found` | `inbox_read` | no | No message with that id for this agent. | Use an id from `inbox_list`/`inbox_wait_for_email`. |
| `wait_timeout` | `inbox_wait_for_email` | yes | Nothing matched within the wait budget. | Widen the lookback or wait again with a longer budget. Do not poll tightly. |
| `send_failed` | `inbox_send` | yes | The mail provider rejected or did not accept the message. Nothing was sent or recorded. | Retry; nothing left the broker. |
| `internal_error` | all tools | yes | Unexpected broker, database, or issuance failure. | Retry with the same inputs after reading `side_effects`. |

`unverified` is not `invalid`. Absence of the optional credential is a complete,
honest verification result; only `require` refuses it. A suspended agent returns
`agent_revoked` with a suspension-specific message and next step; there is no
`agent_suspended` code.

### Reading `side_effects`

| Tool | Keys |
| :--- | :--- |
| `identity_signup`, `identity_prove` | `acceptance_recorded`, `acceptance_created`, `ledger_email_sent_to_agent`, `ledger_email_forwarded_to_human`, `rp_verification_recorded`, `rp_verification_reused`, `assertion_minted` (seven keys; `assertion_minted` appears on errors) |
| `identity_register`, `identity_whoami` | `{}` (nothing happened) |
| `identity_record_signup` | `signup_recorded`, `signup_upgraded`, `rp_verification_recorded`, `conflict_flagged` |
| `inbox_read`, `inbox_list`, `inbox_wait_for_email` | `{}` |
| `inbox_send` | `email_sent`, `message_recorded` |

`identity_signup`/`identity_prove` report all seven keys on gate, discovery,
policy, and issuance failures, so an all-false block is positive evidence that no
acceptance was written. An unexpected catch-all `internal_error` may report `{}`.

## Inbox errors

The three inbox-specific codes are in the tool table above; this section adds the
operational detail.

| Code | Retryable | What to check | Do not |
| :--- | :-: | :--- | :--- |
| `message_not_found` | no | The id belongs to this agent, and the message still exists. | Reuse an id from another agent's inbox. |
| `wait_timeout` | yes | `from_contains`, `subject_contains`, `since_seconds_ago`, and whether the sender used the agent address. | Poll in a tight loop, or assume the mail will never arrive. |
| `send_failed` | yes | The mail provider status in `message`. Nothing was sent. | Resend blindly after a success response; `inbox_send` is not idempotent. |

`inbox_send` also reports `internal_error` when the email left the broker but the
ledger insert failed; `side_effects.email_sent` is `true` there. Check
`inbox_list` before sending again.

## Provider handshake errors

The provider returns these from `POST endpoints.agent_signup`. The envelope is
`error`, optional `error_description`, optional `retryable`; the mapping is fixed
for v1. When `retryable` is absent, infer it from the code.

| Code | HTTP | Retryable | Meaning | Next action |
| :--- | :-: | :-: | :--- | :--- |
| `invalid_request` | `400` | no | Malformed headers or body. | Fix the request. |
| `invalid_assertion` | `401` | no | Signature or shape verification failed. | Mint a new assertion. |
| `issuer_not_allowed` | `403` | no | `iss` is not in the provider's `issuer_allowlist`. | Stop. This broker is not accepted by that provider. |
| `audience_mismatch` | `403` | no | `aud` does not equal the provider's `audience`. | Re-discover and mint for the correct audience. |
| `assertion_expired` | `401` | no | `exp` passed beyond the allowed clock skew. | Mint a new assertion. |
| `assertion_replayed` | `409` | no | The `jti` was used in a conflicting context. | Mint a new assertion. |
| `tos_hash_mismatch` | `409` | no | The accepted terms hash is stale or wrong. | Re-discover, re-read, re-accept, re-prove. |
| `insufficient_scope` | `403` | no | `delegation.scopes` misses a `required_scopes` entry. | Ask the human to re-delegate. |
| `human_unverified` | `403` | no | `human.verified` is not `true`. | Complete human verification. |
| `rate_limited` | `429` | yes | The caller is throttled. | Back off, then retry. |

A `jti` repeated for the **same** agent, account and service is not a replay. The
provider resolves it idempotently, usually with `resolution: "resolved"`.
`assertion_replayed` is reserved for a conflicting context. The normative table
is [spec section
7.2](/agent-identity/v1/specification/#72-error-envelope-and-stable-codes).

## Provider API and Contract F/D.2 errors

Provider-facing routes and the authenticated RP routes use
`{error, error_description, code}`.

| Code | HTTP | Route | Meaning |
| :--- | :-: | :--- | :--- |
| `invalid_api_key` | `401` | `/api/provider/v1/*` | Missing, malformed, revoked, or unknown provider API key. |
| `insufficient_provider_scope` | `403` | provider API | The key lacks the required scope (`account:read`, `keys:write`, `attestation:read`, `attestation:write`). |
| `provider_suspended` | `403` | provider mutations | The provider account is suspended or revoked. |
| `provider_not_eligible` | `403` | attestation issuance | The plan/entitlement, origin, or key prerequisite is not met. Free is conformant but cannot issue. |
| `audience_conflict` / `origin_conflict` | `409` | origin registration | The origin or audience is already registered. Goes to operator review. |
| `key_exists` / `key_limit_reached` / `last_active_key` / `key_not_active` / `no_active_key` | `409` | key routes | Frozen key-lifecycle errors. At most 8 active+retiring keys. |
| `challenge_expired` / `challenge_consumed` | `410` | challenge routes | The challenge TTL passed or it was already used. |
| `challenge_failed` / `pop_invalid` | `422` | origin/key proof | The challenge proof failed, or the key PoP is invalid. |
| `invalid_jwk` / `purpose_mismatch` | `400` | key registration | The public JWK is invalid, or purposes are not exactly `["receipt","abuse"]`. |
| `attestation_not_found` | `404` | status/GET attestation | Unknown attestation `jti` or no current attestation. |
| `invalid_attestation` | `401` | Contract F/D.2 | The attestation failed parse/signature/claims/binding. |
| `rp_broker_untrusted` | `401` | Contract F/D.2 | The attestation `iss` is outside the effective trust list. Checked before JWKS. |
| `attestation_not_valid_at_signing` | `401` | Contract F | The receipt `iat`/`signed_up_at` is outside the named attestation window. |
| `attestation_unavailable` | `401` | Contract F | The exact historical attestation is not retrievable and no snapshot exists. |
| `invalid_signup_token` | `401` | Contract F | The receipt parse/signature/claims failed. The legacy sanitized body is preserved. |
| `signup_conflict` | `409` | Contract F | Same receipt `jti` with a different body. |
| `unauthenticated_report` | `401` | D.2 | A legacy unsigned abuse body. Use the signed `{rp_attestation, report}` form. |
| `rate_limited` | `429` | Contract F/D.2/provider | Bounded ingestion or request rate exceeded. |
| `rp_broker_unavailable` | `503` | Contract F/D.2 | Transient trusted-broker metadata/JWKS outage; retryable, no write. |

## OAuth, DCR and broker API errors

These use the RFC 6749 `error` field or a broker-specific one. They are
HTTP-level failures, not tool results.

| Code | HTTP | Endpoint | Meaning | Next action |
| :--- | :-: | :--- | :--- | :--- |
| `invalid_client_metadata` | `400` | `/api/oauth/register` | Invalid JSON or client metadata. | Fix the registration body. |
| `invalid_redirect_uri` | `400` | `/api/oauth/register`, authorize decision | A redirect URI is not loopback HTTP or HTTPS, or does not match the client. | Register the exact URI you use. |
| `invalid_client` | `400` | `/oauth/authorize`, decision | The `client_id` cannot be resolved. | Register the client or fix the id. |
| `unsupported_response_type` | `400` | authorize | `response_type` is not `code`. | Use `response_type=code`. |
| `invalid_request` | `400` | authorize, token, broker APIs, webhook | Malformed request or schema failure. | Fix the request. |
| `unauthorized` | `401` | authorize decision | No human session. | Sign in, then approve. |
| `access_denied` | `403` | authorize decision | Human suspended, or the human denied the request. | Do not retry; the human decides. |
| `email_verification_required` | `403` | authorize decision | `accept_master_tos` was set but the email is unverified. | Verify the email first. |
| `master_tos_required` | `400` | authorize decision | The Master ToS is unsigned and was not accepted in the decision. | Sign it in onboarding or the consent card. |
| `invalid_grant` | `400` | `/api/oauth/token` | Code unknown/expired/used, PKCE mismatch, refresh token unknown/revoked, human inactive/unsigned. | Restart the authorization flow. |
| `unsupported_grant_type` | `400` | `/api/oauth/token` | Grant type is not `authorization_code` or `refresh_token`. | Fix the request. |
| `server_error` | `500` | OAuth endpoints | The broker could not persist the client, code or token. | Retry later; do not loop. |
| `invalid_webhook_signature` | `401` | `/api/webhooks/resend` | Missing or invalid provider webhook signature. | Operator only; fix the webhook secret. |

## Decision table

| Situation | Do not | Do |
| :--- | :--- | :--- |
| `tos_hash_mismatch` or `tos_text_hash_mismatch` | Retry the same assertion or text | Re-discover, re-read `terms.text`, re-accept, re-prove |
| `rp_unverified` under `require` | Treat the provider as invalid | Ask the human to change policy or use a verified provider |
| `rp_attestation_expired` | Reuse the expired attestation | Renew/publish a current attestation, or use a non-gated policy |
| `rp_broker_unavailable` | Downgrade to unverified or retry tightly | Retry later; the gate fails closed |
| `rp_suspended` / `rp_blocked` | Retry | Stop; the provider or policy is blocking |
| `discovery_source_not_allowed` | Pass a discovery document to `identity_signup` | Call `identity_signup` with `service`; or `identity_prove` with exactly one allowed source |
| `assertion_expired` | Reuse the JWT | Mint a new assertion |
| `assertion_replayed` | Retry blindly | Confirm the same-context idempotent path; otherwise mint new |
| `ledger_email_failed` | Assume the acceptance was lost | Retry; the acceptance is recorded and only mail re-sends |
| `signup_token_invalid` | Substitute a newer attestation or a legacy token | Use the RP-signed v1.2 receipt with the exact historical attestation |
| Inbox `wait_timeout` | Poll in a tight loop | Use a longer timeout or narrower filter |
| Lost provider credential | Re-run signup | Use the provider recovery path in the context guide |
| `agent_revoked` | Retry or register a replacement silently | Tell the human; they review the agent in the portal |
| `-32001` on `/mcp` | Retry the same token | Re-run the harness OAuth step |

## Verification record

The v1.2 code names and retryability were checked against the shared
`McpToolErrorCodeSchema`, the RP verification mapping, the Contract F/D.2
handlers, and the provider route errors at broker commit `5e57ecf` on
2026-10-24. This page does not claim a live deployment of the v1.2 broker; live
error responses are part of the release verification gate.

Source: https://docs.agentregistrar.dev/reference/error-codes/index.mdx
