## List database backups

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

Returns backups of the application's SQLite database from newest to oldest and the state of the latest database restore. The list is read from the platform history, so the request does not wake the host and does not access the container.

## Parameters

| Parameter | Type | Required | Default | 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) |
| `limit` (query) | number | no | `50` | Backups per page, from `1` to `100` |
| `cursor` (query) | string | no | — | The `data.nextCursor` value from the previous response, unchanged. Up to 1024 characters: Latin letters, digits, `-` and `_` |
| `sourceAppId` (query) | string | no | the `id` value | ID of another application in the same Bitrix24 account whose backups to show, including a deleted one. List: [`GET /v1/infra/servers`](/docs/infra/servers/list); the ID of a deleted application is the `sourceAppId` field of its backups. If the key has no access to this application's data or no such application exists, `data.items` comes back empty rather than as an error |

**Pagination.** Backups are ordered by `createdAt`, with ties broken by `id`, both descending. There is no page number: request the next page with `cursor` equal to `data.nextCursor`. A `null` value in `data.nextCursor` means the end of the list. The cursor is valid only for the same application and the same `sourceAppId`. The response does not contain the total number of backups.

## Examples

### curl — personal key

```bash
curl "https://vibecode.bitrix24.com/v1/infra/servers/APP_ID/db/backups?limit=3" \
  -H "X-Api-Key: YOUR_API_KEY"
```

### curl — OAuth application

```bash
curl "https://vibecode.bitrix24.com/v1/infra/servers/APP_ID/db/backups?limit=3" \
  -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}/db/backups?limit=3`,
  { headers: { 'X-Api-Key': 'YOUR_API_KEY' } }
)
const { success, data, error } = await res.json()
if (!success) throw new Error(`${error.code}: ${error.message}`)

