
## Replace an application server

`POST /v1/applications/:id/replace-server`

Moves the application to another server: an existing server of the owner, or a new one that the method creates itself. The old server is not deleted. The application is unlinked from it, and the server stays with its owner. Before calling, find out whether a replacement is possible and choose the server through [`GET /v1/applications/:id/available-servers`](./available-servers.md).

Only the card owner can call this method, and only with their own keys. An administrator of the Bitrix24 account who does not own the application replaces the server in their Vibecode account.

**Authorization:** `X-Api-Key` with the `vibe:infra` scope.

## Parameters

| Parameter | Type | Required | Description |
|-----------|------|:--------:|-------------|
| `id` (path) | string | yes | Application ID. List: [`GET /v1/applications`](./list.md) |
| `Idempotency-Key` (header) | string | no | `new` form only, ignored on `{ serverId }`. 1 to 255 characters from `A-Z`, `a-z`, `0-9`, `_`, `.`, `:`, `-`; surrounding spaces are trimmed. The key is scoped to the calling API key. How a retry works is described under "Known behaviors" |

## Request fields (body)

The body takes one of two forms. Fields of the two forms cannot be mixed, and extra fields return `400 VALIDATION_ERROR`.

**Your own existing server.**

| Field | Type | Required | Description |
|-------|------|:--------:|-------------|
| `serverId` | string | yes | Server ID from `servers[].id` of [`GET /v1/applications/:id/available-servers`](./available-servers.md) |

**A new server.**

| Field | Type | Required | Description |
|-------|------|:--------:|-------------|
| `provider` | string | yes | Provider ID, as in [`POST /v1/infra/servers`](/docs/infra/servers/create) |
| `plan` | string | yes | Provider plan ID, as in [`POST /v1/infra/servers`](/docs/infra/servers/create) |
| `region` | string | no | Provider region ID |

## Examples

### curl — personal key

```bash
curl -X POST \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"serverId": "srv_new"}' \
  https://vibecode.bitrix24.com/v1/applications/cmsw806qi0000tdskiw2aegii/replace-server
```

### curl — OAuth application

```bash
curl -X POST \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"serverId": "srv_new"}' \
  https://vibecode.bitrix24.com/v1/applications/cmsw806qi0000tdskiw2aegii/replace-server
```

### JavaScript — personal key

```javascript
const id = 'cmsw806qi0000tdskiw2aegii'
const res = await fetch(`https://vibecode.bitrix24.com/v1/applications/${id}/replace-server`, {
  method: 'POST',
  headers: { 'X-Api-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' },
  body: JSON.stringify({ serverId: 'srv_new' }),
})
const { data } = await res.json()

