x402 Agent Spending Limits: The SDK Cap and the Rest

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
  1. Does x402 set an agent spending limit for you?
  2. Setting the per-payment cap with spendControls
  3. Where a per-task budget can hook in
  4. What my POC guarded, and what it didn’t
  5. How the x402 payment flow works
  6. What a second 402 after payment can mean
  7. Can you pay again when a payment response drops?
  8. Is an HTTP 402 safe to retry like a 429?
  9. Checklist before an agent can pay through x402
  10. 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: false turns 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 allowedAssets is 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 policies for USD caps or asset allowlists. Policies are for exclusion rules such as a trusted payTo; caps belong in spendControls.

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.

Example x402 client payment flow: request, 402 response, check payment requirements, then pay and resend once if within the limit, and stop if over the limit or if it still fails
Example x402 client flow with a per-request limit check before paying.
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:

  1. The client sends a request.
  2. With no payment attached, the server returns 402 and the payment terms in a PAYMENT-REQUIRED header.
  3. The client checks the terms and sends the request again with payment data in a PAYMENT-SIGNATURE header.
  4. The server verifies and settles the payment.
  5. On success, it returns 200 with a PAYMENT-RESPONSE header.

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:

  1. The SDK is @x402/core 2.23.0 or later (Python 2.20.0 or later), so spendControls exists.
  2. maxAmountPerPayment is set on purpose, not left at the $1 default by accident, and spendControls: false appears nowhere in agent code.
  3. Only the networks you intend to pay on have a registered scheme, and allowedAssets lists nothing you didn’t choose.
  4. A policies filter limits payTo to merchants you trust.
  5. A per-task budget and a daily limit sit in onBeforePaymentCreation or your own wrapper.
  6. 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.
  7. One resend after paying; a second 402 stops the flow.
  8. 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/core README: spendControls ($1 default on recognized pegged assets, allowedAssets, false to disable), policies, selector default, client hooks (checked November 2, 2026)
  • @x402/core changelog: spendControls added in 2.23.0; latest 2.29.0 (npm release dates August 18 and October 28, 2026)
  • x402 Python changelog: spend_controls added in 2.20.0 (August 18, 2026)
  • x402 SDK feature table: spendControls in TypeScript, Go and Python
Show 6 more sources

Related posts


I first wrote this in Korean on my blog: https://jjeongil.tistory.com/3262.