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

By [Stable](https://blog.stable.xyz) · 2026-09-02

---

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](https://docs.stable.xyz/en/explanation/x402?utm_campaign=stable-build-04) 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.

![](https://storage.googleapis.com/papyrus_images/3257cf6741ee62ae4fffd3831bbac51b6d1341935b47a7c16e90de38be2936c7.png)

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](https://eips.ethereum.org/EIPS/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](https://docs.stable.xyz/en/explanation/usdt-as-gas-token?utm_campaign=stable-build-04). 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](https://docs.stable.xyz/en/reference/agentic-facilitators?utm_campaign=stable-build-04).

    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](https://docs.stable.xyz/en/how-to/integrate-gas-waiver?utm_campaign=stable-build-04) 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](https://docs.stable.xyz/en/reference/network-upgrades?utm_campaign=stable-build-04).

A facilitator with access to the gated [Enterprise RPC](https://docs.stable.xyz/en/reference/enterprise-sdk?utm_campaign=stable-build-04) 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**
-------------------

*   **StablePay:** [**https://www.stablepay.to/**](https://www.stablepay.to/)
    
*   **Documentation:** [](https://docs.stable.xyz/)[**https://docs.stable.xyz**](https://docs.stable.xyz)
    
*   **Mainnet Hub:** [](https://hub.stable.xyz/)[**https://hub.stable.xyz**](https://hub.stable.xyz)
    
*   **Blog:** [](https://blog.stable.xyz/)[**https://blog.stable.xyz**](https://blog.stable.xyz)
    
*   **X:** [**@Stable**](https://x.com/Stable)
    
*   **Community:** [](https://discord.gg/stablexyz)[**https://discord.gg/stablexyz**](https://discord.gg/stablexyz)

---

*Originally published on [Stable](https://blog.stable.xyz/stable-build-4-build-an-x402-api-for-ai-agents)*
