Stripe Restricted Keys
Stripe restricted API key permissions: the complete reference for AI agents
A full breakdown of Stripe's permission scope system — every resource category, what Read vs. Write actually unlocks, the minimum permission set each agent role needs, and why scope alone doesn't stop a runaway agent from draining your account.
When you open the Stripe Dashboard and create a Restricted Key for an AI agent, you're confronted with approximately 60 resource toggles — each with three options: None, Read, or Write. The first time you see this form, the temptation is to check "Write" on everything to make sure the agent doesn't run into 403 errors. That approach defeats the purpose of using a restricted key at all.
This post is a reference for engineers who want to do it right. It covers what each permission level actually means, maps out the major resource categories with their risk profiles, gives you minimum permission sets for five agent archetypes, and explains the one gap that Stripe's permission system by design cannot close — the spend volume problem.
If you're new to Stripe API keys with restricted access and want a conceptual introduction before the reference, start there first. For concrete examples with CLI commands for five specific agent use cases, see our Stripe Restricted API key examples post. This post is the technical reference that sits between those two.
The three permission levels
Every resource on a Stripe Restricted Key can be set to one of three levels:
- None — the key receives a 403 Forbidden if it tries to access this resource at all. No reads, no writes. This is the default.
- Read — the key can make GET requests to list and retrieve objects of this type, but cannot create, update, or delete them. This is generally safe to grant.
- Write — the key can make GET, POST, PATCH, and DELETE requests on this resource. For resources that move money (Refunds, Charges, Transfers), Write is the dangerous level. For non-financial resources like Products or Prices, Write is lower risk.
One Stripe quirk: some resources are Write-only from the key's perspective. Tokens is the classic example — you can grant the ability to create tokens but there's no read access to raw token data (for PCI reasons). Keep this in mind when you notice the form doesn't offer Read for certain resources.
Permission categories and risk profiles
The ~60 toggles break into logical clusters. Here's how to think about each cluster for an AI agent:
Core payments (high risk)
| Resource | Read | Write | Agent risk if Write granted |
|---|---|---|---|
Charges | Low | High | Agent can create charges on any stored payment method |
Payment Intents | Low | High | Agent can create and confirm payments; retry loops are expensive |
Refunds | Low | High | Agent can issue unlimited refunds; no per-agent daily cap |
Payment Methods | Low | Medium | Agent can attach/detach cards; misuse leads to unauthorized charges later |
Setup Intents | Low | Medium | Agent can set up future charges without immediate payment |
Tokens | N/A | Medium | Write-only; agent creates tokenized card data (PCI boundary) |
For most agents, you want Payment Intents and Charges at None unless the agent's explicit job is to initiate payments. Read is fine for agents that need to look up transaction history. Write on either should have a human-approval gate or a proxy-layer spend cap in front of it.
Customer data (medium risk)
| Resource | Read | Write | Notes |
|---|---|---|---|
Customers | Low | Medium | Write lets agent create/update customer records and default payment methods |
Payment Sources | Low | High | Write lets agent add/remove cards; risk similar to Payment Methods Write |
Identity | Low | Medium | Stripe Identity verification sessions; Write creates new verification flows |
Billing & subscriptions (medium risk)
| Resource | Read | Write | Notes |
|---|---|---|---|
Subscriptions | Low | High | Write lets agent cancel, upgrade, or create subscriptions; revenue impact is direct |
Invoices | Low | Medium | Write lets agent void invoices or mark paid; Read is safe for billing agents |
Invoice Items | Low | Medium | Write adds/removes line items on upcoming invoices |
Subscription Schedules | Low | Medium | Write lets agent modify future phase changes |
Plans | Low | Low | Legacy API for subscriptions; Read is generally all you need |
Prices | Low | Low | Write creates new price points; rarely needed for agent access |
Products | Low | Low | Product catalog; Write creates/modifies products, low direct financial risk |
Coupons | Low | Medium | Write lets agent create discount codes; revenue impact if misused |
Promotion Codes | Low | Medium | Write creates redeemable promo codes; same risk as Coupons |
Tax Rates | Low | None needed | Billing agents don't need to create or modify tax rates |
Connect / platform (high risk)
| Resource | Read | Write | Notes |
|---|---|---|---|
Accounts | Low | High | Write creates/modifies connected accounts; very rarely agent-appropriate |
Transfers | Low | High | Write sends money to connected accounts; highest financial risk of any permission |
Payouts | Low | High | Write creates payouts to bank accounts; never grant unless agent explicitly manages payouts |
Application Fees | Low | None needed | Platform-level fee management; agents shouldn't need Write here |
For most agents, the entire Connect cluster should be None. These permissions move money out of your platform to third parties. An agent that accidentally or maliciously has Transfers Write can drain your Stripe balance to connected accounts.
Operations & observability (low risk)
| Resource | Read | Write | Notes |
|---|---|---|---|
Balance | Low | N/A | Read-only; safe to grant for monitoring agents |
Events | Low | N/A | Read-only; all webhook event history |
Files | Low | Low | Write uploads files (e.g. dispute evidence); safe for dispute-handling agents |
Webhook Endpoints | Low | Medium | Write lets agent create/modify webhook listeners; keep at None for most agents |
Reporting | Low | N/A | Read-only financial reports; safe for analytics agents |
Disputes | Low | Low | Write submits dispute evidence; appropriate for customer-support agents |
Checkout & other products (situational)
| Resource | Typical agent need |
|---|---|
Checkout Sessions | Write if agent creates payment links or hosted checkout flows; Read for analytics |
Payment Links | Write if agent creates shareable payment links; None otherwise |
Quotes | Write for sales agents generating pricing quotes; None otherwise |
Terminal | None for all cloud-based agents (in-person payments only) |
Radar | Read if agent needs fraud data; Write for custom rule management (rare) |
Reviews | Read if agent monitors fraud reviews; Write to approve/refund (risky) |
Minimum permission sets by agent role
Here are the tightest viable permission sets for the five agent archetypes we see most often. These pair with the detailed examples post which shows the exact CLI commands and gap analysis for each.
Support refund agent
Reads charge history, creates refunds. No payment initiation, no subscription access.
charges:read, customers:read, payment_intents:read, refunds:write
Billing & collections agent
Monitors invoice status, retries failed payments, reads subscription state. Does not create or cancel subscriptions.
customers:read, invoices:read, payment_intents:write, subscriptions:read, balance:read
payment_intents:write here? Retrying a failed payment requires confirming a PaymentIntent. If your agent only monitors and alerts (no automatic retry), use payment_intents:read instead.
Subscription management agent
Can upgrade, downgrade, or cancel subscriptions on customer request. Needs broader billing access.
customers:read, invoices:read, prices:read, products:read, subscriptions:write, subscription_schedules:write
Analytics & reporting agent
Reads transaction history, customer data, and revenue metrics for dashboards and reports. No write access anywhere.
balance:read, charges:read, customers:read, events:read, invoices:read, payment_intents:read, reporting:read, subscriptions:read
Dispute-handling agent
Monitors new disputes, gathers evidence, and submits responses. Reads charges and customers for context.
charges:read, customers:read, disputes:write, files:write
For all five, the Create Key CLI command follows the same pattern. See the single-example walkthrough for the full command syntax and how to use Stripe's API directly if you prefer not to use the CLI.
The principle of least privilege, applied
Least privilege for Stripe keys means: grant the minimum access the agent needs to complete its primary function on its worst day. Not the minimum access for the happy path — the minimum for a retry storm or a logic error.
Three questions to run for each resource before granting Write:
- Does the agent's function require creating or modifying this object? If it only needs to look things up, use Read.
- What's the worst case if the agent calls this endpoint in a tight loop? For Refunds Write: unbounded money out. For Products Write: catalog corruption, not financial loss. Weight the risk accordingly.
- Is there a downstream consequence I'm not thinking about? Granting
subscriptions:writemeans the agent can cancel subscriptions. If your retry logic has a bug and runs cancel → re-subscribe → cancel in a loop, you've churned the customer and generated proration credits you didn't intend.
A useful heuristic: start with Read everywhere, run the agent against a test account, note which calls fail with 403, and only grant Write on the specific resources that fail. Never pre-grant Write "just in case."
What Stripe permissions cannot protect you from
Here's the uncomfortable truth about Stripe restricted key permissions: they are endpoint-level guards, not volume guards. A key with refunds:write can create one refund or ten thousand refunds. Stripe has account-level rate limits (typically 100 req/s), but at $50 per average refund, 100 requests per second is $5,000 per second in potential outbound. The permission system has no concept of "this key is only allowed to issue $500 in refunds today."
The three gaps that come up most in practice:
- No per-key spend caps. The refund example above is the classic. But the same issue applies to any write operation that moves money: charges, transfers, payouts. You can set account-level spend controls via Stripe Radar or your own monitoring, but the restricted key itself has no
max_usd_per_daypolicy. - No customer or merchant scope. A key with
charges:readcan read charges for every customer on your account. If you're running a multi-tenant platform and you give an agent a key with Customers Read, it can read every customer record — not just the ones belonging to the tenant that agent is supposed to serve. Restricted keys don't have acustomer_id_allowlist. - No parameter-level allowlists. A key with
subscriptions:writecan callPOST /v1/subscriptions/{id}with any parameters, includingcancel_at_period_end=true. There's no way to say "this key can call subscriptions update, but only to set themetadatafield."
These gaps are not criticism of Stripe — the permission system solves the problem it was designed to solve (endpoint-level access control). The gaps are where a proxy layer like Keybrake operates: enforcing daily USD caps per vendor, scoping agent keys to a specific merchant or customer set, and blocking specific parameter values at the proxy level before the request ever reaches Stripe.
Layering Stripe permissions with a proxy policy
The pattern that closes all three gaps is a two-layer key architecture:
- Stripe Restricted Key — set to the minimum endpoint permissions the agent needs. This is your hard outer wall: the agent simply cannot call endpoints it doesn't have permission for.
- Proxy-layer vault key — a Keybrake vault key that the agent actually uses. The proxy enforces the policy (spend cap, customer scope, parameter allowlist) before forwarding to Stripe using the restricted key. If the policy is violated, the request is rejected before it touches Stripe.
An example: a refund agent gets a vault key with policy {vendor: "stripe", daily_usd_cap: 500, allowed_endpoints: ["/v1/refunds", "/v1/charges/*", "/v1/customers/*", "/v1/payment_intents/*"], expires_at: "+30d"}. The underlying Stripe key has charges:read, customers:read, payment_intents:read, refunds:write. The Stripe permission blocks payment creation at the API level; the proxy cap blocks refund volume at the business-policy level. Two different threat models, covered by two different controls.
Quick reference: creating restricted keys via CLI
The Stripe CLI is the fastest way to create restricted keys with precise permissions. The general pattern:
stripe restricted_keys create \
--name "agent-{role}-{YYYYMM}" \
--permissions {resource}:{level},{resource}:{level}
Permission level values are read and write (not none — omit a resource entirely to leave it at None). Resource names use snake_case: payment_intents, subscription_schedules, webhook_endpoints.
# Analytics agent (read-only)
stripe restricted_keys create \
--name "agent-analytics-$(date +%Y%m)" \
--permissions balance:read,charges:read,customers:read,\
events:read,invoices:read,payment_intents:read,reporting:read,\
subscriptions:read
# Support refund agent
stripe restricted_keys create \
--name "agent-support-refunds-$(date +%Y%m)" \
--permissions charges:read,customers:read,payment_intents:read,refunds:write
# Dispute agent
stripe restricted_keys create \
--name "agent-disputes-$(date +%Y%m)" \
--permissions charges:read,customers:read,disputes:write,files:write
Name keys after their role and the month they were created. When you audit your API logs three months later trying to figure out what agent made a specific set of calls, the key name is your first signal. The Stripe Developers dashboard groups API calls by key, so a descriptive name makes forensics significantly faster.
Rotate keys monthly. Even if a restricted key has minimal permissions, rotation limits the window of exposure if a key leaks via logs, environment variable exposure, or a compromised deployment. An expired key from a month ago can't be used against you; a key you've never rotated is a liability that grows over time.
Checklist: before you deploy an agent with Stripe access
- Does the key have Write only on resources the agent's primary function explicitly requires?
- Is the key named after its role and creation month?
- Are all Connect permissions (Transfers, Payouts, Accounts) set to None unless this is a Connect-specific agent?
- Have you answered: "what's the maximum damage if this agent retries in a loop for 60 seconds?"
- If the answer to the damage question is more than you're comfortable with: is there a spend cap or confirmation gate in front of the risky operations?
- Is there an alert wired to your Stripe account activity (or to your proxy audit log) that fires if a single agent key creates more than N refunds/charges/transfers in a time window?
The permission system is the first line of defense. Answering these questions before deployment is the second. A proxy-layer cap is the third. None of them are sufficient alone — together they cover the threat models that actually matter when you give an autonomous agent access to your Stripe account.
Stop the next runaway agent before it costs you
Keybrake wraps your Stripe (and Twilio, Resend) keys with a proxy that enforces per-agent spend caps, customer scopes, and an audit log of every call — before anything reaches the vendor. Works alongside your Stripe restricted key permissions.