---
title: "Rotate API Key"
method: POST
path: "/service-accounts/{id}/api-keys/{keyId}/rotate"
tags: ["service-accounts"]
---

# Rotate API Key

`POST /service-accounts/{id}/api-keys/{keyId}/rotate`

## Request

- Base URL: `https://api.instruxi.dev/api/v1/enforcer`
- URL: `https://api.instruxi.dev/api/v1/enforcer/service-accounts/{id}/api-keys/{keyId}/rotate`
- Auth: one of:
  - API key in header `Authorization`
  - API key in header `X-API-Key`

Mints a replacement key carrying the outgoing key's name/scope/expiry, and pulls the outgoing key's own expiry in to now+grace_seconds (0..86400, default 0) instead of revoking it outright — a grace of 0 stops it on its very next use.

## Path parameters

- `id` string, required
- `keyId` string, required

## Request body

- InternalDeliveryHttpHandlerRotateAPIKeyRequest
  - `grace_seconds` integer: GraceSeconds is how long the OUTGOING key keeps working, 0..86400 (default 0 — AGENTS_CONTRACT §6.1).

## Response `200`

OK

- InternalDeliveryHttpHandlerServiceAccountKeyRotatedResponse
  - `data` InternalDeliveryHttpHandlerServiceAccountKeyRotated
    - `api_key` EnforcerV3InternalDomainApikeyAPIKey
      - `account_id` string
      - `active` boolean
      - `created_at` string
      - `created_by` string: CreatedBy is the account that minted the key: provenance, never authority. Equal to AccountID for a self-minted key; nil for keys minted before 000111.
      - `expires_at` string: ExpiresAt is when the key stops authenticating (000111). nil — every key minted before the column existed — never expires. Both transports refuse an expired key through Expired, so the rule cannot differ between them.
      - `id` string
      - `key_prefix` string
      - `last_used_at` string
      - `name` string
      - `scope` string: Scope narrows what the key may do below its owner's grant. nil is the only value existing keys carry and means unnarrowed — the key acts with the account's full permissions, exactly as before the column existed. Omitted from JSON when nil, so a response for an unscoped key is byte-identical to what it was.
      - `tenant_id` string
      - `updated_at` string
    - `key` string
    - `previous` InternalDeliveryHttpHandlerPreviousKeyView
      - `expires_at` string
      - `id` string
  - `success` boolean

## Other responses

- `400`: invalid_grace_seconds
- `401`: Unauthorized
- `404`: Not Found

---

[API](https://skmtc.dev/instruxi/apis/enforcer-api.md) · [All operations](https://skmtc.dev/instruxi/apis/enforcer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/instruxi/apis/enforcer-api/revisions/fa82bc1becc0?raw)
