Identity & Access Management

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

POST
/scopes/{scopeId}/iam/service-accounts/{serviceAccountId}/keys

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).

Authorization

bearerToken
headerAuthorizationBearer <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.

Path Parameters

scopeId*string

Opaque ownership+authorization scope resolving to tenant + subscription + project.

serviceAccountId*string

Service account identifier.

Header Parameters

Idempotency-Key?string

Optional; 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.

Formatuuid

Request Body

application/json
  1. 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.

Items1 <= items

Response Body

Key created. The credential payload is revealed exactly once and never retrievable again.

application/json
  1. 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"  }}