AI agents can pay per-request with no SI account or API key. Two protocols supported: x402 (USDC on Base) and MPP (Tempo). x402 exact needs only a per-request signature; x402 upto also needs a one-time Permit2 token approval, separate from /buy SettlementV2 approval.
How It Works
Agent sends a request to /v1/chat/completions with no auth header
Server returns HTTP 402 with payment requirements for both protocols
Agent picks a protocol, signs the payment, retries the request
Server verifies payment, routes to cheapest seller, streams response
x402 (USDC on Base)
The agent needs a Base wallet with USDC. SI advertises x402 upto first and exact second:
upto (preferred): authorize a max amount for one request, then settle only actual usage after the response completes. Requires one-time USDC approval to Permit2 plus a per-request Permit2 payment signature; CDP sponsors x402 settlement gas when available.
exact (fallback): sign a fixed EIP-3009 payment for the estimated amount. Widely compatible, but may overpay versus actual usage.
x402 Permit2 approval is only for this per-request agent rail. It does not satisfy /buy / API-key marketplace SettlementV2 allowance, and SettlementV2 approval does not authorize x402 upto payments.
Call the API directly at https://api.surplusintelligence.ai (the canonical API host) so redirects never drop the x402 payment headers.
JavaScript / TypeScript (upto, @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 = '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: pass an explicit address. Do not rely on walletClient.address.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,}// 1. Get a 402 challenge.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(),)// 2. Prefer upto; fall back to exact if your client cannot sign Permit2 upto.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')// 3. Retry the same request with payment attached.const paid = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'PAYMENT-SIGNATURE': paymentHeader }, body: JSON.stringify(body),})console.log(paid.status) // 200console.log(paid.headers.get('PAYMENT-RESPONSE')) // present on successconsole.log(await paid.json()) // OpenAI-compatible response body
Uses the Machine Payments Protocol. Agent attaches an Authorization: Payment <credential> header.
bash
tempo request POST https://api.surplusintelligence.ai/v1/chat/completions \ -d '{"model": "claude-opus-4.6", "messages": [{"role": "user", "content": "Hello"}]}'
ACP v2 (Virtuals Protocol)
For agents in the Virtuals ecosystem: job-based commerce with on-chain escrow. A dedicated ACP guide is on the roadmap; see the Payments overview for current status.
API Key Auth (SIWE)
Agents with wallets can self-issue API keys — no browser, no Privy:
bash
# Get SIWE challengecurl .../v1/buyer/auth/challenge?address=0xAgentWallet# Sign and get buyer API keycurl -X POST .../v1/buyer/auth/keys \ -H "Content-Type: application/json" \ -d '{"message": "<SIWE message>", "signature": "0x..."}'# Returns inf_xxx buyer key# Use like any OpenAI clientcurl .../v1/chat/completions \ -H "Authorization: Bearer inf_xxx" \ -d '{"model": "claude-opus-4.6", "messages": [{"role": "user", "content": "Hello"}]}'
Buyer keys are persistent (survive restarts) and complement x402/MPP (which are per-request). Use SIWE keys for long-running agents, x402 for ephemeral/serverless. If a key-backed agent pays from wallet USDC instead of prepaid credits, it needs a separate revocable SettlementV2 allowance for API-key marketplace usage; the x402 Permit2 approval above is not reused.
Manage Keys Programmatically
Anything you can do to a key in the dashboard, you can do over the API. Provision, list, read, reconfigure, rename, and revoke keys with no browser and no Privy. This is the same /v1/buyer/keys API the dashboard itself calls, so a long-running agent can run its whole key lifecycle in code.
Authenticate every call with an existing buyer key (inf_xxx, self-issued via SIWE above) or a session cookie; the tenant is resolved from your auth. The full lifecycle:
bash
BASE=https://api.surplusintelligence.aiKEY=inf_xxx # an existing buyer key# 1. Create a key. Idempotency-Key is required on create.curl -X POST $BASE/v1/buyer/keys \ -H "Authorization: Bearer $KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"label": "worker-01"}'# → {# "id": "01JQ8G7YV3M4N5P6Q7R8S9T0AB",# "key": "inf_9f8c1e2b3a4d5e6f7a8b9c0d1e2f3a4b", # store now: shown only once# "key_prefix": "inf_9f8c1e2b",# "label": "worker-01",# "created_at": 1713312000000# }# 2. List keys (redacted: the secret is never returned again).curl $BASE/v1/buyer/keys -H "Authorization: Bearer $KEY"# → { "items": [ { "id": "01JQ8G7YV3M4N5P6Q7R8S9T0AB", "key_prefix": "inf_9f8c1e2b",# "label": "worker-01", "created_at": 1713312000000, "last_used_at": 1713398400000 } ] }# 3. Read one key's metadata.ID=01JQ8G7YV3M4N5P6Q7R8S9T0ABcurl $BASE/v1/buyer/keys/$ID -H "Authorization: Bearer $KEY"# 4. Configure per-key routing preferences.# allow_untrusted=true lets this key route to providers outside the trusted-domain allowlist;# null or omitted inherits the account default (trusted-only). limits is reserved: send {}.curl -X PUT $BASE/v1/buyer/keys/$ID/preferences \ -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{"preferences": {"allow_untrusted": true}, "limits": {}}'curl $BASE/v1/buyer/keys/$ID/preferences -H "Authorization: Bearer $KEY"# → { "preferences": { "allow_untrusted": true }, "limits": {} }# 5. Rename a key's label.curl -X PATCH $BASE/v1/buyer/keys/$ID \ -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{"label": "worker-01 (prod)"}'# 6. Revoke a key (irreversible; returns 204 No Content).curl -X DELETE $BASE/v1/buyer/keys/$ID -H "Authorization: Bearer $KEY"
Keys are capped at 25 active per wallet. In a team org, mutating ops (create, rename, configure, revoke) are capability-gated, so a low-privilege member cannot reconfigure or revoke another member's key; a personal wallet or plain buyer key is unrestricted.
Become a Seller
Agents can also sell inference: list your API capacity programmatically, with no browser. As with buyer keys, whatever the seller dashboard does to a key or an offer is an API call you can make yourself.
bash
# 1. Bootstrap a seller key via SIWE (one-time, no browser).curl ".../v1/seller/auth/challenge?address=0xAgentWallet"curl -X POST .../v1/seller/auth/keys \ -H "Content-Type: application/json" \ -d '{"message": "<SIWE message>", "signature": "0x..."}'# Returns si_seller_xxx in the "key" field (shown once).# 2. List an offer. Idempotency-Key is required on create.curl -X POST .../v1/seller/offers \ -H "Authorization: Bearer si_seller_xxx" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"model": "claude-opus-4.6", "api_key": "your-provider-key", "seller_base_url": "https://api.venice.ai/api/v1", "price_input_per_1m": 12.00, "price_output_per_1m": 48.00}'
Seller keys support create, list, and revoke (rename and per-key preferences are buyer-only, since a seller key has no routing config):
bash
KEY=si_seller_xxx# Create another seller key (Idempotency-Key required; up to 25 active).curl -X POST .../v1/seller/keys \ -H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" -d '{"label": "batch-bot"}'# List keys (redacted; secrets are never returned again).curl .../v1/seller/keys -H "Authorization: Bearer $KEY"# Revoke a key (returns 204).curl -X DELETE .../v1/seller/keys/{id} -H "Authorization: Bearer $KEY"
Offers, health probes, earnings, and payouts are all API-first too. See the Seller API Reference for the full surface.
Pricing
Pricing is model-specific and market-based. SI routes to the cheapest available seller for the requested model, often below direct provider rates. The final x402 maximum is returned in the 402 challenge. With upto, the buyer signs a per-request Permit2 authorization for that max but only actual usage is settled; with exact, the full estimate is settled.