## Get host preparation status

`GET /v1/infra/servers/:id/app-db/prepare`

Returns the preparation state of the galaxy host for working with the application database: which components are missing and which installation step is in progress. The request only reads the state and does not start preparation.

## Parameters

| Parameter | Type | Required | Description |
|----------|-----|:-----:|----------|
| `id` (path) | string (UUID) | yes | ID of an application with `kind` equal to `GALAXY_APP`. List: [`GET /v1/infra/servers`](/docs/infra/servers/list) |

Query string parameters are not accepted: any parameter is rejected with `400 VALIDATION_ERROR`.

## Examples

### curl — personal key

```bash
curl "https://vibecode.bitrix24.com/v1/infra/servers/APP_ID/app-db/prepare" \
  -H "X-Api-Key: YOUR_API_KEY"
```

### curl — OAuth application

```bash
curl "https://vibecode.bitrix24.com/v1/infra/servers/APP_ID/app-db/prepare" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN"
```

### JavaScript — personal key

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/infra/servers/${appId}/app-db/prepare`,
  { headers: { 'X-Api-Key': 'YOUR_API_KEY' } }
)
const { success, data, error } = await res.json()
if (!success) throw new Error(error.code)
console.log(data.state, data.missing)
```

### JavaScript — OAuth application

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/infra/servers/${appId}/app-db/prepare`,
  {
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
    },
  }
)
const { success, data, error } = await res.json()
```

## Response fields

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | `true` when the state was read successfully |
| `data.state` | string | `not_prepared` — components are missing and preparation is not running. `preparing` — preparation is running. `ready` — all components are in place. `failed` — the last preparation ended with an error; the cause is in `data.errorCode` |
| `data.steps` | array | Seven steps in a fixed order: `disk`, `python3`, `zstd`, `age`, `curl`, `image`, `verify` |
| `data.steps[].id` | string | Step: `disk` — free space and package manager check, `python3`, `zstd`, `age`, `curl` — package installation, `image` — download of the service image for reading the database, `verify` — final check |
| `data.steps[].state` | string | `pending` — the step has not started, `running` — in progress, `done` — completed, `present` — the component was already on the host, `failed` — the step ended with an error |
| `data.missing` | string[] | Components currently missing on the host: `python3`, `zstd`, `age`, `curl`, `image`. With `ready`, the array is empty |
| `data.errorCode` | string | Only with `failed`: `LOW_DISK`, `DPKG_BROKEN`, `APT_PLAN_UNSAFE`, `APT_FAILED`, `PULL_FAILED`, `VERIFY_FAILED` or `UNSUPPORTED_HOST`. For what to do about each code, see the "Known specifics" section |
| `data.freeBytes` | integer | The smaller of the free space on the root partition and on the Docker images partition, in bytes. Returned with `preparing` and `failed` if a measurement was taken |

## Response example

Preparation has not been started; the host is missing only the service image:

```json
{
  "success": true,
  "data": {
    "state": "not_prepared",
    "steps": [
      { "id": "disk", "state": "pending" },
      { "id": "python3", "state": "present" },
      { "id": "zstd", "state": "present" },
      { "id": "age", "state": "present" },
      { "id": "curl", "state": "present" },
      { "id": "image", "state": "pending" },
      { "id": "verify", "state": "pending" }
    ],
    "missing": ["image"]
  }
}
```

The host is prepared:

```json
{
  "success": true,
  "data": {
    "state": "ready",
    "steps": [
      { "id": "disk", "state": "done" },
      { "id": "python3", "state": "present" },
      { "id": "zstd", "state": "present" },
      { "id": "age", "state": "present" },
      { "id": "curl", "state": "present" },
      { "id": "image", "state": "present" },
      { "id": "verify", "state": "done" }
    ],
    "missing": []
  }
}
```

## Error response example

