venice-x402

/home/avalon/.hermes/skills/venice/venice-x402/SKILL.md · raw

Venice x402 (wallet credits)

x402 is Venice's wallet-based payment flow. Pay per request with USDC on Base, no account required. Three admin endpoints plus the protocol-level 402 response returned by every inference endpoint.

Endpoint Auth Purpose
POST /x402/top-up None (discovery) / X-402-Payment (settlement) Discover payment requirements, then settle a signed USDC transfer.
GET /x402/balance/{walletAddress} SIWE (X-Sign-In-With-X) Current USD balance for a wallet.
GET /x402/transactions/{walletAddress} SIWE Paginated ledger: TOP_UP, CHARGE, REFUND.

For the SIWE header format itself, see venice-auth.

Pay with a wallet: end-to-end

1. Call an inference endpoint with no balance → 402

Any inference endpoint (e.g. POST /chat/completions) returns a 402 with structured topUpInstructions and siwxChallenge when the wallet balance is too low. The PAYMENT-REQUIRED response header carries the x402 v2 paymentRequired object (base64-encoded JSON containing x402Version, error, resource, accepts[], and optional extensions) — it is not the same payload as the 402 body, which is a richer balance/top-up document.

{
  "error": "Payment required",
  "code": "PAYMENT_REQUIRED",
  "message": "Insufficient x402 balance",
  "suggestedTopUpUsd": 10,
  "minimumTopUpUsd": 5,
  "supportedTokens": ["USDC"],
  "supportedChains": ["base"],
  "topUpInstructions": {
    "step1": "POST /api/v1/x402/top-up with no payment header to get payment requirements",
    "step2": "Sign a USDC transfer authorization using the x402 SDK (createPaymentHeader)",
    "step3": "POST /api/v1/x402/top-up with the signed X-402-Payment header",
    "receiverWallet": "<RECEIVER_WALLET_ADDRESS>",
    "tokenAddress": "<USDC_TOKEN_ADDRESS>",
    "tokenDecimals": 6,
    "network": "eip155:8453",
    "minimumAmountUsd": 5
  },
  "siwxChallenge": {
    "info": { "domain": "api.venice.ai", "statement": "Sign in to Venice AI", ... },
    "supportedChains": ["eip155:8453"]
  }
}

2. Discover payment requirements — POST /x402/top-up (no header)

curl -X POST https://api.venice.ai/api/v1/x402/top-up

Response 402:

{
  "x402Version": 2,
  "accepts": [{
    "protocol": "x402",
    "version": 2,
    "network": "eip155:8453",
    "asset": "<USDC_TOKEN_ADDRESS>",
    "amount": "5000000",          // base units; USDC = 6 decimals → 5 USDC
    "payTo": "<RECEIVER_WALLET_ADDRESS>"
  }]
}

3. Sign a USDC transfer → POST /x402/top-up with X-402-Payment

The x402 SDK does the EIP-712 USDC transferWithAuthorization signing for you — but you must normalize the discovery response into the SDK's PaymentRequirements shape, not pass accepts[0] directly. Venice's discovery returns {protocol, version, network:"eip155:8453", asset, amount, payTo}, but createPaymentHeader rejects that with Error: Unsupported scheme because it lacks scheme and uses CAIP-2 network notation. Use the short network name ("base") and supply scheme:"exact", extra:{name,version}, plus the other required fields. The SDK also wants a viem wallet client, not an ethers wallet.

npm install x402 viem ethers
import { createPaymentHeader } from 'x402/client'
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'

const PRIVATE_KEY = process.env.WALLET_KEY!  // 0x-prefixed hex

// 1. Discover
const discover = await fetch(`${base_url}/x402/top-up`, { method: 'POST' })
const discBody = await discover.json()
const a = discBody.accepts[0]

// 2. Build SDK-shaped requirement (NOT a passthrough of `a`)
const TOP_UP = '10000000' // $10 in USDC base units (6 decimals)
const req = {
  scheme: 'exact',
  network: 'base',                            // short name, NOT 'eip155:8453'
  maxAmountRequired: TOP_UP,
  resource: `${base_url}/x402/top-up`,
  description: 'Venice x402 top-up',
  mimeType: 'application/json',
  payTo: a.payTo,                             // from discovery
  maxTimeoutSeconds: 300,
  asset: a.asset,                             // from discovery
  extra: { name: 'USD Coin', version: '2' },  // USDC EIP-712 domain
}

// 3. Sign with viem wallet client
const account = privateKeyToAccount(PRIVATE_KEY)
const client = createWalletClient({ account, chain: base, transport: http() })
const header = await createPaymentHeader(client, discBody.x402Version || 2, req)

// 4. Settle
const settle = await fetch(`${base_url}/x402/top-up`, {
  method: 'POST',
  headers: { 'X-402-Payment': header },
})
const { data } = await settle.json()
console.log(data.newBalance, data.amountCredited, data.paymentId)

A complete working top-up script is at scripts/topup.js.

200 response:

{
  "success": true,
  "data": {
    "walletAddress": "0x...",
    "amountCredited": 10,
    "newBalance": 22.5,
    "paymentId": "payment_01HZ..."
  }
}

4. Call inference again — credits are now debited from the wallet

The venice-x402-client SDK wraps steps 1–4: it catches 402, auto-tops-up to a configured amount, and retries.

GET /x402/balance/{walletAddress}

