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
PrefixWhere it belongsCan hold
sk_Server-side onlyAny scope, up to *
pk_Safe in a browserTwo 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 of sk_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