Skip to main content
Paymentsx402

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 signs a per-request Permit2 authorization for 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

The 402 handshake
  1. RequestNo auth header
  2. 402accepts[] — what it costs, how to pay
  3. SignPick one accepted option
  4. RetrySame url + PAYMENT-SIGNATURE
Two requests to the SAME url. The first is not an error you recover from — it is the server telling you the price and how it will take payment.
  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-REQUIRED header — base64-encoded JSON (x402 v2 canonical, used by SDKs)
  • x-payment-required header — same data (legacy compatibility)
  • Response body — accepts[] array with the same data in readable JSON

The payment retry must include:

  • PAYMENT-SIGNATURE header — base64-encoded signed payment payload

Successful x402-paid responses include:

  • PAYMENT-RESPONSE header — 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 + a conservative buffer.
  • After inference succeeds, SI computes actual usage and settles only the actual buyer cost.
Why upto beats exact for inference
Authorized ceiling — the most that could be takensigned up front
Actually settled — what the response really usedcharged after
You authorize a ceiling, not a price. Inference cost is not knowable before the response exists, so exact has to over-quote and take the whole quote.
  • 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) plus the per-request Permit2 payment signature.
  • The Permit2 approval is specific to x402. It authorizes a per-request payment, not a standing balance.

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 402 PAYMENT-REQUIRED challenge (or GET /x402/info); do not hardcode it
  • Facilitator: external HTTP facilitator when available (CDP preferred), with self-facilitation fallback

Example upto accept entry:

json
{
  "scheme": "upto",
  "network": "eip155:8453",
  "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "amount": "5502",
  "payTo": "<operator address from the 402 challenge>",
  "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 upto is 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 USDC transferWithAuthorization tx and pays gas.
  • upto: when CDP advertises Base mainnet upto, CDP submits the x402UptoPermit2Proxy.settle() tx and pays gas.
  • Fallback: if no external upto facilitator 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 — default auto
  • UPTO_FACILITATOR_URL — optional explicit upto facilitator URL
  • UPTO_FACILITATOR_ADDRESS — optional explicit facilitator signer address

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.

The 402 challenge

When no auth is provided, the server returns a 402 that advertises:

  • x402 upto (preferred)
  • x402 exact (fallback)

Pick the scheme you support and retry the same URL with the signed payment.

Client Libraries

  • JavaScript/TypeScript: @x402/core + @x402/evm, or higher-level x402 fetch wrappers
  • Python: x402 package 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:

text
POST $SURPLUS_BASE_URL/chat/completions

Minimal request body:

json
{
  "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 (for upto, a per-request Permit2 payment authorization).
  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)

ts
import { createPublicClient, createWalletClient, http, publicActions } from 'viem'
import { base } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
import { UptoEvmScheme } from '@x402/evm'

const endpoint = '$SURPLUS_BASE_URL/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.