# AP2 Mandates in Practice: Credential Structure, Signing, and Verification

A builder's guide to AP2 mandates as they actually work in v0.2: the Checkout and Payment mandates, their open and closed states, the SD-JWT verifiable-credential structure, how the delegation chain is signed and verified, and how human-present and human-not-present flows differ in the data.

URL: https://agenticcommerceatlas.com/learn/ap2-mandates-in-practice/
Author: Andrew McPherson | Publisher: Agentic Commerce Atlas
Updated: 2026-10-31

---

[AP2](/learn/ap2-explained/) answers one question: when a piece of software buys something for you, how does a merchant or bank get cryptographic proof that you authorized this specific purchase? The answer is the **mandate**, a signed, tamper-evident credential. This page is the practical companion to the conceptual overview: what a mandate actually contains, how it is signed and verified, and how the human-present and human-not-present flows differ in the data. It reflects AP2 v0.2, released on 28 April 2026 and still the current version.

One caveat before the detail: AP2 is still pre-production. The v0.2 release ships an SDK, JSON schemas, and runnable sample scenarios, but the public materials are reference implementations rather than documented live deployments. Build against the v0.2 schemas, and be aware the repository also contains an older model layer (intent and cart mandates) that should not be used for new work.

## Two mandates, two states

AP2 v0.2 has two mandate types, and each exists in two states.

The **Checkout Mandate** is about what is being bought: the cart, the merchant, the terms. The **Payment Mandate** is about what will be charged: the amount, the instrument, the payee. Splitting them lets the parties who need each one see only that one.

The two states are the important part for agents. A **closed** mandate authorizes one specific action; it is the final, transaction-bound proof. An **open** mandate sets constraints on future actions an agent may take on the user's behalf, for example a spending cap or an allowed list of merchants. Open mandates are what make a human-not-present purchase possible: the user signs the constraints once, and the agent later produces a closed mandate that must fall within them.

Each is identified by a `vct` (verifiable credential type) value: `mandate.checkout.1` and `mandate.checkout.open.1` for checkout, `mandate.payment.1` and `mandate.payment.open.1` for payment. The numeric suffix is a schema version number, and the specification requires implementations to match the exact `vct` string including that suffix, so a future incompatible revision would ship as `.2` and simply fail to match rather than being silently misread.

## What a closed mandate contains

A closed Checkout Mandate carries the merchant-signed checkout and a hash that uniquely identifies it:

```json
{
  "vct": "mandate.checkout.1",
  "checkout_jwt": "<base64url merchant-signed JWT of the checkout payload>",
  "checkout_hash": "<base64url hash of checkout_jwt>",
  "iat": 1781200000,
  "exp": 1781203600
}
```

A closed Payment Mandate authorizes the charge and binds itself to that checkout by reusing its hash as the `transaction_id`:

```json
{
  "vct": "mandate.payment.1",
  "transaction_id": "<the checkout_hash from above>",
  "payee": { "id": "M-1", "name": "Cat Store", "website": "https://catstore.example" },
  "payment_amount": { "amount": 27999, "currency": "USD" },
  "payment_instrument": { "id": "pi_123", "type": "card", "description": "Visa ending 4242" }
}
```

Two details matter for implementers. Amounts are integers in minor units, so `27999` means 279.99 dollars. And the link between the two mandates is cryptographic: the Payment Mandate's `transaction_id` is the hash of the merchant-signed `checkout_jwt`, so a payment authorization cannot be detached from the exact checkout it was meant for.

## What an open mandate adds: constraints

An open mandate replaces a specific action with a set of constraints plus a key-binding claim (`cnf`) that names the public key allowed to produce the eventual closed mandate. An open Payment Mandate might say "this agent may spend up to 50 dollars at these merchants":

```json
{
  "vct": "mandate.payment.open.1",
  "constraints": [
    { "type": "payment.amount_range", "currency": "USD", "max": 5000 },
    { "type": "payment.allowed_payees", "allowed": [ { "id": "M-1", "name": "Cat Store" } ] }
  ],
  "cnf": { "jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." } }
}
```

