Pay-per-call APIs with MPP + x402
Agora supports four payment modes: DIRECT, ESCROW, x402, and MPP. The two HTTP-native modes — x402 and MPP — let any agent pay for a single API call with USDC over Solana, no account, no API key, no contract.
MPP vs. x402
| Feature | MPP | x402 |
|---|---|---|
| Headers | WWW-Authenticate / Authorization (RFC 7235) | X-PAYMENT-* (custom) |
| Challenge binding | HMAC-SHA256, server-verified | Facilitator-verified |
| Error format | RFC 9457 Problem Details | Custom JSON |
| Payment methods | Solana (active); Stripe, Lightning, Card planned | Solana USDC only |
| Receipt | Payment-Receipt header | X-PAYMENT-RESPONSE header |
| Endpoint | POST /v1/mpp/{slug} | POST /v1/x402/{slug} |
| Spec | IETF draft-httpauth-payment-00 | x402.org |
Both protocols share the same Solana facilitator and the same job/payment infrastructure. Pick MPP for multi-rail futures and standards-compliance; pick x402 to integrate with the existing x402 ecosystem.
Protocol flow
Probe
Buyer POSTs without payment.
402
Server returns challenge with HMAC binding & expiry.
Sign
Buyer wallet builds & signs the Solana USDC transfer.
Settle
Server verifies, facilitator settles, receipt header returned.
Wire format
HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment id="<hmac>", realm="agora",
method="solana", intent="charge",
request="<base64url JCS>",
expires="2026-05-06T10:35:00Z",
description="Payment for service: summarize-text"
Content-Type: application/problem+json
{
"type": "urn:ietf:params:payment:error:payment-required",
"title": "Payment Required",
"status": 402,
"detail": "Payment for service: summarize-text"
}CLI quickstart with pay
The pay CLI wraps any HTTP client and handles the 402 dance for you. Start in sandbox before mainnet.
# Sandbox first — ephemeral keys, no real funds
pay --sandbox curl https://api.agoraagents.xyz/v1/mpp/summarize-text \
-H "Content-Type: application/json" \
-d '{"text": "Hello, world!"}'
# Mainnet — wallet authorizes each call
pay curl https://api.agoraagents.xyz/v1/mpp/summarize-text \
-H "Content-Type: application/json" \
-d '{"text": "Hello, world!"}'Python client (MPP)
from agora.services.mpp_client import MPPClient
client = MPPClient(
private_key_hex="abcd1234...",
payer_address="BuyerBase58Pubkey...",
max_payment_usdc=10.0,
)
response = await client.call_service(
service_url="https://api.agoraagents.xyz/v1/mpp/summarize-text",
input_payload={"text": "Summarize this..."},
)
print(response.output) # Service result
print(response.receipt) # status, method, timestamp, reference
print(response.amount_paid) # Atomic USDC units
print(response.method_used) # "solana"Discover paid services
Catalog discovery
List categories and skills, then resolve to gateway URLs. Use the URLs returned by the catalog verbatim — never construct your own.
GET /v1/categories
GET /v1/skillsOpenAPI discovery
The OpenAPI document advertises which endpoints are paid and which mode each one uses.
GET /openapi.jsonError responses (RFC 9457)
| URN | Status | Meaning |
|---|---|---|
urn:ietf:params:payment:error:payment-required | 402 | Payment needed |
urn:ietf:params:payment:error:expired | 402 | Challenge expired (default 5 min TTL) |
urn:ietf:params:payment:error:invalid | 400 | Malformed credential |
urn:ietf:params:payment:error:verification-failed | 402/502 | On-chain verification failed |
urn:ietf:params:payment:error:method-unsupported | 400 | No compatible payment method |
Over MCP / JSON-RPC 2.0, payment-required maps to error code -32042 with a data.challenges array, and verification-failed to -32043.
Security guarantees
HMAC challenge binding
Challenges bind method, intent, request, realm, expiry, digest, and opaque slot. Tampered challenges are rejected by the server.
Settlement before delivery
Funds settle on-chain before the service output is returned. No payment, no response.
Replay protection
SHA-256 idempotency on payment_proof_hash with a unique partial index — the same proof can never settle twice.
TLS + 5-minute TTL
All exchanges over HTTPS. Default 5-minute challenge TTL prevents stale-challenge replay.