SDK docs · sdk/06

Reason codes

Seventeen producible codes, six agent-profile codes and two reserved ones explain every accept and reject, byte-identical in the Python SDK, the HTTP contract and every client, and fixed within a contract major.

Producible codes

Every decision carries exactly one of these codes. Changing the list is a major contract version that reaches every language at once, so within a contract major your switch never meets a code it does not know.

CodeConditionMeaningTypical handling
accepted—all nine conditions passed; verified is presentproceed
request_malformedpreconditionthe challenge body violates the signed-challenge shape (e.g. empty claim policy)fix challenge construction
verifier_not_active1your verifier registration is not ACTIVE on proven statecheck your registry state
origin_not_registered1the origin does not resolve to your verifier (no trust-by-name)register/renew the exact origin
revalidation_expired1domain-control proof for the origin lapsedrenew the origin registration
request_key_not_active2the challenge's signing key is unregistered or rotated outrotate/register keys properly
request_not_ours (1.5.0)after 2the request is valid on chain but names another verifier or origin than this service's own — a presentation made for someone else, handed to youreject; a replay across relying parties, never retried
nonce_replayed3this challenge's nonce was already consumedissue a fresh challenge; investigate replays
deadline_exceeded4the response arrived at or after the 10-second deadlineissue a fresh challenge
proof_invalid5the proof failed verification or is bound to a different challenge contextreject; possible tampering
predicate_missing6a requested predicate is not among the proven onesreject; holder cannot satisfy policy
issuer_not_accepted7the credential's issuer is outside your accepted-issuer policypolicy decision — yours
issuer_not_active7the issuer is not operational (or below your state minimum)reject; retry later is legitimate
status_root_stale7proof built against a root that is no longer acceptable (superseded and outside its validity window), or a root that has expired (fail closed)holder retries with a fresh proof
height_stale8the challenge's chain height fell outside [latest − max_height_lag, latest]issue a fresh challenge
state_unproven1/8a chain read could not be cryptographically proven — including claimed absenceinfrastructure alert; never skip the check
nonce_race_lost9a 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 as issuer_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_unproven is 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.

Agent-profile codes (contract 1.2.0, 1.3.0, 1.4.0, 1.5.0, 1.8.0)

Produced only when the challenge was created with profile: "agent_v1" — an agent answering for a principal, under a claim policy of the complete payment-identity profile.

CodeWhereMeaningTypical handling
scope_value_not_in_vocabularychallenge (HTTP 422), or preconditionthe policy names a scope value (or a key-custody class) that no vocabulary version admitted at the challenge's height carries — unknown, gated, or not in its canonical formfix the policy; the service never rewrites a value
policy_axis_uncoveredchallenge (HTTP 422), or preconditionthe policy's transaction limit is at or above your configured fail-closed value threshold, and it does not request every axis (action, counterparty, category, region, time + value)request the missing axes, or lower the limit
scope_axis_unprovable5a scope value the policy names has no anchor at the credential's issuer — the axis cannot be proven, so nothing is acceptedreject; that issuer delegates no agent for that value
pseudonym_verifier_mismatch (1.3.0)challenge (HTTP 422), or preconditionthe policy's pairwise_pseudonym entry names a verifier id other than your own — asking for the identifier another verifier knows the agent by is refusedname your own verifier id
pseudonym_required (1.4.0)challenge (HTTP 422), or preconditionthe policy carries no pairwise_pseudonym entry — mandatory, exactly once, in every agent policy (every agent is recognisable by the party that verifies it)add {"claim": "pairwise_pseudonym", "value": "<your verifier id>"}
unexpired_required (1.5.0)challenge (HTTP 422), or preconditionthe policy does not ask unexpired — mandatory in every agent policy: an agent past its lease is refused by every relying party, whatever else is askedadd {"claim": "unexpired", "value": true}
dispute_retention_unavailable (1.8.0)9the verifier could not keep the agent presentation's dispute retention record (no store, or the store failed), so it does not accept it: an agent presentation that could not be disputed is never accepted. Decided before the nonce is spentoperator alert: fix the retention store; the holder may retry with a fresh challenge

At challenge time scope_value_not_in_vocabulary, policy_axis_uncovered, pseudonym_verifier_mismatch, pseudonym_required and unexpired_required are an HTTP 422 answer (policy_refused, with the code) — no challenge and no session exist. They can also appear as a decision if a challenge comes back for validation that your deployment would not have issued (a lower threshold since, a hand-edited request). An agent presentation whose scope evidence is anchored under an older root than the one it claims is status_root_stale, and one whose evidence does not check is proof_invalid — the identity meanings, unchanged. Since 1.8.0 every agent presentation carries a dispute ciphertext: a missing or invalid one is proof_invalid too, and a presentation whose dispute retention record your verifier cannot keep is dispute_retention_unavailable (condition 9, in the table above), never accepted.

Reserved codes

Present in the contract enum for defence in depth; structurally unreachable through the validation entrypoint — do not wait for them:

CodeWhy it cannot occur
origin_invalida non-exact origin already fails the shape precondition and rejects as request_malformed
domain_validation_faileda generic reason; a more specific code is always returned instead

Seventeen producible codes (sixteen, plus request_not_ours), seven agent-profile codes and two reserved ones: that is the whole enum in the contract, and nothing outside it can reach your switch.