Learn

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

Andrew McPherson

Depth · Advanced

Good for: Builders

AP2 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 in April 2026.

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.

What a closed mandate contains

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

{
  "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:

{
  "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”:

{
  "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. Payment constraints include payment.amount_range, payment.budget, payment.allowed_payees, payment.allowed_payment_instruments, payment.allowed_pisps, and payment.agent_recurrence (with a frequency such as MONTHLY and a max_occurrences). Checkout constraints include checkout.allowed_merchants and a required checkout.line_items. A closed mandate that does not satisfy the open mandate’s constraints is invalid.

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. The specification is explicit that a non-deterministic signature scheme (ECDSA) must be used rather than a deterministic one, to avoid certain hash attacks.

Delegation is expressed as a chain. The root SD-JWT is signed by a root of trust, typically the user’s bank or agent provider. Each subsequent hop is a key-binding SD-JWT signed by the private key whose public half was named in the previous hop’s cnf claim. 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.

  1. 1RootRoot of trust

    The bank or provider signs the root credential.

    root SD-JWT
  2. 2OpenOpen mandate

    The user signs constraints and an allowed key.

    open + cnf
  3. 3ClosedClosed mandate

    The agent signs the exact action, key-bound to the open mandate.

    closed (KB)
  4. 4VerifyVerify

    Trust the root key; each hop checks the previous one.

    follow the chain
Each hop is signed by the key the previous hop authorized, so the closed mandate carries its whole delegation path.

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 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:

# 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.
chain = client.present(holder_key=agent_jwk, mandate_token=open_token,
                       payloads=[closed_payment_mandate])

# The merchant or network verifies, trusting only the root issuer key.
result = client.verify(chain, key_or_provider=issuer_jwk)

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.

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 can present the Payment Mandate to the payment network and receive a scoped purchase credential, a token, in return; a settlement layer such as MPP then verifies the Payment Mandate and its binding to the checkout hash before the money moves. So AP2 produces the authorization, and the payment rails consume it. For the wider money mechanics, see how AI agents pay, and for where AP2 sits among the other standards, see the protocol stack.

FAQ

What is an AP2 mandate, concretely? A signed, tamper-evident credential expressed as an SD-JWT verifiable credential. There are two types, Checkout and Payment, each in two states: an open mandate that constrains future actions and a closed mandate that authorizes one specific action.

How is a mandate signed and verified? With SD-JWT verifiable credentials using EC P-256 keys and ES256 signatures, chained for delegation and joined on the wire by a double tilde. A verifier trusts only the root issuer key; each hop is validated by the previous hop’s cnf key, and the closed mandate’s hash binds the whole chain.

How do the human-present and human-not-present flows differ? The verifier always gets closed Checkout and Payment mandates. In the present flow the user signs the closed mandate; in the not-present flow an agent signs it, with trust supplied by open mandates the user signed earlier.

Is AP2 production-ready? It reached v0.2 in April 2026 with an SDK, schemas, and sample scenarios, but it remains pre-production, with reference implementations rather than documented live real-money deployments. The repo also holds an older model layer that should not be used for new work.

Primary sources

  1. AP2 protocol documentation · AP2 / Google
  2. Agent Payments Protocol (AP2) repository, v0.2 · GitHub, 2026-04-28
  3. AP2 specification (modes and mandates) · GitHub
  4. AP2 agent authorization (SD-JWT and key binding) · GitHub