0TLDR
x401 says who is asking. x402 says how to pay. Neither says whether the payment should happen. x403 is the question in between: the party about to move money asks a risk authority, gets back a signed yes, not yet, or no, with reasons in a shared vocabulary, bound to that one payment and that one executor, and both sides keep a receipt anyone can verify later. After the money moves, the executor reports what happened, so the next decision is better than this one.
Status: implemented by Kredit as the reference authority; open for other authorities, executors, requesters and evidence issuers. The protocol version is the version field of every object; the packages (@kredit/x403 0.3.0, x403 0.3.0) have their own release numbers, mapped in the repository README.
1Definition
x403 is a request and response protocol by which the party about to execute an agentic payment or transfer, called the executor (a merchant, PSP, acquirer, vault, wallet or issuer processor), obtains a signed risk decision from a risk authority before executing, and both sides keep a portable receipt.
The decision is one state, allowed, escalated or denied, computed from five things:
- the payment about to happen,
- the actor chain behind it, human to operator to agent to instance,
- the intent declared for it and the mandate it runs under,
- the evidence presented, made by other protocols,
- the actor's history, as the authority holds it.
The decision is explained with reason codes from a published registry. It is bound to one payment (subject) and one executor (audience). After execution an outcome report closes the loop.
x403 sits between x401 (identity) and x402, MPP and the card rails (payment), and between mandate formats (AP2, Verifiable Intent, Amex Proof of Intent, Visa VIC instructions) and their execution. It consumes their artifacts as evidence and issues nothing that competes with them.
1.1 Terminology
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be read as in RFC 2119.
- Executor: the party that moves the money and asks before doing so: a merchant, PSP, acquirer, vault, wallet, card program or issuer processor.
- Requester: whoever signs and sends the risk query; the executor on the merchant leg, the operator's wallet or card program on the buyer leg.
- Risk authority: the party that computes and signs the decision.
- Agent: a registered software identity with a key, acting for an operator and a human principal.
- Instance: one session of an agent, holding a short-lived key or token.
- Principal: the human on whose behalf the payment is made.
- Verifier: anyone checking a decision or receipt offline: a dispute desk, an auditor, a regulator, the next executor.
- Reporter: a party that posts an outcome: the executor named in the receipt, the requester, the authority, or a party registered in the executor's policy.
2Design principles
- Reasons explain the decision. Reason codes come from a published registry. Whatever the authority computes internally stays inside the authority; a conformant decision contains no risk, trust or confidence field.
- Both legs, one object. The buyer leg (an operator's wallet or card program asking before its agent pays) and the merchant leg (an executor asking before accepting) use the same query and the same decision.
directionis a field. - Consume, don't compete. x401 results, AP2 mandates, Verifiable Intent chains, Amex Proof of Intent tokens, Visa VIC instructions, TAP and Mastercard WBA signatures, Skyfire KYA tokens, Stripe Shared Payment Tokens, MPP challenges, ERC-8004 registrations are evidence. x403 defines how they are referenced and hashed, not how they are made.
- Before, with a loop after. Decide before money moves. Report what happened afterwards.
- Signed on both ends, bound to the payment. The query is signed by the requester, the decision by the authority, the receipt countersigned by the executor, the outcome by its reporter. The decision names the payment and the executor it is for; presented anywhere else it is void.
- Portable. A receipt verifies offline against published keys.
- Thresholds, not histories. The authority discloses signed predicates about an actor, never another executor's data.
- Fail closed, with a path. Every decision that is not allowed names what would change it.
- Milliseconds, with a stated budget. The executor publishes its budget. A timeout is denied, or escalated to the executor's own desk when its policy says so, never allowed; the executor signs that itself as a local decision (4.6), never as the authority's.
- Status-code independent. Headers carry the protocol. The status codes in section 6 are the canonical mapping, not a requirement.
3Roles
- Actor chain. Human principal, then operator (the business that runs the agent), then agent (a registered identity with a key), then instance (a session with a short-lived token). Each hop is
verifiedwith a stated basis,asserted, orabsent. - Requester. Whoever sends the query: the operator's wallet, card program or payment runtime on the buyer leg; the executor on the merchant leg.
- Executor. The party that moves the money.
- Risk authority. Computes and signs the decision. Kredit is the reference. The protocol allows any authority, and an executor may chain several.
- Reporters. Identity vendors, fraud engines, registries, networks and facilitators whose artifacts arrive as evidence or whose outcomes close the loop.
- Verifier. A dispute desk, an auditor, a regulator, or the next executor, checking a receipt offline.
4Objects
All objects are JSON. Signed objects are canonicalized with RFC 8785 (JCS) and signed as compact JWS. ES256 is required of every implementation, EdDSA is optional, and a parallel slot for a post-quantum signature is reserved. A decision may also be wrapped as a W3C Verifiable Credential (jwt_vc_json).
Hashes are sha256: followed by lowercase hex of the canonical form. Amounts are decimal strings. Times are RFC 3339. Keys: executors publish a JWK set at /.well-known/x403/jwks.json; authorities publish /.well-known/x403/authority.json or resolve a did:web.
JSON Schemas for every object are in schemas/ of the repository.
4.1 Payment
The thing about to happen.
{
"type": "payment",
"amount": "189.00", "currency": "USD",
"rail": "card",
"executor": { "id": "bestbuy.com", "category": "5732", "jurisdiction": "US" },
"counterparty": { "id": "bestbuy.com", "kind": "merchant", "age_days": 9124 },
"items": [{ "sku": "6421743", "description": "USB-C hub", "class": "electronics", "qty": 1 }],
"references": {
"order_id": "BBY-2210-7731",
"cart_hash": "sha256:…",
"payment_requirements_hash": "sha256:…"
},
"idempotency_key": "ord_BBY-2210-7731"
}payment_requirements_hash binds the decision to the exact x402 PaymentRequirements or MPP challenge. cart_hash binds it to an AP2 checkout_hash or a Verifiable Intent transaction_id. instruction_id binds it to a Visa VIC instruction. A decision is for one payment: change the payment and you need a new decision.
4.2 Actor presentation
The chain, hop by hop.
{
"human": { "status": "verified", "basis": "x401", "assurance": "IAL2", "ref": "sha256:…" },
"operator": { "status": "verified", "basis": "kyb", "id": "did:web:acme.com", "via": "kredit" },
"agent": { "status": "verified", "basis": "registry", "id": "did:key:z6Mk…",
"registry": [{ "name": "visa-tap" }, { "name": "erc-8004", "chain": "arc" }, { "name": "kredit-passport" }],
"harness": "claude-code", "model": "haiku4.5", "attestation": null },
"instance": { "status": "verified", "basis": "token", "token_ref": "sha256:…",
"issued_at": "…", "expires_at": "…", "origin": "https://agent.acme.com" },
"session": { "ip": "…", "device": "…", "ua": "…", "provenance": "mcp" }
}An unregistered agent still gets a decision, usually escalated on first contact and then a file. Lacking a registration is a reason code, not a refusal.
4.3 Intent
What the agent says it is doing, and the mandate that lets it.
{
"mandate": {
"format": "ap2",
"ref": "sha256:…",
"scope": { "executors": ["bestbuy.com"], "categories": ["electronics"],
"max_amount": "250.00", "window": "2026-10-01/2026-10-31", "budget": "3000.00/month" },
"issued_by": "human", "signed": true
},
"declared": { "goal": "replace broken USB-C hub for Denver office", "expected_amount_range": ["50", "200"] },
"task_ref": "sha256:…"
}A mismatch between declared, the payment, and mandate.scope becomes a reason code. It is not automatically denied: the executor's policy decides what a mismatch is worth.
4.4 Risk query
{
"scheme": "x403", "version": "0.2",
"policy_id": "bestbuy-checkout-v3",
"direction": "inbound",
"mode": "inline",
"payment": { }, "actor": { }, "intent": { },
"evidence": [
{ "type": "x401.result", "issuer": "did:web:x401-issuer.example", "ref": "sha256:…", "inline": "<jwt>" },
{ "type": "ap2.payment_mandate", "ref": "sha256:…", "inline": "<sd-jwt>" },
{ "type": "fraud.vendor", "issuer": "vendor.example", "ref": "sha256:…", "inline": "<jws>" },
{ "type": "x403.receipt", "issuer": "did:web:othermerchant.com", "ref": "sha256:…" }
],
"challenge": { "nonce": "…", "expires_at": "…" },
"budget_ms": 150,
"on_timeout": "denied",
"requester": { "id": "did:web:bestbuy.com", "kid": "2026-10" },
"requester_sig": "<JWS>"
}direction is inbound when the executor asks about a payment arriving, outbound when a wallet asks about a payment leaving. mode is inline (decide now, act on it), preflight (decide now, pay later citing the id) or shadow (decide, log, do not act). on_timeout is denied or escalated and nothing else: an allow on timeout would make a slow authority a bypass. budget_ms is the authority's time from receipt of the query to the first byte of its answer, at least 20 and at most 10000.
4.5 Decision
{
"scheme": "x403", "version": "0.2",
"id": "k_950a4d",
"decision": "allowed",
"direction": "inbound", "mode": "inline",
"subject": { "payment_ref": "sha256:…", "amount": "189.00", "currency": "USD", "executor_id": "bestbuy.com" },
"audience": "bestbuy.com",
"query_ref": "sha256:…",
"policy_id": "bestbuy-checkout-v3", "policy_hash": "sha256:…",
"authority": { "id": "did:web:api.kredit.sh", "kid": "…" },
"verified": { "human": "x401:IAL2", "operator": "kyb", "agent": "visa-tap+erc-8004", "instance": "token", "session": "present" },
"checks": [
{ "key": "identity", "status": "pass" },
{ "key": "mandate", "status": "pass" },
{ "key": "payee", "status": "pass" },
{ "key": "behavior", "status": "warn", "reasons": ["first_use_of_product_class"] }
],
"reasons": ["first_use_of_product_class", "amount_at_cap"],
"constraints": { "max_amount": "250.00", "currency": "USD", "human_stepup_above": "100.00", "expires_at": "…" },
"evidence_hashes": ["sha256:<query>", "sha256:<x401 result>", "sha256:<mandate>"],
"disclosure": { "history": "threshold", "claims": [{ "clean_payments_gte": 50 }, { "disputes_12m_lte": 0 }] },
"binding_hints": { "http_status": 200 },
"supersedes": null,
"issued_at": "…", "expires_at": "…",
"decision_sig": "<JWS by authority>"
}subjectis the one payment this decision is for: the hash of the payment object as presented, its amount, currency and executor.audienceis the executor it is for. Both are inside the signature. A verifier MUST check both before trusting an allow: a decision presented at an executor other than its audience, or for a payment other than its subject, is void, with reasonaudience_mismatchorsubject_mismatch.query_refis the hash of the query as received.checksis the executor-facing summary, four keys, worst-of. Whatever the authority computes behind them stays behind them.reasonsare keys of the registry (section 8), in the order they matter.constraintsis a property of an allowed decision: the yes is scoped by a ceiling, a step-up line, the rails and executors it covers, and an expiry.escalationis present only on an escalated decision, and itskindnames who resolves it:agent_adjust(shrink the amount or scope and retry, no human needed),operator_confirm,human_stepup,executor_review.optionssays how,reduce_tosays to what,retry_aftersays when.binding_hints.http_statusis the status the executor should answer with if it uses the HTTP binding, so the mapping in section 6 lives in the object and not in every client. It is advisory and outside the decision's meaning. Earlier decisions carried it ashttp_statusat the top level, which verifiers still read.supersedeslinks a re-decision to the hash of the one it replaces.revoked.jsonlists decisions by hash for the same reason: an id can be reissued, a hash cannot.- There is no
latency_msand no numeric risk, trust or confidence value of any kind. Timing stays in the authority's logs. - Decisions are single use. An executor that executes the same decision twice has a bug, and the authority will say
decision_consumedthe second time. An idempotent re-query (sameidempotency_key, same payment) returns the same decision and does not consume it.
4.6 Local decision
What the executor signs itself when the authority gave no decision inside budget_ms, or could not be reached. It is never an allow, and it is never presented as the authority's: local is true, authority names the authority that did not answer, and the signature is the executor's.
{
"scheme": "x403", "version": "0.2",
"id": "local_1a2b",
"decision": "denied",
"local": true,
"direction": "inbound",
"subject": { "payment_ref": "sha256:…", "amount": "189.00", "currency": "USD", "executor_id": "bestbuy.com" },
"audience": "bestbuy.com",
"policy_id": "bestbuy-checkout-v3",
"authority": { "id": "did:web:api.kredit.sh" },
"reasons": ["authority_timeout"],
"binding_hints": { "http_status": 403 },
"executor": { "id": "bestbuy.com", "kid": "…" },
"issued_at": "…", "expires_at": "…",
"local_sig": "<JWS by executor>"
}decision is denied, or escalated with kind: executor_review when the query's on_timeout said so. The executor SHOULD sign it; a verifier checks it against the executor's keys, never an authority's. The executor files it with the authority as a receipt (local_decision on the receipt), and the authority stores it as such and never lists it among its own decisions. Schema: schemas/local_decision.json.
4.7 Receipt
{
"decision_ref": "k_950a4d", "decision_hash": "sha256:…",
"executed": true, "executed_at": "…",
"execution": { "rail": "card", "ref": "auth_7c…", "amount": "189.00", "currency": "USD" },
"executor": { "id": "did:web:bestbuy.com", "kid": "…" },
"executor_sig": "<JWS>"
}A receipt is the decision plus the executor's countersignature. It is the dispute-file artifact, and what fills an a-comm AEP policy artifact (outcome, rules_triggered, policy_engine_vendor, policy_version). A receipt for a local decision carries the signed local decision as local_decision and its id as decision_ref; the authority files it without a decision of its own behind it.
4.8 Outcome report
{
"decision_ref": "k_950a4d", "decision_hash": "sha256:…",
"authority_id": "did:web:api.kredit.sh",
"outcome": "settled", "category": "none", "reported_via": "executor",
"at": "…", "amount": "189.00", "currency": "USD", "reason_code": null,
"reporter": { "id": "did:web:bestbuy.com", "kid": "…" }, "sig": "<JWS>"
}Outcomes: settled, delivered, disputed, chargeback, refund, reversal, fraud_confirmed, false_positive. The last two are the ones that make the next decision better. category says what kind of loss, if any: attack, accident, control_failure, commercial_dispute, none. reported_via says which channel the news came through. decision_hash ties the report to the decision as signed, so it cannot be moved to another decision with the same id.
Who may report: the executor named in the receipt, the requester named in the query, the authority itself, and reporters registered in the executor's policy document. The report is signed by its reporter; the authority MUST verify the signature against the reporter's published keys and MUST reject a report from anyone else.
5Flows
F1. Inbound, inline (merchant leg)
The agent attempts a payment. The executor builds a query from the request plus whatever x401, TAP, SPT or mandate artifacts arrived, signs it, and posts it to its authority with a budget. The authority verifies the evidence signatures, resolves the actor chain, evaluates against the executor's policy and returns a signed decision. The executor maps it to a status. The agent: allowed, done, within constraints; escalated with agent_adjust, shrink and retry citing the id; escalated with another kind, push it to the operator or human and retry with step_up_ref; denied, stop and carry the signed denial back to the operator. The executor posts a receipt, and later an outcome.
F2. Outbound, before the payment leaves (buyer leg)
The operator's wallet, card program's real-time authorization webhook or vault proxy builds a query from the agent's own intent and mandate, calls its authority, and releases the payment only when allowed. The decision id rides with the payment. The merchant verifies the signature offline. No merchant change is required.
F3. Preflight before a final-settlement rail
HEAD with a presentation, then allowed with expires_at and constraints, then pay over x402 or MPP citing the decision id, then the merchant checks the id against its own record or the authority's signature, then settle. This is what makes final-settlement rails safe: decide before paying.
F4. Shadow
mode: "shadow". The authority returns a decision, the executor logs it and does not act, outcomes still flow back. This is the pilot shape.
F5. Escalated, then re-evaluated
Modelled on OpenID AuthZEN AARP. The decision that is not allowed names a prerequisite; the requester satisfies it; a new decision is issued with supersedes pointing at the old one; the old one is consumed.
6HTTP binding
Two bindings carry the same objects. Header values are base64url JSON without padding. Every response that carries an x403 header MUST also carry Cache-Control: no-store: signed objects are never cached by an intermediary. A single header value MUST NOT exceed 8 KB; headers carry objects and references, and anything with inline evidence travels in a body with Content-Type: application/x403+json. An executor treats an oversized or unreadable presentation header as no presentation.
6.1 Header binding
The launch binding. It matches the x401 and x402 convention.
| Header | Direction | Carries |
|---|---|---|
X403-Required | executor to agent | a risk query the agent must help satisfy, or a decision that is not allowed |
X403-Presentation | agent to executor | actor presentation, intent and evidence, or the id of a prior preflight decision |
X403-Decision | executor to agent | the signed decision |
6.2 Auth-scheme binding
The IETF path, modelled on MPP's WWW-Authenticate: Payment.
WWW-Authenticate: Risk policy="bestbuy-checkout-v3", realm="checkout", request="<base64url query>", expires="…" Authorization: Risk <base64url presentation> Risk-Decision: <base64url decision>
6.3 Status mapping
The decision carries binding_hints.http_status. This is how it is computed. An executor may override it, and must not invent a status outside this table for an x403 answer. A 403 without an X403-Decision or Risk-Decision header is not an x403 answer: clients MUST read the decision from the header, never from the status.
| Decision | Status | Meaning on the wire |
|---|---|---|
| allowed, with or without constraints | 200, 201 or 202 | the resource, plus X403-Decision |
| escalated, agent_adjust | 403 | the decision says what to shrink to; the agent retries citing the id |
| escalated, operator_confirm or human_stepup | 428 | a precondition is required: present it, then retry with step_up_ref |
| escalated, executor_review | 403 | a person on the executor's side decides; retry_after says when to ask again |
| denied, identity group | 401 | the chain failed: unverified human, expired instance token, revoked credential |
| denied, compliance group | 451 | sanctions, jurisdiction, or a legal threshold |
| denied, velocity group | 429 | too much, too fast; retry_after is set |
| denied, any other group | 403 | understood, and refused on policy |
| replay or expiry: nonce_consumed, decision_expired, decision_consumed, decision_superseded | 409 | the state of the conversation conflicts with the request |
| policy_unknown, presentation_malformed, evidence_hash_mismatch | 400 | the request itself is wrong |
| authority_timeout | 403 | the executor's own local decision (4.6) in the header, signed by the executor, denied or escalated per on_timeout |
| audience_mismatch, subject_mismatch, presentation_signature_invalid | 400 | a decision or presentation for another executor or payment |
| non-disclosure denied | 404 | no header at all, by the executor's choice |
Six codes are normative for clients: 200, 401, 403, 428, 429, 451. A client library must handle all six. The rest are executor choices a client may treat as 403.
6.4 Preflight
HEAD or OPTIONS with X403-Presentation answers 200 with X403-Decision and no body. The decision id is then cited in the x402 PaymentPayload.extensions, the MPP Authorization: Payment payload, or the AP2 Payment Mandate risk_data.
6.5 Discovery
/.well-known/x403/policy/<id>.json: required hops and evidence, step-up lines, accepted authorities and mandate formats, latency budget, timeout behaviour. No weights./.well-known/x403/revoked.json: revoked decisions by hash (the id beside it for readers), and retired policy hashes. Served withCache-Controlof at most 60 seconds./.well-known/x403/authority.json: an authority's id, keys, endpoints, algorithms and the evidence types it reads./.well-known/x403/jwks.json: the keys.
6.6 The ladder
Risk posture first, then identity at the level the posture requires, then payment, then execution with the decision attached. Or 401, 403, 402, 200 where identity is needed to evaluate at all. The policy document says which.
7Other bindings
- Authority API.
POST /x403/decide,POST /x403/receipts,POST /x403/outcomes,GET /x403/decisions/{id},GET /x403/reason-codes, and the well-known documents. - MCP. One tool,
x403, with anaction:decide,preflight,get,receipt,outcome,revoke,authority,jwks,policy,codes,revoked. An agent's payment runtime asks before it pays. - A2A. The decision as a task message part before settlement.
- Verifiable Credential. The decision as a VC, so it rides inside an AP2 Payment Mandate (
risk_data), an x401 result, a UCPsignalsentry or an AEP policy artifact. - Card authorization.
idandpolicy_hashas merchant-defined data in the authorization message. The networks' agentic transaction indicator is the slot it eventually occupies. - Stablecoin.
idin x402extensions; preflight before settlement; the receiving side verifies offline.
8Reason codes
The registry is registry/reason-codes.json, served by every authority at /x403/reason-codes. Each code belongs to one group, each group folds into one of the four checks and names the status a denial on it produces, and each code carries a default effect: denied, escalated (with the kind), or note. The effect is the default; the executor's policy may be stricter or looser, and the decision says what the policy did.
| Code | Check | Status | Effect | Resolved by | Says |
|---|---|---|---|---|---|
| identity | identity | 401 | a hop in the actor chain is missing or failed | ||
human_unverified | identity | 401 | escalated | human_stepup | no verified human behind the chain |
human_assurance_below_policy | identity | 401 | escalated | human_stepup | the human was verified below the level the policy asks for |
operator_unverified | identity | 401 | escalated | operator_confirm | the operator has no KYB on record |
operator_pending | identity | 401 | note | operator verification is in progress | |
agent_unregistered | identity | 401 | escalated | operator_confirm | the agent is in no registry the policy accepts |
agent_unverified | identity | 401 | escalated | operator_confirm | the agent has no KYA on record |
agent_pending | identity | 401 | note | agent verification is in progress | |
agent_frozen | identity | 401 | denied | the operator froze this agent | |
agent_key_rotated_recently | identity | 401 | note | the agent's key changed inside the policy window | |
instance_token_missing | identity | 401 | escalated | agent_adjust | no instance token was presented |
instance_token_expired | identity | 401 | denied | the instance token has expired | |
instance_token_revoked | identity | 401 | denied | the instance token was revoked | |
credential_vouched | identity | 401 | note | a credential from another issuer was presented and recorded, not verified | |
credential_absent | identity | 401 | note | nothing was presented to say who the actor is | |
origin_changed | identity | 401 | escalated | operator_confirm | the instance speaks from a different origin than its token |
relay_mismatch | identity | 401 | denied | the relay named in the presentation is not the one that delivered it | |
| mandate | mandate | 403 | the payment is outside what the principal authorised | ||
mandate_absent | mandate | 403 | escalated | operator_confirm | no mandate was presented for a payment the policy requires one for |
mandate_unsigned | mandate | 403 | escalated | operator_confirm | the mandate carries no signature |
mandate_expired | mandate | 403 | denied | the mandate's window has closed | |
mandate_revoked | mandate | 403 | denied | the principal revoked the mandate | |
off_mandate_category | mandate | 403 | escalated | operator_confirm | the product class is outside the mandate |
off_mandate_executor | mandate | 403 | escalated | operator_confirm | the executor is outside the mandate |
over_mandate_amount | mandate | 403 | escalated | agent_adjust | the amount is above the mandate's ceiling |
over_mandate_budget | mandate | 403 | escalated | operator_confirm | the mandate's budget for the window is used up |
intent_payment_mismatch | mandate | 403 | escalated | agent_adjust | the declared goal and the payment do not agree |
intent_counterparty_mismatch | mandate | 403 | escalated | operator_confirm | the declared payee and the actual payee differ |
po_mismatch | mandate | 403 | escalated | operator_confirm | the purchase order does not match the payment |
| scope | mandate | 403 | the agent was never granted this action | ||
delegation_depth_exceeded | mandate | 403 | denied | the chain is longer than the policy allows | |
scope_not_inherited | mandate | 403 | denied | a hop holds more than the hop above it | |
separation_violation | mandate | 403 | denied | one party fills two roles the policy keeps apart | |
| policy | mandate | 403 | the executor's own policy stops it | ||
blocked_counterparty | mandate | 403 | denied | the payee is on the executor's block list | |
counterparty_not_allowed | mandate | 403 | escalated | operator_confirm | the payee is not on the executor's allow list |
category_not_allowed | mandate | 403 | escalated | operator_confirm | the product class is not on the allowed list |
rail_not_allowed | mandate | 403 | denied | the rail is not one the policy permits | |
amount_over_cap | mandate | 403 | escalated | agent_adjust | the amount is above the per-action cap |
above_review_threshold | mandate | 403 | escalated | human_stepup | above the line where a person decides |
policy_floor | mandate | 403 | denied | below the executor's hard floor | |
policy_review_floor | mandate | 403 | escalated | executor_review | below the executor's review floor |
actor_blocked | mandate | 403 | denied | the executor blocked this actor | |
actor_trusted | mandate | 403 | note | the executor trusts this actor | |
pending_escalation | mandate | 403 | escalated | executor_review | an earlier escalation for this actor is still open |
| financial | behavior | 403 | the amount or pace is out of line | ||
amount_x_baseline | behavior | 403 | escalated | operator_confirm | well above what this actor usually pays |
amount_at_cap | behavior | 403 | note | at the ceiling of the mandate or policy | |
cumulative_over_budget | behavior | 403 | escalated | operator_confirm | the window's budget would be exceeded |
pace_x_baseline | behavior | 403 | escalated | operator_confirm | spending faster than its history |
runaway_loop_pattern | behavior | 403 | denied | the same payment is repeating | |
concentration | behavior | 403 | note | most of the window's spend goes to one payee | |
wallet_budget_exceeded | behavior | 403 | escalated | human_stepup | the wallet's budget would be exceeded |
card_budget_exceeded | behavior | 403 | denied | the card's budget would be exceeded | |
card_expired | behavior | 403 | denied | the card has expired | |
no_baseline | behavior | 403 | note | no history to compare against yet | |
| fraud | behavior | 403 | signals of takeover, injection or collusion | ||
cross_executor_burst | behavior | 403 | escalated | operator_confirm | the actor is hitting several executors at once |
takeover_signals | behavior | 403 | denied | signals that the agent is not under its operator's control | |
injection_pattern | behavior | 403 | denied | prompt injection markers in the intent | |
collusion_pattern | behavior | 403 | denied | payer and payee look like the same party | |
device_new | behavior | 403 | note | a device not seen for this actor before | |
automation_signals | behavior | 403 | note | headless or automation signals in the session | |
first_use_of_rail | behavior | 403 | note | the actor has not paid on this rail before | |
first_use_of_product_class | behavior | 403 | note | the actor has not bought this class before | |
high_risk_product_class | behavior | 403 | escalated | operator_confirm | a class that attracts fraud: gift cards, travel, crypto |
high_risk_terms | behavior | 403 | escalated | operator_confirm | terms in the intent that attract fraud |
off_brief | behavior | 403 | escalated | operator_confirm | the purchase is not part of the agent's brief |
| velocity | behavior | 429 | too much, too fast | ||
velocity_high | behavior | 429 | escalated | agent_adjust | too many actions in the window |
rate_cap_exceeded | behavior | 429 | escalated | agent_adjust | the policy's rate cap for the window is used up |
spend_cap_exceeded | behavior | 429 | escalated | agent_adjust | the policy's spend cap for the window is used up |
| compliance | payee | 451 | sanctions, jurisdiction or reporting law | ||
sanctions_hit | payee | 451 | denied | the payee matches a sanctions list | |
sanctions_adjacent | payee | 451 | escalated | executor_review | the payee is close to a sanctioned party |
jurisdiction_blocked | payee | 451 | denied | the payee's jurisdiction is blocked | |
kyc_required_above_threshold | payee | 451 | escalated | human_stepup | the amount needs a verified human by law |
reporting_threshold | payee | 451 | note | at or above a reporting threshold | |
screening_unavailable | payee | 451 | escalated | executor_review | no screening answer in time |
| counterparty | payee | 403 | who is being paid | ||
counterparty_new | payee | 403 | note | the payee is new to this actor | |
counterparty_young | payee | 403 | escalated | operator_confirm | the payee's account or address is days old |
counterparty_unknown | payee | 403 | note | nothing is known about the payee | |
| disputes | payee | 403 | what came back after earlier payments | ||
dispute_history | payee | 403 | escalated | executor_review | disputes or chargebacks on record |
dispute_rate_high | payee | 403 | escalated | operator_confirm | the payee's dispute rate is above the policy line |
refund_abuse_pattern | payee | 403 | escalated | executor_review | refunds are looping |
| system | identity | per code | the protocol itself: replay, expiry, timeout | ||
authority_timeout | identity | 403 | denied | no decision inside the budget; the executor failed closed | |
evidence_signature_invalid | identity | 401 | denied | a piece of evidence did not verify | |
evidence_hash_mismatch | identity | 400 | denied | evidence inline does not match its ref | |
evidence_unsupported | identity | 403 | note | evidence of a type this authority does not read | |
evidence_expired | identity | 401 | denied | a piece of evidence is past its expiry | |
requester_signature_invalid | identity | 401 | denied | the query's signature did not verify | |
policy_unknown | identity | 400 | denied | no policy by that id | |
presentation_malformed | identity | 400 | denied | the presentation could not be read | |
nonce_consumed | identity | 409 | denied | the challenge nonce was already used | |
idempotency_replay | identity | 200 | note | the same query was already decided; this is that decision | |
decision_expired | identity | 409 | denied | the decision is past its expiry | |
decision_consumed | identity | 409 | denied | the decision was already executed | |
decision_superseded | identity | 409 | denied | a later decision replaced this one | |
shadow_mode | identity | 200 | note | decided and logged, not acted on | |
superseded_by_review | identity | 409 | note | a person re-decided this after an escalation; the earlier decision answers 409 | |
audience_mismatch | identity | 400 | denied | the decision names another executor as its audience | |
subject_mismatch | identity | 400 | denied | the decision was issued for another payment | |
presentation_signature_invalid | identity | 400 | denied | the presentation's signature did not verify |
New codes are added by pull request to the registry with a group, an effect and one sentence.
9Evidence
An evidence item is { type, issuer, ref, inline?, jwks_url? }. ref is the hash of the artifact. inline carries the artifact when the authority is expected to verify it. The authority records each item as verified (signature checked against the issuer's keys, hash matches, not expired), asserted (hash matches, signature not checkable by this authority), or invalid (hash mismatch, bad signature, expired), and the result lands on the decision as verified per hop and as reason codes.
Types this version names:
x401.resultx403.receiptx403.decisionap2.intent_mandateap2.cart_mandateap2.payment_mandateverifiable-intent.chainamex.proof_of_intentvisa.vic_instructionvisa.tap_signaturemastercard.wba_signatureskyfire.kyaskyfire.paykite.passportstripe.shared_payment_tokenmpp.challengex402.payment_requirementserc-8004.registrationkredit.passportkredit.agent_tokencircle.screeningvgs.aliasfraud.vendorother
An authority publishes which of these it verifies in authority.json under evidence_read. Anything else it records as asserted with evidence_unsupported on the decision.
10Trust model
- Executor and authority exchange signed objects over TLS. Keys are published both ways.
- Decisions verify offline against the authority's published key, and are then checked for
audience,subject, expiry, and absence from the revocation list. Receipts verify against the executor's. A local decision verifies against the executor's key and is never an allow. - The actor chain is verified hop by hop and recorded as
verified,assertedorabsentwith its basis. A hop without proof is a reason code, not a failure. - Policy belongs to the executor; computation belongs to the authority. Only a verified human may change a policy version, and the policy hash on every decision says which version decided it.
- Replay and relay are stopped by
challenge.nonce,idempotency_key,expires_at, single-use decisions,audienceandsubject. - Outcomes are accepted only from the executor named in the receipt, the requester, the authority and the reporters registered in the policy, each verified by signature.
- Revocation is published at
/.well-known/x403/revoked.json. Instance tokens are revoked at the authority. - Keys: executor
jwks.json; authority document or DID; agent keys per the registry they come from.
11Privacy
- No raw cross-executor history leaves the authority.
disclosure.claimsare signed threshold predicates. A later version adds zero-knowledge range proofs. - Executors see reasons, never other executors' data.
- Presentations are selective: only the hops and claims the published policy asks for.
- Retention: receipts for the rail's dispute window; outcomes pseudonymized at the authority.
- Lawful basis: fraud prevention for the executor; the authority is a processor for the query and a controller for its own record.
12Security considerations
Each threat, and the field or rule that answers it.
| Threat | Answer |
|---|---|
| Replay of a decision | single use (decision_consumed), expires_at, nonce_consumed |
| Relay of a decision to another executor | audience, checked by every verifier |
| Laundering a decision onto another payment | subject (payment_ref, amount, currency, executor), checked by every verifier |
| Moving an outcome to another decision with the same id | decision_hash on the outcome |
| Outcome poisoning | only the receipt's executor, the requester, the authority and registered reporters may report; signatures verified |
| Impersonating the authority | keys from the well-known documents, pinned authority id, authority.kid in every decision |
| Forcing a timeout to get a yes | on_timeout is never an allow; a local decision is executor-signed, never the authority's |
| Rolling a policy back | policy_hash on every decision; retired hashes on the revocation list |
| Key compromise | retired keys stay published for verification; revocation by hash |
| A tampered object | the signature is over the canonical form of the whole object; a changed field is a different payload |
| Header overflow and cache leakage | 8 KB per header value, bodies as application/x403+json, Cache-Control: no-store on every response that carries an x403 header |
| Reading the status instead of the decision | a 403 without an X403-Decision header is not an x403 answer |
13What a receipt proves
That a named authority, under a named policy version, given the hashed evidence, issued this decision for this payment to this executor at this time, and that a named executor acted on it.
Not that upstream facts were true, that settlement happened, or who is liable. Liability is the rails' and the regulators' question. x403 gives them the record they have said they lack.
14Conformance
An authority conforms when it: signs decisions with ES256 over the RFC 8785 form; sets subject, audience and query_ref; publishes authority.json, jwks.json, reason-codes; honours budget_ms and on_timeout; refuses a consumed nonce and a consumed decision; accepts receipts, local decisions as receipts, and outcomes only from authorized, signed reporters; and never places a numeric risk value in a decision.
An executor conforms when it: asks before executing; checks audience and subject on any decision it did not receive directly from its authority; maps decisions to status per section 6.3 or documents its own mapping; attaches X403-Decision and Cache-Control: no-store to the response; publishes its policy document and keys; produces a signed local decision on timeout and files it; posts a receipt for every execution and an outcome when it knows one.
A client conforms when it: reads the decision from the header or body, not the status; handles the six normative codes; retries an agent_adjust within reduce_to; carries a decision that is not allowed back to its operator unchanged.
An offline verifier checks, in this order: the schema; decision_sig against the authority's published keys; audience against the executor presenting it; subject against the payment; expires_at against executed_at; absence from the revocation list; policy_hash not retired; the receipt's executor_sig; the receipt's decision_hash against the decision as signed.
Akredit as the reference authority
| x403 | Kredit |
|---|---|
| Risk query | POST /x403/decide, or POST /check with the same fields in Kredit's shape |
| Decision | the decision record, signed; GET /x403/decisions/{id} returns the x403 object |
allowed, escalated, denied | the same three states in the console, the records and the signed object; POST /check carries the state in x403.decision next to Kredit's own outcome key |
checks | identity, mandate, payee, behavior |
escalation | review plus POST /decisions/{id}/review |
| Receipt | POST /x403/receipts, and POST /decisions/{id}/execute for Kredit's own rails |
| Outcome | POST /x403/outcomes, and disputes in the console |
| Actor presentation | the Kredit Passport, POST /agents/{id}/passport/present, plus any evidence |
| Policy document | the organization's rules and the agent's guardrails, versioned, passkey-approved |
| Authority keys | /.well-known/x403/authority.json and /.well-known/x403/jwks.json |
| MCP | the x403 tool on the hosted MCP at /mcp |