Skip to content
Solo ProTeamBusinessEnterprise

API keys

Issuing credentials for a machine — the one-time token, the four per-key constraints, mandatory lifetimes and the expiry warnings.

An API key is a pv_live_… bearer token that proves a machine identity to the machine data plane (/api/v1/secrets/**). It carries no permissions of its own: it proves who is calling, and the identity's policies decide what that caller may reach.

Issue one from Credentials → Issue key on the identity's page. You need secrets:api_key:manage.

The one-time token#

The plaintext is displayed exactly once, in the dialog that appears when the key is created.

There is no second chance

The server stores sha256(token) and nothing else. A lost token cannot be recovered by anyone at any level of access — revoke the key and issue a new one.

Keys are 51 characters and always begin pv_live_. The list shows only the display prefix (pv_live_a3f9…), which is enough to identify a key in an audit row without being usable.

What you set per key#

Option Meaning Changeable on a live key?
Description label in the key list no
Permissions everything the machine holds, or a subset of it yes — Re-scope
Expires after lifetime in days, required yes — Settings (extend or shorten, never remove)
IP allowlist CIDR blocks the key may be used from yes — Settings
Rate limit secrets per minute for this key yes — Settings

Permissions (scope)#

The default is everything this machine can do, because the common case — one machine, one key — should require no decisions. Choosing Limited narrows the key to a subset of the identity's attached policies, and a read-only preset selects exactly those policies whose actions are read and list.

A key can only ever hold less than its machine, never more. The model and its consequences are on Re-scope & settings.

Expires after#

Every key has an end date. The console pre-fills 90 days and offers 30 / 60 / 90 / 180 / 365 as one-click presets; any whole number of days from 1 to 365 is accepted.

There is no "never expires"

A credential with no end date is one nobody is ever reminded to rotate, so the platform will not mint one — and keys issued before this rule are refused at authentication rather than honoured indefinitely. If a key predates it, the console shows "No expiry — unusable": issue a replacement.

The floor of one day exists because the form collects days, and anything shorter is either a typo or a request to end the key immediately — which is revocation, and revocation has its own verb and its own audit action. The ceiling of a year keeps the fleet bounded: a credential that leaks and is never noticed still stops working inside a year.

IP allowlist#

Up to 32 entries. Each is a literal address or block — 10.0.0.0/8, a single 203.0.113.7, or IPv6 such as 2001:db8::/32. Empty means the key works from any address, and a malformed entry is refused as you type it, naming the one at fault: the console will not let you save a fence the server rejects.

Host names are not accepted

An allowlist of names would have to be resolved on every authenticated request — a DNS lookup inside the authentication path, and a security control whose real contents live in a zone file where changing them leaves no trace here. A name that stopped resolving would read as a broken credential.

Worth doing whenever your runners have stable egress addresses: it turns a stolen token into a token that only works from your own network.

Rate limit#

Secrets per minute for this one key. The default is 100; the maximum depends on your plan — Solo Professional 1,000, Team 3,000, Business 6,000, Enterprise 15,000. The field states the ceiling you have and refuses anything above it.

The unit is a secret, not an HTTP call. A single-path read spends one. A bulk read of 40 paths spends 40, and a bulk write of 30 upserts plus 10 deletes likewise spends 40 — because the number you set here is a blast-radius control, and one that counted calls would have let a leaked key take a hundred secrets for every unit it was charged.

Your key is also counted against a second budget: everything your whole account spends on the machine plane, summed across every key it holds. That one is 4,000 / 12,000 / 30,000 / 100,000 on the same tiers. A per-key ceiling on its own would be no ceiling at all — an account could simply issue more keys — so the aggregate is what actually bounds you, and either can produce the 429.

The two do not have to add up, and deliberately do not. A per-key limit caps what one credential may burst to; it is not a reservation. Three keys at 1,000/min on a plan sharing 4,000/min is a normal setup — the keys will simply never all reach 1,000 at the same moment. What it means in practice is that issuing more keys does not buy more throughput: the shared budget is what rises with your plan.

The Credentials tab of any machine identity shows all three numbers — what that machine's keys are configured to, what every machine in your account is configured to, and the shared budget — and says so plainly when the account is configured past what it can spend.

A key that sets no limit of its own still spends the default, so this arrives without configuring anything: forty unconfigured keys on Solo Professional is 4,000/min, the whole budget.

Every response on the machine plane carries the current state of whichever budget is tighter, and exceeding either returns 429:

http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1735689600

The window slides. Your last 60 seconds are what count, not the minute on the wall clock, so X-RateLimit-Reset names the moment you will actually have room again rather than the top of the next minute — a job refused at second 55 by traffic that is nearly out of view is usually told to wait a few seconds, not a full minute. It also means you cannot spend a whole allowance at second 59 and another at second 61: both bursts sit inside the same trailing window and the second one is refused.

One header appears only in an unusual case:

http
X-RateLimit-Degraded: true

It means our rate-limit store is unavailable and each of our servers is enforcing your limit on its own, at half the configured value. Your pipelines keep running; the other three headers are still accurate for the server that answered, but they no longer describe one shared budget. It clears by itself.

If you downgrade, keys configured above your new ceiling are not rewritten — they are served at the ceiling instead, and the Credentials tab shows the enforced number with a capped from … badge beside it. Upgrade again and the stored value applies again on its own.

A limit is a blast-radius control, not a performance setting: it caps how much a leaked credential can extract before anybody notices — which is why it is counted in secrets. Raise it when a legitimate job genuinely needs more, not to make an alert stop.

Before it expires#

You are warned, escalating, on the identity that owns the key:

When What happens
30 days out first notification — schedule the rotation
14 days out reminder
3 days out urgent
the day it lapses "API key expired" — so a pipeline failing at 03:00 has an obvious cause

Notifications go to the space owner (or the organization's owner for an organization-owned identity) and link straight to the machine identity. The same dates appear as API_KEY_EXPIRY markers on the Security Calendar, where you can turn one into a task.

The identity page also carries a banner listing credentials inside that window, and tints the Expires column: amber from 14 days out, red at 3 days or already lapsed.

Extending keeps the same token

Changing the lifetime under Settings does not change the credential. Nothing gets redeployed, no overlap window is needed, and CI never notices. Reach for a full rotation when the token itself is suspect — not when a date is simply too close.

Was this page helpful?