Stripe Restricted Keys
Stripe API key with restricted access: the ten-control coverage matrix
Stripe's Restricted Key feature is good at one thing: endpoint-level scope. Engineers shipping AI agents want nine other things too. Here is which ones the built-in key covers, which are partial, and which you still have to build.
TL;DR
Of the ten controls most teams ask about when handing a Stripe key to an AI agent, the built-in Restricted Key covers 3 fully, 2 partially, and 5 not at all. The fully-covered three are endpoint allowlisting, resource-level scope, and manual rotation. The gaps — per-day USD cap, customer scope, mid-run revoke, parameter-level allowlisting, and real-time parsed-cost audit — are what drive teams to put a proxy between the agent and Stripe.
The ten controls people actually ask about
When a staff engineer sits down to figure out how to let an AI agent use their Stripe account, this is the list they work through in order. We'll give each a yes/partial/no next to what a Stripe Restricted Key gives you out of the box.
| # | Control | Native Restricted Key | Notes |
|---|---|---|---|
| 1 | Endpoint-level allowlist (POST /v1/refunds allowed, POST /v1/transfers blocked) |
Yes | This is the core feature. ~60 resource scopes, each with None / Read / Write toggles. |
| 2 | Resource-level scope (Read-only on Customers, Write on Refunds) | Yes | Per-resource Read vs. Write is built in. You cannot split Read/Write on a per-field basis (e.g. "read customer email but not metadata"). |
| 3 | Manual key rotation | Yes | Roll from the Dashboard in one click. Old key revokes immediately. Applications that cached the key in memory will 401 until redeployed. |
| 4 | Per-day USD spend cap | No | There is no built-in cap. A stuck loop on POST /v1/charges runs until it hits Stripe's own rate limits (100/sec for live charges), long past the point where money has moved. |
| 5 | Per-hour request-count cap | Partial | Stripe enforces global rate limits per account (not per key). You cannot set a stricter per-key ceiling. Stripe's Radar can flag unusual charge velocity, but Radar is fraud-scoped, not agent-scoped. |
| 6 | Customer allowlist (key can only touch customer IDs X, Y, Z) | No | Once the key has Customers: Read/Write, it has that across the entire account. There is no scoped_to_customer_ids parameter on a Restricted Key. |
| 7 | Parameter-level allowlist (can create PaymentIntents only if amount < 5000) |
No | Restricted Keys gate at the route, not the payload. The only way to enforce a max-amount is server-side validation before the call, which the agent can be re-prompted past. |
| 8 | Mid-run revoke in under 10 seconds | Partial | Dashboard revoke is ~30 seconds of human work, then propagates near-instantly on Stripe's side. The real latency is your agent's SDK client — if it cached the key in memory, the next call using that client still goes through until the client is recycled or the host redeploys. |
| 9 | Per-call audit with parsed cost | No | Stripe's Events log records what was created, not who created it with which key. Request logs are in the Dashboard under Developers → Logs with a key-ID filter, but parsed cost per call, cap-usage-after, and correlation to an agent run ID are not present. |
| 10 | Multi-account routing (one key enforces policy across several Stripe accounts via Stripe-Account) |
Partial | The Restricted Key works with Connect, but policy (scope) is the same across every connected account the key operates on. Per-account budgets, per-account allowlists, and per-account revoke are not available. |
Reading the matrix
The "Yes" rows are what Restricted Keys were designed for. The feature launched in 2016 to solve "we gave the analytics team a secret key and now they can issue refunds" — in other words, to split which resources along organizational lines. That original framing was for human users of your Stripe account, and for that use case Restricted Keys are sufficient.
The "No" and "Partial" rows are all questions that did not exist in 2016 because there were no non-human users. An AI agent is not a member of the analytics team. It does not respect a written runbook. It retries. It re-prompts. It adds a for (let i = 0; i < 100; i++) around a charge call because that's what the training data says to do when a single call doesn't work. The controls that matter for that user are the controls Stripe has not shipped.
What Stripe has said publicly about the gaps
The stripe/agent-toolkit repository has an open issue titled "Governance layer for Stripe agent payments" (#356) that explicitly catalogs these gaps — per-key spend caps, customer scope, parameter allowlists. Stripe engineers engaged on the thread but no concrete shipping commitment has been made as of April 2026. Worth reading if you want the discussion in their own words rather than ours. In the meantime, the controls either get built by you or by a purpose-built proxy.
Workarounds people try (and where they fall down)
Spend cap via your own server code
You can wrap every Stripe call in a server middleware that sums amount across today's calls and 403s over the cap. This works if all the code calling Stripe is yours, but breaks the moment a Lovable-generated endpoint, a new MCP tool, or a forked agent skips the middleware. A cap belongs at the network boundary, not in application code, exactly because the surface area above it keeps changing.
Customer allowlist via Radar rules
Radar lets you write "block if customer is not in list" rules for charges. This works for blocking a charge creation but doesn't cover the dozen other endpoints (customers.update, subscriptions.create, paymentMethods.attach) that an agent acting on the wrong customer can hit. Radar is also fraud-priced and Radar for Teams costs extra; using it as an authz system is off-label and expensive.
Mid-run revoke by rotating the key
The "kill switch" the Dashboard gives you is key rotation, which is the correct knob but the wrong ergonomics. You cannot rotate in under 10 seconds because the round trip is: notice the problem → open Dashboard → find the key → click Roll → copy new value → deploy the new secret → wait for redeploy to drain old instances. Typical latency is 2–5 minutes. A proxy that holds the upstream key and revokes your downstream vault key is sub-second.
Per-call audit via Dashboard logs
The Dashboard log filter by API key works for spot-checking but not for "show me every call this agent made last Tuesday that was over $100." You can export the Events API and roll your own warehouse — that is a two-engineer-week project. Or you can log at the proxy, which already has the request, response, parsed cost, policy decision, and elapsed time in one row.
What Keybrake covers
Keybrake sits between the agent and api.stripe.com. You keep your Stripe Restricted Key (ours holds it upstream), issue per-agent vault_live_… keys with an attached policy, and every request the agent makes is enforced, logged, and revocable. Specifically:
| Control | Native RK | Keybrake |
|---|---|---|
| Endpoint allowlist | Yes | Yes — mirrors the RK scope set, optionally tighter |
| Resource-level scope | Yes | Yes |
| Manual key rotation | Yes | Yes — rotate vault keys without touching the upstream RK |
| Per-day USD cap | No | Yes — policy daily_usd_cap; 403 after cap, audit row on every rejection |
| Per-hour request-count cap | Partial | Yes |
| Customer allowlist | No | Yes — customer_allowlist: ["cus_…", "cus_…"] enforced at proxy |
| Parameter-level allowlist | No | Partial — max_amount_usd on charges / payment_intents today; more shapes on the roadmap |
| Mid-run revoke <10s | Partial | Yes — POST /vault_keys/:id/revoke, sub-second, no downstream redeploy |
| Per-call audit with parsed cost | No | Yes — cost parsed from Stripe response headers + body, one row per call |
| Multi-account routing | Partial | Yes — policy per Stripe-Account |
Related questions
Does this mean Restricted Keys are useless for agents?
The opposite. Restricted Keys are still the right upstream primitive — you want the key the proxy holds to be as narrow as possible, so even a catastrophic proxy bug cannot widen the blast radius beyond what the RK itself permits. What Restricted Keys don't do is replace the governance layer above them.
Why doesn't Stripe just add per-key spend caps?
We don't know, and we're not in a position to speculate authoritatively. The open issue suggests they are thinking about it. Our guess: the team that owns Restricted Keys (Dashboard/SDKs) and the team that owns Radar (fraud/risk) have to agree on which surface a spend cap belongs on, and historically these conversations at Stripe have taken quarters. We expect some form of native cap to ship in 2026; we also expect it will not cover customer scope or mid-run revoke latency, which is why we're building those regardless.
Is the parsed-cost audit reliable across every Stripe endpoint?
For v1 we cover the money-moving endpoints: charges, refunds, payment_intents, transfers, payouts, invoices. Cost is read from the amount field in the Stripe response and normalized to USD via the response's currency. For non-money endpoints (customer creation, product CRUD) we log the call but leave cost at zero. Endpoints where Stripe's response doesn't surface the settled amount (certain async operations) are flagged as cost_pending until the Stripe webhook settles — you see both rows in the audit.
Can I use Keybrake without my agent knowing?
Yes. The vault key has the same shape as a Stripe key (vault_live_… vs. sk_live_…). The agent's SDK only needs its baseURL swapped to https://proxy.keybrake.com/stripe. No code changes to the agent itself beyond that, no new SDK to learn. If the agent is the kind that reads its own environment and asks "am I calling the real Stripe?", you'd have to tell it — but the typical agent just wraps a library and doesn't care.
Does the proxy become a dependency I have to keep running?
Yes — that is the honest trade. You are adding a network hop and a SPOF between your agent and Stripe. We run multi-region, failover is fast, and we support a break-glass mode that lets a named operator bypass the proxy with a short-lived token when the proxy itself is down. If "add any dependency" is a deal-breaker for your threat model, this product isn't for you and Stripe's native Restricted Keys are the right stopping point.
Further reading
- Stripe Restricted Key permissions map — the 60-row toggle table with runaway-risk per resource.
- Stripe Restricted Key example for an AI agent — a concrete 5-tick scope for a refund-issuing agent.
- Stripe Restricted Key for a Lovable app — exact scopes + the Lovable-specific webhook gotcha.
- How to give an AI agent a Stripe API key — the five-control checklist, end to end, with code.