400 — a query string parameter was passed, but the endpoint accepts no parameters:

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid preparation parameters"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `VALIDATION_ERROR` | A query string parameter was passed |
| 400 | `APP_DB_UNSUPPORTED_PLACEMENT` | The application is not hosted in Galaxy |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header is missing |
| 401 | `INVALID_API_KEY` | The key is not recognized |
| 402 | `BILLING_FROZEN` | The galaxy host is asleep and the portal's billing is frozen: the host cannot be woken |
| 403 | `INFRA_SCOPE_REQUIRED` | The key lacks the `vibe:infra` scope |
| 403 | `APP_DB_DISABLED` | Application database access is not enabled for the portal |
| 403 | `SERVER_WAKE_BLOCKED` | The galaxy host is asleep and waking it is blocked |
| 404 | `NOT_FOUND` | The application or its host was not found, was deleted or is not accessible to this key |
| 409 | `APP_DB_BUSY` | The host did not wake up within 10 seconds or is busy with another operation. Retry after the delay given in `Retry-After` |
| 429 | `RATE_LIMITED` | The read request limit for the portal was exceeded — [Limits](/docs/infra/database#limits). The pause is in the `Retry-After` header |
| 503 | `APP_DB_TOOL_UNAVAILABLE` | The service channel to the host is unavailable, or the host returned an invalid state. Retry after the delay given in `Retry-After` |
| 503 | `user_self_deletion_pending` | The key owner is deleting their account on the portal |

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

## Known specifics

**Preparation starts with a separate call.** On `not_prepared` or `failed`, start it with [`POST /app-db/prepare`](/docs/infra/database/prepare) at the same address.

**Polling wakes a sleeping host** — see [When the database is available](/docs/infra/database#when-the-database-is-available).

**How to poll.** After starting preparation, poll this address every 3 seconds, and on `409`, `429` and `503` wait for the `Retry-After` delay instead if it is longer. On `ready`, re-read the database; on `failed`, stop polling and show the cause from `data.errorCode`. `not_prepared` during polling means preparation is not running: stop polling and start it again. In a UI, pause polling while the tab is hidden, and cancel requests when the user switches to another application or closes the screen.

**What to do on `failed`.** Restarting preparation skips components that are already installed.

- `LOW_DISK` — the root partition or the Docker images partition has less than 6 GiB of free space. Free up space in application data or contact support.
- `DPKG_BROKEN` — the host's package manager is in a broken state. Contact support.
- `APT_PLAN_UNSAFE` — installation would require upgrading or removing already installed packages, and the platform stopped it. Contact support.
- `UNSUPPORTED_HOST` — the host is not Ubuntu 24.04 or lacks required system utilities. Contact support.
- `APT_FAILED`, `PULL_FAILED`, `VERIFY_FAILED` — a package could not be installed, the service image could not be downloaded or the final check failed. Call `POST /app-db/prepare` again.

**Readiness wait loop (ready-to-use example):**

```javascript
async function waitForPreparation(url, headers, signal) {
  while (!signal.aborted) {
    const response = await fetch(url, { headers, signal })
    const retry = Number(response.headers.get('Retry-After'))
    const waitMs = Math.max(3000, Number.isFinite(retry) ? retry * 1000 : 0)
    if (response.ok) {
      const { data } = await response.json()
      // ready, failed and not_prepared are terminal states for polling
      if (data.state !== 'preparing') return data
    } else if (![409, 429, 503].includes(response.status)) {
      throw new Error(`HTTP ${response.status}`)
    }
    await new Promise((resolve, reject) => {
      const cancel = () => {
        clearTimeout(timer)
        reject(signal.reason)
      }
      const timer = setTimeout(() => {
        signal.removeEventListener('abort', cancel)
        resolve()
      }, waitMs)
      signal.addEventListener('abort', cancel, { once: true })
    })
  }
  throw signal.reason
}
```

## See also

- [Prepare the host](/docs/infra/database/prepare)
- [Get database information](/docs/infra/database/info)
- [Create a database backup](/docs/infra/database/snapshot)
- [Application database](/docs/infra/database)