curl "https://api.venice.ai/api/v1/x402/balance/0xYOUR_WALLET" \
  -H "X-Sign-In-With-X: <base64 siwe>"
{
  "success": true,
  "data": {
    "walletAddress": "0x...",
    "balanceUsd": 12.5,
    "canConsume": true,
    "minimumTopUpUsd": 5,
    "suggestedTopUpUsd": 10,
    "diemBalanceUsd": 5.25    // optional — present if the wallet is linked to a Venice account with DIEM
  }
}

The SIWE signer must match the path wallet — 403 otherwise.

GET /x402/transactions/{walletAddress}

curl "https://api.venice.ai/api/v1/x402/transactions/0xYOUR_WALLET?limit=50&offset=0" \
  -H "X-Sign-In-With-X: <base64 siwe>"
{
  "success": true,
  "data": {
    "walletAddress": "0x...",
    "currentBalance": 12.35,
    "transactions": [
      {
        "id": "ledger_01H...",
        "amount": -0.15,
        "balanceAfter": 12.35,
        "type": "CHARGE",
        "createdAt": "2026-04-03T12:34:56.000Z",
        "requestId": "chatcmpl-...",
        "modelId": "zai-org-glm-5-1"
      },
      {
        "id": "ledger_01H...",
        "amount": 10,
        "balanceAfter": 12.5,
        "type": "TOP_UP",
        "createdAt": "2026-04-03T12:00:00.000Z",
        "requestId": null,
        "modelId": null
      }
    ],
    "pagination": { "limit": 50, "offset": 0, "hasMore": false }
  }
}

Transaction types

type Sign of amount Meaning
TOP_UP positive /x402/top-up settlement.
CHARGE negative Inference debit. requestId / modelId link back to the call.
REFUND positive Failed request refund or manual adjustment.

Query parameters

/x402/transactions/{walletAddress}

Param Notes
limit 1–100. Default 50.
offset Number of entries to skip. Default 0.

Use offset + limit and pagination.hasMore for paging.

Constants

Errors

Code Meaning
400 Below minimum top-up, invalid wallet format, or other validation.
401 X-Sign-In-With-X header is present but invalid (bad signature, expired, nonce reuse, unsupported chain) — returned as X402_SIGN_IN_* error codes.
402 Expected discovery response on /x402/top-up (no payment header), on /x402/balance and /x402/transactions when the SIWE header is absent, and on any inference endpoint when the wallet balance is insufficient. Settlement errors use INVALID_PAYMENT / INVALID_PAYMENT_FORMAT / INSUFFICIENT_FUNDS / EXPIRED_PAYMENT codes.
403 SIWE wallet ≠ path wallet.
429 Too many top-ups/balance checks.
500 Settlement failure; retry with a fresh nonce.

Gotchas

js const req = { scheme: 'exact', network: 'base', // string name, NOT 'eip155:8453' maxAmountRequired: '19000000', // base units, USDC = 6 decimals resource: 'https://api.venice.ai/api/v1/x402/top-up', description: 'Venice x402 top-up', mimeType: 'application/json', payTo: accepts[0].payTo, maxTimeoutSeconds: 300, asset: accepts[0].asset, extra: { name: 'USD Coin', version: '2' }, // EIP-712 domain for USDC on Base };

SupportedEVMNetworks in the SDK is the string-name list ('base', 'base-sepolia', 'polygon', ...), not CAIP-2. - Signer must be a viem WalletClient, not an ethers Wallet. x402/client introspects via isEvmSignerWallet and rejects ethers signers. Build with viem: js import { createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { base } from 'viem/chains' const client = createWalletClient({ account: privateKeyToAccount(pk), chain: base, transport: http() }) const header = await createPaymentHeader(client, 2, req) - Package exports gotcharequire('x402') throws ERR_PACKAGE_PATH_NOT_EXPORTED. Import subpaths only: x402/client, x402/schemes, x402/shared, x402/types, etc. - amount override is your choice. Discovery returns amount: "5000000" (the $5 minimum). You can sign for more — set maxAmountRequired to whatever you want to credit (≥ $5, ≤ wallet balance). - See references/topup-walkthrough.md for a full working script (discover → sign with viem → settle → verify balance) and references/wrong-network-recovery.md for recovering USDC sent to the x402 wallet on the wrong chain. - Use the x402 SDK (npm install x402) for signing. Hand-rolling the EIP-712 transferWithAuthorization is risky — nonce reuse ⇒ INVALID_PAYMENT. - The SIWE signer wallet must match the walletAddress path param on balance / transactions. Separate wallets can't inspect each other. - /x402/top-up is unauthenticated on the discovery call — auth is implicit via the signed X-402-Payment header on settlement. - balanceUsd on /x402/balance is the USDC credit balance only. diemBalanceUsd, when present, is a separate linked-account number — sum them yourself if you need a combined figure. - PAYMENT-REQUIRED (uppercase, hyphens) is the header with base64-encoded x402 paymentRequired object; don't confuse it with the body field code: "PAYMENT_REQUIRED" (which only appears on insufficient-balance bodies, not on auth-style 402s). - On /x402/balance and /x402/transactions, missing the SIWE header returns 402 (not 401). Only a present-but-invalid header returns 401 with a X402_SIGN_IN_* code. - The x402 v2 accepts[].amount is in base units (e.g. "5000000" = 5 USDC). Don't multiply by decimals again. - DIEM, BUNDLED_CREDITS, and Bearer-account USD are independent from wallet credits. For account balance, use venice-billing.