The constraint vocabulary is specific. The Payment Mandate defines eight constraint types: `payment.amount_range`, `payment.budget`, `payment.allowed_payees`, `payment.allowed_payment_instruments`, `payment.allowed_pisps`, `payment.agent_recurrence` (with a `frequency` from `ON_DEMAND` through `ANNUALLY` and an optional `max_occurrences`), `payment.execution_date`, and `payment.reference`, which pins a Payment Mandate to one particular open Checkout Mandate by digest. The Checkout Mandate defines two: `checkout.allowed_merchants` and `checkout.line_items`. A closed mandate that does not satisfy the open mandate's constraints is invalid, and a verifier that meets a constraint type it does not recognize must treat it as failing rather than ignore it.

## Signing: a chain of SD-JWTs

Mandates are not plain JWTs. They are **SD-JWT verifiable credentials**, which add two properties AP2 needs: selective disclosure, so a party sees only the fields it should, and key binding, so possession of a private key proves the right to act. Keys are EC P-256 and signatures are ES256.

One signing rule is easy to miss and worth getting right. The Payment Mandate binds itself to the checkout by hashing the merchant-signed checkout JWT, and that hash is only unguessable if the signature it covers carries entropy of its own. So the specification requires the **checkout JWT** to be signed with a non-deterministic scheme such as ECDSA rather than a deterministic one such as Ed25519, specifically to prevent rainbow-table attacks on the checkout contents. If you do use a deterministic scheme, the specification requires you to add a salt of sufficient entropy to the checkout instead.

Delegation is expressed as a chain of arbitrary depth. The root SD-JWT is signed by a root of trust, in AP2 typically the user's bank or agent provider, and carries a `cnf` claim naming the key allowed to sign the next hop. Each subsequent hop is a key-binding SD-JWT signed by that key. Intermediate hops use header `typ` of `kb+sd-jwt+kb` and carry their own `cnf`; the final closed mandate uses `kb+sd-jwt` and must not carry one, which is what makes it terminal. On the wire the hops are joined by a double tilde (`~~`). The merchant separately signs the checkout payload itself (the `checkout_jwt`). The result is a credential that records not just an authorization but the whole delegation path that produced it.

<figure class="flow" aria-label="The AP2 mandate delegation chain">
<ol class="flow-steps">
<li class="flow-step" style="--accent:#b4502e"><span class="flow-head"><span class="flow-num">1</span><span class="flow-tag">Root</span></span><span class="flow-title">Root of trust</span><p class="flow-desc">The bank or provider signs the root credential.</p><span class="flow-chip">root SD-JWT</span></li>
<li class="flow-step" style="--accent:#b4502e"><span class="flow-head"><span class="flow-num">2</span><span class="flow-tag">Open</span></span><span class="flow-title">Open mandate</span><p class="flow-desc">The user signs constraints and an allowed key.</p><span class="flow-chip">open + cnf</span></li>
<li class="flow-step" style="--accent:#b4502e"><span class="flow-head"><span class="flow-num">3</span><span class="flow-tag">Closed</span></span><span class="flow-title">Closed mandate</span><p class="flow-desc">The agent signs the exact action, key-bound to the open mandate.</p><span class="flow-chip">closed (KB)</span></li>
<li class="flow-step" style="--accent:#b4502e"><span class="flow-head"><span class="flow-num">4</span><span class="flow-tag">Verify</span></span><span class="flow-title">Verify</span><p class="flow-desc">Trust the root key; each hop checks the previous one.</p><span class="flow-chip">follow the chain</span></li>
</ol>
<figcaption>Each hop is signed by the key the previous hop authorized, so the closed mandate carries its whole delegation path.</figcaption>
</figure>

## Verification: trust the root, follow the chain

Verification is the mirror image, and it is simple to state. The verifier trusts only the root issuer key. It then validates every hop using the public key bound in the previous hop's `cnf`, and checks that the closed mandate's `sd_hash` binds the entire preceding chain. If every link holds and the closed mandate satisfies the open mandates above it, the authorization is valid. The Payment Mandate's binding to the `checkout_jwt` hash is checked at the same time, so a verifier knows the payment and the cart belong together.

