
# Galaxy app

A Galaxy app is a separate Black Hole application that lives in a container on a shared host — a galaxy — together with other such apps. Each app gets its own HTTPS subdomain and isolation, like a separate virtual machine, but they all share one galaxy — which is many times cheaper than keeping a separate server per app. If the Bitrix24 account administrator enabled placement in galaxies, the same `POST /v1/infra/servers` call creates a Galaxy app instead of a virtual machine.

**Scope:** `vibe:infra` · **Base URL:** `https://vibecode.bitrix24.com/v1` · **Authorization:** `X-Api-Key` header

## Who chooses the placement model

The placement mode is chosen by the Bitrix24 account administrator in the dashboard, on the **Galaxies** page, in the **Placement** section. A regular Bitrix24 account member sees the mode as read-only, and the API caller does not set it. The same `POST /v1/infra/servers` request creates either a separate virtual machine or a Galaxy app, depending on the placement mode. There is no "create a Galaxy app specifically" parameter in the request.

The placement mode is one of three:

- **Galaxy First** (`galaxies-only`) — new apps are placed densely inside galaxies. If an app outgrows its container, it is moved to a separate Black Hole server.
- **Hybrid** (`both`) — an app lands in a galaxy when there is room, otherwise it is deployed on a separate virtual machine.
- **Standalone only** (`standalone-only`) — galaxies are not used, each app gets its own virtual machine.

What the caller should do about it:

- **Detect the model from the response, do not set it.** A Galaxy app is identified by the `createdVia` field equal to `galaxy` in the create response. The full list of signs is in the "How to tell a Galaxy app in the response" section below.
- **Need a separate virtual machine — request it explicitly.** If your Bitrix24 account places apps in galaxies but a particular app needs a dedicated server, pass the `placement` field equal to `dedicated` in the body of [`POST /v1/infra/servers`](./servers/create.md) — the platform will create a separate virtual machine instead of a Galaxy app. By default `placement` equals `auto`, and the placement mode applies.

Readiness differs between the models:

- **Separate virtual machine** — by `status: "running"` and `blackholeStatus: "CONNECTED"`.
- **Galaxy app** — only through a code upload, waiting for `CONNECTED` does not work here (see "Lifecycle" below).

## How to tell a Galaxy app in the response

| Field | Where | Value |
|-------|-------|-------|
| `createdVia` | response of [`POST /v1/infra/servers`](./servers/create.md) | `galaxy` for a Galaxy app |
| `kind` | response of `POST /v1/infra/servers` and [`GET /v1/infra/servers/:id`](./servers/get.md) | `GALAXY_APP` — a Galaxy app (container), `GALAXY` — a galaxy (the carrier host), `STANDALONE` — a separate virtual machine |
| `galaxyId` | response of `POST /v1/infra/servers` and [`GET /v1/infra/servers/:id`](./servers/get.md) | ID of the galaxy the app runs in. `null` for other types |
| `appCount` | response of [`GET /v1/infra/servers/:id`](./servers/get.md) | For a galaxy (`GALAXY`) — the number of non-deleted apps in it. `null` for other types |

A Galaxy app has no SSH access of its own (`ssh` equals `null`), no public IP address and no external virtual-machine identifier — a container has none of these. The `region` and `plan` fields in the response are the region and plan of the carrier galaxy: the values passed at creation have no effect, the platform chooses placement itself.

## Lifecycle — build on code upload

A Galaxy app **never** reaches `blackholeStatus: "CONNECTED"` on its own. Its container and tunnel agent are created by the code upload itself — there is no separate provisioning step like a virtual machine has.

This leads to the main rule: **upload the code right after creation, without waiting for `CONNECTED`.** If you create a Galaxy app and just poll the status, it 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.

## Creating and uploading code

Creation goes through the same [`POST /v1/infra/servers`](./servers/create.md) call. The code is uploaded in one of two scenarios.

- **Scenario 1 — single request.** Pass the code in the `source` field together with `runtime` and `start` right in `POST /v1/infra/servers`. The build runs in the background, no separate upload call is needed.
- **Scenario 2 — two steps.** Create the app without `source`, get `next: "deploy"` in the response, then upload the code via [`POST /v1/infra/servers/:id/deploy`](./deploy/deploy.md). Creating an app without `source` requires `provider`/`plan`/`region` (informational for a galaxy — see the [example in create.md](./servers/create.md)).

Full request bodies, fields and examples for both scenarios are on the [Create a server](./servers/create.md) page.

Creating a Galaxy app via scenario 1 — `source` passed, build running in the background. The model is identified by `createdVia` equal to `galaxy`:

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

## Deploy

Deploying a Galaxy app differs from deploying a virtual machine. The full contract is on the [Deploy application](./deploy/deploy.md) page.

- The code source is only the inline `source.content` (a base64 archive). The `source.url` and `source.versionId` variants are rejected with `400 GALAXY_DEPLOY_CONTENT_ONLY`.
- The `runtime` and `start` fields are required. Without `runtime` the request returns `400 GALAXY_DEPLOY_RUNTIME_REQUIRED`. The runtime list is at [`GET /v1/infra/runtimes`](./deploy/runtimes.md).
- You do not need to add a `Dockerfile` — the platform generates it from the `runtime`, `port` and `start` fields.
- The `env` variables are injected into the container at start, not baked into the image, so secrets do not end up in the image layers.
- The container working directory is `/opt/app`, and the `/app` path is also a link to it. Store persistent data in the `/data` volume.

