Rightshold™ Sign up

Answers · agent payments

How do I cap an AI agent's spend when it pays merchants in more than one currency?

Last updated

TL;DR: A cap is a number plus a currency, and amounts are usually sent in minor units, so a cap written for USD can mean something very different in another currency. 1500 is 15.00 USD but 1,500 JPY (about ten dollars) and 1.500 BHD, because ISO 4217 gives JPY 0 decimals and BHD 3. Converting at the agent's own rate adds a second gap: the card converts later, at the network's rate plus any fees. The safe pattern is one budget currency per policy. On Rightshold a spending policy (set_spend_policy) has exactly one currency, and authorize_spend denies a purchase in any other currency with currency_not_allowed, so a foreign charge can't slip past a USD cap by being counted as a smaller number. Either have the agent authorize the amount in your budget currency before it pays (converted with headroom for FX and fees), or give each foreign currency its own credential and cap in that currency. Keep the card's own limit in the card currency as the backstop: Stripe Issuing converts foreign amounts to the card currency when it checks spending_limits.

What should I check, step by step?

1. Pick one budget currency for the agent (the currency your card or wallet settles in) and write every cap in that currency's minor units. Check the decimals: USD and EUR have 2, JPY and KRW have 0, BHD, KWD, OMR, JOD and TND have 3 (ISO 4217).

  1. Set the policy with set_spend_policy: currency (for example USD), per_tx_max_minor and daily_max_minor. A policy holds one currency, and authorize_spend denies any purchase in a different currency with currency_not_allowed, so mixed currencies never add up as raw numbers.
  2. If the merchant prices in another currency, have the agent convert the price to the budget currency before calling authorize_spend, then add headroom for FX movement and fees (a few percent is a starting point; size it from your card statements). Put the original amount, currency and rate source in reference so the decision can be checked later.
  3. Or give a foreign currency its own lane: issue a separate credential with issue_agent_credential and set_spend_policy on it in that currency (currency JPY, daily_max_minor 20000 means 20,000 yen). Use this when the agent pays in that currency often and you want a cap a person can read in local money.
  4. Don't use reference rates as the price. The ECB publishes its euro reference rates for information purposes only and discourages using them for transactions. Use the rate your card network or payment provider applies, or a quote that includes it.
  5. Watch for dynamic currency conversion at checkout (the merchant offers to charge in your home currency at its own rate). Tell the agent to pay in the merchant's local currency unless the converted price is what was authorized.
  6. Keep the card limit in the card currency as the outer cap: Stripe Issuing spending_limits are set in the card's currency and smallest unit, and amounts in other currencies are converted to the card's currency when the limit is checked.
  7. Reconcile in the budget currency: compare each allow (amount and reference) with the settled card amount. Differences come from FX, fees and later tips or adjustments, so expect small gaps and investigate big ones.

Why doesn't one spend cap number work when an AI agent pays in several currencies?

Payment APIs take amounts as integers in the currency's minor unit. Stripe's docs give 1099 for 10.99 USD and 10 for 10 JPY, and list special cases such as ISK, which ISO 4217 now gives 0 decimals but Stripe still represents as a two-decimal value. A cap copied from a USD config into a JPY or BHD config changes size by a factor of 100 or 10 without anyone noticing.

How do I keep an AI agent's spending policy in one budget currency?

On Rightshold, set_spend_policy stores one currency per credential next to per_tx_max_minor, daily_max_minor and the merchant allowlist. authorize_spend checks the purchase currency first: anything else is denied with currency_not_allowed, and the daily total only adds allows in the policy currency.

How does a card limit in the card's currency cap an AI agent's foreign-currency spend?

Stripe Issuing spending_limits are in the card's currency, and foreign-currency authorizations are converted to that currency before the limit is checked. One card limit therefore caps all currencies together, at the network's rate, which makes it the right backstop for several Rightshold lanes.

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: “My purchasing agent pays some suppliers in USD and some in JPY. Set a USD policy on its main credential (25.00 per purchase, 100.00 per day), and tell me what authorize_spend returns if the agent sends a 3,000 JPY purchase on that credential.”

One-click setup

{
  "tool": "set_spend_policy",
  "arguments": {
    "credential_id": "crd_YOUR_AGENT_CREDENTIAL",
    "currency": "USD",
    "per_tx_max_minor": 2500,
    "daily_max_minor": 10000
  }
}

Rightshold records spending policies and signs allow/deny decisions on a hash-chained ledger. It never holds, moves, converts or settles money and does not supply exchange rates: each policy has one currency, and converting amounts, choosing rates and setting card 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}}'