Rightshold™ Sign up

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.

  1. 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).
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.

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.

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.

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.”

One-click setup

{
  "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"
    ]
  }
}

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?

Related

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}}'