Answers · agent payments
How do I restrict an AI agent to an allowlist of merchant domains and categories?
Last updated
TL;DR: A spend cap limits how much an agent can spend. An allowlist limits where it can spend. Put both on the agent's credential with set_spend_policy: allowed_domains (exact domains, or *.example.com to include subdomains) and allowed_categories (short labels such as data_api or cloud_compute). Every proposed purchase then goes through authorize_spend. A merchant that isn't on the list gets a signed deny with merchant_not_allowed. A category that isn't on the list, or a purchase sent with no category, gets category_not_allowed. The per-purchase and daily caps still apply on top, so an allowlisted merchant can still be denied with over_per_transaction_cap or over_daily_cap. An empty list means no restriction, so set both lists on purpose. This page is about the merchant allowlist itself. Card MCC controls, cap-only policies and mid-task revoke are covered on separate pages. Merchants check the allow receipt free with verify_spend_authorization, and ledger_verify keeps every policy version and decision checkable.
What should I check, step by step?
1. List the merchants the agent really needs (API vendors, cloud providers, one marketplace) and the kinds of purchase it makes. Start narrow. It is easier to add a domain after a deny than to explain a purchase you never meant to allow.
- Write allowed_domains as exact hostnames (api.example.com) or wildcards (*.example.com, which also matches example.com). Rightshold lowercases entries, strips a leading www. and the scheme, and accepts up to 100 domains.
- Write allowed_categories as your own short labels (data_api, cloud_compute, saas_seat), up to 100. The agent must send the same label as category on every authorize_spend call. If categories are set and the category is missing or not on the list, the answer is category_not_allowed.
- Remember that an empty allowed_domains or allowed_categories list means no restriction for that field. Setting both lists is what turns the allowlist on.
- Keep caps on top: currency, per_tx_max_minor and daily_max_minor are still checked for allowlisted merchants. set_spend_policy needs at least one cap, so an allowlist can't be used as an unlimited pass.
- Make authorize_spend the step before every payment and pass the real merchant_domain. A signed deny lists every failing reason at once (for example merchant_not_allowed and over_daily_cap), so the agent and the owner can see exactly what to fix.
- Have merchants (or your own payment step) check the allow receipt with verify_spend_authorization and accept it only before valid_until (15 minutes). On card rails, mirror the same list with Stripe Issuing allowed_categories (merchant category codes) so both layers agree.
- Change the list only by recording a new policy version. Add a domain through a human approval if it widens spend, and test it: a listed domain allows, an unlisted one returns merchant_not_allowed, and a wrong category returns category_not_allowed.
What is the difference between a merchant allowlist and a spend cap for an AI agent?
A per-purchase or daily cap still lets the agent pay any merchant until the money runs out. A prompt-injected or confused agent can stay under the cap and still send money to a site you never chose. An allowlist turns this around: every merchant and category is denied unless you named it.
- Pages on per-request and per-run caps, task-scoped spend policies and virtual cards with spending limits cover how much an agent can spend. This page covers where it can spend. The two layers stack: a purchase must pass both.
- NIST SP 800-53 Rev. 5 AC-6 (least privilege) and CM-7(5) (authorized software: allow-by-exception) are the control language for this: allow only what the task needs, and deny everything else by default.
How do allowed_domains and allowed_categories restrict where an AI agent can spend?
set_spend_policy stores currency, per_tx_max_minor, daily_max_minor, allowed_domains, allowed_categories and an optional expires_at on the agent's credential. A new call records a new version that replaces the old one from the moment it is recorded, and old versions stay on the ledger.
- authorize_spend checks merchant_domain against allowed_domains. An exact match passes, and *.example.com matches example.com and any subdomain. Anything else adds merchant_not_allowed. It checks category against allowed_categories, and a missing or unlisted label adds category_not_allowed.
- The decision is plain code: the same inputs always give the same answer. It is signed and written to the spend_authorizations ledger stream, so an owner can later show that a deny was the policy working, not a bug.
Do spend caps and card MCC controls still apply when an AI agent uses a merchant allowlist?
Caps still apply to allowlisted merchants. With a 10.00 per-purchase cap, a 25.00 order to an allowed domain returns over_per_transaction_cap, and the daily total still counts toward over_daily_cap.
- If the agent pays with a Stripe Issuing card, set spending_controls.allowed_categories (merchant category codes) on the card as a second layer. Stripe blocks every category that isn't on the list. Your Rightshold labels can be finer-grained than MCCs, so map each label to the MCCs it may use.
- Merchants or your checkout step verify the signed allow receipt free with verify_spend_authorization and accept it only before valid_until. ledger_verify is free and keyless for the chain of policy versions and decisions.
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: “Restrict my purchasing agent to api.example.com and any subdomain of cloud.example.net, only for data_api and cloud_compute purchases, with a 10.00 USD per-purchase cap and 50.00 USD per day. Then tell me what authorize_spend returns for a merchant that isn't on the list.”
{
"tool": "set_spend_policy",
"arguments": {
"credential_id": "crd_YOUR_AGENT_CREDENTIAL",
"currency": "USD",
"per_tx_max_minor": 1000,
"daily_max_minor": 5000,
"allowed_domains": [
"api.example.com",
"*.cloud.example.net"
],
"allowed_categories": [
"data_api",
"cloud_compute"
]
}
}- Needs your owner account key. It's in the trust toolset (connect to /mcp?toolset=trust). Empty lists mean no restriction, so set both on purpose.
- authorize_spend returns a signed allow or deny with every failing reason: merchant_not_allowed, category_not_allowed, over_per_transaction_cap, over_daily_cap, policy_expired or credential_revoked.
- verify_spend_authorization is free. Merchants use it to check an allow receipt before valid_until.
- request_human_approval binds widening the allowlist to a human decision. ledger_verify is free and keyless for every policy version.
Rightshold records spending policies and signs allow/deny decisions on a hash-chained ledger. It never holds, moves or settles money. Making the agent call authorize_spend before paying, and making merchants or card controls refuse what was denied, are steps you run around those records. Category labels are yours to define and are not card-network MCCs. Not financial or legal advice.
What are the sources?
- NIST SP 800-53 Rev. 5: AC-6 (least privilege) and CM-7 (least functionality)
- Stripe Issuing: spending controls (allowed_categories, blocked_categories, spending_limits)
- Stripe Issuing: merchant categories
- EU AI Act, Regulation (EU) 2024/1689: Article 14 (human oversight)
- RFC 6962: Certificate Transparency (append-only Merkle hash logs)
- Agent spending policies on Rightshold
Related
- How to give an AI agent a virtual card with a spending limit
- How to set per-request and per-run spend caps for x402 agent payments
- How to give each AI agent a task-scoped spend policy with auto-expiring approvals
- How to enforce AI agent payment caps at the execution boundary, not in the prompt
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}}'
