This is the real control path, not a video. Every button below makes a live call to the AgentGuard gate at /api/gate/authorize, and every receipt it returns is checked by the independent verifier at /api/verify. Open your network tab and watch. Try a refund or a payout before the agent is authorized.
AgentGuard wraps the one function that moves money. The model can ask all day. The money only moves when a signed authorization clears policy, server-side, before dispatch.
The refund function is wrapped, so there is no path to move money that skips the check. Fail-closed by default.
Scope, expiry, and replay are checked server-side. Unsigned, expired, or unauthorized means the provider is never called.
Allow and block both produce an Ed25519 receipt with no customer data in it. Anyone can verify it without trusting your database.
The authorization carries the policy. The receipt carries the proof. Neither carries your customer, their card, or the model's output. That is what keeps AgentGuard under your stack with zero data plane.
No data plane, no model replacement, no workflow rebuild. You wrap the refund function you already have. Bring your own provider key.
// wrap the one function that moves money import { SpendGuard } from "@agentguard-run/spend"; const guard = new SpendGuard({ policy, signingKeys }); // the agent calls refund() instead of the raw Stripe call. // dispatchToolCall fails closed: it throws AgentGuardBlockedError // unless a signed authorization clears policy, so money only moves on approval. const refund = (orderId, amount) => guard.dispatchToolCall( { scope: "money:refund", toolName: "refund_customer", toolArgs: { orderId, amount } }, (args) => stripe.refunds.create(args) );
# wrap the one function that moves money from agentguard_spend import SpendGuard guard = SpendGuard(config) # dispatch_tool_call fails closed: raises AgentGuardBlockedError # unless a signed authorization clears policy, so money only moves on approval. async def refund_customer(order_id, amount): return await guard.dispatch_tool_call( {"scope": "money:refund", "toolName": "refund_customer", "toolArgs": {"order_id": order_id, "amount": amount}}, lambda args: stripe.Refund.create(**args), )
Fair question, and the honest answer matters. In your integration, the wrapped function is the agent's only path to the refund. The raw provider call is not exposed to the agent, so there is nothing to route around, and AgentGuard fails closed when the authorization is missing or invalid. Beyond the wiring, partners keep money-movement inside the gate because externally governed, verifiable refunds are a feature they sell to their own customers, not a checkbox they want to skip. AgentGuard is the audit and authorization layer that sits under your stack. It proves who authorized what. It never holds your keys, your data, or your money.
Install the SDK, wrap one tool, and ship your first signed refund receipt today. Free under 10K enforcement calls a month.