Reason codes
The closed decision vocabulary, byte-identical across the Python SDK, the HTTP contract and every client — producible codes, handling, and the reserved set.
Producible codes
The closed decision vocabulary, byte-identical across the Python SDK, the HTTP contract and every client. Any change is a major version of the contract and flows to every language at once — your switch will not silently meet an unknown code within a contract major.
| Code | Condition | Meaning | Typical handling |
|---|---|---|---|
accepted | — | all nine conditions passed; verified is present | proceed |
request_malformed | precondition | the challenge body violates the §10.5 shape (e.g. empty claim policy) | fix challenge construction |
verifier_not_active | 1 | your verifier registration is not ACTIVE on proven state | check your registry state |
origin_not_registered | 1 | the origin does not resolve to your verifier (no trust-by-name) | register/renew the exact origin |
revalidation_expired | 1 | domain-control proof for the origin lapsed | renew (MsgRenewVerifierOrigin) |
request_key_not_active | 2 | the challenge's signing key is unregistered or rotated out | rotate/register keys properly |
nonce_replayed | 3 | this challenge's nonce was already consumed | issue a fresh challenge; investigate replays |
deadline_exceeded | 4 | the response arrived at or after the 10-second deadline | issue a fresh challenge |
proof_invalid | 5 | the proof failed verification or is bound to a different challenge context | reject; possible tampering |
predicate_missing | 6 | a requested predicate is not among the proven ones | reject; holder cannot satisfy policy |
issuer_not_accepted | 7 | the credential's issuer is outside your accepted-issuer policy | policy decision — yours |
issuer_not_active | 7 | the issuer is not operational (or below your state minimum) | reject; retry later is legitimate |
status_root_stale | 7 | proof built against a superseded root, or the root expired (fail closed) | holder retries with a fresh proof |
height_stale | 8 | the challenge's chain height fell outside [latest − max_height_lag, latest] | issue a fresh challenge |
state_unproven | 1/8 | a chain read could not be cryptographically proven — including claimed absence | infrastructure alert; never bypass |
nonce_race_lost | 9 | a concurrent presentation consumed the nonce first (atomic accept gate) | reject; exactly one winner exists |
Notes:
- Issuer-state policy nuance (condition 7): with the default
issuer_state_minimum: "ACTIVE", an issuer under enhanced monitoring rejects asissuer_not_active; opting into"ENHANCED_MONITORING"accepts it. This is the only policy knob in the vocabulary's behavior, and it is your policy, expressed in the challenge. state_unprovenis your pager, not your bug. It means the SDK refused to decide on unproven state — a node lying, a proof failing, an anchor unreachable. The correct response is operational, never a retry-until-accept loop.
Reserved codes
Present in the contract enum for defense in depth; structurally unreachable through the validation entrypoint — do not wait for them:
| Code | Why it cannot occur |
|---|---|
origin_invalid | a non-exact origin already fails the shape precondition and rejects as request_malformed |
domain_validation_failed | abstract base reason; concrete subclasses are always raised instead |