The x402 protocol itself doesn’t set a spending limit for your agent: the v2 spec puts client-side budget management out of scope. The official SDK does ship one guard. Since @x402/core 2.23.0 (August 18, 2026), the client’s spendControls caps each payment at $1 in recognized USD stablecoins by default. A per-task budget, a daily limit and human approval are still yours to build.
x402 is an open payment protocol that uses the HTTP 402 Payment Required status to ask a client for payment before serving a request. The x402 Foundation, under the Linux Foundation, has maintained it since April 2, 2026, when it launched with 40 organizations.
Public facts here were checked on November 2, 2026, against SDK 2.29.0 (released October 28, 2026). My own experience is a personal POC from early 2026: a payment flow checked on a testnet with small amounts, not a commercial payment service.
On this page
- Does x402 set an agent spending limit for you?
- Setting the per-payment cap with spendControls
- Where a per-task budget can hook in
- What my POC guarded, and what it didn’t
- How the x402 payment flow works
- What a second 402 after payment can mean
- Can you pay again when a payment response drops?
- Is an HTTP 402 safe to retry like a 429?
- Checklist before an agent can pay through x402
- References
Does x402 set an agent spending limit for you?
The protocol leaves it to you, and the SDK covers only part of it. The v2 spec lists client-side budget management as out of scope and calls it implementation-specific. The @x402/core changelog records that 2.23.0 added client spendControls, and npm dates that release to August 18, 2026.
Before that version, the client had no built-in cap.
Here is what each layer covers today:
| Limit | Spec | Official SDK (2.23.0+) | You |
|---|---|---|---|
| Per-payment cap | Out of scope | maxAmountPerPayment, default $1 on recognized USD stablecoins |
Raise, lower or override per asset |
| Which assets | Out of scope | Only default assets allowed unless you opt in with allowedAssets |
Opt in other tokens deliberately |
| Which networks | Out of scope | Only the networks you register a scheme for | Register only what you intend to pay on |
Which merchants (payTo) |
Out of scope | No default; a policies filter can do it |
Write the filter |
| Per-task or daily budget | Out of scope | Not in the README as of 2.29.0 | Build it |
| Human approval | Out of scope | onBeforePaymentCreation hook can abort |
Build the approval step |
That last column is where the work is. Adding x402 itself wasn’t hard in my POC. The harder part came after an AI agent gets a 402. When does it pay, how much can it spend, and may it pay again for a failed request? The moment you give an agent a way to pay, you need a spending policy before a payment protocol.
Setting the per-payment cap with spendControls
The @x402/core README describes accept selection in three stages: spendControls enforce the built-in caps, policies filter what’s left, and paymentRequirementsSelector picks one option. If you don’t pass a selector, it takes the first remaining option.
A client that pays only on Base, caps each payment at $0.25 and refuses unknown recipients looks like this. The values are examples, not my POC’s settings:
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
const TRUSTED_PAY_TO = "0xYourMerchantAddress".toLowerCase();
const client = x402Client.fromConfig({
// network scoping: unregistered networks are never selected
schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(signer) }],
spendControls: {
maxAmountPerPayment: "$0.25", // default is "$1"
},
policies: [
(version, reqs) => reqs.filter(r => r.payTo.toLowerCase() === TRUSTED_PAY_TO),
],
});
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
Three details matter in practice:
spendControls: falseturns off every cap and the asset allowlist. The README describes it as useful for UI-confirmed flows like a paywall and for tests. If an agent signs payments on its own, search your code for it.- A per-asset cap inside
allowedAssetsis an integer in the token’s smallest unit, such as"2000000", not a dollar string like"$1". The SDK treats a non-integer per-asset cap as a config error. - The README says not to use
policiesfor USD caps or asset allowlists. Policies are for exclusion rules such as a trustedpayTo; caps belong inspendControls.
The same controls exist outside TypeScript. The SDK feature table marks spendControls as supported in TypeScript, Go and Python. In Python the changelog dates it to 2.20.0 (August 18, 2026), with the names spend_controls, max_amount_per_payment and register_policy.
The Go changelog adds the same default-asset allowlist and $1 cap.
Where a per-task budget can hook in
The SDK caps one payment at a time. It doesn’t track how much an agent has spent across a task or a day. That’s where the risk is: repetition. An agent calls a search API, then a paid data API. It decides the results aren’t enough, searches again, and moves on to another paid endpoint. Every single payment can sit under $1 while the task keeps spending.
The README’s client hooks give you a place to stop that. onBeforePaymentCreation runs after a payment option is selected and before anything is signed, and it can return { abort: true, reason }. Here is a minimal sketch of a per-task budget built on it. I didn’t build this in my POC; it shows where the layer I judged necessary would go:
// Sketch: per-task budget in the token's smallest unit (USDC uses 6 decimals)
let spent = 0n;
const TASK_BUDGET = 2_000_000n; // 2 USDC, example value
client.onBeforePaymentCreation(async ({ selectedRequirements }) => {
const amount = BigInt(selectedRequirements.amount);
if (spent + amount > TASK_BUDGET) {
return { abort: true, reason: "task budget exceeded" };
}
spent += amount; // counted at signing time, so a failed settlement still counts
});
Counting at signing time is deliberately conservative: the budget can only run out early, never late. A daily limit is the same idea with a store that outlives one task, and human approval is the same hook waiting on a person instead of a counter.
What my POC guarded, and what it didn’t
I built the POC in early 2026, before the SDK had spendControls. The guards lived in my own fetch wrapper. I built a small paid API and implemented both the server-side payment middleware and the client payment flow myself, working from the docs and examples.
The first snag was on the client side. A typical API client treats a 4xx as a failure and stops. In x402, the 402 is part of the flow, so I split the wrapper into steps: 402 → parse the payment terms → check that the payment is allowed → pay → resend once → stop if it still fails. I’m not quoting amounts or the testnet name here, because the POC was about the flow, not a price.
What I took away then: an HTTP request and a payment authorization become one automated flow.

