{
  "question": "How do I set a spending cap for an AI agent that pays with x402?",
  "slug": "how-to-set-a-spending-cap-for-an-ai-agent-paying-with-x402",
  "url": "https://www.rightshold.com/answers/how-to-set-a-spending-cap-for-an-ai-agent-paying-with-x402",
  "short_answer": "Use three layers. First, the x402 client's spendControls: by default the client pays only recognized USD-pegged assets such as USDC and caps each payment at $1, and you can change that with maxAmountPerPayment and allowedAssets, but don't turn it off (spendControls: false removes all caps). Second, a total over time: the base client caps each payment, not the day, so use the CDP SDK's maxCumulativeSpend with a window such as \"24h\", or your own policy or hook that keeps a running total in storage that survives restarts. Third, a hard ceiling: give the agent a wallet that holds only its budget, because a bug or a missing check can get past code but can't spend money the wallet doesn't have.",
  "checklist": [
    "Keep the x402 client's spend controls on. Don't pass spendControls: false (Go: DisableSpendControls(), Python: spend_controls=False). That turns off every cap and allows any asset.",
    "Set the per-payment cap you mean: maxAmountPerPayment (\"$1\" by default) for USD-pegged assets, and an allowedAssets list with an atomic-unit cap for any other token (for 6-decimal USDC, \"2000000\" is 2 USDC).",
    "Add a cap over time. The base client checks each payment on its own. Use the CDP SDK's maxCumulativeSpend and maxCumulativeSpendWindow, or a registered policy or onBeforePaymentCreation hook that keeps a running total.",
    "Keep that running total in persistent storage. The CDP SDK's default spend store is in memory and resets when the process restarts.",
    "Limit where the agent can pay: allowed networks (for example only eip155:8453 for Base mainnet), allowed assets and, in your own policy, allowed payTo addresses or domains.",
    "Treat upto prices as the most you'll pay: the buyer signs for the maximum and the seller settles for the actual amount, so cap the maximum, not the expected charge.",
    "Fund a separate agent wallet with only the budget and top it up on a schedule. If you use CDP Server Wallets, add a Policy Engine reject rule at the signing layer as well.",
    "Log every payment and every refusal with amount, asset, network and payTo, and alert before the limit is reached (the CDP SDK has an onApproachingLimit callback)."
  ],
  "sections": [
    {
      "heading": "What the x402 client caps by default",
      "points": [
        "The x402 buyer quickstart says that by default the client only pays recognized USD-pegged assets (e.g. USDC) and caps each payment at $1. maxAmountPerPayment changes the USD cap; allowedAssets opts into other tokens with an optional per-payment cap in atomic units; spendControls: false disables all of it.",
        "Spend controls run before any custom policies and before the payment is signed. If every payment option the server offers is over the cap or in an asset you didn't allow, the client refuses to pay and throws an error instead of picking one anyway.",
        "In the x402 TypeScript source, spendControls arrived in @x402/core 2.25.0. If your agent pins an older version, there is no default cap. Check the installed version.",
        "For the upto scheme the amount in the 402 response is the maximum. The buyer signs once for it and the seller may settle for less, so a per-payment cap applies to that maximum."
      ]
    },
    {
      "heading": "Caps over a day or a session",
      "points": [
        "A per-payment cap doesn't stop 1,000 payments of $0.99. You need a running total.",
        "The CDP SDK's x402 client (CdpX402Client) takes spendControls with maxAmountPerPayment, maxCumulativeSpend, maxCumulativeSpendWindow (for example \"24h\"), allowedNetworks and an onApproachingLimit callback, and raises a SpendControlError when a payment would break a limit. applySpendControls(client, controls) adds the same checks to any @x402/core client.",
        "Its docs say the default SpendStore is in memory and process-local. Give it a persistent store if the total must survive restarts, or a crash-and-restart loop resets the budget.",
        "Without the CDP SDK, register a policy on the x402 client or an onBeforePaymentCreation hook. The hook can return { abort: true, reason } to stop a payment, and the x402 lifecycle-hooks docs show a spending-limit example. The x402 MCP client takes the same policies and an onPaymentRequested hook for paid MCP tools."
      ]
    },
    {
      "heading": "Why the wallet is the real ceiling",
      "points": [
        "Every cap above is code in the agent's own process. A bug, an old version, or a different client using the same key can skip it. The amount a key can spend is what its wallet holds, so keep the agent's wallet small and refill it on a schedule rather than pointing it at a treasury.",
        "Approvals are not budgets. When a payment uses Permit2, the x402 SDK's helper approves the maximum uint256 to the Permit2 contract (its source says it always approves MaxUint256 regardless of the payment amount), so that token allowance doesn't limit anything; each payment is still signed separately.",
        "With CDP Server Wallets, the Policy Engine evaluates accept and reject rules before signing (reject wins) and can be scoped to the project or one account. Rules can cover operations such as signEvmTransaction and signEvmTypedData, so a limit there applies even if the agent's own code is wrong."
      ]
    },
    {
      "heading": "What can go wrong",
      "points": [
        "Someone sets spendControls: false while debugging and ships it.",
        "The cap is in dollars, but the seller asks for a token you allowed through allowedAssets with a cap in the wrong atomic units (USDC has 6 decimals, so \"2000000\" is 2 USDC, not 2,000,000).",
        "The running total lives in memory and resets on every restart or across several worker processes.",
        "The agent's key is also used by another script that has no caps at all."
      ]
    }
  ],
  "checks": [],
  "ask_rightshold": {
    "start": "https://www.rightshold.com/start?ref=seo",
    "mcp_endpoint": "https://www.rightshold.com/mcp",
    "ask": "Set a spending policy on my agent credential: at most 2.00 USD per purchase and 10.00 USD per day, only at api.example.com.",
    "mcp_call": {
      "tool": "set_spend_policy",
      "arguments": {
        "credential_id": "YOUR_AGENT_CREDENTIAL_ID",
        "currency": "USD",
        "per_tx_max_minor": 200,
        "daily_max_minor": 1000,
        "allowed_domains": [
          "api.example.com"
        ]
      }
    },
    "also": [
      "Needs your owner account key. The policy replaces the previous one, and every version stays on the ledger.",
      "authorize_spend checks one proposed purchase against the policy and the running daily total and returns a signed allow or deny with reasons. Call it from your x402 policy or onBeforePaymentCreation hook and abort on deny.",
      "verify_spend_authorization (free) lets a seller check an authorization receipt.",
      "REST, free: POST /v1/x402/price-tag decodes an x402 402 response so you can see the real amount, token, network and payTo before you pay."
    ],
    "scope": "Rightshold records spending policies and signed allow or deny decisions. It doesn't hold funds, sign x402 payments or stop a client that never asks. Enforce the cap in your x402 client and wallet too."
  },
  "sources": [
    {
      "title": "x402 docs: Quickstart for Buyers (spend controls, $1 default cap)",
      "url": "https://docs.x402.org/getting-started/quickstart-for-buyers"
    },
    {
      "title": "x402 docs: Lifecycle hooks (onBeforePaymentCreation, spending-limit example)",
      "url": "https://docs.x402.org/advanced-concepts/lifecycle-hooks"
    },
    {
      "title": "x402 docs: Upto scheme (buyer authorizes a maximum)",
      "url": "https://docs.x402.org/schemes/upto"
    },
    {
      "title": "x402 source: x402Client (spendControls and policies)",
      "url": "https://github.com/x402-foundation/x402/blob/main/typescript/packages/core/src/client/x402Client.ts"
    },
    {
      "title": "x402 source: ERC-20 approval helper (approves MaxUint256 to Permit2)",
      "url": "https://github.com/x402-foundation/x402/blob/main/typescript/packages/mechanisms/evm/src/exact/client/erc20approval.ts"
    },
    {
      "title": "Coinbase CDP SDK: x402 (maxCumulativeSpend, SpendStore, applySpendControls)",
      "url": "https://docs.cdp.coinbase.com/sdks/cdp-sdks-v2/typescript/x402"
    },
    {
      "title": "Coinbase CDP: Policy Engine overview",
      "url": "https://docs.cdp.coinbase.com/server-wallets/v2/using-the-wallet-api/policies/overview"
    }
  ],
  "related": [
    {
      "title": "How to read an x402 402 Payment Required response as a price",
      "url": "https://www.rightshold.com/answers/how-to-read-an-x402-payment-required-response-as-a-price"
    },
    {
      "title": "How to require human approval before an AI agent spends money",
      "url": "https://www.rightshold.com/answers/how-to-require-human-approval-before-an-ai-agent-spends-money"
    },
    {
      "title": "How to stop an AI agent from paying twice when it retries",
      "url": "https://www.rightshold.com/answers/how-to-stop-an-ai-agent-from-paying-twice-when-it-retries"
    }
  ],
  "computed_from": "Curated guidance; license verdicts computed from Rightshold's license profiles at render time. Not legal advice.",
  "updated": "2026-10-30"
}