---
title: "Regenerate an API key (\"same key, new secret\")"
method: POST
path: "/api/v1/api-keys/{id}/regenerate"
tags: ["ApiKeys"]
---

# Regenerate an API key ("same key, new secret")

`POST /api/v1/api-keys/{id}/regenerate`

## Request

- Base URL: `https://app.lastping.dev`
- URL: `https://app.lastping.dev/api/v1/api-keys/{id}/regenerate`
- Auth: API key in header `X-Api-Key`

Issues a replacement credential under the same name and scope, then
revokes the original. Returns 201 with the plaintext replacement
key exactly once, the same one-time delivery `POST /api/v1/api-keys`
uses.

**Order**: the replacement is created FIRST, then the original is
deleted — the reverse would lose the caller's access entirely if
creation failed.

**Expiry policy**, not remaining time: a key that never expired
stays non-expiring; a key that had any expiry gets a fresh 90-day
window from now, not the old key's remaining balance.

**Non-cascading**: unlike `DELETE /api/v1/api-keys/{id}`, this does
NOT revoke keys the original minted. Regenerating is "same key, new
secret", not "this credential and everything descended from it is
dead": keys the original minted stay alive with a null
`created_by_key_id`.

A key belonging to another project is indistinguishable from a
missing one — both return 404.

Requires `admin` scope, the same tier as the other three
`/api/v1/api-keys` routes: key management is the one capability
that lets a compromised credential outlive its own revocation.

**Caller caps, when the caller is itself an API key** (the key
creation rule, extended to key rotation): the replacement's
`expires_at` is capped to the caller key's own expiry (silently —
whichever is earlier of the policy-derived value above and the
caller's own ceiling, "never" acting as +infinity), and the
replacement's scope must not exceed the caller key's own scope —
that one is refused outright (403, not silently narrowed) rather
than handing back a less powerful key than the target had. A
session-authenticated caller (the dashboard) gets neither cap.

The MCP tool `regenerate_api_key` calls this route. Terraform has
no equivalent, deliberately: its model for rotating a key it
manages is `taint`/recreate.

## Response `201`

Replacement key created. The original has been revoked unless
`old_key_revoked` is `false`.

- ApiKeyRegenerated: An API key returned by the API. The plaintext key value is NEVER included here — only the non-secret `prefix` is returned, so this shape is used for both list and the non-secret half of create.
  - `id` string, uuid, required: Unique API key identifier.
  - `name` string, required: Human-readable label for the key.
  - `prefix` string, required: First 10 characters of the key — a non-secret identifier shown in place of the full value.
  - `created_at` string, date-time, required: RFC 3339 UTC timestamp when the key was created.
  - `expires_at` string, date-time: RFC 3339 UTC expiry timestamp, or omitted for a key that never expires.
  - `last_used_at` string, date-time: RFC 3339 UTC timestamp of the most recent request this key authenticated. Omitted if the key has never been used since this field was added — never rendered as a zero/epoch time.
  - `last_used_surface` 'mcp' | 'terraform' | 'api' | 'ingest': Which client surface last used this key, omitted if unknown. `ingest` means the latest use was sending telemetry (traces, metrics or logs); telemetry use is recorded about once a minute per key, so `last_used_at` can trail the newest export by up to a minute. For `mcp`, `terraform` and `api` the value is best-effort and spoofable: derived from the caller-controlled `User-Agent` header on the authenticating request, not from any cryptographic or protocol-level signal. Useful for an honest "did my client ever successfully authenticate?" indicator — never use it for authorization or access control.
  - `scope` 'read' | 'write' | 'admin' | 'ingest', required: What this key is permitted to do. Always present. Unlike `last_used_surface` above — which is a spoofable hint derived from a caller-supplied header — this is the value the server actually checks. `ingest` sends telemetry only and cannot call this API.
  - `check_id` string, uuid: The one monitor an `ingest` key is bound to. Omitted for an unbound key and for every key of another scope.
  - `created_by_key_id` string, uuid: The API key that minted this one, omitted for a key created from the dashboard (which has no parent key) or whose parent has since been deleted. **Revoking a key revokes every key below it in this chain.** A key minted by a key minted by the key you delete goes with it, all at once. A key created from the dashboard is never below anything and is unaffected.
  - `key` string, required: The plaintext replacement key. Store it immediately — it cannot be retrieved again.
  - `old_key_revoked` boolean, required: `true` when the original key was deleted and no longer works. `false` when the replacement was created but deleting the original failed: the original still works and is still listed by `GET /api/v1/api-keys`, so revoke it by id with `DELETE /api/v1/api-keys/{id}`. The response is `201` either way, because the replacement key works.

## Other responses

- `400`: id is not a valid UUID.
- `401`: Missing or invalid API key, or (a narrow race) the API key that authenticated this request was itself revoked while the request was being handled.
- `403`: Either the authenticating key's scope tier does not reach this route at all (see `InsufficientScope`, `required_scope` in the problem body), or the target key's scope outranks the CALLING key's own scope. The latter carries the same detail text and `max_scope` extension member as `POST /api/v1/api-keys`'s identical create-time refusal: `scope may not exceed the creating key's own scope`.
- `404`: API key not found in this project.
- `429`: Too many requests. The per-principal fixed-window rate limit has been exceeded. Retry after the number of seconds indicated by the `Retry-After` response header. The problem body's `code` is `RATE_LIMITED`. It names this limiter specifically, so a client can tell it apart from the application-level throttles that also answer 429 with their own codes and their own, much longer, `Retry-After` values (`TEST_PING_RATE_LIMITED`, `TOO_MANY_ATTEMPTS`). Back off globally on `RATE_LIMITED`; back off on the one operation for the others. The principal is the API key for a key-authenticated request and the USER for a browser session, so every tab one person has open shares one budget.

---

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