| Guard | In my POC | What it did | SDK today |
|---|---|---|---|
| Per-request amount cap | Built | Paid only at or under my test limit | maxAmountPerPayment |
| Expected network and asset | Built | Refused unexpected networks or assets | Registered networks + allowedAssets |
| Repeated payments in one request | Built | One resend after paying; a second failure stopped the flow | No cap setting in the README; your wrapper |
| Merchant allowlist | Judged necessary, not built | policies filter on payTo |
|
| Per-task cumulative budget | Judged necessary, not built | onBeforePaymentCreation |
|
| Daily limit | Judged necessary, not built | Your own store | |
| Human approval for large payments | Judged necessary, not built | onBeforePaymentCreation |
|
| Recovery from uncertain settlement | Judged necessary, not built | Your reconciliation step |
The most important guard was the first one: not paying just because a 402 came back. The wrapper parsed the 402 terms and refused any network or asset the client didn’t expect, instead of trusting the server’s request. Only the top three rows were code. The other five are judgments I made after the POC, so don’t read this table as a finished payment platform.
The case for a daily limit came from the Gemini API: I had set a spending cap of around $10 there before starting this POC.
How the x402 payment flow works
x402.org (last updated October 27, 2026) puts it simply: if a request arrives without payment, the server answers with 402, and the client pays and retries. The v2 HTTP transport spec spells out five steps:
- The client sends a request.
- With no payment attached, the server returns 402 and the payment terms in a
PAYMENT-REQUIREDheader. - The client checks the terms and sends the request again with payment data in a
PAYMENT-SIGNATUREheader. - The server verifies and settles the payment.
- On success, it returns 200 with a
PAYMENT-RESPONSEheader.
Tutorials online don’t all use these names. The v1 HTTP transport put the terms in the 402 response body and used X-PAYMENT and X-PAYMENT-RESPONSE headers; the spec’s version history dates v2.0 to December 9, 2025.
Before you parse a 402, check which version your SDK or server speaks.
What a second 402 after payment can mean
The v2 HTTP transport uses 402 for two situations: payment is required, or payment verification or settlement failed. A second 402 after you’ve paid can therefore be about the payment you just made. It isn’t a fresh invoice to pay on sight, which is why my POC resent once and then stopped.
Can you pay again when a payment response drops?
Only after you’ve checked whether the earlier payment settled. My POC went only as far as the normal pay-and-resend flow, and I didn’t build recovery for a response that never came back. Later, connecting it to agent retries, one judgment stayed with me: for work with side effects, like a payment, you need to check the earlier payment’s status before paying again. A failed response doesn’t mean the side effect didn’t happen. I ran into the same pattern with workflow retries in my n8n Retry On Fail post, where the cost was duplicate runs instead of money. My Claude outage fallback post covers the other half: changes left behind after a failed response.
The v2 spec has a status close to this case. settlement_pending means the settlement transaction was broadcast, but its confirmation could not be established. The spec marks it as non-terminal, since the transaction may still confirm.
The response has to include the transaction hash and network, so the caller can reconcile on chain before deciding whether to retry. That doesn’t mean the spec prevents double payments. Checking and deciding are up to the caller.
Is an HTTP 402 safe to retry like a 429?
Outside x402, don’t retry it automatically by default. RFC 9110 reserves 402 for future use, and MDN (last modified July 15, 2026) calls it nonstandard with no standard use convention.
I’ve seen 402-type responses tied to quota or billing from payment and API services, and I used to read them all as “out of money.” The status code alone doesn’t tell you whether it’s a failed card, an expired subscription, a usage limit or a required extra payment.
Now I check the provider error code in the body together with the account’s billing status.
| Case | Meaning | Auto-retry | Check first |
|---|---|---|---|
| 429 | Too many requests | Consider after backoff | Retry interval |
| 402 | Varies by service | Not by default | Body error code, billing |
| 402 in x402 | First step of payment | Resend after paying, capped | Payment terms, allowed or not |
Checklist before an agent can pay through x402
How much to pay, to whom and how many times are policy questions outside the protocol. Before an agent gets a way to pay, I write down two conditions: when it may pay after a 402, and when it may pay again after a dropped payment. These checks cover both:
- The SDK is
@x402/core2.23.0 or later (Python 2.20.0 or later), sospendControlsexists. maxAmountPerPaymentis set on purpose, not left at the $1 default by accident, andspendControls: falseappears nowhere in agent code.- Only the networks you intend to pay on have a registered scheme, and
allowedAssetslists nothing you didn’t choose. - A
policiesfilter limitspayToto merchants you trust. - A per-task budget and a daily limit sit in
onBeforePaymentCreationor your own wrapper. - Payments above a threshold wait for a person. Where to put that approval is something I covered in a post where I ran auto-approval myself.
- One resend after paying; a second 402 stops the flow.
- A dropped payment response triggers a status check or on-chain reconciliation, never an immediate second payment.
References
- x402 v2 spec: client-side budget management out of scope and implementation-specific;
settlement_pending; v2.0 dated December 9, 2025 @x402/coreREADME:spendControls($1 default on recognized pegged assets,allowedAssets,falseto disable),policies, selector default, client hooks (checked November 2, 2026)@x402/corechangelog:spendControlsadded in 2.23.0; latest 2.29.0 (npm release dates August 18 and October 28, 2026)- x402 Python changelog:
spend_controlsadded in 2.20.0 (August 18, 2026) - x402 SDK feature table:
spendControlsin TypeScript, Go and Python
Show 6 more sources
- x402 HTTP transport spec (v2):
PAYMENT-REQUIRED,PAYMENT-SIGNATURE, andPAYMENT-RESPONSEheaders; 402 also for failed verification or settlement - x402 HTTP transport spec (v1): terms in the 402 body,
X-PAYMENTandX-PAYMENT-RESPONSEheaders - x402.org: the basic flow of answering an unpaid request with 402 and retrying after payment (last updated October 27, 2026)
- x402 Foundation (Coinbase docs): foundation launch under the Linux Foundation on April 2, 2026
- RFC 9110 §15.5.3 402 Payment Required: defines 402 as a status code reserved for future use
- MDN: 402 Payment Required: 402’s nonstandard, reserved status and generic 4xx handling in browsers (last modified July 15, 2026)
Related posts
- AI Agent Calling the Wrong Tool? I Cut It from 6/20 to 1/20
- RAG Retrieval Failure vs Generation Failure: Top-k First
- Claude Outage Fallback: Request Failed, Files Still Changed
- Claude Code Custom Commands to Skills: Why /project: Fails
I first wrote this in Korean on my blog: https://jjeongil.tistory.com/3262.