SDK docs · sdk/06

Reason codes

One decision, a closed vocabulary: how rejections are structured, the families they fall into, and the two rules of handling them.

One decision, a closed vocabulary

Every validation returns exactly one decision: accepted, or a rejection carrying one machine-readable reason code from a closed vocabulary — byte-identical across the Python SDK, the HTTP contract and every client. Any change to that vocabulary is a major version of the contract and flows to every language at once, so your switch will not silently meet an unknown code within a contract major. The full vocabulary ships with the SDK and the frozen service contract; what matters when you design an integration is the families the rejections fall into.

FamilyWhat it meansTypical handling
Challenge problemsthe challenge your side built is malformed or violates the contract shapefix challenge construction — this is always a bug on the verifier side
Registration & origin problemsyour verifier registration, exact origin, or signing key is not active on proven chain stateoperational: check your registry state, renew the origin proof, rotate keys properly
Freshness & replay problemsthe presentation arrived too late, or its one-time challenge was already consumedissue a fresh challenge; repeated replays are worth investigating
Proof & predicate problemsthe zero-knowledge proof failed verification, is bound to a different challenge, or does not prove what your policy askedreject — the holder cannot satisfy your policy, or something was tampered with
Issuer policy & status problemsthe credential’s issuer is outside your accepted-issuer policy, not operational, or its status root is stale (fail closed)a policy decision on your side, or a legitimate holder retry with a fresh proof
Proven-state problemsa chain read could not be cryptographically proven — including a claimed absencean infrastructure alert, never a bypass: the SDK refuses to decide on unproven state

Two rules of handling

  • Rejections are answers, not errors. A rejection is a first-class, expected outcome of the pipeline: log the reason code, decide per family, move on. Only transport-level failures (a body that cannot be parsed at all) surface as errors instead of decisions.
  • The proven-state family 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, and there is no configuration that makes it accept.