Developer reference: this page collects setup and protocol-specific integration material. For API evaluation and resource architecture, start with the agent payment API guide.
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.
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:
{"mcpServers": {"agentwallet": {"url": "https://api.agentwallet.ai/mcp/agent","headers": {"Authorization": "Bearer pak_live_a1f9c2…"}}}}Tool call shape
// 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
| Tool | What it does | Limit-gated? |
|---|---|---|
| wallet.status | Computed balance + holds + headroom | — |
| wallet.send_payment | Initiate fiat payout via available rail | yes |
| wallet.send_usdc | USDC transfer on Base via CDP | yes |
| wallet.x402_pay | Sign EIP-3009 auth for x402 endpoint | yes |
| wallet.list_activity | Recent transactions across sources | — |
| wallet.request_approval | Surface pending action to Principal widget | — |
| card.details | Last4 + balance + status (no PAN) | — |
| card.recent_authorizations | Card-level transactions only | — |
| comms.send_sms | Outbound SMS via telephony provider | — |
| comms.send_email | Outbound email via email provider | — |
| comms.list_inbox | Unread inbound messages | — |
| identity.who_am_i | Agent metadata + AP2 public key | — |
| identity.sign_mandate | ES256 sign an AP2 IntentMandate | — |
| identity.discover | Resolve another agent via .well-known | — |
| approvals.poll | Check 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"
CartMandate
Specific items + merchant + total + intent_id
PaymentMandate
Rail + recipient + cart_id + final amount
JWS structure
{"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
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
issvia/.well-known/agent.jsonon the agent's domain to fetch the published JWK. - Verify signature.
jwtVerify(token, jwk)with strictalg: '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_auditwithaction='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.
// 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.
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.
{"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
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.
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.
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
| Endpoint | What it does |
|---|---|
| GET /api/agent-portal/agents | List all agents scoped to Principal's client |
| POST /api/agent-portal/agents | Provision a new agent wallet |
| GET /api/agent-portal/agents/:id/wallet/transactions | Unified activity feed |
| POST /api/agent-portal/agents/:id/wallet/credit | Add funds from master balance |
| GET /api/agent-portal/agents/:id/card | Card metadata (no PAN) |
| POST /api/agent-portal/agents/:id/card/reveal | Get short-lived VGS token to show PAN in iframe |
| GET /api/agent-portal/team | List Principals in this client |
| POST /api/agent-portal/auth/magic-link | Request 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
HttpOnlysliding cookie. - Session. Row in
agent_portal_sessionstracks 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
| State | Action required |
|---|---|
| draft | Waiting for OCR / data extraction completion. |
| pending_review | OCR confidence low, or vendor is unknown. Manual review needed. |
| pending_kyb | Vendor exists but hasn't passed business verification. Payment blocked. |
| approved | Invoice clean, vendor verified. Ready for payment scheduling. |
| scheduled | Payment date set. Will auto-execute. |
| paid | Funds disbursed via selected rail. |
| rejected | Terminal 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.