## Cost

Cost is counted per galaxy, not per app. Several Galaxy apps in one galaxy share its cost — five apps on a `bc-small` plan cost roughly the same as one galaxy, not as five servers. Galaxy apps do not count toward the per-API-key server limit (`GET /v1/me` → `infra.limits.used`) — it counts only separate virtual machines.

## Logs

For a Galaxy app, [`GET /v1/infra/servers/:id/logs`](./deploy/logs.md) returns the output stream of the container itself (`stdout` and `stderr`), not the galaxy system journal. Reading logs does not wake a sleeping galaxy. If the galaxy is asleep or unreachable, the response is an empty `data.logs` array plus a diagnostic `data.hint` field. The `since` parameter for a Galaxy app accepts only a duration (`10m`, `2h`, `24h`) or a timestamp in RFC 3339 format (for example, `2026-04-22T10:50:11Z`).

## Deletion

A Galaxy app is removed with the same [`DELETE /v1/infra/servers/:id`](./servers/delete.md) call — the platform tears down its container in the galaxy and marks the record deleted. The platform wakes a sleeping galaxy itself before deletion. A galaxy with non-deleted apps is not deleted by this call — remove its apps first.

## Error codes

| HTTP | Code | Description |
|------|------|-------------|
| 400 | `SOURCE_AT_CREATE_GALAXY_ONLY` | The `source` field was passed at creation in a portal without galaxy mode. Upload the code via [`POST /:id/deploy`](./deploy/deploy.md) after creation |
| 400 | `GALAXY_DEPLOY_CONTENT_ONLY` | Deploying a Galaxy app with `source.url` or `source.versionId`. The source is only the inline `source.content` |
| 400 | `GALAXY_DEPLOY_RUNTIME_REQUIRED` | Deploying a Galaxy app without `runtime`. Specify a runtime, for example `node20` |
| 502 | `GALAXY_APP_BUILD_FAILED` | The container build failed. The tail of the build log arrives in the `buildLog` field, and the breakdown of the cause in `error.category` and `error.buildHint` — see [Upload code](./deploy/deploy.md) |
| 502 | `GALAXY_APP_START_FAILED` | The container built but crashed or went into a restart loop due to lack of memory right after start. The log tail arrives in the `buildLog` field, and the breakdown of the cause in `error.category` and `error.buildHint` — see [Upload code](./deploy/deploy.md) |
| 409 | `GALAXY_HAS_APPS` | The server being deleted is a galaxy that still has non-deleted apps. The body contains `appCount` |
| 502 | `GALAXY_DEPLOY_INTERRUPTED` | The galaxy host is reachable, but a connection drop during the build interrupted the deploy and its successful completion was not confirmed. Your app slot and the `/data` volume are preserved — retry the same request. If the error repeats, the app is likely failing to start — check the start command, port, dependencies, environment variables, and memory limit |
| 502 | `GALAXY_HOST_UNREACHABLE` | The galaxy is unreachable — its tunnel is not in `CONNECTED` status. The response carries `error.hint` with a recovery plan, see below |

The full reference of common API errors is on the [Errors](/docs/errors) page.

### The `error.hint` field of `GALAXY_HOST_UNREACHABLE`

A `502 GALAXY_HOST_UNREACHABLE` response carries an `error.hint` object of four strings:

| Field | What it holds |
|-------|---------------|
| `reason` | Why the galaxy is unreachable right now — its virtual machine is waking up, or the agent is reconnecting the tunnel |
| `recovery` | What to do. The wording depends on where the error came from — see the three branches below |
| `recoveryAction` | The concrete action to retry. Also depends on the source of the error |
| `note` | A caveat: the galaxy status in the listing can lag behind the real tunnel state. If the condition holds beyond 15 minutes, the galaxy is genuinely down |

The `reason` and `note` fields are always the same, while `recovery` and `recoveryAction` differ by the source of the error:

| Source | What the hint advises |
|--------|-----------------------|
| Code upload (`POST /v1/infra/servers/:id/deploy`) | Wait 1–2 minutes and re-send the same deploy to the same server ID. A sleeping galaxy is woken by the deploy itself |
| Command run (`POST /v1/infra/servers/:id/exec`) | ⚠️ If the galaxy is asleep, **retries will not wake it** — wake it first with a deploy or from the cabinet, and only then retry. If the galaxy was running, wait 1–2 minutes for the tunnel to re-establish |
| Deletion (`DELETE /v1/infra/servers/:id`) | Wait 1–2 minutes and retry the deletion. The server record is preserved until the teardown actually runs on the galaxy |

In all three cases **there is no need to delete and recreate the application**: the slot, its container and its `/data` volume are intact, and a fresh slot lands on the same galaxy and meets the same condition.

## See also

- [Create a server](./servers/create.md)
- [Deploy application](./deploy/deploy.md)
- [Server logs](./deploy/logs.md)
- [Delete a server](./servers/delete.md)
- [Infrastructure](/docs/infra)
