---
title: "Rotate a caller key – Superbot API"
canonical_url: "https://superbot.gg/developers/keys/rotateKey"
summary: "Replace a key with a new one of the same shape (name, scopes, route, mode, caps, models, pins, aliases, default model, incognito, the person it is for, the device that minted it) and revoke the old one. The new key is minted first and the old one revoked after, so a failure never leaves the account without a working key. The reply carries the new key in plaintext once (`key`), its row, and `revoked`, the id of the key revoked (`null` if that revoke failed after the mint: the new key is live, so revoke the old one with `DELETE /v1/keys/{id}`). No body. A key that is revoked already answers 409 `revoked`; an id this account does not hold answers 404 `not_found`; a team key answers 400 `team_key` (rotate it on the team pane). A person key is rotated by the account owner only; a caller key that tries answers 403 `caller_key_forbidden`. The new key has made no calls yet, so it lists under Zapier or n8n again with its first call."
---

> Markdown of https://superbot.gg/developers/keys/rotateKey. Site index: /llms.txt.

# Rotate a caller key

`POST /v1/keys/{id}/rotate`

Replace a key with a new one of the same shape (name, scopes, route, mode, caps, models, pins, aliases, default model, incognito, the person it is for, the device that minted it) and revoke the old one. The new key is minted first and the old one revoked after, so a failure never leaves the account without a working key. The reply carries the new key in plaintext once (`key`), its row, and `revoked`, the id of the key revoked (`null` if that revoke failed after the mint: the new key is live, so revoke the old one with `DELETE /v1/keys/{id}`). No body. A key that is revoked already answers 409 `revoked`; an id this account does not hold answers 404 `not_found`; a team key answers 400 `team_key` (rotate it on the team pane). A person key is rotated by the account owner only; a caller key that tries answers 403 `caller_key_forbidden`. The new key has made no calls yet, so it lists under Zapier or n8n again with its first call.

