Rightshold™ Sign up

Answers · agent payments

How do I cap an AI agent's spend per vendor per day across multiple tasks?

Last updated

TL;DR: A per-task cap resets with every task. An agent that runs 40 tasks a day, each under a 5.00 cap, can still spend 200.00 with one vendor. A per-vendor daily cap needs a counter that lives outside any one task, is keyed to the vendor, and resets on a fixed clock. On Rightshold the daily total belongs to the credential, not the task: authorize_spend adds every allowed amount to that credential's total for the current UTC day, whichever task asked. So give each vendor its own lane. Issue one agent credential per vendor (issue_agent_credential), then set_spend_policy on it with allowed_domains set to that vendor only, a daily_max_minor for the vendor's daily cap and a per_tx_max_minor. Every task that buys from that vendor calls authorize_spend with that vendor's credential_id. Once the day's total would pass the cap, the next request gets a signed deny with over_daily_cap, and a lane can't be pointed at another vendor because that returns merchant_not_allowed. Add an outer cap on the card or wallet for the total across vendors. ledger_verify keeps every policy version and decision checkable.

What should I check, step by step?

1. List the vendors the agent pays and give each one a daily number in its smallest currency unit (1500 for 15.00 USD). Base it on what a normal busy day costs, not on the largest single purchase.

  1. Issue one credential per vendor with issue_agent_credential (an agent label such as buyer-agent:api.vendor-a.com, plus the agent's Ed25519 key and its signature). One agent can hold several credentials, one for each vendor lane.
  2. Call set_spend_policy once per lane: currency, per_tx_max_minor, daily_max_minor, and allowed_domains holding only that vendor (vendor-a.com, or *.vendor-a.com to include subdomains). If you use categories, add allowed_categories too.
  3. Have every task, worker and retry call authorize_spend with the lane's credential_id, the real merchant_domain and amount before it pays. The daily total is shared because the credential is shared, so parallel tasks draw down the same counter.
  4. Know the clock. The Rightshold daily total resets at 00:00 UTC, and Stripe Issuing date-based intervals also start at midnight UTC. A run that crosses midnight can spend up to two days' caps, so keep per_tx_max_minor well below the daily cap.
  5. Count allows, not settlements: an allow counts toward the day's total when it is recorded, even if the agent never completes that payment. Treat the cap as an upper bound on what the agent may commit to, and size it with that in mind.
  6. Don't re-issue a lane's credential in the middle of the day. Each new credential_id starts its own daily total at zero. Issue with ttl_hours long enough to cover your rotation window (up to 168), or set the new lane's cap to the remaining_today_minor shown by GET /v1/spend/policies/{credential_id}.
  7. Add an outer cap for the total across vendors: a daily spending_limits entry on the Stripe Issuing card or cardholder, or one virtual card per vendor with its own daily limit. The lanes cap each vendor; the outer cap stops the sum of lanes from becoming the real budget.

Why don't per-task spend caps limit what an AI agent spends with one vendor in a day?

Per-request and per-task caps bound one unit of work. Agents rarely do one unit: schedulers, retries, sub-agents and parallel workers all start new tasks, each with a fresh cap. The vendor sees one customer and one bill, so the vendor total is what you need to cap.

How do I give each vendor its own daily spend counter shared across an AI agent's tasks?

set_spend_policy stores currency, per_tx_max_minor, daily_max_minor, allowed_domains, allowed_categories and an optional expires_at on one credential. authorize_spend sums that credential's allowed amounts for the current UTC day in the policy currency and denies with over_daily_cap when the new amount would pass daily_max_minor.

How do I cap an AI agent's total daily spend across all vendors?

Lanes cap vendors one at a time. Add an overall cap where the money moves. Stripe Issuing spending_limits take an amount and an interval (per_authorization, daily, weekly, monthly, yearly or all_time), and a cardholder's limits apply across all of their cards. Stripe aggregates spend on a best-effort basis, with up to about 30 seconds of delay.

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 up a per-vendor daily cap for my purchasing agent: on the credential I use only for api.vendor-a.com, allow 15.00 USD per day and 5.00 USD per purchase, only for that domain. Then tell me what authorize_spend returns when a second task pushes the day's total over 15.00.”

One-click setup

{
  "tool": "set_spend_policy",
  "arguments": {
    "credential_id": "crd_YOUR_VENDOR_A_LANE_CREDENTIAL",
    "currency": "USD",
    "per_tx_max_minor": 500,
    "daily_max_minor": 1500,
    "allowed_domains": [
      "api.vendor-a.com"
    ]
  }
}

Rightshold records spending policies and signs allow/deny decisions on a hash-chained ledger. It never holds, moves or settles money, and it has no per-merchant counter inside one credential: per-vendor caps come from one credential per vendor. Making every task call authorize_spend, and setting card or wallet limits, are steps you run around those records. Not financial or legal advice.

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