API keys and environments
Reading a Throttle key at a glance, why a publishable key can only do two things, and how keys bind to one workspace environment for life.
Every Throttle API key encodes three things in its prefix: whether it is safe to put in a browser, which environment it talks to, and — through the scopes attached to it — what it is allowed to do. Reading a key correctly tells you most of what you need before you ever make a call.
Reading a key
sk_live_a8f3... pk_test_29bc...
│ │ │ │
│ └── environment │ └── environment
└───── secret └───── publishable
| Prefix | Where it belongs | Can hold |
|---|---|---|
sk_ | Server-side only | Any scope, up to * |
pk_ | Safe in a browser | Two scopes, and only those two |
The middle segment is the environment slug. Production keys read live; every other environment uses its own normalized slug, so a test environment mints sk_test_… and a uat environment mints sk_uat_….
Keys minted before 2026-08-03 read
sk_production_…/pk_production_…instead ofsk_live_…. They remain valid indefinitely and there is no migration to run — nothing in the auth path parses the segment, so both forms authenticate exactly the same. The segment is there for humans reading logs.
Publishable keys can only do two things
A pk_ key may carry exactly two scopes: request a shipping quote, and calculate tax. That is the entire list, and it is enforced at mint time rather than at request time.
The reasoning is that a publishable key is, by definition, public — it ships in your storefront bundle where anyone can read it. So it is restricted to stateless computes that take an input and return a number. It cannot read an order, list a customer, or see anything that already exists.
If you are reaching for a publishable key to read data in the browser, the answer is no. Fetch it from your own backend with a secret key, or use a purpose-built session-scoped route — buyer-facing payment method lists, for example, derive their tenancy from the checkout session rather than from a key. A
pk_key will never be granted a read scope.
One key, one environment, forever
A key is minted into a single workspace environment and stays there. There is no way to point a test key at production data or vice versa — the environment is on the key record itself, and the whole request is scoped by it.
Two consequences worth internalising:
- You cannot “promote” a key. Going live means minting a new key in the production environment and deploying it, not flipping a setting on the key you have.
- Cross-environment reads fail loudly. Fetching a production order with a test key returns
403 environment_mismatch, not an empty result. An empty list means the thing genuinely is not there; a 403 means you are holding the wrong key.
Reserved environment names
You cannot create a custom environment named live, or anything starting with live- or production-. Those are reserved so that a sandbox environment can never mint a key that looks like a production key. Every workspace already has exactly one production environment, created with the workspace and impossible to delete or archive.
Scopes
Keys are gated purely by the scopes they hold — there is no role attached to a key. Scopes are resource:action strings like orders:read or payment_refunds:write, and resource:write implies resource:read on the same resource. Nothing implies anything across resources: holding orders:write grants you nothing on customers.
Grant the narrowest set that works. A key that only creates checkout sessions does not need to read your customer list, and a leaked key is worth exactly what it can reach.
The full catalog, and which scope each endpoint requires, is in the developer docs.
If a key leaks
Delete it. Keys are revealed once at creation and cannot be retrieved afterwards, so rotation is always mint-new-then-delete-old rather than a reset. Deleting takes effect immediately — deploy the replacement first.
Last updated August 9, 2026