Institutional Facilitator flow
- At startup, call
GET /supportedand select one advertised capability combination. - The merchant returns an x402 requirement; the enterprise wallet creates the EIP-712 authorization outside XAPG.
- Send the complete signed payload and unchanged merchant requirements to
POST /verify. - After cryptographic verification succeeds, apply your own institutional policies.
- Only an approved, unchanged request is sent to
POST /settle.
x402 v2 · exact · Base · USDC · EIP-3009. Never infer a capability absent from GET /supported.Credential and signing boundary
Institutional backends use a separately issued xapg_inst_… key. The verify and settle scopes are granted separately.
The enterprise wallet owns the signing key; XAPG validates only the signed payload.
apg_… keys and institution xapg_inst_… keys are mutually isolated.Discover configured capabilities
https://xapg.io/supportedcurl https://xapg.io/supported \ -H 'X-API-Key: xapg_inst_…'
Match kinds, assets, and extensions[].adapters by network before constructing a payment.
{
"kinds": [
{ "x402Version": 2, "scheme": "exact", "network": "eip155:CHAIN_ID" }
],
"extensions": [{
"name": "xapg-payment-adapters",
"version": "1",
"adapters": [{
"id": "eip-3009",
"network": "eip155:CHAIN_ID",
"asset": "0x…",
"settlementSpenders": ["0x…"]
}]
}],
"signers": { "eip155:*": ["0x…"] },
"assets": [{
"network": "eip155:CHAIN_ID",
"address": "0x…",
"symbol": "USDC",
"decimals": 6,
"protocols": ["x402"]
}]
}
Reject unsupported combinations locally and do not hard-code settlement wallet addresses.
EIP-3009 Facilitator request
POST /verify and POST /settle accept the same object. paymentPayload.accepted must exactly match paymentRequirements.
{
"x402Version": 2,
"paymentPayload": {
"x402Version": 2,
"resource": {
"url": "https://merchant.example/resource",
"description": "Order description",
"mimeType": "application/json"
},
"accepted": {
"scheme": "exact",
"network": "eip155:CHAIN_ID",
"amount": "10000",
"asset": "0x…USDC from /supported…",
"payTo": "0x…beneficiary…",
"maxTimeoutSeconds": 120,
"extra": { "name": "USDC", "version": "2", "protocol": "eip-3009" }
},
"payload": {
"signature": "0x…EIP-712 signature…",
"authorization": {
"from": "0x…payer…",
"to": "0x…same beneficiary…",
"value": "10000",
"validAfter": "…unix seconds…",
"validBefore": "…unix seconds…",
"nonce": "0x…32 bytes…"
}
}
},
"paymentRequirements": {
"scheme": "exact",
"network": "eip155:CHAIN_ID",
"amount": "10000",
"asset": "0x…USDC from /supported…",
"payTo": "0x…beneficiary…",
"maxTimeoutSeconds": 120,
"extra": { "name": "USDC", "version": "2", "protocol": "eip-3009" }
}
}
10000 base units means 0.01 USDC.Verify without settlement
https://xapg.io/verifycurl -X POST https://xapg.io/verify \ -H 'Content-Type: application/json' \ -H 'X-API-Key: xapg_inst_…' \ --data-binary @signed-payment.json
{ "isValid": true, "payer": "0x…" }
Verification checks protocol invariants and simulates settlement. It submits no transaction.
Common invalidReason values include unsupported_payment_protocol, recipient_mismatch, signer_mismatch, authorization_expired, insufficient_funds, nonce_already_used, and simulation_failed.
Execute settlement
https://xapg.io/settlecurl -X POST https://xapg.io/settle \ -H 'Content-Type: application/json' \ -H 'X-API-Key: xapg_inst_…' \ --data-binary @signed-payment.json
{
"success": true,
"network": "eip155:CHAIN_ID",
"payer": "0x…",
"transaction": "0x…"
}
Treat success: true plus a valid transaction as completion. Do not retry blindly.
Persist the request, policy result, payer, network, transaction, and final chain receipt.
MPP is a separate resource flow
MPP does not use the institutional /supported → /verify → /settle client sequence. Its middleware uses the shared PaymentService settlement path.
https://xapg.io/mpp/resourceTestnet-only Base Sepolia EIP-3009 Python example
This PoC is specific to Base Sepolia EIP-3009 and never reads a wallet private key.
Create signed EIP-3009 test request
export XAPG_FACILITATOR_API_KEY='xapg_inst_…' export XAPG_ALLOWED_PAY_TO='0x…approved beneficiary…' python3 scripts/test_xapg_api.py python3 scripts/test_xapg_api.py \ --request-file signed-payment.json python3 scripts/test_xapg_api.py \ --request-file signed-payment.json \ --settle \ --confirm-settlement BASE_SEPOLIA \ --receipt-file xapg-settlement-receipt.json
Separate Agent product: Remote MCP
Remote MCP is a separate user-scoped Agent product with browser OAuth.
https://xapg.io/v1/mcpcodex mcp add xapg --url https://xapg.io/v1/mcp codex mcp login xapg claude mcp add --transport http xapg https://xapg.io/v1/mcp --scope user
Pair a wallet and confirm every exact x402 payment in the paired wallet.
payment_capabilities → read configured Base capability, policy, and wallet session list_payment_recipients → read approved recipient IDs request_test_payment → request by recipientId, exact amount, and reason x402_fetch → request an external merchant x402 resource x402_status → check durable approval and wallet-request status x402_resume → continue only when readyToResume is true
payment_capabilities → read Base Mainnet capability, real-funds policy, and wallet session x402_fetch → request an external merchant x402 resource only x402_status → check durable approval and wallet-request status x402_resume → continue only when readyToResume is true non-x402 transfer → unsupported; no direct-transfer fallback
Permit2 · Coming soon
The adapter architecture and internal tests cover a Permit2 path, but Permit2 is not part of the current public integration contract.
GET /supported are supported.