Developer reference: this page collects setup and protocol-specific integration material. For API evaluation and resource architecture, start with the agent payment API guide.

Chapters 05 · 07 · 09 · 10 · 11 · 12

Build agents that
actually transact.

Per-agent MCP tools, AP2 signing, x402 authorization and card-token integrations have distinct contracts and record paths. Verify permissions, signer configuration and provider requirements for your integration.

~15 Per-agent MCP tools1,100+ Tool API endpoints3 Mandate types4 Outbound rails

The per-agent MCP server.

Each agent has its own MCP endpoint at /mcp/agent, scoped to one row in agent_identities. Drop the URL and the agent's pak_… key into any MCP-compatible client and the agent is online — wallet, card, channels, AP2 signing, x402, all surfaced as tools.

✓ shipped

/mcp/agent — per-agent endpoint

Scoped to one agent_identities.id. Available tools vary by permissions: wallet status, send payment, list activity, request approval, sign mandate. Authenticated by pak_… bearer.

✓ shipped

Discovery at /.well-known/agent.json

Public document advertising the agent's DID, AP2 public key, supported rails, and tool catalog — so external agents and merchants can discover and verify before they transact.

Bootstrap an MCP client

Drop the URL + key into any MCP-compatible client (any LLM client, coding agent, or your own host). The server auto-advertises its tool catalog via JSON-RPC tools/list:

~/.config/mcp.jsonjson
{"mcpServers": {"agentwallet": {"url": "https://api.agentwallet.ai/mcp/agent","headers": {"Authorization": "Bearer pak_live_a1f9c2…"}}}}

Tool call shape

JSON-RPC over HTTPjsonrpc
// Request{"jsonrpc": "2.0", "id": 42,"method": "tools/call","params": {"name": "wallet.send_payment","arguments": {"recipient_handle": "[email protected]","amount_cents": 12500,"currency": "USD","memo": "Invoice #INV-208"}}}// Response — auto-approved (under per-txn cap){"jsonrpc": "2.0", "id": 42,"result": {"content": [{ "type": "text","text": "Sent $125.00 USD via ACH. ref=pay_01J…" }],"isError": false}}

Per-agent tool catalog

ToolWhat it doesLimit-gated?
wallet.statusComputed balance + holds + headroom—
wallet.send_paymentInitiate fiat payout via available railyes
wallet.send_usdcUSDC transfer on Base via CDPyes
wallet.x402_paySign EIP-3009 auth for x402 endpointyes
wallet.list_activityRecent transactions across sources—
wallet.request_approvalSurface pending action to Principal widget—
card.detailsLast4 + balance + status (no PAN)—
card.recent_authorizationsCard-level transactions only—
comms.send_smsOutbound SMS via telephony provider—
comms.send_emailOutbound email via email provider—
comms.list_inboxUnread inbound messages—
identity.who_am_iAgent metadata + AP2 public key—
identity.sign_mandateES256 sign an AP2 IntentMandate—
identity.discoverResolve another agent via .well-known—
approvals.pollCheck status of a pending approval—

AP2. Three mandates, one chain.

The Agent Payments Protocol uses ES256-signed JWS tokens to make agent intent cryptographically provable. A complete payment is a chain of three signed mandates: Intent → Cart → Payment. Every signature is verified against the agent's published JWK and audited.

AP2 mandate chain · ES256 / JWS

①

IntentMandate

"I authorize agent X to spend ≤ $Y for purpose Z"

Principal-signed
②

CartMandate

Specific items + merchant + total + intent_id

Agent-signed (under intent)
③

PaymentMandate

Rail + recipient + cart_id + final amount

Agent-signed (under cart)

JWS structure

