For AI agents: markdown of this page — /docs-content-en/infra/servers.md documentation index — /llms.txt

Servers

CRUD operations for servers: create a new server, list your servers, fetch details of a single server by ID, update its name and description, and delete a server. A created server is always in Black Hole mode — invisible from the internet, with access to the application only via an HTTPS subdomain.

Scope: vibe:infra

Create a server

POST /v1/infra/servers

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

There are two placement models, and what to expect after creation depends on the one your Bitrix24 account uses:

  • 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 until status: "running" and blackholeStatus: "CONNECTED". The response contains the SSH credentials 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" section below describes the two startup scenarios, and the full model is on the Galaxy app 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 page.

Do not wait for CONNECTED before uploading the code. A Galaxy application never reaches blackholeStatus: "CONNECTED" on its own — its container is built when you upload the code. 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.

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 until status: "running". A separate code-upload call is not needed.

Terminal
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, healthPath fields have the same meaning as in the POST /:id/deploy body:

Field Type Required Description
source object yes (for this scenario) The code source. source.content — the application archive in base64, up to 96 MB per request body, which is about 72 MB of the archive itself: base64 is roughly a third larger than the raw bytes. A body over the cap is refused with 413 INLINE_SOURCE_TOO_LARGE. Deploy a larger archive in two steps — scenario 2 below
runtime string yes (if source is passed) Runtime ID: node20, python311, php83, static, and others. The list — GET /v1/infra/runtimes
start string yes (if source is passed) The application start command, on a single line. A line break is rejected with 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
healthPath string no The path the platform uses to check the application's readiness inside its container. Up to 500 characters, must start with /. Defaults to /. Ignored when creating a dedicated virtual machine

The one-shot create accepts no other deploy fields: preStart, systemd, serviceName, cleanDeploy, extractTo, hardening, preserveEnv, dataDirs, dataDirsRecursive return 400 UNKNOWN_PARAM, and the full list of accepted fields arrives in details.validParams. These fields belong to a dedicated virtual machine deploy and do not apply to a Galaxy application — put the commands that must run before the application starts into install.

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

During the trial period, first read the deployment and capabilities.servers.create blocks from GET /v1/me. When galaxyApp is present, a one-shot create is worth trying, but the block reflects stored state and does not guarantee success: only the POST checks live capacity. When Galaxy mode is disabled, placementNote is absent and a request carrying source returns 400 SOURCE_AT_CREATE_GALAXY_ONLY. With no host or in dedicated-machine mode, the two-step path is available only while capabilities.servers.create.available is true; select an allowed plan ID from capabilities.servers.create.limits.allowedPlans. When an existing host is unsuitable, wait for it to recover, ask an administrator to repair it, or upgrade the plan before retrying one-shot creation. While available is false, do not switch to a dedicated machine: with trial enforcement active, the occupied slot returns 402 TRIAL_PORTAL_LIMIT. After removing the host, re-read GET /v1/me and use the two-step path advertised there.

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 with the code in source, the runtime, and the start command. Upload the code right away, without waiting for CONNECTED.

Terminal
# 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 and plan are required by the schema for a create without source. region is optional: omit it and the platform resolves the chosen provider's default region. For a Galaxy application the values are informational — the app inherits its host's provider, plan and region.

When a value you sent differs from the one the app actually got, the response carries an entry in warnings[] next to data: it names the diverging fields, shows both the value you sent and the one in force, and warns that re-sending will change nothing. Fields that matched are not mentioned, so a correct call stays free of warnings. If you need a machine whose provider, plan and region you choose, create it with placement set to dedicated.

A dedicated virtual machine instead of a Galaxy app

If your Bitrix24 account places apps in galaxies but this particular app needs its own server, pass the placement field set to dedicated. The provider + plan pair is required in that case; region stays optional. A source field in such a request returns 400 SOURCE_AT_CREATE_GALAXY_ONLY: a dedicated virtual machine is deployed in two steps, and the code is uploaded via POST /:id/deploy.

