Skip to Content
ConceptsPayments (x402)

Payments (x402)

x402  lets an agent pay for an HTTP request on the spot:

  1. The server answers 402 with a price and the payee.
  2. The agent’s wallet signs a payment, and the request is sent again with it.
  3. The server settles the payment and sends the paid response.

The x402 spec leaves budgets on the paying side to you. The x402 guard covers them. It stops a poisoned page that sends the agent to an attacker’s endpoint, a server that asks too much, a retry loop that pays the same endpoint hundreds of times, and a new payee that many runs suddenly pay.

Set it up

The x402 guard wraps an x402 client, the way guard() wraps a tool. quard.x402Fetch() goes under the client and records every payment:

import { x402Client } from "@x402/core/client"; import { wrapFetchWithPayment } from "@x402/fetch"; import { quard } from "quard"; const client = quard.x402(new x402Client() /* your schemes and signer */, { type: "x402", maxPerRun: 2, maxPerDay: 20, }); const paidFetch = wrapFetchWithPayment(quard.x402Fetch(fetch), client);

Before the payment is signed

The guard runs in the client’s onBeforePaymentCreation hook, after the server names its price and before the wallet signs. A refused payment is never signed, so nothing can be sent or settled.

A person is asked only above approveAbove. The approval waits before signing, because a signed payment expires within the server’s maxTimeoutSeconds.

Options

OptionDefaultWhat it does
maxPerPayment$1, observedCaps one payment, in USD. An amount that can’t be read counts as over
maxPerRun$5, observedCaps what one run spends, in USD
maxPerDay$50, observedCaps what the project spends in a UTC day, in USD, across every process
maxPaymentsPerRunOffCaps how many payments one run makes, whatever the token. It catches loops
allowHostsOffOnly these hosts may be paid. *.acme.com also matches subdomains
blockHostsOffThese hosts are never paid
untrustedBlock, observedRefuses a host or payee first seen in untrusted content. "allow" turns the check off
assetCapsOffCaps tokens with no known USD value, in atomic units: { "<asset address>": "1000000" }
fleetCheckOn, observedQuarantines a payee that a 5th separate run pays within 24 hours of first seeing it. false turns it off
approveAboveOffAsks a person for payments above this many USD
namex402The name decisions, records and the policy file use
mode, onBlockBlock, returnWork as on every guard

Options you set follow the guard’s mode, which blocks by default. Options you leave out keep Quard’s defaults, and those start in observe mode, like the other product defaults: they record “would block” and let the payment through. They never ask a person. Set an option, even to its default value, to make it block.

The policy file can set the options too, under guards and the guard’s name. A guard listed there ignores its code options. The file is read again before each payment:

{ "version": "2026-10-04.1", "guards": { "x402": [{ "type": "x402", "maxPerRun": 1, "untrusted": "block" }] } }

When a payment is refused

The refusal says what was stopped and not to retry:

Blocked by the x402 guard: the payment is over the run limit. The x402 payment did NOT happen. Do not retry it.

How the model gets it depends on where the paid call runs:

  • Inside a guarded tool. The tool returns the refusal, as for any other guard, and the model reads it. With onBlock: "throw", the tool throws GuardBlockedError instead.
  • Outside one. The x402 client throws an error, and its message holds the refusal. Catch it and pass the message to the model as the tool’s result:
try { output = await (await paidFetch(url)).text(); } catch (error) { output = (error as Error).message; }
RuleThe refusal says
max-per-paymentthe payment is over the limit for one payment
max-per-runthe payment is over the run limit
max-per-daythe payment is over the daily limit
max-payments-per-runthis run made too many payments
allow-hosts, block-hoststhis host may not be paid
untrustedthe host or payee first appeared in untrusted content
asset-capsthe amount is over the cap for this token
fleet-checkthe payee is new and many runs paid it at once, so it is blocked everywhere

What Quard records

quard.x402Fetch(fetch) sits under the x402 client and records each step of a payment:

StepWhen
challengedThe server named a price, including prices the guard went on to refuse
refusedThe guard refused the payment before it was signed
signedThe wallet signed the payment and it was sent
settledThe server settled it, with the transaction hash
failedSettling failed, or the server turned the payment down

Each step carries the run, step, agent, host, paid URL with secrets removed, network, asset, amount, USD value when known, payee, scheme and transaction hash. The run view shows the payment steps, the runs list and the overview show spend next to cost, and the Summary page shows spend by day, agent, host and payee.

Quard reads both x402 versions: v2 headers, and v1’s X-PAYMENT headers with the price in the body.

A payment can settle while the paid response is an error. Quard then marks it paid, not delivered, and the run view shows it as a warning.

Unguarded payments are not sent

A signed payment that no x402 guard checked never leaves your app. The request gets a 403 with this text, which the model can read, and Quard warns once, as it does for an unguarded tool:

Blocked by Quard: no x402 guard checked this payment. The x402 payment did NOT happen. Do not retry it.

The same goes for a payment sent to another host than the one the guard checked it for. The guard decides on the host your app asked, not the one the paid server names.

Amounts the guard can’t read

An amount that isn’t whole atomic units, written as a number or in hex, is always refused before signing, even in observe mode. A cap can’t hold if the amount can’t be read.

x402 over MCP

A paid MCP tool answers with its price as an error result, and the payment travels in _meta["x402/payment"]. Wrap the MCP client with quard.x402Mcp() and pay through a guarded x402 client:

import { x402MCPClient } from "@x402/mcp"; const tools = new x402MCPClient(quard.x402Mcp(mcpClient), quard.x402(new x402Client(), { type: "x402" }));

The same records, refusals and rules apply. The host is the MCP server’s name.

Wallets, chains and tokens

  • Wallet addresses stay in clear. They are public on chain, so Quard doesn’t hash them. They are still a value kind, so labels, search and the fleet check find them.
  • The chain doesn’t matter. The network is recorded, but no rule depends on it. Any scheme and network your x402 client supports works.
  • USD values come from stablecoins. Quard knows USDC on Base, Ethereum, Polygon, Avalanche and Solana, and on their test networks. Other tokens are recorded as “value unknown”. The USD caps skip them, while maxPaymentsPerRun, the host and payee checks and assetCaps still apply. No token is blocked for what it is.
Last updated on