PaymentMandate JWS payloadjws
{"iss": "did:agentwallet:ag_01J…", // agent identity"sub": "[email protected]","aud": "https://vendor.example/ap2/v1","iat": 1746345600,"exp": 1746349200, // 1 hour"jti": "01J9…uuidv7","mandate_type": "payment","intent_id": "01J9intent…","cart_id": "01J9cart…","rail": "x402_usdc_base","amount": { "value": "125.00", "currency": "USD" },"recipient": { "address": "0xabc…" }}// Header{ "alg": "ES256", "kid": "ag_01J9…#k1", "typ": "JWS" }

Sign + verify

server/services/ap2/sign-mandate.tstypescript
import { SignJWT, jwtVerify, importJWK } from 'jose';export async function signPaymentMandate(agentId: string, claims: PaymentMandateClaims) {const agent = await getAgentIdentity(agentId);const privJwk = await decryptAp2PrivateKey(agent.ap2PrivateKeyEncrypted);const key = await importJWK(privJwk, 'ES256');return new SignJWT(claims) .setProtectedHeader({ alg: 'ES256', kid: agent.ap2KeyId, typ: 'JWS' }) .setIssuer(`did:agentwallet:${agent.id}`) .setIssuedAt() .setExpirationTime('1h') .setJti(uuidv7()) .sign(key);}

Inbound verification flow

  • Resolve issuer. Look up iss via /.well-known/agent.json on the agent's domain to fetch the published JWK.
  • Verify signature. jwtVerify(token, jwk) with strict alg: 'ES256'.
  • Verify chain. CartMandate must reference an existing IntentMandate. PaymentMandate must reference an existing Cart. Each must be unexpired.
  • Verify policy. Cart total ≤ Intent cap. Payment amount = Cart total. Merchant in Intent allowlist (if set).
  • Audit. Insert row into ap2_mandate_audit with action='verify' and the result.
  • Execute. Hand the verified PaymentMandate to the rail processor (x402, ACH via banking partner, or Shared Payment Token).

Distinct payment paths with route-specific authorization.

Outbound payments leave through one of three protocol stacks. The choice is decided by the merchant's capability advertisement (or by the rail the agent's policy permits).

x402 over EIP-3009

The merchant returns HTTP 402 Payment Required with a payment offer. The agent signs an EIP-3009 transferWithAuthorization (USDC on Base) for the offered amount and replays the request with the signed authorization in the X-PAYMENT header.

x402 round triphttp
// 1. Agent's first requestGET /api/dataset/v1 HTTP/1.1 Host: data.example Authorization: Bearer pak_…// 2. Merchant returns 402 with offerHTTP/1.1 402 Payment Required Content-Type: application/json{"x402": {"amount": "0.10", "currency": "USDC","network": "base","recipient": "0xabc…","validBefore": 1746349200}}// 3. Agent signs EIP-3009 (CDP-managed key) and replaysGET /api/dataset/v1 HTTP/1.1 Host: data.example X-PAYMENT: eyJ0eXAiOiJ4NDAyIiwiYWxnIjoi… // signed auth

Shared Payment Tokens (SPT)

For card-rail payments to merchants that accept network-issued single-use tokens. The agent requests an SPT, scoped to a single use and to a specific merchant + amount. The token expires; replay is structurally impossible.

SPT issuancetypescript
const spt = await issuer.paymentTokens.create({type: 'card', card: { vault_alias: card.vaultAlias }, single_use: true, restrictions: {amount: cartTotalCents, merchant: merchantId, expires_at: Math.floor(Date.now() / 1000) + 300}});// Persist for auditawait db.insert(acpSptIssuances).values({agentIdentityId, tokenId: spt.id, cartMandateId, amountCents: cartTotalCents});

vault-tokenized card tokens — when the merchant only takes a PAN

VGS aliases can be used on configured proxy paths. Other reveal or checkout paths may handle raw card data; do not assume that the application or agent can never receive a PAN.

Discovery · /.well-known/agent.json

Agentic Commerce Protocol describes merchant checkout interactions. Agent identity and capability discovery are separate interfaces, not an Agentic Commerce Protocol standard.

capabilities documentjson
{"agent_id": "did:agentwallet:ag_01J9…","name": "Acme Procurement Agent","public_keys": [{ "kid": "ag_01J9…#k1", "alg": "ES256","jwk": { "kty": "EC", "crv": "P-256", "x": "…", "y": "…" }}],"endpoints": {"ap2_inbound": "https://agent.acme.example/ap2/v1/payment","a2a": "https://agent.acme.example/a2a/v1"},"accepts": ["x402_usdc_base", "spt", "ach"],"erc8004": { "chainId": 8453, "tokenId": "42" }}

Approvals & Caps. The velocity engine.

