How do you authorize an AI agent to spend money — without giving it unlimited payment authority?
Your agent can now call APIs, buy compute, book things, and trigger paid actions. Somewhere between the agent and the payment rail, someone has to decide: is this payment okay? If the answer lives in the agent's own judgment — or nowhere at all — then any prompt injection, runaway loop, or misread tool result can move real money.
The question isn't "should agents be able to pay?" It's who decides, with what rules, and how is that decision enforced at the moment of execution?
Three common answers, and why each falls short:
| Obvious answer | Why it's not authorization |
|---|---|
| "Check the wallet balance first" | A balance is a fact about money, not a decision about this payment. Balance ≠ authority. |
| "Set a per-transaction cap on the card/wallet" | Caps bound one payment, not the agent. Retries, parallel calls and transaction splitting all change the effective total. |
| "Ask the model to be careful" | The model is the thing being attacked. A prompt injection is exactly a request the model will obey. |
The missing layer is a deterministic authorization decision — made by code, outside the agent loop, against rules a human wrote, with an outcome the execution path cannot ignore.
agent intends to pay
│
▼
authorize() ── policy: budget / per-tx cap / merchant allow-block / approval line
│
├── ALLOW → signed, single-use grant issued
├── APPROVAL → human approves → grant issued
└── DENY → nothing executes
│
▼
executor.verify(grant) ← consumed once; replay → REFUSED
│
▼
payment rail executes (Stripe / x402 / wallet — yours, unchanged)
The two halves are deliberately separated: authorization decides, execution acts. A bare policy check is advice an agent can ignore — so ALLOW produces a signed grant that the executor requires. No grant, no execution. That is what makes the decision real instead of advisory.
A policy file says what the agent may spend; one call gates the payment; the executor enforces the grant.
# policy.yaml — rules a human writes, code enforces
version: "2.1.0"
policy:
budget: { daily: 100 }
transaction: { max: 50 }
merchants:
allowed: [mcdonalds.com, stripe.com, openai.com, amazon.com]
blocked: [scam-vip.com]
approval: { over: 30, channel: console }
agents:
shopping-agent:
transaction: { max: 50 }
approval: { over: 30 }
result = shield.authorize(agent="shopping-agent", amount=25, to="mcdonalds.com")
if result.decision == "ALLOW":
grant = issuer.issue(agent="shopping-agent", amount=25,
currency="USD", merchant="mcdonalds.com",
policy_version=result.policy_version)
ok, reason = executor.verify(grant, agent="shopping-agent", amount=25,
currency="USD", merchant="mcdonalds.com")
if ok:
execute_payment() # your rail, unchanged
Real output from the AgentKit integration example, where executions are counted at the wallet:
| Agent payment | Decision | Payment executed |
|---|---|---|
| $25 to a trusted merchant | ALLOW | once |
| $50 (above the $30 autonomous line) | APPROVAL → human grants | once |
| same grant replayed | REFUSED (reused) | zero |
| $500 to a blocked merchant | DENY | never invoked |
SpendShield is the authorization layer described above: open source, MIT, Python + MCP. It holds no money, moves no money, and is agnostic to the payment rail — it returns ALLOW / APPROVAL / DENY with a signed grant, and your existing executor stays yours.
pip install spendshielduvx --from spendshield spendshield-mcp --policy policy.yaml (Claude Desktop / Code / any MCP client)