Design choices and AP2
Why the lifecycle is split into issue, confirm, verify and exercise, plus the precise AP2 compatibility boundary.
In short
1 callFor people and agentsThe same list heads this page's Markdown:/mandate/design-and-ap2.md
Mandate turns user intent into payment authority that another service can enforce. The design optimizes for five properties: explicit consent, bounded delegation, a verifiable handoff, race-safe settlement, and an audit trail that survives after the authority ends.
Those goals explain why the API has separate issue, confirm, verify, and exercise operations instead of one endpoint that creates and spends a token.
Authority is not payment
A mandate answers:
Does this signed authority permit this caller to make this exact charge now?
It does not authorize a card with a processor, reserve funds, execute a transfer, or issue a receipt. Settlement remains responsible for moving money and reconciling the result.
Keeping those responsibilities separate gives the permission layer one job: describe, sign, evaluate, and record delegated authority.
Why issue and confirm are separate
Choice: Issue creates an unsigned draft; confirm creates the signed JWT.
Reason: An agent can assemble a proposed purchase, but proposed terms are not user consent. The user needs a stable object to review before it becomes usable authority.
Consequence: Integrations must present the claims_preview, confirm before confirm_expires_at, and require confirmed: true. The confirmation deadline and the signed token lifetime are intentionally separate clocks.
Confirmation also stamps consent_method: partner_attested. The value is controlled by the confirming flow, not copied from caller input.
Why verify and exercise are separate
Choice: Verify checks without settling; exercise checks and commits a settlement event.
Reason: Agents and partner workflows need an early answer before they attempt payment. That answer should not consume a closed mandate or draw down an open budget. The money-moving service needs a stronger operation that can recheck state and record the result atomically.
Consequence: Verify is repeatable and advisory. Exercise is owned by settlement and writes:
exercisedonce for a closed mandate,chargedfor each successful open-mandate draw.
Never treat a successful verify as a reservation. Pass the same token and charge to settlement and require exercise to succeed.
Why status is derived from events
Choice: The store keeps an append-oriented event history; current status is computed.
Reason: A mutable status field can tell you where an object ended up but not how it got there. Payment authority needs both the current answer and the evidence behind it.
Consequence: Retrieve and list calculate status from confirmation, exercise, charge and revocation events plus time. The evidence endpoint can show the original consent material and the ordered events that followed.
The status precedence is:
exercisedrevokedpendingexpiredexhaustedactive
That precedence is observable. For example, revoking an already exercised closed mandate records the revocation, but later reads remain exercised.
Why exercise re-checks open budgets
Choice: Verify reads spend as it stands; open exercise serializes charges and recalculates stateful limits.
Reason: Two charges may both look valid when checked at the same time. If they settled concurrently, their combined amount could exceed the total or category budget.
Consequence: A verify response can become stale. Exercise recalculates total spend, category spend and charge counts, then writes charged only if every limit still passes. For a closed mandate, only one of several concurrent exercises can succeed.
Why unknown constraints fail closed
Choice: A supplied charge fails any constraint type the service does not understand.
Reason: Ignoring an unknown name would turn a typo or unsupported rule into broader authority than the user approved.
Consequence: New constraint types require evaluator support before they can authorize a charge. Verify without a charge skips all constraints, which is why payment decisions must always include the intended charge.
Why ordinary ES256 JWTs
Choice: Confirmation returns a conventional ES256 JWT with a P-256 signing key and a published JWKS.
Reason: The current OEX flow needs a compact, portable, widely supported signed handoff. It does not currently require selective disclosure between holders and verifiers.
Consequence: The whole claims set is visible to anyone holding the token. Integrations must treat the token as sensitive, forward it unchanged, and pin ES256 and the issuer when verifying it themselves. The signing key is published at GET /v1/mandate/.well-known/jwks.json under key ID oex-mandates-1.
The gateway supplies identity
Every call is authenticated at the OEX gateway with your API key or bearer token. The gateway derives who is calling, and that identity is what agent_binding compares with a mandate's agent_id. Never send identity headers yourself; the gateway replaces them.
AP2 is a compatibility anchor
Mandate uses AP2 v0.2 payment-mandate vocabulary where it improves shared understanding:
payment_amount,payment_instrumentandpayee,payment.*constraint names, and- the closed-payment credential type
vct: "mandate.payment.1".
That relationship is intentional, but it is not an assertion that this endpoint is interchangeable with every AP2 implementation. OEX has its own lifecycle, token profile, evidence model, open-budget extension and deployed API contract.
| Area | AP2 relationship | OEX Mandate choice |
|---|---|---|
| Payment vocabulary | Reuses AP2 v0.2 payment-mandate names | Keeps familiar fields in issue requests and signed claims |
| Credential type | Uses mandate.payment.1 | Uses it as the compatibility anchor for the signed authority |
| Token | AP2 defines an SD-JWT-based mandate flow | Returns an ordinary ES256 JWT without selective disclosures |
| Checkout binding | AP2 flows can bind mandates to a transaction_id | Does not require transaction_id; jti identifies the mandate |
| Receipt | AP2 includes a wider payment and receipt flow | Settlement is outside this service; no AP2 payment receipt is issued |
| Consent evidence | AP2 provides broader protocol context | Stores the local Evidence model and confirmation metadata |
| Constraints | Reuses selected payment.* names | Evaluates five supported types locally and fails unknown types closed |
| Repeated spending | AP2 mandate.payment.1 is the closed-payment credential type | Adds local mode: "open" with a charged-event budget ledger |
Where this API and AP2 differ, the API reference and these guides are authoritative. Do not infer fields or flows from AP2 that are not documented here.