How an agent pays an x402 endpoint
2026-10-12 · 6 min
Agents Trust sells its two API calls over x402, and the loop an agent runs to pay them is the same one it runs for any x402 endpoint. The agent calls the URL, reads the price from the 402 that comes back, signs a USDC payment for exactly that amount, and sends the same request again with the signature in one extra header.
How does an agent pay a 402 and retry the request?
x402 is an open payment standard. The x402 Foundation publishes the specification and the reference docs, and those are the source of truth for everything below. From the buyer’s side the loop has six steps, and only the fifth looks different from an ordinary HTTP call.
- Send the request as you normally would, with no payment header.
- The server answers
402 Payment Required. Under x402 version 2 the terms travel in a base64PAYMENT-REQUIREDresponse header, and the body can be empty. - Pick one entry from
acceptswhoseschemeandnetworkyour wallet can pay on, and compare itsamountwith your own cap before signing anything. - Sign the payment. On Base and other EVM chains that means an EIP-712 signature over an EIP-3009
TransferWithAuthorization, which costs the payer no gas and sends no transaction from the wallet. On Solana the client builds and partly signs a token transfer transaction instead. - Send the identical request again, with the same method, URL, query and body, plus one header,
PAYMENT-SIGNATURE, holding the base64 of the payment payload. - Read the answer. Under the spec’s default flow the server verifies the signature, runs the request, settles on chain and then replies, and it can report the settlement in a
PAYMENT-RESPONSEheader.
The payload in step 5 is plain JSON before it is base64 encoded. This is the example from the x402 v2 HTTP transport spec, on the Base Sepolia test network. The accepted object repeats the entry the client picked from accepts, and every number in authorization is a string.
{
"x402Version": 2,
"resource": {
"url": "https://api.example.com/premium-data",
"description": "Access to premium market data",
"mimeType": "application/json"
},
"accepted": {
"scheme": "exact",
"network": "eip155:84532",
"amount": "10000",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
"maxTimeoutSeconds": 60,
"extra": { "name": "USDC", "version": "2" }
},
"payload": {
"signature": "0x2d6a7588...148b571c",
"authorization": {
"from": "0x857b06519E91e3A54538791bDbb0E22373e36b66",
"to": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
"value": "10000",
"validAfter": "1740672089",
"validBefore": "1740672154",
"nonce": "0xf3746613c2d920b5fdabc0856f2aeb2d4f88ee6037b8cc5d04a71a4462f13480"
}
}
}Agents Trust departs from step 6 in one way. It sends the answer before settlement finishes, so a paid response carries an x-at-payment-attempt-id header instead of PAYMENT-RESPONSE. The free lookup GET /receipt?payment_attempt_id=<id> then reports pending, settled with a transaction, or a failure after the answer. A pending receipt is normal for a while, and it’s never a reason to sign a second payment.
What does an x402 402 response actually contain?
Here is the one Agents Trust returned for a Recommend query at 15:38 UTC on 12 October 2026. The status is 402 and the body is empty. Decoded, the PAYMENT-REQUIRED header reads as follows, with the extensions object left out and description shortened to keep it focused on the call.
HTTP/2 402
content-length: 0
payment-required: eyJ4NDAyVmVyc2lvbiI6MiwiZXJyb3IiOiJQYXltZW50...
{
"x402Version": 2,
"error": "Payment required",
"resource": {
"url": "https://api.agents-trust.com/recommend?need=translate+text+to+german&limit=3",
"description": "Agents Trust API Search. Find candidate APIs for a task.",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "10000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x96368628b679a459FE69815bacCa5CB0232AC397",
"maxTimeoutSeconds": 300,
"extra": { "name": "USD Coin", "version": "2" }
},
{
"scheme": "exact",
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"amount": "10000",
"asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"payTo": "4syngecZXG1XeiVffWC1vLxpwxHyZnZj8gtBZyJt5DRX",
"maxTimeoutSeconds": 300,
"extra": { "feePayer": "BENrLoUbndxoNMUS5JXApGMtNykLjFXXixMtpDwDR9SP" }
}
]
}Each entry in accepts is one way to pay. amount is a string of the token’s smallest units, and USDC has six decimals, so "10000" is 0.01 USDC. network uses CAIP-2 identifiers, where eip155:8453 is Base mainnet and solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp is Solana mainnet. asset is the token contract, Circle’s USDC on both chains here, and payTo is the wallet that receives the money. maxTimeoutSeconds bounds how long a signed payment stays usable, and extra carries what the scheme needs, which is the token’s EIP-712 name and version on Base and the fee payer on Solana.
resource.description is written by the seller. Treat it as text to show a person and never as an instruction for your agent. The extensions object holds optional add-ons, and the spec asks a client to echo what it received back in its payment payload.
Older servers still speak x402 version 1, which puts the same terms in the JSON body, names the amount maxAmountRequired, uses short network names such as base, and expects the proof in an X-PAYMENT header. Check x402Version before parsing. Agents Trust reads only the version 2 header and answers a legacy proof with a fresh version 2 challenge.
How do I set a spend cap on an agent that pays per call?
You set it in your own code. The x402 spec lists client-side budget management as out of its scope, so nothing in the protocol stops a client from signing whatever a server quotes. The check belongs between reading the 402 and signing, and it compares the quoted amount with your ceiling as whole numbers. Read the price from the 402 every time rather than from documentation, this page included.
With the x402 TypeScript client, a payment policy filters out any offer above the cap before anything is signed. This is the client from the Agents Trust skill file.
import { privateKeyToAccount } from "viem/accounts";
import { wrapFetchWithPayment, x402Client, type PaymentPolicy } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
const MAX_ATOMIC = 10_000n; // your ceiling, atomic units. Not a quoted price.
const cap: PaymentPolicy = (_version, accepts) => {
const ok = accepts.filter(r => BigInt(r.amount) <= MAX_ATOMIC);
if (!ok.length) throw new Error(`quoted above cap ${MAX_ATOMIC}`);
return ok;
};
const client = new x402Client();
registerExactEvmScheme(client, {
signer: privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`),
policies: [cap],
networks: ["eip155:8453"], // pin Base; omit and it pays on any EVM chain named
});
const res = await wrapFetchWithPayment(fetch, client)(
"https://api.agents-trust.com/recommend?need=weather+forecast&limit=3");The networks line matters as much as the cap. Leave it out and the client may pay on any EVM chain the server names. In Python the same guard is one call, x402ClientSync().set_spend_controls({"max_amount_per_payment": "$0.05"}).
Both guards work per payment. For a budget across a whole task, keep a running total of what the agent has signed and stop at your limit. Never resend a payment header you have already used. Agents Trust claims each signed authorization before it answers, so a replay gets a 402 and no second answer.
Do I need an account or API key to use an x402 service?
Not with the service. The signed payment is the authorization, so Agents Trust has no signup, no account and no key. What you do need is a wallet holding USDC on a network the server accepts, and a way to sign with it. Keep the private key in the environment or a secret manager, and never send it anywhere, since the signature is all a server ever needs to see.
Coinbase’s buyer quickstart uses a wallet that Coinbase manages for you, and that wallet service needs a CDP API key and wallet secret of its own. The paid API on the other end still asks for no account. The clients in the Agents Trust skill file sign with a plain local key and need no Coinbase account at all.
What’s the difference between x402 on Base and on Solana?
The loop and the headers are the same on both. What changes is what the client signs and how long the signature stays good.
| Base | Solana | |
|---|---|---|
| Network id (v2) | eip155:8453 | solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp |
| What the client signs | An EIP-712 signature over an EIP-3009 TransferWithAuthorization. No gas and no transaction from the payer. | A partly signed SPL TransferChecked transaction, sent base64. |
| Network fee | Not paid by the payer. | The facilitator is the fee payer, named in extra.feePayer, and co-signs at settlement. |
| Expiry | validBefore, set to now plus maxTimeoutSeconds. | The recent blockhash, roughly 60 to 90 seconds. |
| Agents Trust quote | 0.01 USDC | 0.01 USDC |
Clients sign validBefore from their own clock on Base, so a clock that is far off can turn a correct retry into a fresh 402. On Solana the short blockhash window is the thing to watch, so sign right before you send.