Mint, rotate and revoke a virtual key
A virtual key is what an application presents instead of a provider’s own credential. It is an identity and nothing else: a name, some metadata, an expiry, and the name of a profile that carries what it may do.
Keys are replicated state, not configuration. There is no keys: block
in the config document and there never was. A key is minted through the
admin API, lands in the raft log, and is on every node before the call
returns.
Mint one
Section titled “Mint one”Run this command:
$ pistra admin POST /admin/v1/keys \ -d '{"name":"checkout-api","profile":"payments","metadata":{"team":"payments"}}'The response looks like this:
{"name":"checkout-api", "token":"pistra_...", "created_at":"2026-09-01T10:11:25.451701Z"}The token is in that response and nowhere else. Only its SHA-256 is stored, so nothing, not the admin API, not the console, not a raft snapshot, not you, can produce it again. A lost token is a rotation.
name is the only required field. profile may be omitted, which means
no profile: unrestricted, but for the deployment-wide rules. A profile
that does not exist is refused with 400 and a list of the ones that do,
because the mistake is nearly always a spelling of something real.
metadata is free-form and is what CEL reads as key.metadata:
access_rules: - name: eu-only condition: '!has(key.metadata.region) || key.metadata.region != "eu"' action: denyThe has() guard is not decoration. Reading key.metadata.region on a
key that was minted without one is an error, not a false. A rule that
omits the guard refuses every key that predates the label rather than
ignoring them.
Metadata is fixed at mint. Changing it is a rotation too. Put slow-moving facts in it, such as a team or a service name, and leave anything that changes to the profile.
The client presents it as Authorization: Bearer or as x-api-key,
so an OpenAI-style and an Anthropic-style SDK both work with no special
configuration.
The token is meant to be findable
Section titled “The token is meant to be findable”The layout is deliberate: pistra_, then 43 base62 characters of
entropy, then 6 more that are a CRC-32 over them.
The prefix is the product’s whole name rather than an abbreviation, because an abbreviation is not identifiable. Two letters and a hyphen match locale tags and ordinary identifiers, and a scanner that fires on those is a scanner nobody keeps switched on. The checksum lets a scanner confirm a candidate on its own, without asking your gateway whether a string it found in a public repository is real.
Your own deployment is one of those scanners. PISTRA_KEY is a built-in
credential recognizer, so a key pasted into a prompt is found in traffic
like any other credential. See the
Credential coverage.
Four ways a key stops working
Section titled “Four ways a key stops working”They are not interchangeable. Three are somebody’s decision and one is a mistake.
| what happened | the client sees | pistra_auth_failures_total reason |
how it ends |
|---|---|---|---|
expires_at passed |
401 invalid_api_key |
expired_key |
it does not; mint a new key |
| suspended | 403 key_suspended, naming when it lifts |
key_suspended |
the lease lapses, by itself |
| revoked | 401 invalid_api_key |
revoked_key |
never, it is permanent |
| its profile was deleted | 403 unknown_profile, naming the profile |
unknown_profile |
put the profile back, or revoke the keys |
Both 401s say exactly what an unrecognized token says. A prober must not be able to tell a key that never existed from one somebody revoked this morning. The difference lives in the audit trail and in the counters. There it is useful, and the caller cannot read it.
The two 403s are not folded in with them, because the credential is real and the gateway knows whose it is. Answering “invalid API key” would send somebody looking for a lost token when the problem is a profile an operator removed, or a suspension somebody else opened an incident for.
Revoke one
Section titled “Revoke one”Run this command:
$ pistra admin DELETE /admin/v1/keys/checkout-apiIn-flight streams finish. The next request is refused. Revocation is final, and there is no un-revoke. The record is a tombstone rather than a deletion, so a revoked key stays in the list and its name is never reusable.
To stop a key serving right now when you are not yet sure it is compromised, suspend it rather than revoke it. A suspension is a lease with an expiry, on the key or on its whole profile, that ends itself if nobody comes back to it. See Suspend a key, provider or rule.
Rotate one
Section titled “Rotate one”There is no rotate verb, and there is no way to attach a second token to one key. Rotation is three steps with an overlap in the middle. The overlap lets it happen without an outage:
$ pistra admin POST /admin/v1/keys \ -d '{"name":"checkout-api-2","profile":"payments","metadata":{"team":"payments"}}'# deploy the new token to the client, and wait for every instance to have it$ pistra admin DELETE /admin/v1/keys/checkout-apiNothing about the two keys is linked except that they name the same profile. The shared profile is what makes the overlap work. Both are live, both get the same policy, and the second one is doing real traffic before the first one stops.
Three things about this are worth knowing before the first time.
The replacement needs a different name. A revoked key keeps its
name forever, so re-minting checkout-api is a 409. Pick a naming
convention that admits a successor, such as a date or a counter, before
the first rotation.
A rotated key starts with a full budget allowance. A non-shared
budget, which is the default, gives each key its own bucket, and that
bucket is keyed by the key’s name. A new name is a new bucket, so a
team that rotates mid-window gets its allowance again for the rest of
that window. If that matters, set shared: true on the budget and the
whole profile draws from one bucket that rotation does not reset. See
Cap what a team spends.
Nothing reminds you to finish. Both keys work. That is the point of the overlap, and it is also why the last step gets forgotten. A half-finished rotation looks like a working deployment. The key list below is the only thing that will tell you.
Rotating on a schedule instead
Section titled “Rotating on a schedule instead”expires_at turns the last step into something that happens whether
anybody remembers or not:
$ pistra admin POST /admin/v1/keys \ -d '{"name":"ci-2026-q4","profile":"ci","expires_at":"2027-01-01T00:00:00Z"}'An expiry that nobody replaced is an outage on a date. The alternative
is a credential that outlives the service it was minted for. Which of
those you would rather have is a decision, but only one of them is
visible in advance. Watch
pistra_auth_failures_total{reason="expired_key"}. It goes from zero
to non-zero at the moment somebody needed to have known.
See what is out there
Section titled “See what is out there”Run this command:
$ pistra admin /admin/v1/keysThe response looks like this:
{"keys":[ {"name":"checkout-api","profile":"payments","revoked":true}, {"name":"checkout-api-2","profile":"payments","metadata":{"team":"payments"}}, {"name":"orphan","profile":"deleted-last-week","unknown_profile":true}]}Revoked and expired keys are listed on purpose. A key that vanished on
revocation would leave revoked_key refusals in the trail with nothing
to attribute them to. Grep for unknown_profile. Those keys are already
failing, and an operator reading a healthy-looking list would have no
way to know.
What is recorded
Section titled “What is recorded”The trail records refusals, not admissions. A record of every admitted
request is the traffic log, and duplicating it would make the trail
something nobody could verify the completeness of. An expired, revoked
or orphaned key produces a request.auth record naming the key and the
reason and nothing about the token. A suspended one produces a
request.suspended record instead, because nothing was wrong with the
caller. The deployment was deliberately not serving.
Minting and revoking are themselves admin-plane decisions, subject to
Cedar like every other one. createKey and revokeKey are separate
actions, so a CI pipeline can be allowed to mint without being allowed
to revoke. See
Restrict what an admin caller can do.
Related
Section titled “Related”- Give a team its own policy, what the profile a key names actually carries
- Cap what a team spends, the bucket a rotation resets, and the flag that stops it
- Suspend a key, provider or rule, suspending a key or a whole profile, on a lease
- Authorization, why a key carries no limits of its own
- Admin API,
createKey,listKeys,revokeKey