> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# BYOK rotation and lifecycle runbook

> The operational procedure for customer-managed keys: pre-rotation checks, the rotate and activate sequence over the API, what in-flight traffic sees, every failure mode that aborts a rotation, and recovery from a revoked key.

# BYOK rotation and lifecycle runbook

This runbook walks a real key rotation end to end: what to verify in your
KMS before you start, the exact API sequence, what your traffic sees while
you rotate, the failure modes that abort a rotation, and how to recover
from a revoked key. The [Customer-Managed Keys (BYOK)
reference](/compliance/byok-customer-managed-keys) documents the state
machine, the endpoint surface, and the provider grammar; this page is the
operational procedure you follow during a rotation window.

<Warning>
  **When NOT to BYOK:** if your obligation is simply "encryption at rest,"
  platform-managed encryption already covers it; adopt BYOK only when a
  policy, contract, or addendum requires your organization to hold the
  governing key reference in your own KMS.
</Warning>

***

## 1. Lifecycle refresher

A registered key moves through three states; rotation is available from
`pending` and `active` alike and never changes the state it finds:

| From | Action | Endpoint | To |
| - | - | - | - |
| none / `revoked` | register | `PUT /compliance/byok` | `pending` |
| `pending` | activate (optional `enforce`) | `POST /compliance/byok/activate` | `active` |
| `pending` / `active` | rotate | `POST /compliance/byok/rotate` | same state, `rotation_count` + 1 |
| `pending` / `active` | revoke | `POST /compliance/byok/revoke` | `revoked` |

Two rules shape every procedure below:

* **A live key is rotated, never replaced.** `PUT /compliance/byok` only
  accepts a new reference when no key exists or the previous one is
  `revoked`; against a `pending` or `active` key it returns
  `409 BYOK_KEY_ALREADY_REGISTERED` and points you at rotate.
* **A revoked key is never rotated.** Rotate on a revoked key returns
  `409 BYOK_REVOKED`; re-register afresh with `PUT` instead.

Every transition writes an audit-log entry, so the audit trail is your
source of truth for what happened during the window.

***

## 2. Pre-rotation checklist

Complete all three checks before calling rotate. The rotation endpoint
never calls your KMS; every check here is on your side of the boundary.

1. **The new key exists and matches the expected pattern.** Copy the
   canonical reference for your provider and confirm it is the same
   shape, region, and account your policy expects. An ARN from a
   different AWS region or account is a different key reference; a
   mistyped one fails validation with
   `409 BYOK_INVALID_KEY_REFERENCE`.

   | Provider | Canonical shape |
   | - | - |
   | AWS KMS | `arn:aws:kms:<region>:<account>:key/<uuid>` |
   | Google Cloud KMS | `projects/<project>/locations/<location>/keyRings/<ring>/cryptoKeys/<key>` |
   | Azure Key Vault | `https://<vault>.vault.azure.net/keys/<name>/<version>` |
   | HashiCorp Vault | `https://<vault-host>/v1/transit/keys/<name>` |

2. **Orbit's service identity has decrypt access on the new key.** Grant
   the same access you granted at activation (an AWS key policy
   statement, a Google Cloud IAM binding on the CryptoKey, an Azure Key
   Vault access policy, or a Vault policy) before you rotate, not after.

3. **Know your `enforce` position.** If the current key is enforced,
   rotation re-asserts enforcement by unwrapping the internal
   data-encryption key under the old reference and re-wrapping it under
   the new one in the same call. That unwrap needs the old key to still
   be usable, so keep the old key enabled and the grant in place until
   rotate returns success.

***

## 3. Rotation, step by step

All endpoints are rooted at
`https://api.orbit.devotel.io/api/v1/compliance/byok`.

**Step 1 — Read the current state (`GET /compliance/byok`).**

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/compliance/byok \
  -H "X-API-Key: $ORBIT_API_KEY"
