## Restore the database from a backup

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

Replaces the application's SQLite file with the contents of a completed database backup. The restore runs in the background, and before the replacement the platform saves a protective backup of the current file.

## Parameters

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

## Request fields (body)

| Field | Type | Required | Description |
|------|-----|:-----:|----------|
| `backupId` | string | yes | ID of a backup with status `COMPLETED` and an unexpired `expiresAt`. List: [`GET /db/backups`](/docs/infra/database/backups), the `data.items[].id` field. The backup may belong to another application in the same Bitrix24 account, including a deleted one, if the key has access to that application's data. Backups of another application: [`GET /db/backups?sourceAppId=...`](/docs/infra/database/backups) |
| `file` | string | no | Path of the file to replace relative to the `/data` volume, up to 1024 bytes in UTF-8. Without the field, the file set in the application container's `VIBE_DB_PATH` variable is replaced, or `app.db` if the variable is not set. An absolute path, `.` and `..` segments, empty segments and control characters are rejected |

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/restore" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"backupId":"BACKUP_ID"}'
```

### curl — OAuth application

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

### JavaScript — personal key

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/infra/servers/${appId}/db/restore`,
  {
    method: 'POST',
    headers: { 'X-Api-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' },
    body: JSON.stringify({ backupId }),
  }
)
const { success, data, error } = await res.json()
if (!success) throw new Error(`${error.code}: ${error.message}`)
// Restore progress is in data.restore of the GET /db/backups response with the same restoreId
console.log('Restore accepted:', data.restoreId)
```

### JavaScript — OAuth application

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/infra/servers/${appId}/db/restore`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ backupId }),
  }
)
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 restore has been queued, and the database has not been replaced yet.

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | `true` when the restore has been queued |
| `data.restoreId` | string | Restore ID. Use it to find the restore in the `data.restore` field of the [`GET /db/backups`](/docs/infra/database/backups) response |
| `data.backupId` | string | Backup ID from the request |
| `data.targetAppId` | string (UUID) | ID of the application whose database is being replaced — the `id` value from the path |
| `data.status` | string | Always `QUEUED` |

## Response example

```json
{
  "success": true,
  "data": {
    "restoreId": "cm2r7x4k10003qv8n5d2b6w1z",
    "backupId": "9e4d2c1b-6a3f-4b8e-a7d5-0c1f2e3d4b5a",
    "targetAppId": "5c0e8f2a-3d1b-4a7e-9f60-2b8c4d1e7a93",
    "status": "QUEUED"
  }
}
```

## Error response example

404 — there is no backup with this `backupId`:

```json
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Server not found"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `VALIDATION_ERROR` | `backupId` is missing, the body contains an unknown field, `backupId` has invalid characters or `file` violates the path rules |
| 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 |
| 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 |
| 404 | `NOT_FOUND` | There is no application with this `id` on the portal, it was deleted or the key has no access to it |
| 404 | `NOT_FOUND` | There is no backup with this `backupId` on the portal, it is not yet in status `COMPLETED`, its retention period has expired or the key has no access to the data of the application it was taken from. The `message` text is the same for both `NOT_FOUND` cases |
| 404 | `APP_DB_NOT_FOUND` | The application has no data volume |
| 409 | `APP_DB_BUSY` | The restore was not accepted: the application has a backup, another restore, a database write, a deploy or migration in progress, waking the application or host is blocked, or the portal balance is frozen. Retry after `details.retryAfterSeconds` seconds |
| 413 | `PAYLOAD_TOO_LARGE` | The body exceeds 4 KiB |
| 422 | `APP_DB_BACKUP_INVALID` | The backup is completed, but it lacks the data required for a restore |
| 429 | `RATE_LIMITED` | The write 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 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 is restored once the deletion is cancelled |

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

## Known specifics

**Restore progress.** A `202` response means only that the request was accepted. Read the state in `data.restore` of the [`GET /db/backups`](/docs/infra/database/backups) response, matching `restoreId`. On a test application, the restore took 12 minutes and went through the phases in order: `VALIDATE`, `PREPARE`, `PRE_CHANGE`, `STAGE`, `STOPPING`, `SWAPPING`, `VERIFY`, `STARTING`, `CLEANUP`. After completion, the database data matched the backup. The restore stays in status `QUEUED` while another restore is running on the platform or a backup or database write is in progress on the same host.

**The database is busy until the restore ends.** Until the restore completes, writes to this application's database and new backups return `409 APP_DB_BUSY`. Reads also get `409 APP_DB_BUSY`: on a live call, a read right after the restore started returned this response, and from the `STOPPING` phase reads are blocked until the restore ends.

**Application restart.** A running application is stopped in the `STOPPING` phase and started again in the `STARTING` phase. An application that was stopped before the restore stays stopped.

**Protective backup before the replacement.** In the `PRE_CHANGE` phase, the platform saves the current file as a backup with `triggerType` equal to `PRE_CHANGE` — it is visible in the [backup list](/docs/infra/database/backups). To undo the restore, restore the database from this backup. If the protective backup could not be created, the restore ends with status `FAILED` and the file is not replaced. If the file specified by `file` does not exist yet, no protective backup is created.

**Failure during the replacement.** A failure in the `VALIDATE`, `PREPARE` or `PRE_CHANGE` phase does not touch the file. A failure in the phases from `STAGE` to `VERIFY` returns the original file through the `ROLLBACK` phase. In both cases, the restore ends with status `FAILED` and `errorCode` equal to `APP_DB_RESTORE_FAILED`. A failure in the `STARTING` phase or three failed `ROLLBACK` attempts move the restore to the `RECOVERY_REQUIRED` phase with status `RUNNING` without an automatic rollback: from the `STARTING` phase the application is already running on the restored database. Such a restore waits for support to intervene, and the application's database stays busy until then.

Database access conditions and backup retention rules — [When the database is available](/docs/infra/database#when-the-database-is-available) and [Backups and retention](/docs/infra/database#backups-and-retention).

## See also

- [List database backups](/docs/infra/database/backups)
- [Create a database backup](/docs/infra/database/snapshot)
- [Protective backups before writes](/docs/infra/database#protective-backups-before-writes)
- [Application database](/docs/infra/database)
