Answers · agent payments
How do I set per-request and per-run spend caps for an AI agent that pays with x402?
Last updated
TL;DR: Treat them as two different limits. The per-request cap stops one expensive payment; the per-run cap stops many cheap ones in the same job. In the x402 client, keep spendControls on and set maxAmountPerPayment (default "$1" for recognized USD-pegged assets such as USDC) for the per-request ceiling. For the per-run total, either use the Coinbase CDP SDK's maxCumulativeSpend with a window that matches the run (or your own onBeforePaymentCreation hook that keeps a running total keyed by run id), or check every payment with a policy that lives outside the agent's process. Fund a wallet that holds only that run's budget so a missed check still can't spend more than the wallet has.
What should I check, step by step?
1. Keep x402 spendControls on. Don't ship spendControls: false (Go: DisableSpendControls(), Python: spend_controls=False); that removes every cap and allows any asset.
- Set the per-request cap with maxAmountPerPayment (default "$1"). For non-default tokens, list them in allowedAssets with an atomic-unit cap (for 6-decimal USDC, "2000000" is 2 USDC).
- Treat x402 upto prices as the most you'll pay: the buyer signs for the maximum and the seller may settle for less, so apply the per-request cap to that maximum.
- Add a per-run total the agent can't reset: CDP SDK maxCumulativeSpend plus maxCumulativeSpendWindow, or an onBeforePaymentCreation hook that aborts when spent_this_run + amount would exceed the run budget.
- Key the running total by a run or session id you create before the first payment, and store it in durable storage the agent process can't rewrite (shared Redis/Postgres, or a policy service). The CDP SDK's default SpendStore is in memory and process-local.
- Call the check from the payment path before signing: spendControls run before custom policies; use onBeforePaymentCreation for dynamic run totals and return { abort: true, reason } when over budget.
- Fund a separate wallet with only that run's budget and top it up when the run starts. Cap the wallet, not a shared treasury key.
- Log every allow and deny with run id, amount, asset, network and payTo, and alert before the run limit is reached (CDP has onApproachingLimit).
Per-request vs per-run
A per-request cap (x402 maxAmountPerPayment) blocks one payment that is too large. It does not stop 200 payments of $0.99 in the same research job. That needs a running total for the run.
- The x402 buyer quickstart says spend controls run before any custom policies and before the payment payload is signed. If every option the server offers is over the cap or in a disallowed asset, the client refuses to pay instead of picking one anyway.
- For static USD caps and asset allowlists, x402 docs prefer spendControls. Use onBeforePaymentCreation for dynamic checks such as a per-run budget, interactive approval, or a call out to a policy service.
- x402 MCP clients expose the same idea per tool: onPaymentRequired can abort a paid tool, and onBeforePayment / onAfterPayment are where you meter a run's spend across tool calls.
Where to keep the per-run total
Coinbase's CDP SDK SpendControls type takes maxAmountPerPayment, maxCumulativeSpend (asset required), maxCumulativeSpendWindow (for example "24h"), allowedNetworks, allowedAssets, allowedPayees, onApproachingLimit and an optional store. applySpendControls attaches that once to an x402Client; a second call throws already_applied.
- Its SpendStore docs say the default implementation is in-memory and process-local. For a cap that must survive restarts or several replicas, pass a store backed by Redis, Postgres or similar. Without that, a crash-and-restart loop resets the run budget.
- Without the CDP SDK, register an onBeforePaymentCreation hook that loads spent_so_far for this run_id, compares it to the run budget, and returns { abort: true, reason } when the next payment would break it. Persist the allow before you sign, or two concurrent workers can both pass the check.
- A policy service outside the agent (per_tx_max for the request, a daily or run-scoped total checked with a reference = run_id) keeps the numbers somewhere the model can't edit from chat history or a writable session file.
What can go wrong?
Only a per-request cap is set, and the agent burns the whole wallet on many small payments in one run.
- The run total lives in the agent's context window or a file it can write, so a prompt or tool call resets it.
- The cumulative store is in memory, and several worker processes each think they still have budget.
- spendControls: false was left on from debugging, or an older @x402/core without spendControls is pinned.
How do I ask Rightshold about this (free, no key)?
Add the MCP server https://www.rightshold.com/mcp to Claude, Cursor, ChatGPT, Grok or VS Code, then ask: “Set a spending policy on my agent credential: at most 0.50 USD per x402 payment and 5.00 USD per day, only at api.example.com, so each request and the whole run stay under budget.”
{
"tool": "set_spend_policy",
"arguments": {
"credential_id": "YOUR_AGENT_CREDENTIAL_ID",
"currency": "USD",
"per_tx_max_minor": 50,
"daily_max_minor": 500,
"allowed_domains": [
"api.example.com"
]
}
}- Needs your owner account key (MCP toolset=trust). The policy replaces the previous one; every version stays on the ledger. per_tx_max_minor is the per-request cap; daily_max_minor is a running total that resets at 00:00 UTC (use it as the run ceiling for short jobs, or set a lower wallet balance for a stricter per-run limit).
- From your x402 onBeforePaymentCreation hook, call authorize_spend with amount_minor, merchant_domain and reference set to your run id; abort on a signed deny (over_per_transaction_cap, over_daily_cap, merchant_not_allowed).
- verify_spend_authorization (free) lets a seller or auditor check an allow receipt.
- REST, free: POST /v1/x402/price-tag decodes a 402 response so you see the real amount, token, network and payTo before you pay.
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 per-request and per-run caps in your x402 client and wallet too.
What are the sources?
- x402 docs: Quickstart for Buyers (spend controls, $1 default per-payment cap)
- x402 docs: Lifecycle hooks (onBeforePaymentCreation; prefer spendControls for static caps)
- Coinbase CDP SDK: SpendControls (maxAmountPerPayment, maxCumulativeSpend, SpendStore)
- Coinbase CDP SDK: applySpendControls
- Coinbase CDP SDK example: payForApiWithSpendControls (per-payment + cumulative caps)
- Agent spending policies on Rightshold
Related
- How to set a spending cap for an AI agent paying with x402
- How to stop an AI agent from paying twice when it retries
- How to require human approval before an AI agent spends money
- How to set a separate budget for each AI agent calling paid APIs
Not legal advice. JSON · Markdown · All answers
Get a free API key in one call
No card and no email needed. 100 free credits; the license and policy checks stay free without a key.
curl -s -X POST https://www.rightshold.com/v1/accounts/signup -H "Content-Type: application/json" -d '{"accept_terms":true}'
export RIGHTSHOLD_API_KEY=rs_... # the api_key from the reply (shown once)
curl -s https://www.rightshold.com/v1/licenses/check -H "Authorization: Bearer $RIGHTSHOLD_API_KEY" \
-H "Content-Type: application/json" -d '{"license":"MIT","intended_use":{"commercial_use":true}}'
