Para agentes de IA: markdown desta página — /docs-content-en/infra/servers.md índice da documentação — /llms.txt
Os artigos da documentação estão disponíveis atualmente em inglês.
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
provisioningstatus. Provisioning takes 1–3 minutes, after which you need to pollGET /v1/infra/servers/:iduntilstatus: "running"andblackholeStatus: "CONNECTED". The response contains the SSH credentialsssh.passwordandssh.privateKeyonly 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/serverscreates a Galaxy application instead of a virtual machine. In the response, this model is marked bycreatedVia: "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.
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 until status: "running". A separate code-upload call is not needed.
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.
# 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.
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
provisionErrorfield. If the build or startup failed,GET /v1/infra/servers/:idreturns the failure reason indata.provisionError— plus the tail of the Docker build log when the image build failed. Read this field to find the cause. envvariables are injected at start. Values fromenvare passed into the container at start time viadocker run --env, not baked into the image. The platform passesPORTalongside them (equal to theportfield,3000by default): thePORTkey is reserved, your own value is overridden, and the response carries awarnings[]entry — see POST /v1/infra/servers/:id/deploy. So API keys and secrets fromenvdo 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
runtimeparameter is not accepted inPOST /v1/infra/servers— passing it will return400 RUNTIME_PARAM_REMOVED. The runtime is installed at the deploy stage. See POST /:id/deploy. Exception — Galaxy mode: whensourceis passed,runtimeis required (see the "Galaxy application" section), and400 RUNTIME_PARAM_REMOVEDis not returned in that case.
Examples
curl — personal key
# 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
# 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
// 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
// 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:
{
"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:
{
"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:
{
"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:
{
"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):
{
"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 |
{
"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 request — status, 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 name ⊕ requestedName 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.privateKeyandssh.passwordarenull, and anotefield 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
graduateFromfor a dedicated server — it returns400 IDEMPOTENCY_UNSUPPORTED_WITH_GRADUATION. - The key does not protect against a repeated build on a reused server. A
reused: trueresponse is not stamped with the key, so retrying a request that carriessourceagainst a reused Galaxy application that has no container yet starts the build again. PollGET /v1/infra/servers/:idfirst.
| 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).
- Valid:
sshPublicKeyvs auto-generation. If you pass your own public key, the platform does not generate a private one, andssh.privateKeyin the response will benull. The password inssh.passwordis still returned.- When
regionin 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 fromGET /v1/infra/providers/:providerId/regions, and records this in the audit log with theSERVER_ZONE_FALLBACKevent. In Bitrix24 Cloud, a capacity fallback may select another availability zone inside the same public region; in that casedata.regiondoes not change. - Provisioning timeout is 15 minutes. If
statusstays inprovisioninglonger, the platform moves the server toerrorand fills inprovisionErrorandprovisionErrorCode. After that, all that remains is to callDELETEand create a new one. - A new regular machine gets a 60-minute auto-sleep timeout. The actual saved value is returned immediately in
data.sleepAfterMinutesby the create response and later byGET /v1/infra/servers/:id. On reuse and idempotency replay this is the existing record's value, so it can benullor 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 throughPATCHwithnull, see Configure auto-sleep. - The runtime is installed at the deploy stage. The
runtimeparameter withoutsourceis not accepted inPOST /v1/infra/servers(it will return400 RUNTIME_PARAM_REMOVED). Specifyruntimein the body ofPOST /:id/deploy— for a dedicated virtual machine, the pipeline begins in the orderruntime → stop_existing → clean → download → install. Together withsourcethe runtime is accepted and required: that is the one-shot Galaxy application create, whereruntimeandstartgo in the same request. - Non-ASCII characters in
displayNameanddescriptionfrom 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 withUTF8.GetBytes— Windows / PowerShell and UTF-8.