- Operation id: `rotateKey`
- Group: [Keys](https://superbot.gg/developers/keys.md)
- Auth: A caller key holding `keys:write` (`Authorization: Bearer sbc_…` or `x-api-key: sbc_…`), or a signed-in session.
- Scopes: `keys:write`

## Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | required |  |

## Response 201

The new key, once.

`application/json`: RotatedKey.

- `key` (string, required): The new key in plaintext. Shown once.
- `row` (CallerKey, required)
  - `id` (string, required)
  - `user_id` (string, required)
  - `name` (string, required): What the tool calls itself, 1-64 chars.
  - `mask` (string, required): The only form shown after mint.
  - `created_at` (string (date-time), required)
  - `last_used_at` (string (date-time))
  - `revoked_at` (string (date-time))
  - `daily_limit_tokens` (number): Daily spend cap in whole credits.
  - `monthly_limit_tokens` (number): Monthly spend cap in whole credits.
  - `caps_unit` (string): One of `credit`.
  - `allowed_models` (array of string): Glob allow-list over model names; absent = every model. With models_listed: the exact canonical ids the key may dial, and empty = none.
  - `models_listed` (boolean): true = the listed reading: allowed_models are literal canonical ids (no globs), the key lists exactly those as its per-app picker names, and an empty list allows nothing.
  - `model_pins` (object): Canonical model id to the stable account id it is pinned to: a pinned model dials only that account, and one id pinned to two accounts gets two picker names.
  - `model_aliases` (object): Canonical model id (a member of allowed_models) to the custom name its picker shows: 1-64 characters of letters, numbers, dots, dashes or colons; no Cursor routing word (claude, gemini, anthropic, sonnet-, opus-, haiku-, grok, composer); not a house id (superbot, superbot-fast, superbot-omni); unique among this key's forwards; not another model's id. A refused name stores nothing. A custom name replaces the default name for that model.
  - `app` (string): Forwarding app this listed key's model names are spelled for: cursor, copilot-cli, cline, roo, qwen-code, hermes, openclaw, zazen, default. Set at creation, cannot be changed. Absent = cursor.
  - `default_model` (string): The model a call that names the bare house agent `superbot` (the default model of Zapier and n8n), or names no model, is answered by instead: `superbot-fast`, `superbot-omni` or a model of one of your own accounts. A call that names any other model is dialled as named. Absent = a call naming `superbot` gets `superbot`.
  - `route` (string, required): One of `byo`, `credits`, `auto`.
  - `mode` (string, required): One of `model`, `agent`.
  - `incognito` (boolean, required)
  - `device_id` (string)
  - `org_id` (string): Set on a team key.
  - `person_id` (string): Set on a key minted for a person: `per_` + 16 hex. Every key minted for the same person carries the same id. A person key has no device_id and no org_id, and is billed to the owner.
  - `person_name` (string): The person's display name, 1-64 characters. Present on every person key.
  - `person_email` (string): The person's email address, when the owner gave one.
  - `person_group` (string): A free-text grouping the owner sorts people by (a team, a class), 1-64 characters.
  - `prompt_history` (PromptHistory): What the owner told a person about their prompts, and how long they are kept. Absent on a key until the owner sets it: the person's prompts are then not visible to the owner and follow the account's own retention.
    - `visible_to_owner` (boolean, required): The owner may read this person's prompts.
    - `retention_days` (integer, required): Whole days the prompts are kept, 1-3650.
    - `attested_at` (string (date-time), required): ISO 8601 time the owner attested the person was told. The owner's own claim, stored as given and not verified; the answer normalises it to UTC.
  - `paused_at` (string (date-time)): Set while the owner holds this person: until a PATCH clears it the key answers 403 `key_paused` at the model gateway, the `/v1/keys` routes, the relay door and the Cosmos door; every other route does not recognise a held key and answers 401 `unauthorized`. Held is not revoked. It is written to every live key of the person.
  - `person_limit_usd` (number): The owner's monthly spend limit for this person, in US dollars to the cent. Set by the owner's `PUT /v1/keys/person/{person_id}/limit` (the Helper), written to every live key of the person, and absent while no limit is set.
  - `scopes` (array of Scope, required): The EFFECTIVE scopes: stored list or the legacy set, team keys capped.
- `revoked` (string | null, required): The id of the key that was revoked, or null if that revoke failed after the mint.

## Errors

Every refusal carries `error`, `message`, `doc_url` and `request_id`; the gateway routes answer in the dialect you dialed.

| Status | Codes | When |
| --- | --- | --- |
| 400 | [`invalid_request`](https://superbot.gg/developers#errors-invalid_request) | The request is malformed or a parameter is invalid; `param` names the field. |
| 401 | [`unauthorized`](https://superbot.gg/developers#errors-unauthorized) | The bearer is missing, malformed or unknown. |
| 403 | [`insufficient_scope`](https://superbot.gg/developers#errors-insufficient_scope) | The key lacks a scope (insufficient_scope), is revoked, or may not call this route. |
| 404 | [`not_found`](https://superbot.gg/developers#errors-not_found) | No such resource on this account. |
| 409 | [`conflict`](https://superbot.gg/developers#errors-conflict) | The resource is not in a state that allows this change. |

## Examples

```sh
curl -sS -X POST "https://superbot.gg/v1/keys/$ID/rotate" \
  -H "Authorization: Bearer $SUPERBOT_API_KEY"
```

```js
const id = 'ID';
const res = await fetch(`https://superbot.gg/v1/keys/${id}/rotate`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SUPERBOT_API_KEY}`,
  },
});
console.log(res.status, await res.json());
```

```python
import os
import requests

id = "ID"
res = requests.request(
    "POST",
    f"https://superbot.gg/v1/keys/{id}/rotate",
    headers={
        "Authorization": f"Bearer {os.environ['SUPERBOT_API_KEY']}",
    },
)
print(res.status_code, res.json())
```
