## Rotate an API key

`POST /v1/keys/:id/rotate`

Issues a new API key to replace the previous one with the same settings and moves the previous key's bindings to it.

## Parameters

| Parameter | Type | Required | Description |
|----------|-----|:-----:|----------|
| `id` (path) | string | yes | Record ID of the previous key. List: [`GET /v1/keys`](/docs/management-keys/list) |

The request body may be empty or `{}`: body fields are not read, and the new key's settings are taken from the previous one.

## Examples

The request accepts only a management key — see [Limitations](/docs/management-keys#limitations).

### curl — management key

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/keys/6dbad0ce-79a9-43f5-8ca4-3e1af7ef63bc/rotate" \
  -H "X-Api-Key: YOUR_MANAGEMENT_KEY"
```

### JavaScript — management key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/keys/6dbad0ce-79a9-43f5-8ca4-3e1af7ef63bc/rotate', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_MANAGEMENT_KEY',
  },
})

const { success, data, error } = await res.json()
if (success) {
  // The new key's value arrives only in this response
  console.log('New key:', data.rawKey)
} else if (error.details?.orphanKeyId) {
  // The replacement was rolled back, the steps are in "Known specifics"
  console.error(error.code, error.details)
}
```

## Response fields

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | Always `true` on success |
| `data.id` | string | Record ID of the new key |
| `data.name` | string | Name carried over from the previous key |
| `data.type` | string | Key record type, always `APP` |
| `data.userId` | string | Key owner ID |
| `data.portalId` | string | Bitrix24 account ID |
| `data.clientId` | string \| null | OAuth client ID, if recorded on the key. Otherwise `null` |
| `data.scopes` | array | [Scopes](/docs/scopes) carried over from the previous key |
| `data.ipWhitelist` | array | Allowed IP addresses. An empty array means no restriction |
| `data.expiresAt` | null | The new key has no expiry |
| `data.rateLimit` | number \| null | Individual request limit for AI endpoints, `null` — the platform-wide limit |
| `data.status` | string | Status of the new key, `ACTIVE` |
| `data.lastUsedAt` | null | The new key has not been used yet |
| `data.createdAt` | string | Creation date (ISO 8601) |
| `data.updatedAt` | string | Last update date (ISO 8601) |
| `data.accessMode` | string | `READWRITE` or `READONLY` |
| `data.totalDefault` | boolean \| null | Whether `meta.total` arrives in lists requested with this key. `null` — the platform setting applies |
| `data.issuedVia` | string \| null | Channel through which the new key's webhook was issued: `per-portal` and `cloud-shared` — the connector module on the Bitrix24 account, `dev_key` — the owner's developer key, `legacy` — registration through Bitrix24 Network, `none` — no webhook was created |
| `data.rawKey` | string | Full value of the new key. Not returned again |

The `prefix`, `suffix` and `b24Ready` fields are not in the response — they arrive in [`GET /v1/keys/:id`](/docs/management-keys/get).

## Response example

201 — the new key is issued:

```json
{
  "success": true,
  "data": {
    "id": "9b1f2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
    "name": "CRM integration",
    "type": "APP",
    "userId": "fafa9780-eed7-4d29-b040-2448d924f452",
    "portalId": "0d9f7744-f853-47c7-b06e-6f61f9dd1b7f",
    "clientId": null,
    "scopes": ["crm"],
    "ipWhitelist": [],
    "expiresAt": null,
    "rateLimit": null,
    "status": "ACTIVE",
    "lastUsedAt": null,
    "createdAt": "2026-10-29T12:22:53.937Z",
    "updatedAt": "2026-10-29T12:22:53.937Z",
    "accessMode": "READONLY",
    "totalDefault": null,
    "issuedVia": "cloud-shared",
    "rawKey": "vibe_api_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
  }
}
```

## Error response example

409 — the previous key is already revoked:

```json
{
  "success": false,
  "error": {
    "code": "KEY_NOT_ACTIVE",
    "message": "Key is no longer active"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `PERSONAL_KEY_WEBHOOK_SCOPES_INVALID` | The previous key is left with only application-context scopes — `placement`, `entity`, `userfieldtype`. The scope set is checked before the Bitrix24 account is contacted, so every account receives the same refusal. First add a data scope to the previous key through [`PATCH /v1/keys/:id`](/docs/management-keys/update) |
| 400 | `BOX_NO_DEVELOPER_KEY` | On-premise Bitrix24: the key owner has no developer key, and the previous key carries Bitrix24 scopes |
| 400 | `BOX_NO_DEVELOPER_KEY` | On-premise Bitrix24: the previous key has a webhook, and it cannot be removed without the owner's developer key |
| 402 | `ACCOUNT_FROZEN` | The account balance is frozen |
| 403 | `SYSTEM_KEY_ROTATE_FORBIDDEN` | The key being rotated is one the platform issues on its own endpoint: a Cowork/Code desktop key, an agent key, a project deploy key. The response text says where this key is issued again |
| 403 | `COWORK_PLATFORM_DISABLED` | A key billed by the Cowork/Code subscription: Cowork/Code is disabled at the platform level |
| 403 | `COWORK_ACCESS_REQUIRED` | A Cowork/Code subscription key: the key owner has not been granted Cowork/Code access |
| 403 | `COWORK_EXTERNAL_CLIENTS_DISABLED` | A Cowork/Code subscription key: the Bitrix24 account administrator has disallowed third-party clients |
| 403 | `COWORK_SUBSCRIPTION_INACTIVE` | A Cowork/Code subscription key: the subscription is paused or cancelled |
| 403 | `INT_TARIFF_REQUIRED` | The account's Bitrix24 plan does not grant access, so the webhook is not issued. Rotation resumes once the account moves to a commercial Bitrix24 plan |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The management key that sent the request is in `READONLY` mode |
| 403 | `MANAGEMENT_SCOPE_REQUIRED` | The management key lacks the `vibe:mgmt:keys` scope |
| 404 | `KEY_NOT_FOUND` | No key with this `id` exists, it belongs to another user, it is a management key, or it is a key without a Bitrix24 account |
| 404 | `KEY_NOT_FOUND` | The key was deleted while issuance was running. No new key is created |
| 409 | `KEY_NOT_ACTIVE` | The previous key is no longer active: it was revoked or its status is `EXPIRED`. No new key is created. Re-read the key with [`GET /v1/keys/:id`](/docs/management-keys/get) |
| 409 | `KEY_NOT_ACTIVE` | The authorization key (`vibe_app_`) is not active, was deleted, its application was deleted, or the key was changed after the request started. No new key is created |
| 409 | `REISSUE_IN_PROGRESS` | Authorization keys (`vibe_app_`) only: another operation on this application's key is already running — a rotation, an update or a deletion. Retry the request once it finishes |
| 409 | `KEY_ROTATION_RECOVERY_REQUIRED` | The replacement of this key is unfinished: an earlier rotation ended with a `500` response, or another rotation of the same key has not finished yet. The refusal applies to both the old and the new key of that replacement. No new key is created. If there was no `500` response and the refusal still arrives after a few minutes, contact support |
| 409 | `KEY_ROTATION_SOURCE_CHANGED` | The source key's binding changed during issuance. No new key is created. Re-read the key record and state before starting a new operation |
| 409 | `PORTAL_ORIGIN_CUTOVER_BUSY` | The address of the key's Bitrix24 account is changing. Retry the rotation later |
| 500 | `KEY_ROTATE_REVOKE_FAILED` | The replacement was rolled back: for a blocked key, it was not confirmed that its access tokens were revoked. The new key was created, and its value is not in the response. The steps are in "Known specifics" |
| 500 | `KEY_ROTATION_REPOINT_FAILED` | The replacement was rolled back: the move of bindings to the new key was not confirmed. The steps are in the same place |
| 500 | `KEY_ROTATION_FINALIZE_FAILED` | The replacement was rolled back: the completion of the replacement was not confirmed. The steps are in the same place |
| 503 | `PORTAL_ORIGIN_CUTOVER_UNAVAILABLE` | Could not check whether the address of the key's Bitrix24 account is changing. Retry the rotation later |
| 401 | `WRONG_KEY_TYPE` | The request was sent with a Bitrix24 account key — a personal `vibe_api_` key or an authorization key `vibe_app_`. The endpoint accepts only a management key |
| 401 | `KEY_INACTIVE` | The management key is revoked or blocked |
| 401 | `KEY_EXPIRED` | The management key has expired |
| 401 | `INVALID_API_KEY` | No key with the given value was found |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header is missing |

Rotation shares its other issuance refusals with creation: both methods issue the key the same way. These are the issuance refusals with status `409`, `410` and `502`, with status `503` — `NETWORK_DEVKEY_REQUIRED` and `CONNECTOR_KEY_ISSUE_TIMEOUT`, and with status `403` — `B24_INSUFFICIENT_SCOPE`, `B24_ACCESS_DENIED` and `CONNECTOR_KEY_ISSUE_FORBIDDEN`. Their descriptions and the `error.reason` field are in [Create an API key](/docs/management-keys/create#errors). Status `503` arrives both from issuance and from the account address check, so branch on the code, not on the status: `PORTAL_ORIGIN_CUTOVER_UNAVAILABLE` from the table above may be retried later, while `CONNECTOR_KEY_ISSUE_TIMEOUT` must not be retried right away — the webhook may already exist on the Bitrix24 account. The check of the Bitrix24 account's access to the platform does not apply to rotation, so `402 INT_TARIFF_REQUIRED` and `403 PORTAL_TARIFF_UNREADABLE` do not arrive here. With status `402`, rotation receives only Bitrix24's own refusal to issue the key due to the plan — `INT_VIBE_PLUS_REQUIRED`.

The `OWNER_DELETED`, `OWNER_BLOCKED`, `ACCOUNT_PENDING_ERASURE` and `B24_USER_DELETED` refusals depend on the key owner's account — see [Owner account state](/docs/management-keys#owner-account-state).

Full list of common API errors — [Errors](/docs/errors).

## Known specifics

**What is carried over to the new key.** The name, scopes, allowed IP addresses, access mode, individual limit and the `totalDefault` setting are carried over from the previous key as they are. The previous key's servers, applications, access tokens, deploy operations and bots move to the new one. The expiry is not carried over: the new key does not expire.

**The previous key's transition period is up to 24 hours.** The previous key keeps working for 24 hours after rotation, then becomes invalid. If its own expiry comes earlier, that expiry is kept: rotation does not extend the key and does not revive an expired one.

**A blocked key can be rotated too.** Rotation is the only way to replace a key in `BLOCKED` status; there is no unblocking. The previous key stays blocked and gets no transition period, and its access tokens are revoked rather than moved to the new key.

**A `500` response means the new key was created, but the replacement was rolled back.** The new key's value is not in the response. Its ID arrives in `error.details.orphanKeyId`. The `error.details.strandedSlots` field appears if the rollback could not return something to the previous key, and lists it: `applications`, `servers`, `accessTokens`, `deployOperations`, `bots`, `previousKeyGrace`. If the field is absent or has no `previousKeyGrace`, the previous key keeps its previous expiry. Next steps:

- The `strandedSlots` field is present — contact support. Do not delete the new key while anything still points at it.
- The `strandedSlots` field is absent — delete the new key with [`DELETE /v1/keys/:id`](/docs/management-keys/delete) using the ID from `orphanKeyId`. After the `204` response, rotate the previous key once more. If the deletion returns `409 KEY_ROTATION_RECOVERY_REQUIRED`, contact support.

Until the replacement is finished, rotating or deleting either the previous or the new key returns `409 KEY_ROTATION_RECOVERY_REQUIRED`, so retrying the rotation without these steps fails. The exception is deleting the new key as described in the second item.

**A refusal for a platform-issued key leaves the previous key untouched.** On a `SYSTEM_KEY_ROTATE_FORBIDDEN` refusal or a Cowork/Code subscription refusal, the previous key keeps working. Ordinary dashboard keys are unaffected by these checks. What a subscription key can do — [Your own agent on the subscription](/docs/cowork/harness).

## See also

- [List Bitrix24 account API keys](/docs/management-keys/list)
- [Create an API key](/docs/management-keys/create)
- [Get an API key](/docs/management-keys/get)
- [Update an API key](/docs/management-keys/update)
- [Delete an API key](/docs/management-keys/delete)
- [Management keys](/docs/management-keys)
