Cover photo

Stable Build #4: Build an x402 API for AI Agents

AI agents can call APIs autonomously, but paying for those calls still usually requires accounts, subscriptions, or API keys. On Stable, x402 enables a simpler model: agents  pay for API requests with USDT0 as part of the HTTP flow itself.

An x402 API is an HTTP endpoint that charges for one response. The server quotes a price in a 402 Payment Required response, the client signs a stablecoin authorization, and the server returns the resource after that payment settles.

In this edition, we'll build both sides of that flow: a paid API route and an AI agent that can pay for it with USDT0. We’ll also cover spending controls, retries, idempotency, and how x402 settlement scales on Stable.

What is x402?

x402 gives the unused HTTP 402 status code a payment flow. It separates the buyer from the resource server. A facilitator verifies and settles between them; Stable holds the payment state.

post image

Four components take part:

  1. Agent holds a wallet and decides whether the quoted price fits its budget.

  2. Resource server owns the route and its price.

  3. Facilitator checks the authorization, pays transaction gas, and settles it onchain.

  4. Stable moves USDT0 from the agent to the resource server's receiving address.

The useful split is between intent and execution. The agent signs an EIP-3009 authorization for an exact transfer. It does not broadcast a transaction. The facilitator can execute only the transfer the agent signed: payer, recipient, amount, validity window, and nonce are all inside the signature.

USDT0 already implements EIP-3009, so this flow needs no payment contract of your own. On Stable, USDT0 is also the native gas asset. The facilitator funds one USDT0 balance for gas and settles payments in the same asset.

Exploring x402 Use Cases

Suppose a treasury agent is about to pay 0x7a2e…91c4. It calls GET /risk/0x7a2e…91c4 for a counterparty report priced at 0.01 USDT0. No account to create or API key to rotate. The agent needs a wallet and enough USDT0 for the call.

The interesting boundary sits between HTTP and the chain. The buyer and seller exchange headers. A facilitator turns the signed authorization into a USDT0 transfer on Stable.

There is no account-registration step. The payment receipt is an onchain transaction hash.

Put a one-cent price on one route

The seller puts x402 middleware in front of the paid route. This example points to Heurist because its live /supported response lists x402 v2 on eip155:988 today; Stable keeps the current options in its facilitator reference.

npm install express @x402/express @x402/evm @x402/core
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { HTTPFacilitatorClient } from "@x402/core/server";
import { ExactEvmScheme } from "@x402/evm/exact/server";

const NETWORK = "eip155:988";
const USDT0 = "0x779Ded0c9e1022225f8E0630b35a9b54bE713736";
const payTo = process.env.PAY_TO_ADDRESS as `0x${string}`;

const facilitator = new HTTPFacilitatorClient({
  url: "https://facilitator.heurist.xyz/",
});
const resourceServer = new x402ResourceServer(facilitator)
  .register(NETWORK, new ExactEvmScheme());

const app = express();

app.use(paymentMiddleware({
  "GET /risk/:address": {
    accepts: [{
      scheme: "exact",
      network: NETWORK,
      price: {
        amount: "10000", // 0.01 USDT0
        asset: USDT0,
        extra: { name: "USDT0", version: "1", decimals: 6 },
      },
      payTo,
    }],
    description: "Counterparty risk report for one Stable address",
    mimeType: "application/json",
  },
}, resourceServer));

app.get("/risk/:address", async (req, res) => {
  res.json(await buildRiskReport(req.params.address));
});

app.listen(4021);

buildRiskReport is the product code. It sits behind the middleware and never sees a header.

The amount is a decimal string in the token's base units. USDT0's x402 surface uses 6 decimals, so "10000" equals 0.01 USDT0. Stable's native gas surface uses 18 decimals for the same balance, and the split catches people. Writing parseEther("0.01") here quotes the call at ten billion USDT0.

Routes you leave out of the map stay free. Keep /health out so a load balancer can check the service without buying anything.

Paying from the agent side

The buyer wraps fetch with an x402 client. Tether's WDK account already implements the signer interface the EVM payment scheme expects, so there's no adapter to write.

npm install @x402/fetch @x402/evm @tetherto/wdk-wallet-evm
import WalletManagerEvm from "@tetherto/wdk-wallet-evm";
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";

const account = await new WalletManagerEvm(process.env.SEED_PHRASE!, {
  provider: "https://rpc.stable.xyz",
}).getAccount(0);

const payments = new x402Client();
registerExactEvmScheme(payments, { signer: account });

const paidFetch = wrapFetchWithPayment(fetch, payments);
const response = await paidFetch(
  "https://api.example.com/risk/0x7a2e0000000000000000000000000000000091c4",
);

console.log(await response.json());

paidFetch sends the first request, reads the quote, signs, and retries. The agent never submits a transaction; gas and confirmation tracking belong to the facilitator. A facilitator wired into Stable's Gas Waiver can settle without folding a gas charge into the buyer's price.

Automatic signing needs a hard ceiling. Use a dedicated wallet with a per-call cap, and reserve the session budget in the policy store before anything gets signed. A prompt is not a spending policy.

Recovering from an x402 timeout

The reference flow verifies the authorization before the route handler runs, then settles before releasing the 200. So an expensive handler can finish and then watch settlement time out, while the chain confirms the transfer seconds after the client gave up.

Do the idempotency lookup in an x402 lifecycle hook. If you reach for pre-middleware instead, key on a hash of the complete PAYMENT-SIGNATURE; returning a report for an unverified nonce turns the cache into a data leak. Store the authorization nonce beside a hash of the route and request body. Write the generated report into that record, then attach the transaction hash and settlement state.

On retry, the lookup returns the cached report and stored PAYMENT-RESPONSE for a confirmed payment, or holds the request while settlement is unresolved. The USDT0 contract stops the same authorization from settling twice. It will not serve the cached result.

If the client mints a fresh authorization after every timeout, both can settle, and the agent pays twice for one report. Reconcile the receipt or call authorizationState(from,nonce) before asking the wallet to sign again. validBefore stops execution after expiry; it says nothing about whether the authorization already settled.

Sub-second finality shortens that ambiguous window. It does not close it.

What v1.8.0 changes at facilitator scale

One paid call needs no release-specific code. At thousands of settlements, the facilitator's single account becomes a serial queue, and that queue is the ceiling. Stable has tested v1.8.0 as a Testnet release candidate; the Mainnet date is still open in the network upgrade notes.

A facilitator with access to the gated Enterprise RPC could submit CustomTx type 0x3F across independent 2D nonce channels, so one pending settlement stops blocking the ones behind it. Guaranteed Blockspace can reserve gas capacity for eligible settlement traffic through a governance-configured lane. The public-facilitator code above does not get either automatically. These transaction nonces have nothing to do with the ERC-3009 nonce the buyer signed.

Underneath that submission path, OPE (Block-STM) runs non-conflicting settlements across CPU cores, MemIAVL shortens the commit path, and Selective RecheckTx cuts the work after it. The release targets roughly 10,000 TPS. None of the code above changes.

Check before production

  1. Use the v2 @x402/* packages and PAYMENT-* headers.

  2. Confirm the facilitator supports eip155:988, USDT0, and its ERC-3009 domain fields. EVM support elsewhere does not prove Stable support.

  3. Price against 6-decimal USDT0 base units. Keep native 18-decimal gas math out of the quote.

  4. Put the buyer's signer behind per-call and per-session limits.

  5. Make the paid handler replay-safe, and persist its output before settlement begins.

  6. Reconcile an ambiguous authorization before you accept a fresh signature.

Build on Stable