Sign and Verify AP2 Mandates
AP2 (Agent Payments Protocol) is a mandate-based payment protocol for autonomous agents. A human signs an intent mandate bounding what the agent may spend; the agent later signs a cart mandatebinding a concrete cart to that intent. Both are wrapped in W3C-aligned Verifiable Digital Credentials (VDC) and verified against Tenzro's TDIP identity layer.
What You'll Build
- A signed intent mandate issued by a human DID
- A signed cart mandate issued by the agent's machine DID
- Verification via
tenzro_ap2VerifyMandateandtenzro_ap2ValidateMandatePair - Delegation-scope wiring so the agent is actually allowed to present AP2 carts
Prerequisites
- One human DID (
did:tenzro:human:...) with an MPC wallet - One machine DID (
did:tenzro:machine:...) controlled by the human - Familiarity with TDIP delegation scopes
Step 1: Prepare Delegation Scope
The agent cannot present a cart mandate unless its TDIP delegation includes AP2:
# The agent's machine DID must have AP2 in its allowed_payment_protocols
# before a cart mandate will be accepted.
tenzro identity update-delegation \
--did did:tenzro:machine:c3d4... \
--allowed-payment-protocols mpp,x402,ap2 \
--max-transaction-value 50.00 \
--max-daily-spend 200.00Step 2: Issue the Intent Mandate
The human signs a VDC with kind: "intent". Spending is bounded by max_amount, restricted to allowed_merchants/allowed_categories, and time-bounded by expires_at.
{
"vdc": {
"id": "mandate-01HQ...",
"kind": "intent",
"issuer": "did:tenzro:human:a1b2...",
"subject": "did:tenzro:machine:c3d4...",
"issued_at": "2026-04-20T12:00:00Z",
"expires_at": "2026-04-20T14:00:00Z",
"claims": {
"max_amount": "50.00",
"currency": "USDC",
"purpose": "book a flight to SFO under 50 USDC",
"allowed_merchants": ["did:tenzro:machine:air-shop"],
"allowed_categories": ["travel.flights"]
}
},
"signature": "base64(Ed25519(canonical(vdc)))",
"signer_did": "did:tenzro:human:a1b2...",
"alg": "EdDSA"
}Step 3: Issue the Cart Mandate
Once the agent has picked a merchant and line items, it signs a cart mandate referencing the intent via parent_id. The issuer on the cart must match the subjectof the parent intent — this is what binds the agent to the original human authorization.
{
"vdc": {
"id": "mandate-01HR...",
"kind": "cart",
"issuer": "did:tenzro:machine:c3d4...",
"subject": "did:tenzro:machine:air-shop",
"parent_id": "mandate-01HQ...",
"issued_at": "2026-04-20T12:05:00Z",
"expires_at": "2026-04-20T12:10:00Z",
"claims": {
"merchant_did": "did:tenzro:machine:air-shop",
"line_items": [
{ "sku": "SFO-Y-MON-AM", "qty": 1, "unit_price": "42.00" }
],
"total_amount": "42.00",
"currency": "USDC",
"payment_method": "x402"
}
},
"signature": "base64(Ed25519(canonical(vdc)))",
"signer_did": "did:tenzro:machine:c3d4...",
"alg": "EdDSA"
}Step 4: Verify a Single Mandate
tenzro_ap2VerifyMandate({
"vdc": { /* intent envelope */ }
})
-> {
valid: true,
mandate_id: "mandate-01HQ...",
kind: "intent",
signer_did: "did:tenzro:human:a1b2...",
alg: "EdDSA"
}Step 5: Validate the Pair
This is the call the merchant or facilitator makes before accepting payment:
tenzro_ap2ValidateMandatePair({
"intent_vdc": { /* intent envelope */ },
"cart_vdc": { /* cart envelope */ }
})
-> {
valid: true,
intent_id: "mandate-01HQ...",
cart_id: "mandate-01HR...",
total_within_intent: true,
merchant_allowed: true,
agent_matches: true,
time_window_valid: true
}The validator enforces five checks:
- Both VDC signatures verify against the resolved DID documents
- Intent issuer is a human DID, cart issuer is the machine DID named as the intent's subject
- Cart
total_amountis within intentmax_amount - Cart
merchant_didis inallowed_merchants - Both mandates are within their
expires_atwindows
Step 6: Protocol Info
tenzro_ap2ProtocolInfo()
-> {
version: "ap2/1.0",
supported_algs: ["EdDSA"],
supported_kinds: ["intent", "cart"],
chain_id: 1337
}CLI
# Verify a single mandate (intent or cart)
tenzro ap2 verify --vdc-file intent.json
# Validate an intent/cart pair
tenzro ap2 validate-pair \
--intent intent.json \
--cart cart.json
# Protocol metadata
tenzro ap2 infoTypeScript SDK
import { TenzroClient } from "@tenzro/sdk";
const client = new TenzroClient();
const intent = await loadJson("intent.json");
const cart = await loadJson("cart.json");
const pair = await client.ap2.validateMandatePair({ intent_vdc: intent, cart_vdc: cart });
if (!pair.valid) {
throw new Error("AP2 validation failed");
}
// Safe to now settle the cart via the chosen payment rail (x402, MPP, etc.)
await client.payments.payX402({ /* ... */ });AP2 vs MPP vs x402. MPP is for long-lived streaming sessions (e.g., per-token AI billing). x402 is for stateless one-shot payments. AP2 sits above both: the VDC chain authorizes what the agent may spend, and MPP/x402/Tempo handles how that charge is actually settled.
What You Learned
- Intent / cart mandates — parent-child VDC chain for non-repudiable agent spending
- VDC envelope — Ed25519 signature over canonical JSON, W3C-aligned format
- Pair validation — five-check verifier anchored in TDIP delegation scopes
- Layering — AP2 authorization composed with MPP/x402/Tempo settlement rails
Next Steps
- See the x402 tutorial for the stateless settlement rail often paired with AP2
- See the Agentic Wallet tutorial to set up DIDs and delegation
- Read the AP2 reference docs for the full RPC surface