```

Note `config.state`, `config.enforced`, and `config.rotation_count` so you
can confirm the change afterwards. If `config` is `null`, no key is
registered: use the register path in the worked GCP sequence below
instead of rotate.

**Step 2 — Rotate to the new reference.**

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/byok/rotate \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "aws_kms",
    "key_reference": "arn:aws:kms:us-east-1:111122223333:key/5678efab-56ef-78ab-90cd-5678901234ab",
    "justification": "Annual key rotation, policy K-12"
  }'
```

An `active` key stays `active`; a `pending` key stays `pending`. The
response returns the updated view with the new fingerprint.

**Step 3 — Activate, only if the key is `pending` and you are ready.**
Pass `enforce: true` only after checklist item 2 is verified for this
key; see [What `enforce` is](#4-what-enforce-is-and-is-not).

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/byok/activate \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enforce": true }'
```

**Step 4 — Confirm.** Re-run the `GET` from step 1 and check that
`rotation_count` incremented by one, `rotated_at` moved, and the
fingerprint changed. Then, and only then, retire the old key in your KMS.

### Worked sequence: rotating an active AWS KMS key

```bash theme={null}
# 1. State before: active, enforced, rotation_count 2
curl https://api.orbit.devotel.io/api/v1/compliance/byok \
  -H "X-API-Key: $ORBIT_API_KEY"

# 2. Rotate (old key stays enabled in AWS until step 3 succeeds)
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/byok/rotate \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "aws_kms",
    "key_reference": "arn:aws:kms:us-east-1:111122223333:key/5678efab-56ef-78ab-90cd-5678901234ab"
  }'

# 3. State after: active, enforced, rotation_count 3, new fingerprint
curl https://api.orbit.devotel.io/api/v1/compliance/byok \
  -H "X-API-Key: $ORBIT_API_KEY"
```

No activate call is needed: rotation preserves `active` and preserved
enforcement.

### Worked sequence: re-registering a Google Cloud KMS key after revoke

```bash theme={null}
# 1. Register the fresh key (PUT is open because the old one is revoked)
curl -X PUT https://api.orbit.devotel.io/api/v1/compliance/byok \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "gcp_kms",
    "key_reference": "projects/acme/locations/europe-west1/keyRings/orbit/cryptoKeys/tenant-data-2027",
    "justification": "Re-registration after annual revoke"
  }'

# 2. Activate once the IAM binding for Orbit's service identity is in place
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/byok/activate \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enforce": true }'

# 3. Confirm: state active, enforced true, rotation_count reset with the new registration
curl https://api.orbit.devotel.io/api/v1/compliance/byok \
  -H "X-API-Key: $ORBIT_API_KEY"
