Reason codes are how a decision explains itself. Read them with the per-layer result in the response: a single fail code on a required layer is enough to deny, while an allow lists the pass codes for every check it cleared. The effect column below is the code's nature on its own; the final decision also depends on which layers your policy marks required and on the mode.
pass supports allow fail drives deny review uncertainty info contextual only reserved defined but not emitted in 0.1.0
Three codes below are reserved: they exist in the API's code vocabulary but the current version does not emit them yet, so do not branch on them. Where one is reserved, the note says which code fires in its place today.
transport
Web Bot Auth · RFC 9421Answers "which agent is this?" by verifying an HTTP message signature against the agent's published key directory.
| Code | Meaning | Effect |
|---|---|---|
| signature_present | A signature was on the request. Contextual on its own. | info |
| signature_missing | No signature found, so the agent could not be identified. | fail |
| signature_valid | The signature verified against the resolved key. Transport passed. | pass |
| signature_invalid | A signature was present but did not verify. | fail |
| required_components_missing | The signature did not cover the minimum RFC 9421 components, so it does not bind the request. | fail |
| key_directory_unreachable | The agent's key directory could not be fetched, so the signature is unproven. | review |
| agent_untrusted | Valid signature, but the agent is not on the merchant's trusted_agents allow-list. | fail |
mandate
AP2 v0.2 · SD-JWTAnswers "is this agent allowed to spend, and on this exact cart?" by verifying the signed spending mandate and binding it to the real basket.
| Code | Meaning | Effect |
|---|---|---|
| absent | Reserved. When no mandate is supplied and the mandate layer is required, the gateway emits policy.required_layer_missing instead, which denies. | reserved |
| parse_error | The mandate could not be parsed as a valid SD-JWT. | fail |
| signature_valid | The mandate signature verified against its issuer key. | pass |
| signature_invalid | The mandate signature did not verify. | fail |
| exp_valid | The mandate is within its validity window. | pass |
| expired | The mandate's expiry has passed. | fail |
| vct_supported | The credential type is one the gateway supports. | pass |
| vct_unsupported | The mandate declares an unrecognized credential type. | fail |
| checkout_hash_match | The mandate's checkout_hash matches the merchant-signed checkout terms. | pass |
| checkout_hash_mismatch | The mandate authorizes a different checkout than the one in play. | fail |
| cart_amount_match | The signed checkout amount matches the server-built cart. | pass |
| cart_amount_mismatch | The signed amount differs from the real cart (low-mandate, high-cart attack). | fail |
| constraint_satisfied | The cart satisfies every constraint the mandate carries. | pass |
| constraint_amount_exceeded | The cart total exceeds the mandate's authorized amount. | fail |
| constraint_currency_mismatch | The cart currency differs from the mandate's currency. | fail |
| constraint_scope_violation | Reserved for merchant or category scope constraints. The current version enforces amount and currency constraints, not scope. | reserved |
| cnf_chain_valid | The key-confirmation chain from the user's open mandate to the agent key is intact. | pass |
| cnf_chain_broken | The chain is broken, so the agent's authority cannot be traced to the user. | fail |
| replay_detected | This mandate jti was already used. | fail |
checkout
merchant attestationAnswers "did the merchant actually sign these checkout terms, or did the agent invent them?" Active only when a merchant checkout-signing key is provisioned, which is what standard mode turns on.
| Code | Meaning | Effect |
|---|---|---|
| signature_valid | The checkout terms were signed by the merchant, not the agent. | pass |
| signature_invalid | The checkout-terms signature does not verify against the merchant key. | fail |
| signature_missing | Attestation is enabled but no merchant-signed terms were presented. | fail |
| merchant_mismatch | The terms were signed for a different merchant than the one verifying. | fail |
allowance
ACP · Shared Payment TokenAnswers "does the Agentic Commerce Protocol session add up?" for merchants running in ACP mode.
| Code | Meaning | Effect |
|---|---|---|
| valid | The allowance covers the cart and is unexpired. | pass |
| expired | The allowance expiry has passed. | fail |
| amount_exceeded | The cart exceeds the allowance max_amount. | fail |
| currency_mismatch | The cart currency differs from the allowance currency. | fail |
| session_mismatch | Reserved for binding the allowance to a specific checkout session id. The current version does not check the session id yet. | reserved |
| token_absent | No Shared Payment Token was supplied for an ACP verification. | fail |
| authentication_required | The session needs a 3DS or SCA step-up, so it cannot allow yet. | review |
policy
merchant policySignals that come from the merchant's own policy rather than a protocol layer.
| Code | Meaning | Effect |
|---|---|---|
| required_layer_missing | A layer marked required produced no usable input. | fail |
| clock_skew_exceeded | The gap between the signature's created time and received_at is larger than the allowed skew. | fail |
How codes combine into a decision
The gateway evaluates each required layer, collects its checks, and resolves the layer to pass, fail, indeterminate, or skipped. The decision then follows three rules:
- Allow needs every required layer to pass. A presented but optional layer that fails still denies, which closes a downgrade where an agent under-supplies to dodge a check.
- Deny follows any required-layer fail, or any presented-layer fail, or an indeterminate layer in strict mode.
- Review is for genuine uncertainty (a
key_directory_unreachable, anauthentication_required) under standard mode. Monitor mode never denies and logs the decision it would have made.
This is why two stores can send the same codes and get different decisions: the verdict is the codes read through the store's policy mode.