
## Create a server

`POST /v1/infra/servers`

Creates an application with a cloud provider. Always in Black Hole mode — iptables blocks all inbound ports, the application is invisible from the internet.

Your Bitrix24 account has two placement models, and what to expect after creation depends on the model:

- **A dedicated virtual machine** (the default model). The response is returned immediately with the `provisioning` status. Provisioning takes 1–3 minutes, after which you need to poll [`GET /v1/infra/servers/:id`](./get.md) until `status: "running"` and `blackholeStatus: "CONNECTED"`. The response contains the SSH data `ssh.password` and `ssh.privateKey` **only once** — they will be needed when switching to OPEN mode, so save them right away. This page describes that model in the request fields, response fields, and error table below.
- **A Galaxy application** — a container on a shared host. If your Bitrix24 account places apps in galaxies, the same `POST /v1/infra/servers` creates a Galaxy application instead of a virtual machine. In the response, this model is marked by `createdVia: "galaxy"`. The ["Galaxy application"](#galaxy-application) section below describes the two startup scenarios, and the full model is on the [Galaxy app](/docs/infra/galaxy) page.

## Galaxy application

If your Bitrix24 account places apps in galaxies, `POST /v1/infra/servers` creates a **Galaxy application** — a container on a shared host rather than a dedicated virtual machine. The request and the call order are the same, but the lifecycle differs. In the response, this model is marked by `createdVia: "galaxy"`. The full model, cost, and deploy differences are on the [Galaxy app](/docs/infra/galaxy) page.

**Do not wait for `CONNECTED` before uploading the code.** A Galaxy application **never** reaches `blackholeStatus: "CONNECTED"` on its own — its container is built on code upload. If you just poll the status, the app stays in `provisioning`, and after about 20 minutes the platform marks it as `error` and records in the `provisionError` field that the code was never uploaded. So upload the code right away — using one of the two scenarios below.

### Scenario 1 — transparent, recommended

A single request. Pass the application code in the `source` field of `POST /v1/infra/servers` together with `runtime` and `start` (and, if needed, `install`, `env`, `port`). The build runs in the background. After the response, poll [`GET /v1/infra/servers/:id`](./get.md) until `status: "running"`. A separate code-upload call is not needed.

```bash
curl -X POST https://vibecode.bitrix24.com/v1/infra/servers \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-crm-app",
    "displayName": "My CRM bot",
    "source": { "content": "<base64-archive>" },
    "runtime": "node20",
    "start": "node index.js",
    "install": "npm ci",
    "port": 3000
  }'
```

The `source`, `runtime`, `start`, `install`, `env`, `port` fields have the same meaning as in the [`POST /:id/deploy`](/docs/infra/deploy/deploy) body:

| Field | Type | Required | Description |
|------|-----|:-----:|----------|
| `source` | object | yes (for this scenario) | The code source. `source.content` — the application archive in base64, up to 500 MB per request body |
| `runtime` | string | yes (if `source` is passed) | Runtime ID: `node20`, `python311`, `php83`, `static`, and others. The list — [`GET /v1/infra/runtimes`](/docs/infra/deploy/runtimes) |
| `start` | string | yes (if `source` is passed) | The application start command, on a single line. A line break will return `400` |
| `install` | string | no | The dependency-install command, on a single line |
| `env` | object | no | Environment variables `{ "KEY": "value" }`. Injected into the container at start, see below |
| `port` | number | no | The port the application listens on |

Without the `runtime` and `start` fields, the `source` field returns `400` — both fields are required. On a Bitrix24 account without Galaxy mode, `source` is forbidden and returns `400 SOURCE_AT_CREATE_GALAXY_ONLY`.

### Scenario 2 — two-step

Create the application **without** `source` — the response comes with the `provisioning` status and the hint `next: "deploy"`. Immediately call [`POST /v1/infra/servers/:id/deploy`](/docs/infra/deploy/deploy) with the code in `source`, the runtime, and the start command. Upload the code right away, without waiting for `CONNECTED`.

```bash
# Step 1 — create the application without code
curl -X POST https://vibecode.bitrix24.com/v1/infra/servers \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "provider": "bitrix-cloud", "name": "my-crm-app", "plan": "bc-medium", "region": "bc-eu-central" }'

# The response contains data.id, data.next = "deploy", and data.hint.
# Step 2 — upload the code right away, without waiting for CONNECTED
curl -X POST https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/deploy \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": { "content": "<base64-archive>" },
    "runtime": "node20",
    "start": "node index.js",
    "port": 3000
  }'
```

`provider`, `plan` and `region` are required by the schema for a create without `source`; for a galaxy app the values are informational — the app inherits its host's plan and region.

### Diagnostics and environment variables

- **The `provisionError` field.** If the build or the startup failed, [`GET /v1/infra/servers/:id`](./get.md) returns the failure reason in `data.provisionError`, and on an image-build failure — also the tail of the Docker build log. Read this field to find the cause.
- **`env` variables are injected at start.** Values from `env` are passed into the container at start time via `docker run --env`, not baked into the image. So API keys and secrets from `env` do not end up in the image layers.

## Request fields (body)

| Field | Type | Required | Description |
|------|-----|:-----:|----------|
| `provider` | string | yes | Provider ID from [`GET /v1/infra/providers`](../providers/list.md), for example `bitrix-cloud`. 1–50 characters |
| `name` | string | yes | System name of the server — the technical identifier. 2–63 characters, only lowercase Latin letters, digits, and `-`, with a letter as the first character. Pattern: `^[a-z][a-z0-9-]*$`. Used in the subdomain URL (`app-<hex>` is generated separately), logs, the audit log. **Immutable after creation.** For a human-readable name, use `displayName` |
| `displayName` | string | no | Human-readable name in any language (Russian, Chinese, emoji — anything). 2–100 characters, no control bytes. Displayed in the UI, server-down notifications, security alerts, billing lines, the Bitrix24 catalog. If not passed — `name` is substituted. The `name` itself remains the technical identifier, immutable, and is used in the subdomain URL and logs |
| `description` | string | no | Application description shown on the Bitrix24 catalog card, up to 500 characters. Line breaks and tabs are allowed, other control bytes are rejected. The value is trimmed at both ends. If not passed — the description stays empty, and it can be set later via [`PATCH /v1/infra/servers/:id`](./update.md) |
| `plan` | string | yes | Plan ID from [`GET /v1/infra/providers/:providerId/plans`](../providers/plans.md), for example `bc-small` |
| `region` | string | yes | Region ID from [`GET /v1/infra/providers/:providerId/regions`](../providers/regions.md), for example `bc-eu-central` |
| `image` | string | no | OS image ID from [`GET /v1/infra/providers/:providerId/images`](../providers/images.md). If omitted — the platform resolves the provider's current image itself. If you pass it, take the current one from the response: the ID changes when the build is updated |
| `sshPublicKey` | string | no | Your SSH public key (`ssh-rsa …`, `ssh-ed25519 …`, `ecdsa-sha2-nistp256/384/521 …`, security keys). Up to 8192 characters. If not passed — the platform will generate a private key and return it once in `ssh.privateKey` |
| `placement` | string | no | Placement model: `auto` (default) or `dedicated`. On a Bitrix24 account in Galaxy First mode (`galaxies-only`), the value `dedicated` creates a standalone virtual machine instead of a Galaxy application — "graduating" the application onto its own server. It passes the same checks as a normal server create: the `serverCreation` policy and the per-user server quota. With `auto` the behavior is unchanged (in Galaxy First mode a Galaxy application is created) |
| `graduateFrom` | string | no | The identifier of your Galaxy application (`kind=GALAXY_APP`) to delete after the dedicated server is created (graduation cleanup — so the old application does not linger in the galaxy). Owner-scoped: the same key, the same Bitrix24 account, `kind=GALAXY_APP`. A foreign or non-Galaxy identifier returns `404` and deletes nothing. Meaningful only together with `placement: dedicated`. For a dedicated server, the `Idempotency-Key` header is not compatible with this parameter — see [Idempotency](#idempotency) |

> **Note:** for the dedicated-virtual-machine model the `runtime` parameter is not accepted in `POST /v1/infra/servers` — passing it will return `400 RUNTIME_PARAM_REMOVED`. The runtime is installed at the deploy stage. See [POST /:id/deploy](/docs/infra/deploy/deploy). **Exception — Galaxy mode:** when `source` is passed, `runtime` is required (see the ["Galaxy application"](#galaxy-application) section), and `400 RUNTIME_PARAM_REMOVED` is not returned in that case.

## Examples

### curl — personal key

```bash
# name — lowercase Latin letters, digits and hyphens only, first character a letter (^[a-z][a-z0-9-]*$)
curl -X POST https://vibecode.bitrix24.com/v1/infra/servers \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "bitrix-cloud",
    "name": "my-crm-app",
    "displayName": "My CRM bot",
    "plan": "bc-small",
    "region": "bc-eu-central",
    "image": "ubuntu-2404-lts"
  }'
```

### curl — OAuth application

```bash
# name — lowercase Latin letters, digits and hyphens only, first character a letter (^[a-z][a-z0-9-]*$)
curl -X POST https://vibecode.bitrix24.com/v1/infra/servers \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "bitrix-cloud",
    "name": "my-crm-app",
    "displayName": "My CRM bot",
    "plan": "bc-small",
    "region": "bc-eu-central",
    "image": "ubuntu-2404-lts"
  }'
```

### JavaScript — personal key

```javascript
// name — lowercase Latin letters, digits and hyphens only, first character a letter (^[a-z][a-z0-9-]*$)
const res = await fetch('https://vibecode.bitrix24.com/v1/infra/servers', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    provider: 'bitrix-cloud',
    name: 'my-crm-app',
    displayName: 'My CRM bot',
    plan: 'bc-small',
    region: 'bc-eu-central',
    image: 'ubuntu-2404-lts',
  }),
})
const { data } = await res.json()
console.log('Server ID:', data.id, 'Subdomain:', data.subdomain)

// Save the SSH credentials just in case — they are returned only once
if (data.ssh.privateKey) {
  await saveLocally(`${data.id}.key`, data.ssh.privateKey)
}
```

### JavaScript — OAuth application

```javascript
// name — lowercase Latin letters, digits and hyphens only, first character a letter (^[a-z][a-z0-9-]*$)
const res = await fetch('https://vibecode.bitrix24.com/v1/infra/servers', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    provider: 'bitrix-cloud',
    name: 'my-crm-app',
    displayName: 'My CRM bot',
    plan: 'bc-small',
    region: 'bc-eu-central',
    image: 'ubuntu-2404-lts',
  }),
})
```

## Response fields

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | Always `true` on success |
| `data.id` | string (UUID) | Unique server identifier |
| `data.status` | string | Always `"provisioning"` right after creation. Wait for `"running"` in 1–3 minutes |
| `data.provider` | string | Echo of the passed `provider` |
| `data.name` | string | Echo of the passed `name` — the system name of the server |
| `data.kind` | string | Server type, always: `STANDALONE` or `GALAXY_APP` |
| `data.galaxyId` | string | Only for the galaxy branch (the galaxy host id); absent from a standalone response |
| `data.displayName` | string \| null | Human-readable name. If `displayName` was not passed at creation — it will equal `name` |
| `data.description` | string \| null | Echo of the passed `description`. The key is present only in the response for a dedicated virtual machine. **For a Galaxy application**, where `createdVia` equals `galaxy`, the key is absent from the response even though the description is stored — read it via [`GET /v1/infra/servers/:id`](./get.md) |
| `data.ip` | string \| null | Public IP. `null` right after creation, filled in when the virtual machine starts |
| `data.ssh.user` | string | User for SSH: `root` for new servers |
| `data.ssh.port` | number | SSH port: `22` |
| `data.ssh.password` | string \| null | **One-time!** The root password. Save it right away — it will not be returned later. Needed when switching to OPEN mode |
| `data.ssh.privateKey` | string \| null | **One-time!** Private key in OpenSSH format (if `sshPublicKey` was not passed at creation). Save it right away |
| `data.plan` | string | Echo of the passed `plan`. **Exception — a Galaxy application** (`createdVia: "galaxy"`): the host's plan is returned, not the one requested |
| `data.region` | string | The region the server actually landed in. May differ from the requested one when a zone fallback triggers (see "Known specifics"). **For a Galaxy application** — the host's region |
| `data.image` | string | Echo of the passed `image` |
| `data.mode` | string | Always `"BLACKHOLE"` right after creation |
| `data.createdVia` | string | `"api"` for calls via the v1 API, `"ui"` for calls from the Vibecode dashboard |
| `data.subdomain` | string | Subdomain for the application, for example `app-92fb1c34`. Used in `appUrl` |
| `data.blackholeStatus` | string | State of the agent tunnel. Right after creation — `"NONE"`, then it passes through `WAITING` and ends at `CONNECTED` |
| `data.accessPolicy` | string | Access policy for the application. By default `"OWNER_ONLY"` — only the key owner |
| `data.runtimeId` | string \| null | Always `null` at creation. Filled in on deploy with `runtime` |
| `data.runtimeStatus` | string \| null | Always `null` at creation. Updated during deploy |
| `data.appUrl` | string \| null | HTTPS address of the application: `https://{subdomain}.vibecode.bitrix24.com` |
| `data.createdAt` | string (ISO 8601) | Creation timestamp |

## Response example

A dedicated virtual machine:

```json
{
  "success": true,
  "data": {
    "id": "db008c84-91a5-4e15-b9d5-6c6aa2838448",
    "status": "provisioning",
    "provider": "bitrix-cloud",
    "name": "docs-test-temp",
    "kind": "STANDALONE",
    "ip": null,
    "ssh": {
      "user": "root",
      "port": 22,
      "password": "0FPIIR9EfO2OAeSSMK6bKA",
      "privateKey": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmU…\n-----END OPENSSH PRIVATE KEY-----\n"
    },
    "plan": "bc-small",
    "region": "bc-eu-central",
    "image": "ubuntu-2404-lts",
    "mode": "BLACKHOLE",
    "createdVia": "api",
    "subdomain": "app-92fb1c34",
    "blackholeStatus": "NONE",
    "accessPolicy": "OWNER_ONLY",
    "runtimeId": null,
    "runtimeStatus": null,
    "appUrl": "https://app-92fb1c34.vibecode.bitrix24.com",
    "createdAt": "2026-04-22T10:50:11.477Z"
  }
}
```

A Galaxy application via scenario 1 — `source` was passed, the build runs in the background. There are no `ssh` or `next` fields:

```json
{
  "success": true,
  "data": {
    "id": "7c2b1f08-3a4d-4e91-9b6c-2f5e8a1d0c33",
    "status": "provisioning",
    "provider": "bitrix-cloud",
    "name": "my-crm-app",
    "kind": "GALAXY_APP",
    "galaxyId": "<galaxy-host-id>",
    "displayName": "My CRM bot",
    "ip": null,
    "ssh": null,
    "plan": "bc-medium",
    "region": "bc-eu-central",
    "image": "ubuntu-2404-lts",
    "mode": "BLACKHOLE",
    "createdVia": "galaxy",
    "subdomain": "app-7c2b1f08",
    "blackholeStatus": "NONE",
    "accessPolicy": "OWNER_ONLY",
    "runtimeId": null,
    "runtimeStatus": null,
    "appUrl": "https://app-7c2b1f08.vibecode.bitrix24.com",
    "createdAt": "2026-04-22T10:50:11.477Z"
  }
}
```

A Galaxy application via scenario 2 — `source` was not passed, `next` and `hint` appear:

```json
{
  "success": true,
  "data": {
    "id": "7c2b1f08-3a4d-4e91-9b6c-2f5e8a1d0c33",
    "status": "provisioning",
    "provider": "bitrix-cloud",
    "name": "my-crm-app",
    "kind": "GALAXY_APP",
    "galaxyId": "<galaxy-host-id>",
    "displayName": "My CRM bot",
    "ip": null,
    "ssh": null,
    "plan": "bc-medium",
    "region": "bc-eu-central",
    "image": "ubuntu-2404-lts",
    "mode": "BLACKHOLE",
    "createdVia": "galaxy",
    "subdomain": "app-7c2b1f08",
    "blackholeStatus": "NONE",
    "accessPolicy": "OWNER_ONLY",
    "runtimeId": null,
    "runtimeStatus": null,
    "appUrl": "https://app-7c2b1f08.vibecode.bitrix24.com",
    "createdAt": "2026-04-22T10:50:11.477Z",
    "next": "deploy",
    "hint": "This is a galaxy app — POST /v1/infra/servers/:id/deploy with source.content now; it will not reach \"running\" on its own."
  }
}
```

If a Galaxy application's build failed, [`GET /v1/infra/servers/:id`](./get.md) returns the reason in `data.provisionError`:

```json
{
  "success": true,
  "data": {
    "id": "7c2b1f08-3a4d-4e91-9b6c-2f5e8a1d0c33",
    "status": "error",
    "createdVia": "galaxy",
    "provisionError": "Docker build failed: npm ci exited with code 1"
  }
}
```

## Error response example

400 — validation failed (the name starts with an uppercase letter):

```json
{
  "success": false,
  "error": {
    "code": "INVALID_REQUEST",
    "message": "name: Name must start with a letter and contain only lowercase letters, digits, and hyphens"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `INVALID_REQUEST` | Field validation failed (invalid name format, a required field is missing, an invalid SSH key, etc.). The `message` field contains the specific reason. On a portal that places applications in a galaxy, when the body carries neither `source` nor the full `provider` + `plan` + `region` tuple, an `error.hint` object is added (see below) |
| 400 | `SOURCE_AT_CREATE_GALAXY_ONLY` | The `source` field was passed on a portal without Galaxy mode. Upload the code via [`POST /:id/deploy`](/docs/infra/deploy/deploy) after creation |
| 400 | `RUNTIME_PARAM_REMOVED` | The `runtime` parameter was passed without `source`. Specify the runtime in [`POST /:id/deploy`](/docs/infra/deploy/deploy), or pass `runtime`, `start` and `source` together in this same request. On a portal that places applications in a galaxy, an `error.hint` object is added (see below) |
| 400 | `UNKNOWN_PARAM` | The request body has an unknown field (e.g. `deployMode` instead of `placement`). `details.unknownFields` lists the extra fields, `details.suggestions` proposes the correct name, and `details.validParams` is the full list of accepted fields |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not passed |
| 401 | `INVALID_API_KEY` | Invalid or expired API key |
| 402 | `COMMERCIAL_PLAN_REQUIRED` | The Bitrix24 plan is free and the trial period is unavailable (already used). See the [Plan and access](/docs/infra#plan-and-access) section |
| 402 | `TRIAL_EXPIRED` | The trial was used and has ended |
| 402 | `TRIAL_PORTAL_LIMIT` | The portal's server quota on trial was exceeded |
| 402 | `TRIAL_USER_LIMIT` | The per-user server quota on trial was exceeded |
| 402 | `PLAN_NOT_ALLOWED_ON_TRIAL` | The requested plan is not available during the trial period. `error.details.allowedPlans` lists the plans the trial gate is configured for; `error.details.requestedPlan`, when present, echoes the rejected plan |
| 402 | `ACCOUNT_FROZEN` | The Vibecode balance is frozen, a top-up is required |
| 403 | `INFRA_NOT_PERMITTED` | Infrastructure is disabled on the platform or on the portal |
| 403 | `SERVER_CREATION_DISABLED` | Server creation is forbidden by portal policy |
| 403 | `MAX_SERVERS_REACHED` | The per-API-key server limit was exceeded. Delete unneeded ones via [`DELETE`](./delete.md) or create a new API key |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The key is in read-only mode, but creating a server is a write operation. Switch the key to read and write — see [Access mode](/docs/keys-auth/access-mode) |
| 403 | `INFRA_SCOPE_REQUIRED` | The key lacks the `vibe:infra` scope — infrastructure management is unavailable. Add the scope or use a key with infrastructure permissions |
| 403 | `INFRA_FORBIDDEN_FOR_COWORK_KEY` | The call was made with a Co-work key. Such a key works with data only and does not manage servers. `error.details.requiredAction` carries the steps for obtaining a key with deploy rights |
| 404 | `NO_CREDENTIALS` | The `provider` is not configured on the platform |
| 502 | `PROVIDER_ERROR` | The cloud provider returned an error while creating the virtual machine. The server record is marked as deleted, the quota is not consumed — you can retry immediately. The `message` field contains the text from the provider |
| 429 | `RATE_LIMITED` | The platform's overall request limit was exceeded |
| 503 | `POOL_EXHAUSTED` | The service is temporarily overloaded — the database connection pool is exhausted. The response carries `retryAfter` and a `Retry-After` header, retry after a few seconds |

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

### The `error.hint` object

On a Bitrix24 account that places applications in a galaxy, the `INVALID_REQUEST` and `RUNTIME_PARAM_REMOVED` errors are extended with an `error.hint` object. It names the reason for the refusal and gives a ready-made request body. The condition differs between the two codes. For `INVALID_REQUEST` the hint arrives when the body carries neither `source` nor the full `provider` + `plan` + `region` tuple. For `RUNTIME_PARAM_REMOVED` it arrives when `runtime` is passed without `source`, even if the tuple is filled in completely. On a Bitrix24 account with dedicated virtual machines there is no hint in either case.

Do not confuse it with `data.hint` from a successful response: that one is a string about the next step after creating an application via scenario 2, this one is an object inside `error`.

| Field | Type | Description |
|-------|------|-------------|
| `error.hint.reason` | string | Why the request was refused |
| `error.hint.recovery` | string | What to change in the request so it succeeds |
| `error.hint.example` | object | A ready-made request body to start from |

```json
{
  "success": false,
  "error": {
    "code": "INVALID_REQUEST",
    "message": "provider: Required; plan: Required; region: Required",
    "hint": {
      "reason": "This portal places new apps on shared galaxy hosts, and the request lacked `source` and the full provider/plan/region tuple.",
      "recovery": "RECOMMENDED: create-and-deploy in ONE call — POST /v1/infra/servers { name, source: { content }, runtime, start }; OMIT provider/plan/region. Two-step also works: pass provider/plan/region (informational for a galaxy — values from GET /v1/infra/providers catalogs) to create an empty slot, then POST /v1/infra/servers/:id/deploy with the source. See GET /v1/me -> deployment.galaxyApp.checklist. For a deliberate dedicated standalone VM pass placement: \"dedicated\" together with provider/plan/region.",
      "example": {
        "name": "<slug>",
        "source": { "content": "<base64 gzip-tar of the app>" },
        "runtime": "node20",
        "start": "node server.js",
        "port": 3000
      }
    }
  }
}
```

If the body carries `placement: "dedicated"`, the hint is different: it suggests adding `provider`, `plan` and `region` while keeping the dedicated server, instead of moving the application into a galaxy container.

## Idempotency

Pass the optional `Idempotency-Key` header to safely retry creating a **standalone** server. If the response to the first request is lost (a network drop, a load-balancer timeout), a retry with the same key does not create a second server — it returns the same server the first request created, with status `201` and an `Idempotent-Replayed: true` response header.

- The key is a 1–255 character string from `[A-Za-z0-9_.:-]`. It is scoped to your API key.
- **On a replay the one-time SSH credentials are not re-exposed.** In the response body `ssh.privateKey` and `ssh.password` are `null`, and a `note` field explains this. Keep the credentials from the first create response.
- **The header applies to standalone servers only.** On galaxy-placement Bitrix24 accounts a well-formed key is ignored without an error, and the retry protection does **not** extend to that path.
- The header is not supported together with `graduateFrom` for a dedicated server — it returns `400 IDEMPOTENCY_UNSUPPORTED_WITH_GRADUATION`.

| Status | `code` | When |
|--------|--------|------|
| 400 | `INVALID_IDEMPOTENCY_KEY` | The key fails validation (length or disallowed characters) |
| 400 | `IDEMPOTENCY_UNSUPPORTED_WITH_GRADUATION` | The key together with `graduateFrom` for a dedicated server |
| 409 | `IDEMPOTENCY_KEY_ALREADY_USED` | The key was already used for a server that has since been deleted — pick a new key |
| 409 | `IDEMPOTENCY_CONCURRENT_RETRY` | A concurrent request with the same key is still in progress — retry shortly |

## Known specifics

- **Examples of valid and invalid names** by the pattern `^[a-z][a-z0-9-]*$`:
  - Valid: `my-app`, `bot-1`, `crm-dashboard`.
  - Invalid: `My-App` (uppercase), `bot_1` (underscore), `my.app` (dot), `café` (non-ASCII letters).
- **`sshPublicKey` vs auto-generation.** If you pass your own public key — the platform does not generate a private one, and `ssh.privateKey` in the response will be `null`. The password in `ssh.password` is still returned.
- **When `region` in the response differs from the requested one.** The requested zone may have run out of IP addresses (`Address space exhausted`) — then the platform automatically tries the next zones in the order from [`GET /v1/infra/providers/:providerId/regions`](../providers/regions.md), and records this in the audit log with the `SERVER_ZONE_FALLBACK` event.
- **Provisioning timeout is 15 minutes.** If `status` stays in `provisioning` longer, the platform moves the server to `error` and fills in `provisionError` and `provisionErrorCode`. After that, all that remains is to call [`DELETE`](./delete.md) and create a new one.
- **The runtime is installed at the deploy stage.** The `runtime` parameter has been removed from `POST /v1/infra/servers` (it will return `400 RUNTIME_PARAM_REMOVED`). Specify `runtime` in the body of [`POST /:id/deploy`](/docs/infra/deploy/deploy) — it will be installed between archive extraction and application start.

## See also

- [Providers and catalogs](/docs/infra/providers)
- [Get a server](./get.md)
- [List servers](./list.md)
- [Update name and description](./update.md)
- [Delete a server](./delete.md)
- [Deploy API](/docs/infra/deploy)
- [Full application deploy](/docs/infra/deploy/deploy)
- [List of runtimes](/docs/infra/deploy/runtimes)
- [Plan and access](/docs/infra#plan-and-access)
- [Access mode](/docs/keys-auth/access-mode)
