# POST /v1/api-keys/:keyId/rotate (/docs/api/reference/api-keys/rotate)



<Endpoint method="POST" path="/v1/api-keys/{keyId}/rotate" auth="Bearer" phase="1" />

**Planned rotation.** Replaces both the prefix and the hashed secret. The old secret is instantly invalid - any consumer using it gets `401 UNAUTHENTICATED` on the next request. Rotation also re-activates the key: `kill_switch` is cleared, `revoked_at` is nulled, `is_active` flips back to `true`.

Use this for calendar-driven rotation (quarterly / annual), post-leak recovery after a `kill`, or bootstrapping a fresh secret for a rotation drill. For active incident response where you want the key dead *immediately* and recoverable only by a human, use [`/kill`](/docs/api/reference/api-keys/kill).

The new plaintext secret is returned **exactly once** in the response body. Store it immediately - Layers cannot retrieve it again.

<Parameters
  title="Path"
  rows="[
  { name: 'keyId', type: 'string (UUID)', required: true, description: 'The api_keys.id of the key to rotate. Must belong to the same org as the calling key.' },
]"
/>

<Parameters
  title="Headers"
  rows="[
  { name: 'Idempotency-Key', type: 'string (UUID)', required: true, description: 'Strongly recommended. Replays return the SAME new secret - without this, a network retry silently issues two rotations and leaves you with the wrong secret.' },
]"
/>

## Example [#example]

```bash
# Quarterly rotation
NEW_KEY=$(curl -s -X POST https://api.layers.com/v1/api-keys/c2037bb9-354d-4662-96b7-97a28ad6b6e1/rotate \
  -H "Authorization: Bearer $LAYERS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" | jq -r .secret)

# Deploy NEW_KEY to your services, then verify:
curl https://api.layers.com/v1/whoami -H "Authorization: Bearer $NEW_KEY"
```

<Response status="200" description="OK - key rotated, new secret returned once">
  ```json
  {
    "apiKey": {
      "id": "c2037bb9-354d-4662-96b7-97a28ad6b6e1",
      "organizationId": "org_2481fa5c-a404-44ed-a561-565392499abc",
      "name": "production-service",
      "prefix": "lp_...",
      "killSwitch": false,
      "isActive": true,
      "rotatedAt": "2026-04-20T18:14:02.187Z",
      "revokedAt": null
    },
    "secret": "lp_...",
    "warning": "Store this secret now. It cannot be retrieved again. Rotate the key if it's lost."
  }
  ```
</Response>

## Rotation runbook [#rotation-runbook]

1. **Call `/rotate`** with a fresh `Idempotency-Key`. Grab `secret` from the response body.
2. **Deploy the new secret** to every service that uses the old one. Do this BEFORE relying on the new key - the old secret is already dead.
3. **Verify** with `GET /v1/whoami` using the new secret. A 200 confirms end-to-end.
4. **Audit** via [`GET /v1/audit-log?eventType=api_key.rotated`](/docs/api/reference/audit-log/list) - the rotation is recorded with the calling key + target key + request id.

Idempotency is load-bearing here. If step 1 times out and you retry without the same `Idempotency-Key`, you'll mint a second rotation and your first `secret` becomes stale. Always pass an `Idempotency-Key`.

## Errors [#errors]

| Status | Code                   | When                                                 |
| ------ | ---------------------- | ---------------------------------------------------- |
| 404    | `NOT_FOUND`            | Key ID doesn't exist, or belongs to a different org. |
| 409    | `IDEMPOTENCY_CONFLICT` | Idempotency-Key reused with a different body.        |
| 422    | `VALIDATION`           | Missing `:keyId`.                                    |

## See also [#see-also]

* [Kill](/docs/api/reference/api-keys/kill) - emergency, one-way; use this when the old secret is compromised.
* [Delete](/docs/api/reference/api-keys/delete) - retire a key cleanly without replacing it.
* [API keys concept](/docs/api/concepts/api-keys) - lifecycle diagram.
* [Audit log](/docs/api/reference/audit-log/list) - `api_key.rotated` events.
