x402 Protocol
HTTP 402-based per-request payment using USDC on Base. No account and no API key are required — just a wallet.
Surplus Intelligence supports two x402 payment schemes for inference:
upto(preferred): buyer authorizes a maximum, then only the actual post-response cost is settled.exact(fallback): buyer signs a fixed pre-charge and the full estimate is settled.
Flow
1. Agent sends a request with no auth header.
2. Server returns HTTP 402 with payment requirements in accepts[].
3. Agent signs one accepted payment option.
4. Agent retries the same request with PAYMENT-SIGNATURE.
5. Server verifies payment, routes to the cheapest available seller, runs inference, then settles payment.
6. Seller is paid by the SI operator; for x402 upto, CDP sponsors the buyer→operator settlement gas when available.
Response Headers
The 402 response includes payment requirements in two places:
PAYMENT-REQUIREDheader — base64-encoded JSON (x402 v2 canonical, used by SDKs)x-payment-requiredheader — same data (legacy compatibility)- Response body —
accepts[]array with the same data in readable JSON
The payment retry must include:
PAYMENT-SIGNATUREheader — base64-encoded signed payment payload
Successful x402-paid responses include:
PAYMENT-RESPONSEheader — base64-encoded settlement result with transaction hash
Scheme: upto (Preferred)
upto is designed for variable-cost resources like LLM inference.
- Buyer signs a Permit2 witness authorization for a maximum USDC amount.
- The max is based on estimated input +
max_tokens+ x402 flat fee + buffer. - After inference succeeds, SI computes actual usage and settles only the actual buyer cost.
- If actual usage is below the max, the buyer keeps the difference.
- If actual usage is $0, no x402 settlement tx is needed.
- Requires one-time USDC approval to Permit2 (
0x000000000022D473030F116dDEE9F6B43aC78BA3).
Current production setup:
- Network: Base mainnet (
eip155:8453) - Asset: USDC (
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) - x402 Upto Permit2 Proxy:
0x4020A4f3b7b90ccA423B9fabCc0CE57C6C240002 payTo: SI operator/treasury wallet, resolved dynamically — read it from the 402PAYMENT-REQUIREDchallenge (orGET /x402/info); do not hardcode it- Facilitator: external HTTP facilitator when available (CDP preferred), with self-facilitation fallback
Example upto accept entry:
{
"scheme": "upto",
"network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "5502",
"payTo": "",
"maxTimeoutSeconds": 120,
"extra": {
"name": "USD Coin",
"version": "2",
"facilitatorAddress": "0x..."
}
}
amount is the maximum authorized amount in USDC micro-units (6 decimals). For example, 5502 means 0.005502 USDC.
Scheme: exact (Fallback)
exact uses EIP-3009 TransferWithAuthorization.
- Buyer signs a fixed USDC transfer for the estimated max amount.
- The facilitator settles the full fixed amount before inference proceeds.
- This is widely supported by current x402 tooling and remains available for compatibility.
- It may overcharge relative to actual token usage, so
uptois preferred when the client supports it.
Facilitators and Gas Sponsorship
exact and upto can both be gasless for the buyer.
exact: CDP facilitator submits the USDCtransferWithAuthorizationtx and pays gas.upto: when CDP advertises Base mainnetupto, CDP submits thex402UptoPermit2Proxy.settle()tx and pays gas.- Fallback: if no external
uptofacilitator is available, SI can self-facilitate using the operator wallet.
Configuration knobs:
X402_FACILITATOR_URL— default HTTP facilitator (production uses CDP)UPTO_FACILITATOR_MODE=auto|cdp|http|self— defaultautoUPTO_FACILITATOR_URL— optional explicituptofacilitator URLUPTO_FACILITATOR_ADDRESS— optional explicit facilitator signer addressX402_FLAT_FEE_MICRO— x402-only flat fee in USDC micro-units
Pricing
Pricing is model-specific and market-based. SI routes to the cheapest available seller for the requested model. Prices are often below direct provider rates, but the final amount is always returned in the 402 challenge.
For x402 payments, SI adds an x402-only flat convenience fee (X402_FLAT_FEE_MICRO) to cover facilitation/settlement overhead. This fee does not apply to the normal SIWE + one-time USDC approval path.
Dual 402 Response
When no auth is provided, the server returns a 402 that advertises:
- x402
upto(preferred) - x402
exact(fallback) - MPP / Tempo, when available
Agents choose the payment rail they support.
Client Libraries
- JavaScript/TypeScript:
@x402/core+@x402/evm, or higher-level x402 fetch wrappers - Python:
x402package where available
Agent Happy Path: Buy Chat Inference with upto
x402 inference is native on the /v1 API surface — pay per request with the PAYMENT-SIGNATURE header, no separate wrapper path needed:
POST https://api.surplusintelligence.ai/v1/chat/completions
Minimal request body:
{
"model": "llama-3.3-70b",
"messages": [{ "role": "user", "content": "Say exactly: pong" }],
"max_tokens": 8
}
Algorithm for agents:
1. POST the request body without Authorization.
2. Decode the PAYMENT-REQUIRED header as base64 JSON.
3. Select accepts.find((a) => a.scheme === "upto") when present.
4. Sign that payment requirement.
5. Retry the same request body with PAYMENT-SIGNATURE: base64(JSON.stringify(paymentPayload)).
6. Success is HTTP 200 plus a PAYMENT-RESPONSE header and an OpenAI-compatible response body.
TypeScript example (@x402/evm + viem)
import { createPublicClient, createWalletClient, http, publicActions } from 'viem'
import { base } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
import { UptoEvmScheme } from '@x402/evm'
const endpoint = 'https://api.surplusintelligence.ai/v1/chat/completions'
const account = privateKeyToAccount(process.env.PRIVATE_KEY as 0x${string})
const publicClient = createPublicClient({ chain: base, transport: http() })
const walletClient = createWalletClient({ account, chain: base, transport: http() }).extend(publicActions)
// Important: UptoEvmScheme needs a signer with an explicit address.
// Do not rely on walletClient.address if it is undefined.
const signer = {
address: account.address,
signTypedData: (msg: any) => account.signTypedData(msg),
readContract: publicClient.readContract.bind(publicClient),
getTransactionCount: publicClient.getTransactionCount.bind(publicClient),
estimateFeesPerGas: publicClient.estimateFeesPerGas.bind(publicClient),
}
const body = {
model: 'llama-3.3-70b',
messages: [{ role: 'user', content: 'Say exactly: pong' }],
max_tokens: 8,
}
const challenge = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
const paymentRequired = JSON.parse(
Buffer.from(challenge.headers.get('PAYMENT-REQUIRED')!, 'base64').toString(),
)
const upto = paymentRequired.accepts.find((a: any) => a.scheme === 'upto')
const paymentPayload = await new UptoEvmScheme(signer).createPaymentPayload(2, upto)
const paymentHeader = Buffer.from(JSON.stringify(paymentPayload)).toString('base64')
const paid = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'PAYMENT-SIGNATURE': paymentHeader,
},
body: JSON.stringify(body),
})
console.log(paid.status) // 200
console.log(paid.headers.get('PAYMENT-RESPONSE')) // settlement metadata
console.log(await paid.json()) // OpenAI-compatible completion
See /docs/getting-started/agent-quickstart.md for more examples.