Terminal
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-small",
    "region": "bc-eu-central",
    "placement": "dedicated"
  }'

The response carries kind set to STANDALONE. From there the dedicated virtual machine flow applies: wait for status: "running" and blackholeStatus: "CONNECTED", then deploy.

Diagnostics and environment variables

  • The provisionError field. If the build or startup failed, GET /v1/infra/servers/:id returns the failure reason in data.provisionError — plus the tail of the Docker build log when the image build failed. 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. The platform passes PORT alongside them (equal to the port field, 3000 by default): the PORT key is reserved, your own value is overridden, and the response carries a warnings[] entry — see POST /v1/infra/servers/:id/deploy. 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, 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 or script (emoji included). 2–100 characters, no control bytes. Displayed in the UI, server-down notifications, security alerts, billing line items, the Bitrix24 catalog. If not passed, name is used instead. 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; you can set it later via PATCH /v1/infra/servers/:id
plan string yes Plan ID from GET /v1/infra/providers/:providerId/plans, for example bc-small
region string no Region ID from GET /v1/infra/providers/:providerId/regions, for example bc-eu-central. Omit it and the platform resolves the provider's default region
image string no Public OS image identifier from GET /v1/infra/providers/:providerId/images. Stable family identifiers such as ubuntu-2404-lts are accepted; the platform resolves them to the provider's current concrete image. If omitted, the provider's default image is resolved
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 generates a private key and returns 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 dedicated 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) that the platform deletes as soon as it has created the dedicated server for it — so the old application does not linger in the galaxy. Owner-scoped: the same key, the same Bitrix24 account, kind=GALAXY_APP. An identifier that is not yours, or that does not belong to a Galaxy application, 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

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. Exception — Galaxy mode: when source is passed, runtime is required (see the "Galaxy application" section), and 400 RUNTIME_PARAM_REMOVED is not returned in that case.

Examples

curl — personal key

Terminal
# 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

Terminal
# 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 "provisioning" right after a new server is created — wait for "running", which takes 1–3 minutes. On a reuse response (reused: true) this is the existing server's status and can be anything, including "running" immediately — in which case waiting for "running" verifies nothing
data.provider string Echo of the passed provider. Exception — a Galaxy application (createdVia: "galaxy"): the host's provider is returned, not the one requested
data.name string The server's system name. Echo of the passed name when a new server is created. On a reuse response it is the existing server's name, and the one you asked for is in data.requestedName
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 equals 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
data.ip string | null Public IP. null right after creation, filled in when the virtual machine starts
data.ssh.user string SSH user: 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 Resolved public image identifier. It may be the provider default when image was omitted, or a stable family identifier normalized from the provider's concrete 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. On a reuse response it is the existing server's value, so a v1 create response may carry "ui"
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.sleepAfterMinutes number | null Saved auto-sleep timeout in minutes. The field is present in every successful 201 response — for a new dedicated machine and Galaxy application, and on reuse and idempotency replay. null is the saved “Never” choice and disables idle auto-sleep. Setting it is rejected with enabled wake windows, while storing it blocks schedule creation and update even for a disabled window; an older effective conflicting record uses the platform's post-window timeout
data.runtimeId string | null Always null at creation. Filled in on deploy with runtime
data.runtimeStatus string | null A deprecated field, kept for compatibility. For servers created after 2026-04-25 it always returns null. Runtime readiness after a deploy is determined by the success of the runtime step in the POST /:id/deploy response, not this field
data.appUrl string | null HTTPS address of the application: https://{subdomain}.vibecode.bitrix24.com
data.createdAt string (ISO 8601) Creation timestamp
warnings array<string> Next to data, not inside it. Returned only when there is something to report: for example, displayName or description lost their non-ASCII characters on the way in (Windows PowerShell without explicit UTF-8 serialization is the usual case). The server is still created, and both values reach the Bitrix24 catalog card as is

