Stripe Restricted Keys
Stripe Restricted API key examples: five real configurations for AI agent use cases
Exact scope sets for five common agent roles — refund agent, billing agent, subscription manager, payment capturer, customer data reader — plus the two lines of code that generate each key, and the gaps that each configuration still leaves open.
The Stripe Dashboard has approximately 60 resource-level toggles on the Restricted Key form. When you're staring at them for the first time, trying to scope a new agent, the question is almost always the same: "which ones do I actually need to check?" The documentation explains what each toggle does, but it doesn't say "here is the right scope set for an agent that issues refunds and nothing else."
This post answers that question for five agent use cases that come up repeatedly. For each one you get: the exact permission set (which resources, which access level), how to create the key in one CLI command, the specific API calls the key enables, and — critically — the controls this configuration still does not give you, which is where most incidents happen.
If you want the concept explained from scratch first, our single-example walkthrough for a refund agent covers the fundamentals. This post assumes you understand the primitive and want the real configurations for five distinct roles.
How to read the scope tables
Stripe Restricted Keys have three access levels per resource: None (key gets a 401 if it tries this resource), Read (GET operations only), and Write (includes both GET and POST/PATCH/DELETE on that resource). Write on a resource that moves money — charges, refunds, transfers — is the risky one. Every permission you leave at None is a hard wall the agent cannot cross.
The tables below show only resources that need to be set above None. Everything else is implied to be None.
Example 1: Support refund agent
This is the most common starting point. An agent reads support tickets, determines whether a refund is warranted, and issues the refund via Stripe. It should not be able to create new charges, edit customer records, or touch subscriptions.
| Resource | Access level | Why this level |
|---|---|---|
Charges | Read | Look up the charge the ticket refers to |
Customers | Read | Confirm the customer ID matches the ticket reporter |
PaymentIntents | Read | Resolve the PaymentIntent that backed the charge |
Refunds | Write | Create the refund — the only money-moving operation this agent needs |
# Create via Stripe CLI
stripe restricted_keys create \
--name "agent-support-refunds-$(date +%Y%m)" \
--permissions charges:read,customers:read,payment_intents:read,refunds:write
Name the key after the role and the month you created it. When you look at the key ID in the Stripe Developers log six weeks later trying to figure out what agent made a specific call, the name is the only signal you have.
Refunds: Write on your entire Stripe account. If the agent's ticket-matching logic misfires — or if the agent retries a failed refund call in a loop — it will issue refunds until Stripe's account-level rate limit stops it. A 100-call-per-second rate limit on a $50 average refund is $300,000/hour. The scope set prevents the agent from creating charges, but it does nothing to limit the volume of refunds.
Example 2: Billing agent (invoice lookup and retry)
A billing agent monitors failed payment attempts, reads invoice status, and retries collection on past-due invoices. It does not create subscriptions or modify pricing — it just works the existing collection queue.
| Resource | Access level | Why this level |
|---|---|---|
Customers | Read | Look up payment method and email for the failing customer |
Invoices | Write | Mark invoice uncollectible, finalize, or trigger a retry attempt |
PaymentIntents | Write | Confirm or capture the payment intent backing the invoice |
PaymentMethods | Read | Read available payment methods to pick one for retry |
Subscriptions | Read | Check subscription status and billing cycle anchors — no writes |
# Create via Stripe CLI
stripe restricted_keys create \
--name "agent-billing-collections-$(date +%Y%m)" \
--permissions customers:read,invoices:write,payment_intents:write,payment_methods:read,subscriptions:read
Invoices: Write is global — the agent can act on any invoice in your account, not only the cohort it was assigned. If a logic bug makes the agent pick the wrong customer segment, you're looking at involuntary collections against customers who shouldn't be retried. There's no customer allowlist parameter on a Restricted Key.
Example 3: Subscription management agent
This agent handles plan changes, trial extensions, and cancellations on behalf of a customer service team. It can modify subscriptions but should not be able to issue refunds, move funds, or see raw payment method numbers.
| Resource | Access level | Why this level |
|---|---|---|
Customers | Read | Look up customer to confirm identity before modifying their subscription |
Products | Read | Read available plans and price IDs the agent can switch to |
Prices | Read | Validate the target price before applying it |
Subscriptions | Write | Upgrade, downgrade, pause, resume, or cancel the subscription |
SubscriptionItems | Write | Modify quantity or price on individual items in the subscription |
Invoices | Read | Read upcoming invoice to show the customer what the change costs |
# Create via Stripe CLI
stripe restricted_keys create \
--name "agent-subscriptions-csm-$(date +%Y%m)" \
--permissions customers:read,products:read,prices:read,subscriptions:write,subscription_items:write,invoices:read
Subscriptions: Write lets the agent set any price in your catalog — including a custom $0/month price someone created for a beta user three years ago. You need parameter-level enforcement ("the target price field must be in this approved set") that runs at the network boundary. A Restricted Key doesn't have that — it gates at the endpoint level, not the payload level.
Example 4: Payment-capturing agent (async auth/capture flow)
Some checkout flows authorize a payment immediately but capture it later — after inventory is confirmed, order is fulfilled, or identity is verified. This agent handles the capture step once the triggering condition is met. It should not be able to create new authorizations; it only captures existing ones.
| Resource | Access level | Why this level |
|---|---|---|
PaymentIntents | Write | Capture the previously authorized PaymentIntent |
Charges | Read | Confirm the underlying charge state before capture |
Customers | Read | Look up customer context for the fulfillment record |
# Create via Stripe CLI
stripe restricted_keys create \
--name "agent-capture-fulfillment-$(date +%Y%m)" \
--permissions payment_intents:write,charges:read,customers:read
This is one of the tighter scope sets. PaymentIntents: Write is required for capture, but note that it also allows the agent to create new PaymentIntents — an operation this agent should never perform. The Restricted Key cannot distinguish "write capture on an existing intent" from "write create a new intent." That distinction requires payload inspection at the proxy level.
POST /v1/payment_intents/:id/capture — it covers POST /v1/payment_intents (create) as well. An adversarial prompt or a logic bug could get the agent to create a new $10,000 PaymentIntent rather than capture the existing $89 one. The only defense against this at the Stripe layer is Radar — which is fraud-scoped and approximate, not policy-exact.
Example 5: Customer data reader (read-only analytics agent)
An analytics or reporting agent that needs to pull customer lifetime value, payment history, and subscription metrics across your account — but should never be able to move money or modify any record. Read-only agents are the safest pattern, but they still need a minimal scope.
| Resource | Access level | Why this level |
|---|---|---|
Customers | Read | Pull customer list, metadata, default payment method |
Charges | Read | Historical payment amounts and outcomes |
Invoices | Read | Subscription billing history and MRR calculation |
Subscriptions | Read | Current plan, status, and churn risk signals |
Refunds | Read | Refund rate calculation |
PaymentIntents | Read | Payment intent status for conversion funnel analysis |
# Create via Stripe CLI
stripe restricted_keys create \
--name "agent-analytics-readonly-$(date +%Y%m)" \
--permissions customers:read,charges:read,invoices:read,subscriptions:read,refunds:read,payment_intents:read
This is an all-Read scope set — no Write anywhere. That makes it the lowest-risk of the five. But read-only does not mean zero-risk.
Customers: Read on the full account lets the agent list and read every customer record — including PII, payment method fingerprints, and billing addresses. If this agent is exposed to an adversarial document (a "prompt injection" in a support ticket the agent reads), it could be prompted to export your entire customer list to an attacker-controlled endpoint. The Restricted Key controls what the agent can write; it does not control what the agent can exfiltrate from the read side. A customer allowlist at the proxy layer limits the read surface.
The pattern in all five gaps
Reading the gap boxes together, a pattern emerges. Stripe's Restricted Key answers one question: which endpoints can this key reach? That is an important question, and the Restricted Key answers it cleanly. But the five questions your security-conscious engineer actually asks about an agent running against production money are different:
- How much can it spend before it's stopped automatically? (Spend cap — not in Restricted Keys)
- Can it act on any record, or only the records it was assigned? (Customer/record scope — not in Restricted Keys)
- Can I stop it mid-run in under ten seconds? (Sub-second revoke — partial; Dashboard rotation takes 30 seconds of human work)
- What specific payload parameters can it send? (Parameter-level allowlist — not in Restricted Keys)
- What exactly did it do, with how much money, on which record? (Per-call parsed-cost audit — not in Restricted Keys)
The answers to questions 1 through 5 require a governance layer above the Restricted Key, not a different configuration of the Restricted Key itself. This is the same gap documented in our earlier post on why Restricted Keys aren't restricted enough — and in the open issue on the stripe/agent-toolkit repo where Stripe's own engineers have acknowledged the gap.
One pattern that does work: test mode first, then promote
Whatever scope set you configure, the right workflow is:
- Create the restricted key in test mode first (
rk_test_…) with the identical permission set. - Run the agent against test-mode endpoints. Use test fixtures (
tok_visa,cus_test_…) that represent your expected data shape. - Deliberately try to exceed the scope from your test code — call an endpoint that should be blocked and confirm you get a 401. This validates the key, not just the agent.
- Only create the live key (
rk_live_…) once you've observed the correct 401 patterns in test mode.
This is especially important for the payload-level gaps. In test mode, you can simulate the stuck-loop scenario — call POST /v1/refunds in a tight loop — and watch your monitoring before real money is involved. You cannot test the spend-cap gap itself because there's no Stripe cap to observe — but you can observe how much the test account charges pile up in 30 seconds and extrapolate the live risk.
How Keybrake closes the remaining gaps
Keybrake sits between your agent and api.stripe.com. You keep the Stripe Restricted Key (our upstream holds it), issue per-agent vault_live_… credentials with policies attached, and every request the agent makes is enforced, logged with parsed cost, and revocable in under a second.
For each of the five examples above, adding Keybrake means:
- Refund agent: add
daily_usd_cap: 500to the policy. The 301st dollar in refunds today gets a 403 instead of passing through. - Billing agent: add
customer_allowlist: ["cus_past_due_cohort_id_1", "cus_past_due_cohort_id_2", ...]. The agent can only act on the invoices it was assigned, not the full account. - Subscription manager: add
allowed_price_ids: ["price_hobby_monthly", "price_team_monthly"]. The agent cannot downgrade a customer to the $0 beta price even if prompted to. - Payment capturer: restrict the allowed paths to
POST /v1/payment_intents/:id/captureonly — excludingPOST /v1/payment_intents(create). The Restricted Key allows both; the Keybrake policy allows only capture. - Analytics reader: add
customer_allowlistscoped to the segment the agent was authorized to analyze, orread_rate_limit: 10/secto make bulk exfiltration slow enough to detect in your logs.
In each case the agent code changes are two lines: swap the base URL from api.stripe.com to proxy.keybrake.com/stripe, and swap the key from the Stripe Restricted Key to the Keybrake vault key. The Stripe SDK doesn't need to know.
Keybrake closes the five gaps
Per-agent spend caps, customer allowlists, parameter-level enforcement, sub-second revoke, and a per-call audit log with parsed cost. You keep your Stripe Restricted Key as the upstream; we add the governance layer above it.
Further reading
- Stripe Restricted API key example (single walkthrough) — the canonical minimal scope for a refund agent, step by step.
- The ten-control coverage matrix — what Restricted Keys cover, what's partial, what's missing.
- Why your Stripe Restricted Key probably isn't restricted enough — the four production gaps in depth.
- How to give an AI agent a Stripe API key safely — the five-control checklist, end to end, with code.
- The full Stripe Restricted Key permissions map — what every toggle does and which ones carry the most blast-radius risk.