Skip to main content
Programmatic key rotation lets you replace an API key via the Rotate API Key endpoint. The new key is provisioned programmatically, but an authorized approver must still approve the rotation in the Prime UI before it activates.

How rotation works

  1. Request rotation — Call POST /v1/api-keys/rotate. The response returns an encrypted_credentials payload and an activity_id.
  2. Decrypt the response — Decrypt encrypted_credentials using your secret_key to retrieve the new key credentials. See Decrypting the response.
  3. Approve in Prime UI — One or more authorized approvers must approve the rotation activity in the Prime UI, depending on your consensus policy. Once all required approvals are met, the new key activates.
  4. Transition period — For duration_seconds after approval, both keys are active. Set duration_seconds: 0 to expire the old key immediately on approval.
  5. Swap credentials — Update your integration with the new credentials before the transition period ends.

Operational guidance

When to rotate
  • Expiring keys — Rotate at least 24 hours before expiry. If the key expires before the rotation activity is approved, the rotation fails and you will need to create a new key manually through the Prime UI.
  • Non-expiring keys — Rotate periodically as part of your security hygiene.
Choosing duration_seconds
  • Set to 0 for an immediate cutover — the old key expires the moment the rotation is approved. Use this when all services can be updated atomically.
  • Set a grace period long enough to redeploy every service that uses the key. Both keys remain active during this window, so you can roll out new credentials without downtime. The maximum is 30 days.

Required scope

The invoking key must have the Rotate API Key scope enabled. This scope is found under the API Key Management section when creating a key in the Prime UI and cannot be changed after creation.

Decrypting the response

encrypted_credentials is encrypted with HKDF-SHA256 + AES-256-GCM using your secret_key.

Wire format

After base64-decoding:
version(1 byte) | salt(32 bytes) | nonce(12 bytes) | ciphertext+tag(plaintext_len + 16 bytes)
  • version — Always 0x01. Reject any other value.
  • salt — Random 32-byte HKDF salt.
  • nonce — Random 12-byte AES-GCM nonce.
  • ciphertext+tag — AES-256-GCM encrypted payload with the 16-byte authentication tag appended. Pass the entire value to your GCM decrypt function.
HKDF-SHA256 key derivation parameters:
ParameterValue
ikmsecret_key.encode() — raw bytes of your secret key string
salt32-byte salt from the wire format
infoapi-key-rotation
length32
After decryption, the plaintext is a JSON object:
{
  "access_key": "...",
  "secret_key": "...",
  "passphrase": "...",
  "service_account_id": "..."
}

Decryption example

import base64
import json

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.primitives.kdf.hkdf import HKDF

HKDF_INFO          = b"api-key-rotation"
VERSION_LEN        = 1
SALT_LEN           = 32
NONCE_LEN          = 12
SUPPORTED_VERSIONS = {1}


def parse_wire(encrypted_b64: str) -> tuple[bytes, bytes, bytes]:
    raw     = base64.b64decode(encrypted_b64 + "==")  # pad-tolerant
    version = raw[0]
    if version not in SUPPORTED_VERSIONS:
        raise ValueError(f"unsupported wire version: {version}")
    salt       = raw[VERSION_LEN:VERSION_LEN + SALT_LEN]
    nonce      = raw[VERSION_LEN + SALT_LEN:VERSION_LEN + SALT_LEN + NONCE_LEN]
    ciphertext = raw[VERSION_LEN + SALT_LEN + NONCE_LEN:]
    return salt, nonce, ciphertext


def decrypt(secret_key: str, encrypted_b64: str) -> dict:
    salt, nonce, ciphertext = parse_wire(encrypted_b64)
    ikm     = secret_key.encode()
    aes_key = HKDF(algorithm=SHA256(), length=32, salt=salt, info=HKDF_INFO).derive(ikm)
    plaintext = AESGCM(aes_key).decrypt(nonce, ciphertext, None)
    return json.loads(plaintext)


creds = decrypt(
    secret_key=SECRET_KEY,
    encrypted_b64=response["encrypted_credentials"],
)
# Store creds["access_key"], creds["secret_key"], and creds["passphrase"]

Monitoring approval

Use the activity_id from the response to track approval status via Get Activity or List Activities.

Error cases

HTTPMessageCause
400duration must be within allowable timespanduration_seconds exceeds the 30-day maximum.
400Input duration exceeds current api key expiryduration_seconds would keep the old key active past its expiry date.
400API key is already rotated.A rotation is already pending approval. Reject the existing activity via the Prime UI before initiating a new one.
500Sorry, something went wrong.Unrecoverable error. If your key has expired, create a new key manually through the Prime UI.