for (const warning of data.warnings) {
  console.warn('Needs manual follow-up:', warning)
}
```

### JavaScript — OAuth application

```javascript
const id = 'cmsw806qi0000tdskiw2aegii'
const res = await fetch(`https://vibecode.bitrix24.com/v1/applications/${id}/replace-server`, {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ serverId: 'srv_new' }),
})
const { data } = await res.json()
```

A new server is created with the body `{"provider": "bitrix-cloud", "plan": "bc-small", "region": "bc-eu-central"}`. This call is paid. To keep a retry from creating a second server, send the `Idempotency-Key` header, see "Known behaviors".

## Response fields

| Field | Type | Description |
|-------|------|-------------|
| `success` | boolean | Always `true` on success |
| `data.serverId` | string | ID of the server the application now points to |
| `data.oldServerId` | string \| null | ID of the previous server. `null` if the application had no server |
| `data.branch` | string | `existing` — moved to your own server, `new` — a new server was created |
| `data.appUrlMoved` | boolean | Whether the application address was moved to the new server |
| `data.warnings` | array | What could not be completed after the move. An empty array if everything went through. Values are in the table below |
| `data.callerKeyManagesServer` | boolean \| null | Whether the key that made the call manages the new server. `null` if the platform could not read it back |

Values of `data.warnings`:

| Value | What happened and what to do |
|-------|------------------------------|
| `KEY_ASSIGN_FAILED` | The new server could not be handed to the application key. The application points to the new server, but no key was assigned to manage it |
| `APP_URL_MOVE_FAILED` | The application address was not moved to the new server. Move it manually |
| `APP_URL_MANUAL` | The application has a custom address and the platform kept it as is |
| `CATALOG_STALE` | The old server had a published catalog card. Publish the card again |
| `SERVER_KEY_STALE` | The chosen existing server keeps an application key in its environment that was revoked or has expired. Update the key in the server environment |

## Response example

```json
{
  "success": true,
  "data": {
    "serverId": "srv_new",
    "oldServerId": "srv_old",
    "branch": "existing",
    "appUrlMoved": true,
    "warnings": [],
    "callerKeyManagesServer": true
  }
}
```

## Error response example

409 — the server is already linked to another application:

```json
{
  "success": false,
  "error": {
    "code": "SERVER_ALREADY_LINKED",
    "message": "Server is linked to another application"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|------|-------------|
| 400 | `VALIDATION_ERROR` | The body matches neither `{ serverId }` nor `{ provider, plan, region? }`, or has extra fields |
| 400 | `INVALID_IDEMPOTENCY_KEY` | `new` form only: the `Idempotency-Key` header is empty, longer than 255 characters or contains disallowed characters |
| 400 | `SERVER_ALREADY_CURRENT` | The server is already linked to this application |
| 400 | `INVALID_PLAN` | `new` form only: the plan in `plan` is not in the provider catalog |
| 400 | `INVALID_REGION` | `new` form only: the region in `region` is not in the provider catalog |
| 400 | `PROVIDER_DEPRECATED` | `new` form only: the provider is retired |
| 400 | `KEY_NOT_ELIGIBLE` | `new` form only: the key can no longer own a server |
| 401 | `NO_PORTAL` | The key is not bound to a portal |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header is missing |
| 401 | `INVALID_API_KEY` | The key is not recognized: no such key exists on the platform |
| 402 | `ACCOUNT_FROZEN` | The portal balance is frozen. If only paid calls are closed, moving to your own existing server works, and the `new` form gets this refusal with `userMessage` and `alternatives` |
| 402 | `COMMERCIAL_PLAN_REQUIRED` | `new` form only: the platform did not find the record of the portal the key is bound to and cannot check access |
| 402 | `TRIAL_PORTAL_LIMIT` | `new` form only: the server quota of the portal on trial access is used up |
| 402 | `PLAN_NOT_ALLOWED_ON_TRIAL` | `new` form only: the plan in `plan` is not available on the trial. Allowed plans are in `error.details.allowedPlans` |
| 402 | `INT_TARIFF_REQUIRED` | `new` form only: the account gets access through its Bitrix24 plan, and a commercial plan is required |
| 402 | `INT_VIBE_PLUS_REQUIRED` | `new` form only: the Bitrix24 account needs a Vibe+ plan |
| 402 | `SELFHOSTED_NOT_AVAILABLE` | `new` form only: on-premise Bitrix24 is not yet available on this installation. Buying a plan does not lift the refusal |
| 403 | `INFRA_SCOPE_REQUIRED` | The key lacks the `vibe:infra` scope |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The key is read-only |
| 403 | `INFRA_FORBIDDEN_FOR_COWORK_KEY` | A key with the `vibe:cowork` scope works with data only |
| 403 | `FORBIDDEN` | The application does not belong to the key owner |
| 403 | `MANAGEMENT_KEY_NO_ENTITY_ACCESS` | Management key: use a personal key or an authorization key |
| 403 | `PORTAL_KEY_REQUIRED` | The key record is damaged and has no owner |
| 403 | `INFRA_NOT_PERMITTED` | `new` form only: infrastructure is turned off on the platform or for the portal |
| 403 | `SERVER_CREATION_DISABLED` | `new` form only: the portal policy forbids creating servers |
| 403 | `SERVER_CREATION_ADMINS_ONLY` | `new` form only: the portal policy lets only administrators create servers |
| 403 | `VIBE_DEMO_EXPIRED` | `new` form only: the Vibe+ plan or trial has ended |
| 404 | `APPLICATION_NOT_FOUND` | No such application on the portal the key is bound to |
| 404 | `SERVER_NOT_FOUND` | The server does not exist, belongs to someone else, or cannot be linked to an application: an agent server, a bot server or a galaxy host |
| 404 | `PROVIDER_NOT_FOUND` | `new` form only: the provider is unknown or closed for this portal |
| 409 | `IDEMPOTENCY_KEY_ALREADY_USED` | `new` form only: the `Idempotency-Key` was already used with another application or body, the application has since moved to another server, or the outcome of the previous call is unknown |
| 409 | `IDEMPOTENCY_CONCURRENT_RETRY` | `new` form only: a concurrent request reserved the same `Idempotency-Key` |
| 409 | `SERVER_ALREADY_LINKED` | The server is linked to another application |
| 409 | `APPLICATION_OP_IN_PROGRESS` | Another operation is changing the application's server link, or the link changed during the request. Retry shortly |
| 409 | `REISSUE_IN_PROGRESS` | The application key or its server link is busy with a key reissue, secret delivery or deletion. Retry shortly |
| 409 | `APPLICATION_KEY_RECOVERY_REQUIRED` | An unfinished key replacement blocks the move. Finish or recover it first |
| 409 | `PORTAL_ORIGIN_CUTOVER_BUSY` | The portal address is being changed by another operation, retry shortly |
| 429 | `RATE_LIMITED` | 10 requests per minute for the card owner, whichever key they call with |
| 429 | `SERVER_LIMIT` | `new` form only: the per-key server limit is reached |
| 429 | `USER_SERVER_LIMIT` | `new` form only: the per-user server limit is reached |
| 502 | `APPLICATION_SERVER_ATTACH_FAILED` | The new server was created but could not be linked. The server was removed and the application stays on the old one |
| 502 | `PROVIDER_ERROR` | `new` form only: the call to the cloud provider failed. The response carries `error.incidentCode`, quote it to support |
| 502 | `PROVIDER_NOT_CONFIGURED` | `new` form only: the provider is not configured on the platform — it has no credentials or no price for the chosen plan. The response carries `error.incidentCode` |
| 503 | `PORTAL_ORIGIN_CUTOVER_UNAVAILABLE` | The portal address guard is unavailable, retry later |

The `error.message` text is in English and meant for logs: build interface texts from `error.code`. The full list of common API errors is in [Errors](/docs/errors).

## Known behaviors

- **A failure after the link is written does not become an error.** Once the application points to the new server, the method answers `200` and lists what could not be completed in `warnings`. An error response happens only for what went wrong before the link was written. A client that looks at the response status alone will miss the warnings: always read `warnings`.
- **Retrying the `new` form with `Idempotency-Key`.** A retry with the same key and the same body within 24 hours of the first call creates no second server. If the replacement completed, the `200` response repeats the original `data.serverId`, `data.oldServerId` and `data.appUrlMoved` with an empty `data.warnings` and carries the `Idempotent-Replayed: true` header. If the outcome is unknown or the application has already moved to another server, the answer is `409 IDEMPOTENCY_KEY_ALREADY_USED`. Then read [`GET /v1/applications/:id`](./get.md), where `server.id` is the application's current server, and check your Vibecode account for an unlinked server before buying again. After 24 hours the duplicate protection may expire. Without the header, a retry may create a second server.
- **The key of the call is not reassigned.** The managing key of the new server is the application's own key: the authorization key if it has one, otherwise the personal key. The key you called with stays as it was. `callerKeyManagesServer` tells whether it manages the new server. It is `false` when the application has an authorization key or the target server is a galaxy container.
- **The server is created for the card owner.** Limits and payment conditions apply to the owner, not to the administrator of the Bitrix24 account.
- **The old server stays.** It is unlinked from the application and keeps running for the owner. If you do not need it, delete it separately with [`DELETE /v1/infra/servers/:id`](/docs/infra/servers/delete).
- **Whether to show the button is decided by `available-servers`.** It also names the reason for a refusal of the `new` form in advance. All other reasons arrive as error codes from the table above.

## See also

- [Servers for moving an application](./available-servers.md)
- [Application card](./get.md)
- [Create a server](/docs/infra/servers/create)
- [Application showcase](/docs/applications)
- [Errors](/docs/errors)
