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.

ResourceAccess levelWhy this level
ChargesReadLook up the charge the ticket refers to
CustomersReadConfirm the customer ID matches the ticket reporter
PaymentIntentsReadResolve the PaymentIntent that backed the charge
RefundsWriteCreate 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.

What this scope set doesn't give you: A spend cap. This key has 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.

ResourceAccess levelWhy this level
CustomersReadLook up payment method and email for the failing customer
InvoicesWriteMark invoice uncollectible, finalize, or trigger a retry attempt
PaymentIntentsWriteConfirm or capture the payment intent backing the invoice
PaymentMethodsReadRead available payment methods to pick one for retry
SubscriptionsReadCheck 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
What this scope set doesn't give you: A per-customer scope. 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.

ResourceAccess levelWhy this level
CustomersReadLook up customer to confirm identity before modifying their subscription
ProductsReadRead available plans and price IDs the agent can switch to
PricesReadValidate the target price before applying it
SubscriptionsWriteUpgrade, downgrade, pause, resume, or cancel the subscription
SubscriptionItemsWriteModify quantity or price on individual items in the subscription
InvoicesReadRead 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
What this scope set doesn't give you: An allowlist of which plans the agent can switch to. 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.

ResourceAccess levelWhy this level
PaymentIntentsWriteCapture the previously authorized PaymentIntent
ChargesReadConfirm the underlying charge state before capture
CustomersReadLook 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.

What this scope set doesn't give you: The key cannot be limited to 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.

ResourceAccess levelWhy this level
CustomersReadPull customer list, metadata, default payment method
ChargesReadHistorical payment amounts and outcomes
InvoicesReadSubscription billing history and MRR calculation
SubscriptionsReadCurrent plan, status, and churn risk signals
RefundsReadRefund rate calculation
PaymentIntentsReadPayment 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.

What this scope set doesn't give you: Data exfiltration protection. 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:

  1. How much can it spend before it's stopped automatically? (Spend cap — not in Restricted Keys)
  2. Can it act on any record, or only the records it was assigned? (Customer/record scope — not in Restricted Keys)
  3. Can I stop it mid-run in under ten seconds? (Sub-second revoke — partial; Dashboard rotation takes 30 seconds of human work)
  4. What specific payload parameters can it send? (Parameter-level allowlist — not in Restricted Keys)
  5. 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:

  1. Create the restricted key in test mode first (rk_test_…) with the identical permission set.
  2. Run the agent against test-mode endpoints. Use test fixtures (tok_visa, cus_test_…) that represent your expected data shape.
  3. 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.
  4. 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:

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