for (const copy of data.items) {
  console.log(copy.id, copy.triggerType, copy.status, copy.expiresAt)
}
if (data.restore) {
  console.log('Restore:', data.restore.status, data.restore.phase)
}
// Next page: pass data.nextCursor to the cursor parameter unchanged
```

### JavaScript — OAuth application

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

## Response fields

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | `true` on success |
| `data.items` | array | Database backups on the current page |
| `data.items[].id` | string | Backup ID. Pass it in `backupId` when [restoring](/docs/infra/database/restore) |
| `data.items[].sourceAppId` | string (UUID) | ID of the application whose database the backup was taken from |
| `data.items[].platformPaid` | boolean | `true` — the platform pays for storing the backup, `false` — it is stored in the Bitrix24 account's paid storage |
| `data.items[].captureScope` | string | What was backed up. Always `SQLITE` for database backups |
| `data.items[].status` | string | `QUEUED` — the backup is queued, `RUNNING` — being created, `COMPLETED` — ready, `FAILED` — failed, `EXPIRED` — the retention period has expired, `FLAGGED` — the backup was stopped and awaits the support team, `DELETED` — deleted |
| `data.items[].triggerType` | string | `MANUAL` — a manual backup, `SCHEDULED` — daily, `PRE_CHANGE` — protective, before a database change, `PRE_DELETE` — before the application is deleted |
| `data.items[].file` | string \| null | Path of the backed-up file relative to the `/data` volume |
| `data.items[].sizeBytes` | string \| null | Backup size in bytes, a decimal number in a string. `null` until the backup is complete |
| `data.items[].createdAt` | string | Time the backup was queued, ISO 8601 in UTC |
| `data.items[].completedAt` | string \| null | Time the backup completed, ISO 8601 in UTC. `null` until the backup is complete |
| `data.items[].expiresAt` | string \| null | End of the retention period, ISO 8601 in UTC. `null` until the backup is complete |
| `data.items[].errorCode` | string \| null | Failure cause for a backup with status `FAILED`: `APP_DB_BUSY`, `APP_DB_WAL_CLOSED`, `APP_DB_BACKUP_TIMEOUT`, `APP_DB_CORRUPT`, `APP_DB_TOOL_UNAVAILABLE`, `APP_DB_BACKUP_INVALID`. `null` for causes without a public code |
| `data.nextCursor` | string \| null | Cursor of the next page. `null` — there are no more pages |
| `data.restore` | object \| null | Restore of this application's database: the one in progress, or, if there is none, the latest. `null` if the application's database has never been restored. Does not depend on `sourceAppId` |
| `data.restore.restoreId` | string | Restore ID from the [`POST /db/restore`](/docs/infra/database/restore) response |
| `data.restore.backupId` | string | ID of the backup the database is restored from |
| `data.restore.targetAppId` | string (UUID) | ID of the application whose database is being restored |
| `data.restore.sourceAppId` | string (UUID) | ID of the application whose database the backup was taken from |
| `data.restore.status` | string | `QUEUED` — queued, `RUNNING` — in progress, `COMPLETED` — the database was replaced, `FAILED` — the restore failed |
| `data.restore.phase` | string \| null | Restore step. Normal order: `VALIDATE`, `PREPARE`, `PRE_CHANGE`, `STAGE`, `STOPPING`, `SWAPPING`, `VERIFY`, `STARTING`, `CLEANUP`. `CLEANUP` remains after completion, with both `COMPLETED` and `FAILED`. `ROLLBACK` — returning the original file after a failure, `RECOVERY_REQUIRED` — the restore was stopped and awaits the support team |
| `data.restore.errorCode` | string \| null | Failure cause, for example `APP_DB_RESTORE_FAILED`. `null` while there is no failure |
| `data.restore.backupCreatedAt` | string | Creation time of the source backup, ISO 8601 in UTC |
| `data.restore.createdAt` | string | Time the restore request was accepted, ISO 8601 in UTC |
| `data.restore.completedAt` | string \| null | Completion time, ISO 8601 in UTC. `null` while the restore is in progress |

## Response example

First page of three backups (`?limit=3`), no restores so far:

```json
{
  "success": true,
  "data": {
    "restore": null,
    "items": [
      {
        "id": "9e4d2c1b-6a3f-4b8e-a7d5-0c1f2e3d4b5a",
        "sourceAppId": "5c0e8f2a-3d1b-4a7e-9f60-2b8c4d1e7a93",
        "platformPaid": false,
        "captureScope": "SQLITE",
        "status": "COMPLETED",
        "triggerType": "MANUAL",
        "file": "app.db",
        "sizeBytes": "937",
        "createdAt": "2026-10-20T08:26:17.942Z",
        "completedAt": "2026-10-20T08:28:00.046Z",
        "expiresAt": "2026-10-27T08:28:00.046Z",
        "errorCode": null
      },
      {
        "id": "1f7a3b9c-2e4d-4c6a-8b0e-5d9f7a1c3e2b",
        "sourceAppId": "5c0e8f2a-3d1b-4a7e-9f60-2b8c4d1e7a93",
        "platformPaid": false,
        "captureScope": "SQLITE",
        "status": "COMPLETED",
        "triggerType": "PRE_CHANGE",
        "file": "app.db",
        "sizeBytes": "937",
        "createdAt": "2026-10-20T08:23:15.565Z",
        "completedAt": "2026-10-20T08:25:00.061Z",
        "expiresAt": "2026-10-27T08:25:00.061Z",
        "errorCode": null
      },
      {
        "id": "4b8c6d2e-9f1a-4e3b-b5c7-7a2d0e6f8c19",
        "sourceAppId": "5c0e8f2a-3d1b-4a7e-9f60-2b8c4d1e7a93",
        "platformPaid": false,
        "captureScope": "SQLITE",
        "status": "COMPLETED",
        "triggerType": "PRE_CHANGE",
        "file": "app.db",
        "sizeBytes": "937",
        "createdAt": "2026-10-20T08:21:47.863Z",
        "completedAt": "2026-10-20T08:23:00.087Z",
        "expiresAt": "2026-10-27T08:23:00.087Z",
        "errorCode": null
      }
    ],
    "nextCursor": "eyJjcmVhdGVkQXQiOiIyMDI2In0"
  }
}
```

After a restore has completed (`?limit=2`). The first item is the protective backup the platform saved before replacing the file:

```json
{
  "success": true,
  "data": {
    "restore": {
      "restoreId": "cm2r7x4k10003qv8n5d2b6w1z",
      "backupId": "9e4d2c1b-6a3f-4b8e-a7d5-0c1f2e3d4b5a",
      "targetAppId": "5c0e8f2a-3d1b-4a7e-9f60-2b8c4d1e7a93",
      "sourceAppId": "5c0e8f2a-3d1b-4a7e-9f60-2b8c4d1e7a93",
      "status": "COMPLETED",
      "phase": "CLEANUP",
      "errorCode": null,
      "backupCreatedAt": "2026-10-20T08:26:17.942Z",
      "createdAt": "2026-10-20T08:28:12.755Z",
      "completedAt": "2026-10-20T08:40:30.029Z"
    },
    "items": [
      {
        "id": "7d3e1a5b-8c2f-4d9a-9e6b-3f0a4c7d2e81",
        "sourceAppId": "5c0e8f2a-3d1b-4a7e-9f60-2b8c4d1e7a93",
        "platformPaid": false,
        "captureScope": "SQLITE",
        "status": "COMPLETED",
        "triggerType": "PRE_CHANGE",
        "file": "app.db",
        "sizeBytes": "937",
        "createdAt": "2026-10-20T08:30:30.278Z",
        "completedAt": "2026-10-20T08:32:00.080Z",
        "expiresAt": "2026-10-27T08:32:00.080Z",
        "errorCode": null
      },
      {
        "id": "9e4d2c1b-6a3f-4b8e-a7d5-0c1f2e3d4b5a",
        "sourceAppId": "5c0e8f2a-3d1b-4a7e-9f60-2b8c4d1e7a93",
        "platformPaid": false,
        "captureScope": "SQLITE",
        "status": "COMPLETED",
        "triggerType": "MANUAL",
        "file": "app.db",
        "sizeBytes": "937",
        "createdAt": "2026-10-20T08:26:17.942Z",
        "completedAt": "2026-10-20T08:28:00.046Z",
        "expiresAt": "2026-10-27T08:28:00.046Z",
        "errorCode": null
      }
    ],
    "nextCursor": "eyJjcmVhdGVkQXQiOiIyMDI2In1"
  }
}
```

## Error response example

400 — the cursor was modified or issued for a different source application:

```json
{
  "success": false,
  "error": {
    "code": "APP_DB_INVALID_FILE",
    "message": "Invalid database file inside /data"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `APP_DB_INVALID_FILE` | The `cursor` value cannot be read as a cursor of this list: it was modified manually or issued for a different `sourceAppId`. The `message` text refers to a file, but in this method the code refers to the cursor. Restart pagination without `cursor` |
| 400 | `VALIDATION_ERROR` | An unknown query parameter, `limit` outside the `1`–`100` range, invalid characters in `cursor` or `sourceAppId` |
| 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_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 |
| 429 | `RATE_LIMITED` | The read request limit for the portal was exceeded — [Limits](/docs/infra/database#limits). The wait time is in the `Retry-After` header |
| 503 | `APP_DB_TOOL_UNAVAILABLE` | The platform temporarily cannot serve this application's database. Retry the request later |
| 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

**Waiting for a backup and a restore.** Check whether a manual backup or a restore has completed by calling this list again: a backup — by the `status` of the item with the matching `id`, a restore — by `data.restore` with the matching `restoreId`. For typical durations, see [Create a database backup](/docs/infra/database/snapshot) and [Restore the database from a backup](/docs/infra/database/restore). Each poll counts against the read request limit, which is shared by all database reads in the Bitrix24 account.

**Stopped operations that need support.** A backup with status `FLAGGED` and a restore in the `RECOVERY_REQUIRED` phase do not complete on their own: in this case the restore remains in status `RUNNING`. Repeated polls do not change the response — contact support and give the backup `id` or the `restoreId`.

## See also

- [Create a database backup](/docs/infra/database/snapshot)
- [Restore the database from a backup](/docs/infra/database/restore)
- [Get database information](/docs/infra/database/info)
- [Backups and retention](/docs/infra/database#backups-and-retention)
- [Application database](/docs/infra/database)
