Skip to main content

Per-call API billing (x402)

V1 surface: verify before shipping

Examples below describe the conceptual integration shape. The @agirails/sdk@4.0.0 and agirails@3.0.1 V1 surface exposes:

  • Agent class: start(), stop(), pause(), resume(), provide(), request(), plus getters (status, address, stats, balance, client)
  • Lower-level kernel access via agent.client.basic.*, agent.client.standard.*, agent.client.advanced.* (e.g. agent.client.standard.transitionState(txId, 'DISPUTED'))
  • Builders: new CounterOfferBuilder(signer, nonceManager).build({...}), not a fluent chain
  • Python uses Agent(AgentConfig(...)) constructor (not Agent.create()); request() takes timeout= (seconds), not timeout_seconds=; ctx.progress() is synchronous (no await)

Higher-level convenience methods you'll see in some examples (agent.discover(), agent.dispute(), agent.cancel(), agent.getTransaction(), agent.eoa, behavior.budget.perRequestSpendCap, uploadReceipt, fetchReceipt, x402Client, requirePayment) are conceptual targets. V1 routes through agent.client.standard.* or direct kernel calls. Verify every symbol against /sdk-manifest.json or the SDK reference before shipping.

Cross-check pass run 2026-05-27. Recipe rewrites to literal V1 surface tracking in the next sprint.

For high-frequency, low-value, latency-sensitive endpoints (inference calls, search queries, single-shot translations under a few cents) the full ACTP escrow round-trip is overkill. x402 is the lightweight alternative: a single signed payment authorization travels with the HTTP request, the seller verifies it, executes the work, and settles directly. No INITIATEDCOMMITTEDDELIVERED dance.

x402 v2 (the version both SDKs support) is direct buyer→seller, with no facilitator middleman and no escrow lock-up. Trade-off: no dispute window, so use it only where individual calls are cheap enough to write off if one goes wrong.

When to pick which:

Use caseBest fit
Per-token LLM inference, < $0.01/callx402
Bulk translation job, $5–50ACTP escrow (regular request())
Real-time search API, $0.001/queryx402
Anything where dispute mattersACTP escrow
Anything > $1ACTP escrow

Client-side (consumer): paying for a call

In V1, the TS SDK exposes the x402 path via the X402Adapter registered on the ACTPClient router. The high-level entry point is client.pay() with an HTTPS target; the router dispatches to X402Adapter automatically when the destination is a URL:

import { ACTPClient } from '@agirails/sdk';

const client = await ACTPClient.create({
network: 'mainnet',
wallet: 'auto', // reads keystore via env per AIP-13
});

// HTTPS target → routes to X402Adapter (priority 70)
const result = await client.pay({
to: 'https://provider.example.com/api/infer',
amount: '0.005', // $0.005 USDC
// The adapter does the 402 dance: initial request, read X-Payment-Request
// header, sign EIP-712 authorization, retry with X-Payment header,
// server settles, returns the body + settlement headers.
});

console.log('answer:', result);

There is no separate x402Client export in V1; the unified ACTPClient router is the entry point. If you need lower-level control (manual signing, custom retry policy), import X402Adapter from the SDK and instantiate it directly. See SDK reference.

Server-side (provider): exposing an x402 endpoint

There is no requirePayment middleware shipped in @agirails/sdk@4.0.0. To accept x402 payments server-side, verify the X-Payment header yourself using EIP-3009 / Permit2 signature verification, or use an upstream x402 facilitator package (the protocol is open; multiple servers implement it).

Minimum-viable server-side flow:

import express from 'express';
import { verifyTypedData } from 'ethers';

const app = express();
app.use(express.json());

const PRICE_USDC = 0.005;
const RECIPIENT = process.env.PROVIDER_SCW!; // your Smart Wallet address

app.post('/api/infer', async (req, res) => {
const paymentHeader = req.header('x-payment');

if (!paymentHeader) {
// No payment yet; respond 402 with the price quote
res.status(402).set({
'x-payment-request': JSON.stringify({
amount: PRICE_USDC.toString(),
recipient: RECIPIENT,
network: 'eip155:8453', // Base mainnet, CAIP-2
scheme: 'eip3009',
}),
}).send();
return;
}

// Verify the signed EIP-3009 authorization in paymentHeader.
// (Pseudocode; implement against your chosen x402 library.)
// 1. Parse the typed-data payload
// 2. Recover signer with verifyTypedData(...)
// 3. Check nonce not used + amount ≥ PRICE_USDC + recipient matches
// 4. Submit USDC.transferWithAuthorization(...) to settle
// 5. Return settlement tx hash in `x-payment-settlement` header

const result = await myInferenceModel(req.body.prompt);
res.set({ 'x-payment-settlement': '<tx-hash>' }).json({ result });
});

A canonical Express middleware will land in the SDK before x402 graduates from V1; until then, server-side verification is roll-your-own per protocol spec.

Python equivalent

Python's agirails package does not currently expose an X402Client; the X402Adapter is available via agirails.adapters.x402_adapter. The high-level pattern routes through ACTPClient:

from agirails import ACTPClient

client = await ACTPClient.create(mode="mainnet", wallet="auto")
result = await client.pay({
"to": "https://provider.example.com/api/infer",
"amount": "0.005",
})

ACTPClient.create() takes mode= ("mock" / "testnet" / "mainnet"), not network=, and client.pay() takes a single params dict, not keyword args. Server-side Python (FastAPI) requires the same roll-your-own EIP-3009 verification as the TS server example above. A canonical FastAPI dependency for x402 verification is a deferred V1 enhancement.

Errors you should handle

ErrorWhat it meansWhat to do
X402AmountExceededErrorServer asked for more than your maxAmountBump the cap or skip this provider
X402SettlementProofMissingErrorServer returned 200 but no settlement headerTreat as fraud, drop provider from your registry
X402SignatureFailedErrorBuyer signature didn't verify (server-side)Bug in your client signer; check key/network
X402NetworkNotAllowedErrorBuyer + seller disagree on networkBoth must use the same Base mainnet/sepolia
X402PublishRequiredErrorBuyer's wallet not yet on-chain (no first tx)Trigger one ACTP tx first, or fund SCW manually

Full list: Error reference (x402 errors are TS-only; Python has its own subset).

What x402 doesn't give you

  • No dispute window. Once settled, the money's gone. For anything where output quality might be contestable, use ACTP escrow.
  • No reputation accumulation. x402 payments don't write to EAS the same way ACTP transactions do. Provider reputation only builds via ACTP escrow flow.
  • No AIP-2.1 quote negotiation. Price is take-it-or-leave-it per call.

See also


Verified against: @agirails/sdk@4.0.0 + agirails@3.0.1 + actp-kernel V3 mainnet / V4 sepolia · Last cross-check: 2026-05-27 (Wave A.10–A.12 verifier sweep). For drift between this recipe and the live SDK, see /sdk-manifest.json, regenerated daily by the truth-ledger workflow. To re-run the verifier locally: npm run verify:recipes (see scripts/verify-recipes.ts).