
## Transfer bot ownership

`POST /v1/bots/:botId/transfer`

Moves a bot's ownership to another API key of the same Bitrix24 account and the same user (or an account admin). Resolves the case where a bot is orphaned after the app is recreated: the owning key is revoked and the bot's runtime stops working. The transfer changes only the key binding — the `botId`, chat history, and subscriptions are preserved. No B24 call is made; after the transfer, verify the new key's B24 binding via [`POST /v1/bots/:botId/reauth`](/docs/bots/management/reauth).

## Parameters

| Parameter | Type | Required | Description |
|----------|-----|:-----:|---------|
| `botId` | number | yes | Bot ID (path parameter) |
| `targetApiKeyId` | string | yes | ID of the target API key (request body) |

The target key must be active (`ACTIVE`), in the same account as the bot, belong to the same user (or the caller is an account admin), carry the `imbot` scope, and not be a platform-managed service key (cowork, agent-seat, agent-maintenance) — those have a platform-driven lifecycle, so the bot would be orphaned again when they rotate.

## Examples

### curl

```bash
curl -X POST https://vibecode.bitrix24.com/v1/bots/42/transfer \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "targetApiKeyId": "ck_target_key_id" }'
```

### JavaScript

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/bots/42/transfer', {
  method: 'POST',
  headers: { 'X-Api-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' },
  body: JSON.stringify({ targetApiKeyId: 'ck_target_key_id' }),
})
const { data } = await res.json()
console.log(data) // { transferred: true, botId: 42, fromApiKeyId: '...', toApiKeyId: '...' }
```

## Response fields

| Field | Type | Description |
|------|-----|---------|
| `data.transferred` | boolean | `true` if the binding changed; `false` if the key already owned the bot (idempotent no-op) |
| `data.botId` | number | Bot ID |
| `data.fromApiKeyId` | string | ID of the previous owner key (only when `transferred: true`) |
| `data.toApiKeyId` | string | ID of the new owner key |

## Response example

```json
{
  "success": true,
  "data": {
    "transferred": true,
    "botId": 42,
    "fromApiKeyId": "ck_old_owner",
    "toApiKeyId": "ck_target_key_id"
  }
}
```

If the target key already owns the bot, no transfer happens and `transferred: false` is returned (with `fromApiKeyId` equal to `toApiKeyId` — the current owner).

## Error response example

400 — the target key is not eligible (with a `reason` field):

```json
{
  "success": false,
  "error": {
    "code": "TARGET_KEY_INVALID",
    "message": "Target API key is not eligible to own this bot.",
    "reason": "wrong_user"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 400 | `INVALID_BOT_ID` | `botId` is not a number |
| 400 | `INVALID_PARAMS` | `targetApiKeyId` is missing or not a string |
| 400 | `TARGET_KEY_INVALID` | The target key is not eligible. `reason`: `not_active` · `wrong_portal` · `wrong_user` · `missing_scope` · `expired` · `system_key` (platform-managed service key) |
| 403 | `NOT_BOT_OWNER` | The caller does not own the bot (must be the same user as the owner key, or an account admin) |
| 403 | `SCOPE_DENIED` | The API key does not have the `imbot` scope |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The key is in read-only mode |
| 404 | `BOT_NOT_FOUND` | The bot was not found in the account |
| 404 | `TARGET_KEY_NOT_FOUND` | The target key was not found |
| 409 | `BOT_TRANSFER_NOT_ALLOWED` | The bot is managed by an agent or a managed bot — transfer it via that resource, not directly |
| 409 | `BOT_TRANSFER_CONFLICT` | Ownership changed concurrently — re-read the current owner and retry if still needed |

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

## Known specifics

**When to use.** The bot stopped working after the OAuth app was recreated: the owning key is revoked and the bot's runtime no longer starts. Transferring to an active personal key (`vibe_api_*`) of the same account restores the bot — the runtime does not depend on which app registered the bot.

**Authorization is by user, not by key.** The transfer is allowed for the bot owner (the same Vibe user as the current owner key — even if that key is already revoked) or an account admin. Holding the current owner key is not required — that is exactly why the transfer works when the original key is revoked.

**Idempotency.** Transferring to the current owner key (including a revoked one) is a safe no-op: `transferred: false` is returned, the binding is unchanged, and no audit entry is written.

**Verify after the transfer.** No B24 call is made during the transfer. To confirm the new key can drive the bot, call [`POST /v1/bots/:botId/reauth`](/docs/bots/management/reauth) — it validates the new key's B24 binding.

## See also

- [Bot re-authorization](/docs/bots/management/reauth)
- [Troubleshooting](/docs/bots/troubleshooting)
- [Bot platform](/docs/bots)
