{"slug": "funded-key-with-limits-or-approve-each-payment-putting-a-spend-policy-between-an", "title": "Funded key with limits, or approve each payment? Putting a spend policy between an AI agent and HTTP 402", "summary": "A developer built spendctl, a local sidecar that sits between an AI agent and HTTP 402 payment endpoints (x402/MPP), keeping the private key and spend policy outside the model. The sidecar enforces per-request and daily budgets, domain allowlists and human approval thresholds, and adds an explicit 'unknown' ledger state for payments that settle while the paid API times out, backed by idempotency keys and a manual resolve command. A hosted sandbox is available as a remote MCP server with tools including sandbox_pay, check_budget and approve.", "body_md": "Agents can pay for APIs now. With x402 and MPP a server answers `402 Payment Required` with a price, the client signs a stablecoin authorization, retries, and gets the data. No API key, no invoice, no account. It works, and it is the part people demo.\n\nThe part nobody demos is *who decides*. Two patterns keep coming up:\n\nBoth put the money decision inside the agent's reach: in a prompt, in a tool argument, in an environment variable the model can read. I wanted the decision outside the model, in something a human writes once and can audit later. That became **spendctl**, a local sidecar. This post is about the design, including the one state that turned out to matter most.\n\nThe agent never holds a key. It sends its HTTP request through the sidecar instead: as a gateway URL, through `HTTP_PROXY`, with a `wrapFetch` in the SDK, or as an MCP `pay` tool. When the seller answers 402, the sidecar does three things:\n\n`$0.40` would exceed the daily budget of `$0.25` for agent `researcher`, `$0.21` already spent\"), not as a stack trace.\n\n```\nagents:\n  researcher:\n    budget: { perRequest: \"0.05\", daily: \"0.25\" }\n    allow: [\"api.example.com\"]\n    requireApprovalAbove: \"0.02\"\n```\n\nThe model sees a budget, a refusal or an approval id. It never sees a private key, and it cannot change the policy.\n\nA payment that crosses the threshold needs a human. Two modes:\n\n`X-Spendctl-Approval: <id>`. One approval pays for exactly one retry, and it is bound to what the human saw: recipient, network, asset, amount. If the seller's next 402 asks for anything else, the retry is refused and nothing is paid.\nHumans approve from the CLI, a local dashboard, or a review link in a Slack or Discord webhook. Agents cannot approve; the admin token lives in a file the agent does not read.\n\nSomeone on r/x402 asked the question that every payment system eventually has to answer: *the payment settles, but the paid API times out before the agent gets the result. Does the ledger know the difference?*\n\nMost agent payment demos do not model this at all. A retry is a second payment. spendctl has an explicit state for it:\n\n`settled` (the seller answered 2xx with a settlement receipt), `failed` (the seller rejected it), or `unknown`` Idempotency-Key` inside the window, the sidecar refuses and points at the original row, so paying twice takes deliberate effort.`spendctl resolve <id> --settled --tx 0x…` or `--failed` after checking the chain or the seller's dashboard.\nThis is not clever. It is the boring bookkeeping that is missing from most \"let the agent pay\" code, and it is where the money actually leaks.\n\nThere is a hosted sandbox: a real sidecar running against mock paid APIs, exposed as a remote MCP server at `https://mcp.spendctl.dev/mcp`. Add it to Claude, Claude Code, Cursor or VS Code, then ask the model to create a sandbox, make a paid call, blow the budget, hold a payment for approval and approve it. Throwaway wallet, mock facilitator, no outbound network, nothing real. The tools are `sandbox_create`, `sandbox_pay`, `check_budget`, `list_receipts`, `get_policy` and `approve`.\n\nLocally:\n\n```\nnpx spendctl init && npx spendctl start   # sidecar on :8402\nnpx spendctl mock                          # mock facilitator + two paid APIs\nnpx spendctl fetch \"http://localhost:4022/search?q=x402\" --agent researcher\n```\n\n`await_approval` tool that long-polls under the client timeout is under consideration, after a good suggestion in the r/mcp thread.\nIf you are putting agents in front of paid APIs, I would like to hear which ledger fields you would want to see, and what your clients actually do when a payment is held. Docs are at spendctl.dev/docs; the sandbox needs no account.", "url": "https://wpnews.pro/news/funded-key-with-limits-or-approve-each-payment-putting-a-spend-policy-between-an", "canonical_source": "https://dev.to/nomunomu0504/funded-key-with-limits-or-approve-each-payment-putting-a-spend-policy-between-an-ai-agent-and-1nfl", "published_at": "2026-10-30 06:36:46+00:00", "updated_at": "2026-10-30 06:52:59.464490+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "agent-protocols", "ai-infrastructure", "developer-tools"], "entities": ["spendctl", "x402", "MPP", "Claude", "Claude Code", "Cursor", "VS Code"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/funded-key-with-limits-or-approve-each-payment-putting-a-spend-policy-between-an", "markdown": "https://wpnews.pro/news/funded-key-with-limits-or-approve-each-payment-putting-a-spend-policy-between-an.md", "text": "https://wpnews.pro/news/funded-key-with-limits-or-approve-each-payment-putting-a-spend-policy-between-an.txt", "jsonld": "https://wpnews.pro/news/funded-key-with-limits-or-approve-each-payment-putting-a-spend-policy-between-an.jsonld"}}