data.sleepAfterMinutes is a response field, not a create parameter. To change the saved policy after creating a dedicated machine, call PATCH /v1/infra/servers/:id/sleep. Do not substitute the expected default for this field: on reuse or idempotency replay the response carries the existing record's value, including null or a historical numeric value. It is not a computed effective timeout: enabled wake-schedule windows must be deleted or disabled before null can be set, and once null is stored every schedule create/update is rejected, including a disabled window. If a reuse or replay exposes an older conflicting record with an effective schedule, the platform's post-window fallback applies — 15 minutes by default.

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",
    "sleepAfterMinutes": 60,
    "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",
    "sleepAfterMinutes": 60,
    "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",
    "sleepAfterMinutes": 60,
    "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 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 (an invalid name format, a missing required field, 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 required provider + plan pair, an error.hint object is added (see below)
400 SOURCE_AT_CREATE_GALAXY_ONLY The request carrying source could not be placed in a galaxy. This happens when Galaxy mode is disabled, together with placement: "dedicated", or when the actual check finds no suitable host. Follow error.hint and capabilities.servers.create: with no galaxy host the two-step path is available only at available: true; with an existing host and available: false, the hint rules that path out because with trial enforcement active a dedicated-machine create returns 402 TRIAL_PORTAL_LIMIT
400 GALAXY_SOURCE_URL_NOT_ALLOWED The link in source.url did not pass the check that runs before it is sent to the galaxy host. Reasons: the address does not point at the platform source storage, the value is not an absolute URL, the scheme is neither http nor https, or the address does not point at the public network — a local, internal or service host. The response text names the rule that fired, and for an outside address it names the replacement — save the archive as a version and deploy it as a second step through code deploy. The same check runs on that deploy too
400 RUNTIME_PARAM_REMOVED The runtime parameter was passed without source. Specify the runtime in POST /:id/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
400 INVALID_PLAN The plan value is not in the provider catalog. List — GET /v1/infra/providers/:providerId/plans
400 INVALID_REGION The region you passed is not in the provider catalog. List — GET /v1/infra/providers/:providerId/regions. Second case: region was omitted and the provider exposes no regions at all — message then names the provider, and the platform has no default to substitute
401 MISSING_API_KEY The X-Api-Key header was not passed
401 INVALID_API_KEY Invalid or expired API key
401 UNAUTHENTICATED An app key (vibe_app_) was sent without the Authorization: Bearer header — server creation runs a plan check that has to know who is creating the server. The error.hint field names the next step: complete the OAuth flow, or use a personal API key (vibe_api_), which needs no session. See Keys and authentication
402 INT_TARIFF_REQUIRED The portal is on a free Bitrix24 plan — creating servers requires a commercial plan (a trial plan grants limited access). For accounts where access is narrowed to the Vibe+ plan line, infrastructure requires a Vibe+ plan, and an ordinary commercial plan is declined with INT_VIBE_PLUS_REQUIRED
402 COMMERCIAL_PLAN_REQUIRED The Bitrix24 plan is free and the trial period is unavailable (already used). See the Plan and access section
402 TRIAL_EXPIRED The trial was used and has ended
402 TRIAL_PORTAL_LIMIT The portal's server quota for the trial period was exceeded
402 TRIAL_USER_LIMIT The per-user server quota for the trial period 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 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
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 Cowork/Code key — such a key works with data only and cannot perform write operations. To issue a key that can, see Project key for deploy
404 NO_CREDENTIALS The provider is not configured on the platform
409 GALAXY_FULL The portal hosts applications in galaxies and there is no room for another one. Before refusing, the platform tries to place the application on another galaxy, so this response means there really is no room left. Delete an unneeded Galaxy application, or ask for a dedicated machine — the placement field with the value dedicated
409 REISSUE_IN_PROGRESS The application card owner is re-issuing its authorization key right now, so no server was created: it would have landed on a key that is being revoked at the same moment. The refusal is transient — repeat the request once the re-issue finishes
413 INLINE_SOURCE_TOO_LARGE The body carrying inline source.content is over 96 MB, which is about 72 MB of the archive itself. The decision is based on the Content-Length header before the body is read, so the refusal is deterministic — re-sending the same body fails identically. It saves no traffic: the platform accepts the whole body first, and the refusal arrives once the upload has finished. error.hint carries four strings — reason, recovery, recoveryAction and note: the recipe here is to create the server without source, save the archive as a version on the identifier that comes back, and deploy it with {"source": {"versionId": "vN"}}, because every URL on the versioned path is built from the server identifier, which does not exist before creation
429 RATE_LIMITED The platform's overall request limit was exceeded
429 DEPLOY_BACKEND_BUSY The request carried an archive in source.content while the backend was already running the maximum number of such heavy requests (one shared counter with POST /:id/deploy and POST /:id/upload). The response carries a Retry-After: 30 header — retry in half a minute. To avoid the queue entirely, create the server without source and upload the code in a separate request with a link, {source: {url: ...}} — that path has no concurrency cap
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 carries a localized description of the failure plus an incident code — the provider's raw response never reaches the body; it stays in the platform logs. Quote the incident code when contacting support
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.

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 required provider + plan pair. The region field is optional and defaults to the chosen provider's region. For RUNTIME_PARAM_REMOVED the hint arrives when runtime is passed without source, even if provider and plan are filled in. On a Bitrix24 account with dedicated virtual machines there is no hint in either case.