In practice you do not implement SD-JWT by hand. The AP2 SDK provides a `MandateClient` with `create`, `present`, and `verify`:

```python
# uv pip install git+https://github.com/google-agentic-commerce/AP2.git@main
from ap2.sdk.mandate import MandateClient

client = MandateClient()

# The user (or their provider) issues an open mandate constraining the agent.
open_token = client.create(
    payloads=[open_payment_mandate],   # the v0.2 model shown above
    issuer_key=issuer_jwk,             # EC P-256 root-of-trust key
)

# Later, the agent presents a closed mandate as a new hop in the chain,
# bound to this transaction by nonce and audience.
chain = client.present(holder_key=agent_jwk, mandate_token=open_token,
                       payloads=[closed_payment_mandate],
                       nonce="tx_abc", aud="merchant")

# The merchant or network verifies, trusting only the root issuer key.
payloads = client.verify(
    token=chain,
    key_or_provider=lambda token: issuer_jwk,
    expected_aud="merchant",
    expected_nonce="tx_abc",
)
```

Two things are worth doing rather than skipping. Pass `expected_aud` and `expected_nonce`: without them the final hop's transaction binding is not enforced. And feed the returned payloads to `PaymentMandateChain.parse` or `CheckoutMandateChain.parse` and call `verify(...)`, which is what actually evaluates the constraints; `MandateClient.verify` checks the cryptography, not the business rules.

Use the v0.2 models under the SDK's generated package and the canonical JSON schemas, not the older `ap2.models` shapes, which are the superseded intent-and-cart design.

## Receipts: the other half of the record

A mandate proves what was authorized. A **receipt** proves what the verifier did about it, and v0.2 makes receipts mandatory rather than optional: on accepting or rejecting a mandate, the verifier must return a signed receipt. The Merchant returns a Checkout Receipt, the Merchant Payment Processor returns a Payment Receipt.

A receipt is a verifier-signed JWT carrying a `status` of `Success` or `Error`, the issuer, a timestamp, and a `reference` that is the hash of the closed mandate it is binding to. Payment Receipts add a `payment_id` and, on success, `psp_confirmation_id` and `network_confirmation_id`; Checkout Receipts add an `order_id`. In the SDK this is `ReceiptClient`, and the canonical reference is computed the same way every time, as the SHA-256 of the leaf JWT of the chain:

```python
reference = compute_sha256_b64url(client.get_closed_mandate_jwt(chain))
```

Receipts are not bookkeeping. They are load-bearing in two places. The specification's double-spend rule says a shopping agent must not present a further mandate against the same open mandate until it has received a receipt rejecting the previous one, so receipts are the mechanism that stops one open mandate authorizing two carts. And at dispute time the evidence bundle is mandate *and* receipt on both sides: the checkout mandate with its receipt, the payment mandate with its receipt, each receipt's `reference` independently recomputed and matched. AP2 stops there, though. It states plainly that retention, retrieval, and how any of this is used to resolve a dispute are outside its scope.

## Human-present versus human-not-present, in the data

This is where the open and closed states pay off. A verifier always receives a closed Checkout Mandate and a closed Payment Mandate, regardless of how the purchase happened. The difference is who signed the closed mandate.

In the **human-present** (direct) flow, the user sees the final checkout and signs the closed mandates themselves. There is no open mandate in the chain; trust comes directly from the user's signature.

In the **human-not-present** (autonomous) flow, the user is not there at the moment of purchase. They earlier signed open mandates that set constraints, and the agent now assembles and signs the closed mandates on their behalf, using the key the open mandate authorized through its `cnf`. Trust is transitive: the verifier trusts the agent's closed mandate because it sits in a chain rooted in the user's signed constraints. The data signal for this flow is the presence of an open mandate (a `vct` ending in `.open.1`, carrying a `cnf`) in the chain. The two are convertible: a merchant can turn an autonomous flow into a present one by returning an unresolved-constraint error that forces the user back into the loop.