Supported spend-gated routes check per-transaction, daily and monthly caps and can require approval. Generic x402.pay_url has an optional per-call maximum, not the mandate-bound spend gate.

The Decision Rule

IF tx_amount > per_tx_cap THEN DENY
IF tx_amount + daily_spent > daily_cap THEN DENY
IF merchant NOT IN allowlist THEN DENY
IF tx_amount > auto_approve_threshold THEN PEND
ELSE APPROVE

Three-cap policy

Spend-gated routes read per-transaction, daily and monthly limits from params.spendingLimits. Examples below are illustrative, not the internal implementation or current API contract.

policy gatetypescript
const [dailySpent, monthlySpent] = await Promise.all([ getSpendSince(agentId, startOfDay()), getSpendSince(agentId, startOfMonth()) ]);if (amountCents > agent.perTxnLimitCents) throw new Error('PER_TXN_EXCEEDED');if (amountCents + dailySpent > agent.dailyLimitCents) throw new Error('DAILY_EXCEEDED');if (amountCents + monthlySpent > agent.monthlyLimitCents) throw new Error('MONTHLY_EXCEEDED');

Widget state in memory

When a payment is PEND, the agent's MCP call hangs open. The approval engine spawns an ApprovalTicket in memory and pushes a widget to the Principal via AgentSim (iOS/Android push) or WhatsApp.

Memory loss on restart

Yes, pending widget approvals are lost if the Express process restarts. The agent's MCP call will timeout. The agent handles the timeout by retrying, which spawns a new approval ticket. No double-spend due to idempotency keys.

Approve spend requestWhatsApp

triage-bot-01 is requesting to pay LLM provider

$180.00

Force-approve / Admin override

Authorized approval actions are recorded. Approval does not waive hard caps. Check permissions and the current approval contract rather than relying on this illustrative admin_audit_log entry and forcefully transitions the widget state.

admin overridetypescript
await db.insert(adminAuditLog).values({adminId: req.user.id, action: 'force_approve_tx', targetId: txId, reason: req.body.reason});// Bypass policy gateawait executePayment(txId, { bypassPolicy: true });

Portal API & Auth.

The frontend dashboard is driven entirely by the /api/agent-portal router. It is RBAC-gated and distinct from the MCP and Admin endpoints.

Agent Portal API Endpoints

EndpointWhat it does
GET /api/agent-portal/agentsList all agents scoped to Principal's client
POST /api/agent-portal/agentsProvision a new agent wallet
GET /api/agent-portal/agents/:id/wallet/transactionsUnified activity feed
POST /api/agent-portal/agents/:id/wallet/creditAdd funds from master balance
GET /api/agent-portal/agents/:id/cardCard metadata (no PAN)
POST /api/agent-portal/agents/:id/card/revealGet short-lived VGS token to show PAN in iframe
GET /api/agent-portal/teamList Principals in this client
POST /api/agent-portal/auth/magic-linkRequest OTP email

Auth flow: Magic-link OTP

We removed passwords in v0.4.4. Principals authenticate via a 6-digit code sent to their email.

  • Request. Principal submits email. Server generates 6-digit code, hashes it, stores in agent_portal_otps, sends email.
  • Verify. Principal submits code. Server hashes, compares, issues 30-day HttpOnly sliding cookie.
  • Session. Row in agent_portal_sessions tracks device, IP, last seen. Revocable.

Invoice Pipeline.

For AP automation, agents can push invoices into a structured pipeline. The pipeline handles OCR, KYB checks, and eventual payout routing.

Invoice State Machine

StateAction required
draftWaiting for OCR / data extraction completion.
pending_reviewOCR confidence low, or vendor is unknown. Manual review needed.
pending_kybVendor exists but hasn't passed business verification. Payment blocked.
approvedInvoice clean, vendor verified. Ready for payment scheduling.
scheduledPayment date set. Will auto-execute.
paidFunds disbursed via selected rail.
rejectedTerminal state. Will not be paid.

KYB Blockers

An invoice cannot transition from pending_kyb to approved until the vendor's KYB status is clear. If an agent attempts to pay a vendor that fails KYB, the API returns a 403 and the mandate audit logs a KYB_FAILED error.

Compare AgentWallet