```

***

## 4. What `enforce` is (and is not)

`enforce` is a **compliance-evidence assertion** recorded at activation:
"this customer key governs our organization's key-management policy." It
is not a runtime data re-encryption. Tenant data at rest stays under
platform-managed encryption in every state, and activation, rotation, and
revocation all complete without touching stored data or in-flight
traffic.

What enforcement does change is the failure posture of BYOK-consulting
field-encryption calls: with `enforce` on they fail closed with
`409 BYOK_KEY_UNAVAILABLE` when the key is revoked, unprovisioned, or its
wrapped key is unrecoverable, instead of falling back silently to the
platform key.

Two operating rules follow:

* **Do not set `enforce` unless your KMS access policy is verified.**
  Enforcement turns a KMS misconfiguration into a hard refusal by
  design; confirm the service-identity grant first.
* **Rotation is not the way to clear enforcement.** Enforcement survives
  rotation (that is the re-wrap in checklist item 3). The only exit that
  clears it is revocation, below.

***

## 5. Failure modes

| What you see | Cause | Recovery |
| - | - | - |
| `409 BYOK_INVALID_KEY_REFERENCE` | The reference does not match the provider's grammar. The common case is one provider's shape sent under another provider code: an AWS ARN with `provider: "gcp_kms"`, a GCP resource name shaped as an ARN, an Azure Key Vault URI missing `/keys/`, or a Vault path missing `/v1/transit/keys/`. | Copy the canonical `keyReferenceFormat` and `example` for your provider from the `providers` array in the `GET` response, correct the reference, retry. |
| `409 BYOK_KEY_ALREADY_REGISTERED` | `PUT` used while a key is `pending` or `active`. | Use `POST /compliance/byok/rotate`; `PUT` is for first registration and re-registration after revoke only. |
| `409 BYOK_REVOKED` | Rotate called on a revoked key. | Register a fresh key with `PUT`, then activate. |
| `409 BYOK_REWRAP_FAILED` | The key is enforced and the existing wrapped data-encryption key could not be unwrapped. Typical cause: the service identity lost decrypt access on the old key (KMS policy lockout, deleted key version, suspended KMS account) before rotation ran, or the old key was disabled in the KMS before rotate completed. | Restore the grant or re-enable the old key in your KMS, then retry the rotation. If the old key is unrecoverable, revoke and re-register. |
| `409 BYOK_KEY_UNAVAILABLE` | Enforced org, and the key is revoked, unprovisioned, or unrecoverable when a BYOK-consulting field-encryption call runs. | Fix the key state in your KMS and re-activate, or revoke to clear enforcement. |
| `409 BYOK_NOT_PENDING` | Activate called on an `active` or `revoked` key. | Nothing to do for `active`; re-register after `revoked`. |
| `404 BYOK_NOT_FOUND` | Rotate, activate, or revoke called with no key registered. | Register first with `PUT`. |

**Rotated key orphaned.** If the new reference passes validation but the
key it points at is already scheduled for deletion or lives in a recycled
region, rotation succeeds at the control plane and fails later at the
enforcement seam. Verify the new key is live in your KMS (checklist item

1. before rotating; the fingerprint Orbit records identifies only the
   reference, not the key's health.

**Enforce stuck on after a mistaken activate.** Revocation is the
deliberate exit ramp: `POST /compliance/byok/revoke` clears enforcement
and destroys the wrapped data-encryption key. It is irreversible for that
registration, and it does not re-encrypt or otherwise affect stored
tenant data. Re-register afresh when you are ready.

***

## 6. Revocation and recovery

Revoke withdraws the customer key: it clears enforcement from your
recorded policy and destroys the wrapped data-encryption key so it can
never be unwrapped again.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/byok/revoke \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "justification": "Key retired after annual rotation policy" }'
```

**In-flight traffic: no impact.** Revocation does not re-encrypt or
crypto-shred anything; platform-managed encryption remains authoritative
for tenant data at rest throughout, so no stored field becomes unreadable
and no request fails because of the revoke itself.

Recovery is re-registration from scratch: `PUT` a new key (the revoked
config no longer blocks registration), run the pre-rotation checklist,
and activate. Revoking an already-revoked key returns
`409 BYOK_ALREADY_REVOKED`.

***

## Roles and visibility

Writes (`PUT`, and the `activate` / `rotate` / `revoke` posts) require the
**owner** or **admin** role, in the dashboard and via the API. `GET` is
available to any authenticated member of the organization, and read views
echo a truncated fingerprint, never the raw ARN or vault path, so a
viewer cannot learn the exact key reference.

***

## See also

* [Customer-Managed Keys (BYOK) reference](/compliance/byok-customer-managed-keys) — state machine, endpoint surface, and provider grammar.
* [BYOK dashboard walkthrough](/guides/compliance-byok-keys) — the same lifecycle from Settings → Compliance.
* [Error codes reference](/reference/error-codes) — every `409 BYOK_*` conflict code.
* [Audit log](/guides/audit-log) — filter by fingerprint to reconstruct the rotation window.
* [Compliance evidence binder](/compliance/evidence-binder) — cite the resolved posture as evidence.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.