## How this connects to getting paid

A closed Payment Mandate is the proof a credential provider, network, or processor needs to release funds. In practice the credential provider verifies the Payment Mandate before returning a scoped payment credential, and the specification is strict about the ordering: the credential or token must only be released to the merchant once a final Payment Mandate has been received and verified, which is what binds the token to that one transaction. The merchant payment processor then has to satisfy itself that the credential it received is correctly scoped to the checkout before the money moves.

One naming trap for anyone reading the spec alongside the rest of this site: **AP2 uses "MPP" to mean Merchant Payment Processor**, one of its five roles. That is not the [Machine Payments Protocol](/learn/mpp-explained/), the Stripe and Tempo standard that shares the abbreviation. AP2's other four roles are the Shopping Agent, the Credential Provider, the Merchant, and the Trusted Surface, the last of which must be non-agentic because it is the surface that captures user consent.

So AP2 produces the authorization, and the payment rails consume it. For the wider money mechanics, see [how AI agents pay](/learn/how-ai-agents-pay/), and for where AP2 sits among the other standards, see [the protocol stack](/learn/agentic-commerce-protocol-stack/).

## Frequently asked questions

### What is an AP2 mandate, concretely?

A mandate is a signed, tamper-evident credential that records an authorization. In AP2 v0.2 it is expressed as an SD-JWT verifiable credential. There are two types, a Checkout Mandate (what is being bought) and a Payment Mandate (what will be charged), and each exists in two states: an open mandate that sets constraints on future actions, and a closed mandate that authorizes one specific action.

### How is a mandate signed and verified?

Mandates are SD-JWT verifiable credentials using EC P-256 keys and ES256 signatures. Delegation is expressed as a chain of key-bound SD-JWTs joined on the wire by a double tilde. A verifier trusts only the root issuer key; every subsequent hop is validated by the public key bound in the previous hop's cnf claim, and the closed mandate's hash binds the whole chain. The merchant separately signs the checkout payload (the checkout_jwt).

### How do human-present and human-not-present flows differ?

A verifier always receives a closed Checkout Mandate and a closed Payment Mandate. The difference is who signed the closed mandate. In the human-present (direct) flow the user signs it directly. In the human-not-present (autonomous) flow an agent signs it, and trust comes from open mandates the user signed earlier that constrain what the agent may do.

### What is an AP2 receipt?

A receipt is the verifier's signed answer to a mandate. On accepting or rejecting a mandate the verifier must return one: a Checkout Receipt from the merchant, a Payment Receipt from the merchant payment processor. It carries a status of Success or Error and a reference that is the hash of the closed mandate it binds to. Receipts are what stop one open mandate authorizing two carts, and at dispute time the evidence bundle is the mandate and its receipt on both the checkout and the payment side.

### Is AP2 production-ready?

AP2 reached v0.2 on 28 April 2026 and ships an SDK, JSON schemas, and runnable sample scenarios in Python, Go, and Android, but it is still pre-production: the public materials are reference implementations and demos rather than documented live real-money deployments. Build against the v0.2 schemas and SDK, and note the repo also contains an older model layer that should not be used for new work.

## Primary sources

- AP2 protocol documentation: https://ap2-protocol.org/
- Agent Payments Protocol (AP2) repository, v0.2: https://github.com/google-agentic-commerce/AP2
- AP2 specification v0.2 (roles, modes, mandate versioning, dispute evidence): https://ap2-protocol.org/ap2/specification/
- AP2 agent authorization framework (SD-JWT, key binding, verification rules): https://ap2-protocol.org/ap2/agent_authorization/
- AP2 Checkout Mandate schema, constraints, and receipt: https://ap2-protocol.org/ap2/checkout_mandate/
- AP2 Payment Mandate schema, constraints, and receipt: https://ap2-protocol.org/ap2/payment_mandate/
- AP2 Python SDK API reference (MandateClient, ReceiptClient, wire format): https://github.com/google-agentic-commerce/AP2/blob/main/code/sdk/python/ap2/sdk/README.md
