# Errors

The strings your app branches on when a deed fails to verify, plus the
handful an operator's own boot-time self-check can raise. Source of truth:
`crates/grantor-verify/src/guard.rs`'s `Rejected` enum — the same codes in
every language.

## Verifying a presented deed

These are what `DeedGuard`/`DeedVerifier` returns from a normal request —
see [Verify a deed](verify-tokens.md). `code()` is the stable string an app
branches on.

| HTTP | Code | When it happens |
|---|---|---|
| `401` | `MissingDeed` | The caller sent no `X-Grantor-Deed` header. |
| `401` | `MissingChallenge` | The caller sent no `X-Grantor-Challenge` header. |
| `401` | `UnknownChallenge` | The challenge is unissued, expired, or already spent — burned before verification, so this also fires for a flood of bogus deeds. |
| `401` | `BadDeedEncoding` | The deed header is not base64url JSON (a bare JSON object is tolerated). |
| `401` | `UnsupportedMode` | `token.mode` is not one this verifier accepts (only `agent-zk`/`user-sig` reach `verify_deed`; `admin-sig` has its own entry point). |
| `401` | `WrongAudience` | The deed was minted for a different `aud`/tenant than this `DeedPolicy`. |
| `401` | `ChallengeMismatch` | The deed is bound to a different challenge than the one presented. |
| `401` | `Expired` | Past its own `exp`, or `exp` exceeds the policy's `max_ttl_secs`. |
| `401` | `Malformed` | Wrong field shape for its claimed mode (e.g. `agent-zk` carrying `pubkey`/`signature`, or `user-sig` carrying `root`/`proof`). |
| `401` | `BadProof` | The ECDSA signature (`user-sig`) or Semaphore ZK proof (`agent-zk`) failed to verify. |
| `401` | `StaleRoot` | `agent-zk` only — the membership root has aged out of the registry's recent-root window; possible revocation. |
| `401` | `TenantInactive` | **Billing** — the tenant is neither `Active` nor `Grace` on-chain. |
| `503` | `Chain` | An on-chain read (`eth_call`) failed. |

`Chain` is `503` rather than `401` deliberately: an RPC outage is not the
caller's fault, and answering `401` sends clients into a login loop that
discards good credentials and cannot succeed. The verifier **fails closed**
— if the chain cannot be read, no deed is accepted.

Ordering is load-bearing: a token that's the wrong version, wrong
audience/tenant, wrong/replayed challenge, expired, unparseable, or carrying
a bad proof fails before either chain read, so most rejections cost zero RPC
calls. See [Sovereign tier § What the verifier
checks](../sovereign-tier.md#what-the-verifier-checks) for the exact order.

## The operator's own origin-vouch self-check (boot-time, not caller-facing)

These three codes are **not** returned while verifying a caller's deed —
they come only from `DeedGuard::verify_own_origin_vouch` /
`verifyOwnOriginVouchAt`, the DX check an RP calls once at boot to catch its
*own* misconfigured vouch before it causes a stream of failed logins. See
[Sovereign tier § Origin provenance](../sovereign-tier.md#origin-provenance).

| Code | When it happens |
|---|---|
| `OriginVouchExpired` | This guard's own origin vouch is past its `exp`. Checked before any chain read. |
| `OriginVouchTtlExceeded` | This guard's own vouch has an `exp` further out than the maximum accepted TTL (90 days). Checked before any chain read. |
| `BadOriginVouch` | The vouch signature is malformed, or the chain says the recovered signer is not a current voucher for this tenant/epoch. |

## See also

- [Verify a deed](verify-tokens.md) — the relying-party side.
- [Wallet login](wallet-login.md) and [Agent tokens](agent-tokens.md) — the flows that produce the deeds these errors are about.
- [Sovereign tier](../sovereign-tier.md) — the full reference, including exact verification order.
- [`../llms-full.txt`](../llms-full.txt) — the entire product in one file.
