## Create a database backup

`POST /v1/infra/servers/:id/db/backups`

Queues a manual backup of the application's SQLite file. The backup is created in the background; the application keeps running during the backup.

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

## Request fields (body)

| Field | Type | Required | Description |
|------|-----|:-----:|----------|
| `file` | string | no | Database file path relative to the `/data` volume, up to 1024 bytes in UTF-8. If the field is omitted, the file named in the application container's `VIBE_DB_PATH` variable is backed up, or `app.db` if the variable is not set. An absolute path, `.` and `..` segments, empty segments and control characters are rejected. Database file list: [`GET /db`](/docs/infra/database/info) |

The body is optional: without a body or with an empty object `{}`, the default file is backed up. With the `Content-Type: application/json` header, the body cannot be empty — pass `{}`. The body accepts no other fields and is limited to 4 KiB.

## Examples

### curl — personal key

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/APP_ID/db/backups" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

### curl — OAuth application

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/APP_ID/db/backups" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

### JavaScript — personal key

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/infra/servers/${appId}/db/backups`,
  {
    method: 'POST',
    headers: { 'X-Api-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' },
    body: JSON.stringify({}),
  }
)
const { success, data, error } = await res.json()
if (!success) throw new Error(`${error.code}: ${error.message}`)
console.log('Backup queued:', data.backupId)
```

### JavaScript — OAuth application

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/infra/servers/${appId}/db/backups`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({}),
  }
)
const { success, data, error } = await res.json()
if (!success) throw new Error(`${error.code}: ${error.message}`)
```

## Response fields

The HTTP status of a successful response is `202 Accepted`: the backup has been queued but not created yet.

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | `true` when the backup has been queued |
| `data.backupId` | string | Backup ID. The backup appears under this `id` in [`GET /db/backups`](/docs/infra/database/backups) |
| `data.status` | string | Always `QUEUED` |

## Response example

```json
{
  "success": true,
  "data": {
    "backupId": "9e4d2c1b-6a3f-4b8e-a7d5-0c1f2e3d4b5a",
    "status": "QUEUED"
  }
}
```

## Error response example

409 — a repeated request right after creating a backup; the previous backup of this application has not completed yet:

```json
{
  "success": false,
  "error": {
    "code": "APP_DB_BUSY",
    "message": "The database is busy with a live write, retry shortly",
    "details": {
      "source": "sqlite",
      "retryable": true,
      "retryAfterSeconds": 1
    }
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `VALIDATION_ERROR` | The body contains an unknown field or `file` violates the path rules |
| 400 | `FST_ERR_CTP_EMPTY_JSON_BODY` | The `Content-Type: application/json` header was passed without a body. Pass `{}` or remove the header |
| 400 | `APP_DB_INVALID_FILE` | The `file` field was not passed, and the container's `VIBE_DB_PATH` value does not point to a valid path inside `/data` |
| 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 portal balance is frozen; the backup was not queued |
| 402 | `BILLING_INSUFFICIENT` | The portal balance is exhausted and storage is suspended. The backup was not queued |
| 403 | `INFRA_SCOPE_REQUIRED` | The key lacks the `vibe:infra` scope |
| 403 | `APP_DB_READONLY_KEY` | The key was issued as read-only |
| 403 | `APP_DB_DISABLED` | Application database access is not enabled for the portal |
| 403 | `SERVER_WAKE_BLOCKED` | The host is asleep and waking it is blocked |
| 404 | `NOT_FOUND` | There is no application with this `id` on the portal, it has been deleted, or the key has no access to it |
| 404 | `APP_DB_NOT_FOUND` | The application has no data volume |
| 409 | `APP_DB_BUSY` | The application is busy: the previous backup is still `QUEUED` or `RUNNING`, or a database restore, deploy or migration of the application is in progress. With `details.source` equal to `host` — another application's database is being restored on the host, the host is busy with another operation, or the host has not responded. Retry after `details.retryAfterSeconds` seconds |
| 413 | `PAYLOAD_TOO_LARGE` | The body exceeds 4 KiB |
| 429 | `RATE_LIMITED` | The write request limit for the portal was exceeded — [Limits](/docs/infra/database#limits). Retry after the delay in the `Retry-After` header |
| 503 | `APP_DB_TOOL_UNAVAILABLE` | The host is not prepared for database operations, or the platform temporarily cannot reach it. Host readiness — [`GET /app-db/prepare`](/docs/infra/database/prepare-status) |
| 503 | `user_self_deletion_pending` | The key owner requested deletion of their account on the portal. Access returns after the deletion is cancelled |

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

## Known specifics

**Backup readiness.** A `202` response does not mean the backup has been created. Poll [`GET /db/backups`](/docs/infra/database/backups) and look for the item with `id` equal to `backupId`: the backup is ready when `status` is `COMPLETED`, and if it is `FAILED`, `errorCode` gives the cause. On a test application with a 937-byte backup, the backup went from `QUEUED` to `COMPLETED` in 1 min 40 s.

**One backup at a time.** Until the application's previous backup completes, retries after `details.retryAfterSeconds` get the same `409 APP_DB_BUSY`. The `message` text does not name the cause: with `details.source` equal to `sqlite`, it refers to a database write even when the database is occupied by an unfinished backup. Instead of retrying frequently, poll the backup list until the backup completes.

Where backups are stored, how long they live and how they are billed — [Backups and retention](/docs/infra/database#backups-and-retention).

## See also

- [List database backups](/docs/infra/database/backups)
- [Restore the database from a backup](/docs/infra/database/restore)
- [Get database information](/docs/infra/database/info)
- [Application database](/docs/infra/database)
