Preview — pending review
This reference is generated from the platform API contract and is still under review. Operations, fields, and descriptions can change before publication.
Create a long-lived key and reveal its credential once
Key scopes: the optional body names the key's scopes. With no body (or no
scopes) the key gets the default key scopes: the whole ApiTokenScope catalogue minus iam:write
(the same set keys minted before scopes existed were given). An explicit empty list is 422
VALIDATION_FAILED (field scopes). A service-account key follows the API-token scope model: it acts only within its scopes AND its account's
role bindings. It carries the same ApiTokenScope catalogue as a token minus iam:write --
requesting iam:write is 422 VALIDATION_FAILED (field scopes, details.reason
iam_write_not_grantable_to_service_account_key). A service-account key gets 403
INTERACTIVE_SESSION_REQUIRED on EVERY operation that requires iam:write (role grants, invites,
minting tokens or keys, authentication policy -- this operation included): those need an interactive
console session with two-factor step-up.
Token callers: a pc_live_ token may only mint a key whose scopes (explicit or
defaulted) it holds itself -- otherwise 403 FORBIDDEN TOKEN_SCOPE_MISSING (missingTokenScopes); the caller (for a
token: its creator's CURRENT role) must also dominate every binding of the account. A key has no IP
allowlist, so a token with an ipAllowlist cannot mint one (403 TOKEN_FENCE_EXCEEDED); a key minted by
an expiring token expires no later than that token.
Mints a new long-lived authentication key for the service account (UI "Create key" on the detail header,
Keys tab and list row menu). The response is the ONLY place the private-key credential JSON (type
service_account, account email, private_key_id fingerprint, PEM private key, token URI) is ever returned —
the UI shows it in a reveal-once dialog with "Download JSON" and it cannot be retrieved again afterwards.
The new key starts active, expires after the 365-day maximum lifetime, and the account's keyCount and
key-hygiene signals update immediately.
Validation: account must exist and be visible in the scope (else 404 NOT_FOUND); account must not be
mid-deletion (else 409 CONFLICT); per-account key quota is enforced — the UI already warns above 2 keys and
the backend rejects exceeding the hard limit → 409 QUOTA_EXCEEDED; caller must hold a key-management role
(owner|admin) else 403 FORBIDDEN.
Async & state: Synchronous create — 201 with the key metadata plus the one-time credential. Key state
machine thereafter: active → expiring (≤30 days remaining) → expired at 365 days, or revoked via the revoke
action. The account's keyCount and oldestKeyAgeDays hygiene counters update immediately.
Errors: 401 unauthenticated; 403 lacks key-management permission; 404 unknown account; 409 key quota
exceeded (the account already holds the maximum of 5 live keys); 500 key-minting failure.
Idempotency/Concurrency: Idempotency-Key is optional; when sent it is reserved
atomically (single-winner) — replaying
the same key returns the byte-identical response including the same one-time credential envelope, and never
mints a second key.
Step-up: from a console session whose account has two-factor authentication, the
factor must have been verified in the last 5 minutes, otherwise 403 MFA_STEP_UP_REQUIRED
(verify with POST /auth/step-up, then retry). An API token is not asked; a service-account key is
refused (403 INTERACTIVE_SESSION_REQUIRED).
bearerTokenAuthorizationBearer <token>An API token (pc_live_…) or a service-account key (sak_live_…), created in Identity & Access Management and sent as Authorization: Bearer <token>. Requests are rate limited per credential and per tenant: every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, and a refused request gets 429 RATE_LIMITED with Retry-After.
scopeId*stringOpaque ownership+authorization scope resolving to tenant + subscription + project.
serviceAccountId*stringService account identifier.
Idempotency-Key?stringOptional; recommended for safe retries. When sent, the same key+body within 24h replays the original result and the same key with a different body is 409 CONFLICT; keys are unique per tenant. When an API token or service-account (bearer) caller omits it, the request is applied once with no replay protection (a retry may apply it again). A console session gets one from the edge.
uuidapplication/json- body
Key-create payload, optional: the action scopes the new key acts within, on top of the account's role bindings. The ApiTokenScope catalogue minus iam:write (422 if requested). Omitted body or scopes = the default key scopes (the catalogue minus iam:write).
scopes?array<>Non-empty when given (an explicit empty list is 422). Omitted = the catalogue minus iam:write.
1 <= itemsKey created. The credential payload is revealed exactly once and never retrievable again.
application/json- response
Key-create response with the reveal-once credential.
key*Long-lived key metadata. Private key material is never present here — it is revealed exactly once at creation.
credential*One-time private-key credential JSON. Returned ONLY by service-account create (with createKey) and key create; never retrievable afterwards and never present in GET/list/export responses.
curl -X POST "https://example.com/scopes/string/iam/service-accounts/string/keys" \ -H "Content-Type: application/json" \ -d '{}'{ "key": { "id": "string", "fingerprint": "string", "createdAt": "2019-08-24T14:15:22Z", "lastUsedAt": "2019-08-24T14:15:22Z", "expiresAt": "2019-08-24T14:15:22Z", "status": "active", "ageDays": 0, "scopes": [ "compute:read" ] }, "credential": { "type": "service_account", "account": "string", "account_id": "string", "private_key_id": "string", "private_key": "string", "secret": "string", "token_uri": "string", "auth_provider": "string", "universe_domain": "string", "region": "string" }}