During trial access, these early hints use the same advisory assessment as GET /v1/me. A host that is suitable according to stored state produces a recommendation to try the one-shot request. With no host, the two-step path is available only while capabilities.servers.create.available is true. When an existing host is unsuitable, wait for recovery, repair the host, or upgrade the plan before retrying one-shot creation; after removing the host, re-read GET /v1/me and use the two-step path advertised there. The hint is advisory: the POST makes the final decision.

When a request carrying source has already reached the actual placement check and returns SOURCE_AT_CREATE_GALAXY_ONLY, its error.hint reflects that result. With an existing host during trial access, the hint rules out the two-step path while capabilities.servers.create.available is false: with trial enforcement active, that path returns 402 TRIAL_PORTAL_LIMIT. With no host, it permits creating a dedicated machine without source and deploying afterwards only after that capability check succeeds.

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",
    "hint": {
      "reason": "This portal places new apps on shared galaxy hosts, and the request lacked `source` and the required provider/plan pair. `region` is optional and defaults to the provider's region.",
      "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 and plan (region is optional and defaults to the provider's region) 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\" with provider and plan; region is optional.",
      "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 the required provider and plan while keeping the dedicated server; region may be omitted to use the provider default, instead of moving the application into a galaxy container.

Reusing the application's server

The calling key belongs to an application from the Applications section, and an application can have exactly one server bound to it. When a server is already bound and running, POST /v1/infra/servers does not create a second one — it returns 201 with the server that is already there. The name you sent plays no part in that decision: the deduplication key is the calling key, not the name.

On a reuse response (reused: true) every data field describes the existing bound server, not your requeststatus, provider, plan, region, image, createdVia, createdAt all belong to it. The status can be anything, so waiting for "running" is not a check that the server is yours. The check is reused plus the namerequestedName pair.

Such a response always carries data.reused: true and, next to data, a warnings array with at least one entry. Compare data.name with data.requestedName — they differ whenever the application's server was created earlier under a different name.

Field Type When
data.reused boolean Always on such a response. No new server was created
data.reusedReason string APPLICATION_ALREADY_HAS_SERVER
data.requestedName string Echo of your name — always, even when it equals the existing name
data.sourceIgnored boolean The source you sent was not deployed and was discarded
data.deploying boolean The source you sent is being built onto the application's container right now
data.metaIgnored boolean The displayName/description you sent were not applied
warnings array Next to data, not inside it. At least one entry

What to do before deploying. Call GET /v1/infra/servers/:id and inspect what is already deployed there. A deploy replaces the code currently running — it is not additive. If this is not the server you intended, stop and ask the user which application this key should be working on.

About the source you sent. A one-shot create builds your source onto a reused Galaxy application only when it has no container yet (never deployed, or its last build failed) — the response then carries deploying: true, and you poll GET /v1/infra/servers/:id until running. In every other case, including a reused dedicated virtual machine, the archive is discarded and the response carries sourceIgnored: true — deploy it explicitly via POST /v1/infra/servers/:id/deploy once you have confirmed the server is the one you want.

About the displayName and description you sent. A reuse never renames the server it hands back: it keeps its own name and description, and the values you sent are discarded — the response shows this as data.metaIgnored: true plus a dedicated warning. Rename it deliberately with PATCH /v1/infra/servers/:id once you have confirmed the server is the right one.

About data.next. On a reuse response the field arrives only when there is nothing to overwrite on the returned server — a Galaxy application with no container that is still attached to its galaxy host. When there is no container but the app is detached from its host, there is nowhere to deploy, so next does not arrive either. When the server can run code, next is deliberately absent — the missing recommendation is the signal: work out whether this is the right server first. Do not read its absence as an error.

How to get a different server for the application. There is no separate call that changes the binding. When you need a dedicated virtual machine and want the binding left alone, create it with placement: "dedicated" — reuse does not fire, but the new server is not bound to the application either. When you need to change the bound server itself, delete the current one via DELETE /v1/infra/servers/:id: the next POST /v1/infra/servers with the same key then clears the stale binding and binds the newly created server. Deletion is irreversible and destroys everything deployed on the server — confirm via GET /v1/infra/servers/:id that it is not someone else's work first.

A create with placement: "dedicated" is not affected by reuse.

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 Bitrix24 accounts that place applications in galaxies, 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.
  • The key does not protect against a repeated build on a reused server. A reused: true response is not stamped with the key, so retrying a request that carries source against a reused Galaxy application that has no container yet starts the build again. Poll GET /v1/infra/servers/:id first.
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. If the requested zone has run out of IP addresses (Address space exhausted), the platform automatically tries the next zones in the order from GET /v1/infra/providers/:providerId/regions, and records this in the audit log with the SERVER_ZONE_FALLBACK event. In Bitrix24 Cloud, a capacity fallback may select another availability zone inside the same public region; in that case data.region does not change.
  • 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 and create a new one.
  • A new regular machine gets a 60-minute auto-sleep timeout. The actual saved value is returned immediately in data.sleepAfterMinutes by the create response and later by GET /v1/infra/servers/:id. On reuse and idempotency replay this is the existing record's value, so it can be null or differ from 60. Inbound HTTP requests and SSH activity reset the timer; cron, background processes, and the application's own outbound requests do not. For the allowed values and deliberate disabling of auto-sleep through PATCH with null, see Configure auto-sleep.
  • The runtime is installed at the deploy stage. The runtime parameter without source is not accepted in POST /v1/infra/servers (it will return 400 RUNTIME_PARAM_REMOVED). Specify runtime in the body of POST /:id/deploy — for a dedicated virtual machine, the pipeline begins in the order runtime → stop_existing → clean → download → install. Together with source the runtime is accepted and required: that is the one-shot Galaxy application create, where runtime and start go in the same request.
  • Non-ASCII characters in displayName and description from Windows PowerShell. Sent without explicit UTF-8 serialization, they are stored as question marks (?): the bytes are lost on the client side, before the request is sent. A ready-to-run call with UTF8.GetBytesWindows / PowerShell and UTF-8.

See also