# Backups API

21 operations under /api/v1. Errors are RFC 9457 problem+json with a stable `code` field. The complete schemas are in https://nebulactrl.com/api/v1/openapi.json.

## GET /backup-destinations

List backup destinations

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Response 200: OK (DestinationListOutputBody)
  - `items` (DestinationView[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 500 Internal Server Error

## POST /backup-destinations

Add a backup destination

An S3-compatible bucket or a directory on the database's host (local; not off-site). Credentials are write-only. Requires the admin scope. Recommended S3 permissions: s3:PutObject, s3:GetObject, s3:DeleteObject, s3:ListBucket, s3:AbortMultipartUpload on a bucket-scoped key.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `Idempotency-Key` (header, string): Client-chosen key (<=255 chars). A retry with the same key replays the original response for 24 hours.

Request body (application/json, required): CreateDestinationInputBody
  - `accessKey` (string): Write-only.
  - `bucket` (string)
  - `compatMode` (boolean): Limit SDK checksums for non-AWS stores. Default: on when an endpoint is set.
  - `endpoint` (string): S3-compatible endpoint URL; empty means AWS S3.
  - `kind` (enum: s3 | local, required)
  - `localDir` (string): Local destinations: relative sub-directory of the agent's backup directory.
  - `name` (string, required)
  - `pathStyle` (boolean): Use path-style addressing (MinIO, SeaweedFS, Garage, most self-hosted stores).
  - `prefix` (string): Key prefix inside the bucket or directory.
  - `region` (string)
  - `secretKey` (string): Write-only.

Response 201: Created (DestinationView)
  - `accessKeySet` (boolean, required)
  - `bucket` (string)
  - `compatMode` (boolean, required)
  - `createdAt` (string (date-time), required)
  - `endpoint` (string)
  - `id` (string, required)
  - `inUseBySchedules` (integer (int64), required)
  - `kind` (enum: s3 | local, required)
  - `lastTestAt` (string (date-time))
  - `lastTestMessage` (string)
  - `lastTestOk` (boolean)
  - `localDir` (string)
  - `name` (string, required)
  - `offSite` (boolean, required): False for local directories: a backup on the database's own host does not survive the loss of that host.
  - `pathStyle` (boolean, required)
  - `prefix` (string)
  - `region` (string)
  - `secretKeySet` (boolean, required)
  - `storedBackups` (integer (int64), required)
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## PATCH /backup-destinations/{destinationId}

Update a backup destination

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `destinationId` (path, string, required)

Request body (application/json, required): PatchDestinationInputBody
  - `accessKey` (string): Omit or leave empty to keep the stored key.
  - `bucket` (string)
  - `compatMode` (boolean)
  - `endpoint` (string)
  - `localDir` (string)
  - `name` (string)
  - `pathStyle` (boolean)
  - `prefix` (string)
  - `region` (string)
  - `secretKey` (string): Omit or leave empty to keep the stored key.

Response 200: OK (DestinationView)
  - `accessKeySet` (boolean, required)
  - `bucket` (string)
  - `compatMode` (boolean, required)
  - `createdAt` (string (date-time), required)
  - `endpoint` (string)
  - `id` (string, required)
  - `inUseBySchedules` (integer (int64), required)
  - `kind` (enum: s3 | local, required)
  - `lastTestAt` (string (date-time))
  - `lastTestMessage` (string)
  - `lastTestOk` (boolean)
  - `localDir` (string)
  - `name` (string, required)
  - `offSite` (boolean, required): False for local directories: a backup on the database's own host does not survive the loss of that host.
  - `pathStyle` (boolean, required)
  - `prefix` (string)
  - `region` (string)
  - `secretKeySet` (boolean, required)
  - `storedBackups` (integer (int64), required)
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## DELETE /backup-destinations/{destinationId}

Delete a backup destination

409 `destination_in_use` while a schedule or a stored backup uses it.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `destinationId` (path, string, required)

Response 204: No Content
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict (`destination_in_use`); 422 Unprocessable Entity; 500 Internal Server Error

## POST /backup-destinations/{destinationId}/test

Test a backup destination

The agent on the chosen server writes, reads back and deletes a probe object. Waits up to 25 seconds; if the agent has not answered by then the response carries no test result yet (poll the destination).

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `destinationId` (path, string, required)

Request body (application/json, required): TestDestinationInputBody
  - `serverId` (string, required): The server whose agent performs the test (it is the one that talks to the destination).

Response 200: OK (DestinationView)
  - `accessKeySet` (boolean, required)
  - `bucket` (string)
  - `compatMode` (boolean, required)
  - `createdAt` (string (date-time), required)
  - `endpoint` (string)
  - `id` (string, required)
  - `inUseBySchedules` (integer (int64), required)
  - `kind` (enum: s3 | local, required)
  - `lastTestAt` (string (date-time))
  - `lastTestMessage` (string)
  - `lastTestOk` (boolean)
  - `localDir` (string)
  - `name` (string, required)
  - `offSite` (boolean, required): False for local directories: a backup on the database's own host does not survive the loss of that host.
  - `pathStyle` (boolean, required)
  - `prefix` (string)
  - `region` (string)
  - `secretKeySet` (boolean, required)
  - `storedBackups` (integer (int64), required)
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## GET /backup-restores/{restoreId}

Get a restore

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `restoreId` (path, string, required)

Response 200: OK (RestoreView)
  - `actor` (string)
  - `backupId` (string, required)
  - `createdAt` (string (date-time), required)
  - `error` (string)
  - `finishedAt` (string (date-time))
  - `id` (string, required)
  - `mode` (enum: new | in_place, required)
  - `preBackupId` (string)
  - `sourceServiceId` (string, required)
  - `status` (enum: provisioning | pre_backup | restoring | succeeded | failed, required)
  - `targetServiceId` (string)
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## PATCH /backup-schedules/{scheduleId}

Update a backup schedule

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `scheduleId` (path, string, required)

Request body (application/json, required): PatchScheduleInputBody
  - `cron` (string)
  - `destinationId` (string)
  - `enabled` (boolean)
  - `retentionCount` (integer (int64))
  - `retentionDays` (integer (int64))
  - `timezone` (string)

Response 200: OK (ScheduleView)
  - `createdAt` (string (date-time), required)
  - `cron` (string, required)
  - `destinationId` (string, required)
  - `destinationName` (string)
  - `enabled` (boolean, required)
  - `id` (string, required)
  - `lastRunAt` (string (date-time))
  - `nextRunAt` (string (date-time))
  - `retentionCount` (integer (int64), required)
  - `retentionDays` (integer (int64), required)
  - `serviceId` (string, required)
  - `timezone` (string, required)
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## DELETE /backup-schedules/{scheduleId}

Delete a backup schedule

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `scheduleId` (path, string, required)

Response 204: No Content
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## GET /backups/{backupId}

Get a backup

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `backupId` (path, string, required)

Response 200: OK (BackupView)
  - `actor` (string)
  - `createdAt` (string (date-time), required)
  - `database` (string)
  - `destinationId` (string, required)
  - `destinationKind` (string)
  - `destinationName` (string)
  - `engine` (string, required)
  - `engineVersion` (string)
  - `error` (string)
  - `finishedAt` (string (date-time))
  - `format` (string)
  - `id` (string, required)
  - `locked` (boolean, required)
  - `objectDeleted` (boolean, required): True once retention or a user removed the stored object; the row stays as history.
  - `objectKey` (string)
  - `restorable` (boolean, required)
  - `scheduleId` (string)
  - `serverId` (string)
  - `serviceId` (string, required)
  - `sha256` (string)
  - `sizeBytes` (integer (int64), required)
  - `startedAt` (string (date-time))
  - `status` (enum: queued | running | succeeded | failed | skipped | expired, required)
  - `trigger` (enum: manual | schedule | pre_restore, required)
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## DELETE /backups/{backupId}

Delete a backup

Deletes the stored object through the agent. 409 when the backup is locked or its server is offline.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `backupId` (path, string, required)

Response 204: No Content
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## PUT /backups/{backupId}/lock

Lock or unlock a backup

Locked backups are never removed by retention or deleted.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `backupId` (path, string, required)

Request body (application/json, required): LockInputBody
  - `locked` (boolean, required)

Response 200: OK (BackupView)
  - `actor` (string)
  - `createdAt` (string (date-time), required)
  - `database` (string)
  - `destinationId` (string, required)
  - `destinationKind` (string)
  - `destinationName` (string)
  - `engine` (string, required)
  - `engineVersion` (string)
  - `error` (string)
  - `finishedAt` (string (date-time))
  - `format` (string)
  - `id` (string, required)
  - `locked` (boolean, required)
  - `objectDeleted` (boolean, required): True once retention or a user removed the stored object; the row stays as history.
  - `objectKey` (string)
  - `restorable` (boolean, required)
  - `scheduleId` (string)
  - `serverId` (string)
  - `serviceId` (string, required)
  - `sha256` (string)
  - `sizeBytes` (integer (int64), required)
  - `startedAt` (string (date-time))
  - `status` (enum: queued | running | succeeded | failed | skipped | expired, required)
  - `trigger` (enum: manual | schedule | pre_restore, required)
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## POST /backups/{backupId}/restore

Restore a backup

mode `new` (default) creates a sibling database, deploys it empty and restores into it; the original is untouched. mode `in_place` first takes a safety backup, then overwrites the database; it needs `confirmSlug`. Poll the restore.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `Idempotency-Key` (header, string): Client-chosen key (<=255 chars). A retry with the same key replays the original response for 24 hours.
- `backupId` (path, string, required)

Request body (application/json, required): RestoreInputBody
  - `confirmSlug` (string): In-place restores: the database's slug.
  - `mode` (enum: new | in_place): new: restore into a new sibling database (default, the original is untouched). in_place: overwrite this database after a safety backup.
  - `name` (string): Name of the new database (mode new).
  - `serverId` (string): Server for the new database (mode new). A backup stored in a local directory can only be restored on its own server.

Response 202: Accepted (RestoreView)
  - `actor` (string)
  - `backupId` (string, required)
  - `createdAt` (string (date-time), required)
  - `error` (string)
  - `finishedAt` (string (date-time))
  - `id` (string, required)
  - `mode` (enum: new | in_place, required)
  - `preBackupId` (string)
  - `sourceServiceId` (string, required)
  - `status` (enum: provisioning | pre_backup | restoring | succeeded | failed, required)
  - `targetServiceId` (string)
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## GET /dump-restores/{restoreId}

Get a restore from a dump file

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `restoreId` (path, string, required)

Response 200: OK (DumpRestoreView)
  - `actor` (string)
  - `bytesReceived` (integer (int64), required): Bytes the agent has accepted so far.
  - `createdAt` (string (date-time), required)
  - `error` (string)
  - `fileName` (string)
  - `finishedAt` (string (date-time))
  - `format` (string): Detected from the file's first bytes: pg_custom, pg_tar, sql or mongo_archive.
  - `gzip` (boolean)
  - `id` (string, required)
  - `log` (string): Progress of the restore (the newest 64 KiB), one line per entry. Omitted from lists.
  - `maxUploadBytes` (integer (int64), required): The largest file the control plane accepts.
  - `mode` (enum: new | in_place, required)
  - `preBackupId` (string)
  - `sha256` (string): Checksum of the uploaded bytes, known once the upload completed.
  - `sizeBytes` (integer (int64), required): Bytes announced by the upload.
  - `sourceServiceId` (string, required)
  - `startedAt` (string (date-time))
  - `status` (enum: provisioning | pre_backup | awaiting_upload | uploading | restoring | succeeded | failed, required)
  - `targetServiceId` (string)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## POST /dump-restores/{restoreId}/cancel

Cancel a restore that has not received its file

Only while the restore is provisioning, taking its safety backup or awaiting its file. The new database it created stays; delete it like any service.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `restoreId` (path, string, required)

Response 200: OK (DumpRestoreView)
  - `actor` (string)
  - `bytesReceived` (integer (int64), required): Bytes the agent has accepted so far.
  - `createdAt` (string (date-time), required)
  - `error` (string)
  - `fileName` (string)
  - `finishedAt` (string (date-time))
  - `format` (string): Detected from the file's first bytes: pg_custom, pg_tar, sql or mongo_archive.
  - `gzip` (boolean)
  - `id` (string, required)
  - `log` (string): Progress of the restore (the newest 64 KiB), one line per entry. Omitted from lists.
  - `maxUploadBytes` (integer (int64), required): The largest file the control plane accepts.
  - `mode` (enum: new | in_place, required)
  - `preBackupId` (string)
  - `sha256` (string): Checksum of the uploaded bytes, known once the upload completed.
  - `sizeBytes` (integer (int64), required): Bytes announced by the upload.
  - `sourceServiceId` (string, required)
  - `startedAt` (string (date-time))
  - `status` (enum: provisioning | pre_backup | awaiting_upload | uploading | restoring | succeeded | failed, required)
  - `targetServiceId` (string)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## GET /services/{serviceId}/backup-restores

List restores of a database

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `serviceId` (path, string, required)

Response 200: OK (RestoreListOutputBody)
  - `items` (RestoreView[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## GET /services/{serviceId}/backup-schedules

List a database's backup schedules

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `serviceId` (path, string, required)

Response 200: OK (ScheduleListOutputBody)
  - `items` (ScheduleView[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## POST /services/{serviceId}/backup-schedules

Schedule recurring backups

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `Idempotency-Key` (header, string): Client-chosen key (<=255 chars). A retry with the same key replays the original response for 24 hours.
- `serviceId` (path, string, required)

Request body (application/json, required): CreateScheduleInputBody
  - `cron` (string, required): 5-field cron expression or @hourly/@daily/@weekly/@monthly/@yearly. At most one run every 5 minutes.
  - `destinationId` (string, required)
  - `enabled` (boolean)
  - `retentionCount` (integer (int64)): Keep the newest N successful backups (default 7, 0 = no limit).
  - `retentionDays` (integer (int64)): Delete backups older than N days (0 = no limit).
  - `timezone` (string): IANA zone, default UTC.

Response 201: Created (ScheduleView)
  - `createdAt` (string (date-time), required)
  - `cron` (string, required)
  - `destinationId` (string, required)
  - `destinationName` (string)
  - `enabled` (boolean, required)
  - `id` (string, required)
  - `lastRunAt` (string (date-time))
  - `nextRunAt` (string (date-time))
  - `retentionCount` (integer (int64), required)
  - `retentionDays` (integer (int64), required)
  - `serviceId` (string, required)
  - `timezone` (string, required)
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## GET /services/{serviceId}/backups

List a database's backups

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `serviceId` (path, string, required)
- `limit` (query, integer (int64))

Response 200: OK (BackupListOutputBody)
  - `items` (BackupView[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## POST /services/{serviceId}/backups

Back up a database now

Returns 202 with a queued backup; poll it or listen for `backup.updated` events. 409 `server_offline` / `database_not_running` when it can not start.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `Idempotency-Key` (header, string): Client-chosen key (<=255 chars). A retry with the same key replays the original response for 24 hours.
- `serviceId` (path, string, required)

Request body (application/json, required): CreateBackupInputBody
  - `destinationId` (string, required)

Response 202: Accepted (BackupView)
  - `actor` (string)
  - `createdAt` (string (date-time), required)
  - `database` (string)
  - `destinationId` (string, required)
  - `destinationKind` (string)
  - `destinationName` (string)
  - `engine` (string, required)
  - `engineVersion` (string)
  - `error` (string)
  - `finishedAt` (string (date-time))
  - `format` (string)
  - `id` (string, required)
  - `locked` (boolean, required)
  - `objectDeleted` (boolean, required): True once retention or a user removed the stored object; the row stays as history.
  - `objectKey` (string)
  - `restorable` (boolean, required)
  - `scheduleId` (string)
  - `serverId` (string)
  - `serviceId` (string, required)
  - `sha256` (string)
  - `sizeBytes` (integer (int64), required)
  - `startedAt` (string (date-time))
  - `status` (enum: queued | running | succeeded | failed | skipped | expired, required)
  - `trigger` (enum: manual | schedule | pre_restore, required)
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict (`server_offline`); 422 Unprocessable Entity; 500 Internal Server Error

## GET /services/{serviceId}/dump-restores

List restores from a dump file of a database

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `serviceId` (path, string, required)

Response 200: OK (DumpRestoreListOutputBody)
  - `items` (DumpRestoreView[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## POST /services/{serviceId}/dump-restores

Restore a database from a dump file

Prepares a restore of a managed database from a file you upload: a `pg_dump` file (custom `-Fc`, tar `-Ft` or plain SQL, optionally gzipped), a `mysqldump`/`mariadb-dump` SQL file (optionally gzipped) or a `mongodump --archive` file. mode `new` (default) creates a sibling database and deploys it empty; mode `in_place` first takes a safety backup (needs `confirmSlug`) and then replaces this database's data. Poll the restore until its status is `awaiting_upload`, then send the file: `PUT /api/v1/dump-restores/{restoreId}/upload` with the file as the raw request body (`Content-Type: application/octet-stream`, `Content-Length` required, optional `?fileName=`). It answers 202 once the agent has the whole file (status `restoring`); poll `GET /dump-restores/{restoreId}` for `succeeded` or `failed`. The control plane stores nothing: the bytes stream through it to the agent. The format is detected from the file's first bytes (422 `unsupported_dump_format` when it does not fit the engine); 413 `file_too_large` above the limit (`maxUploadBytes`, default 5 GiB).

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `Idempotency-Key` (header, string): Client-chosen key (<=255 chars). A retry with the same key replays the original response for 24 hours.
- `serviceId` (path, string, required)

Request body (application/json, required): CreateDumpRestoreInputBody
  - `confirmSlug` (string): In-place restores: the database's slug.
  - `destinationId` (string): In-place restores: the backup destination of the safety backup; default the destination of the database's backup schedule.
  - `fileName` (string): The name of the file about to be uploaded (shown in the history).
  - `mode` (enum: new | in_place): new: restore into a new sibling database (default, the original is untouched). in_place: replace this database's data after a safety backup.
  - `name` (string): Name of the new database (mode new).
  - `serverId` (string): Server for the new database (mode new); default the source's server.
  - `sizeBytes` (integer (int64)): The size of the file about to be uploaded. A file above the limit is refused before anything is created.

Response 202: Accepted (DumpRestoreView)
  - `actor` (string)
  - `bytesReceived` (integer (int64), required): Bytes the agent has accepted so far.
  - `createdAt` (string (date-time), required)
  - `error` (string)
  - `fileName` (string)
  - `finishedAt` (string (date-time))
  - `format` (string): Detected from the file's first bytes: pg_custom, pg_tar, sql or mongo_archive.
  - `gzip` (boolean)
  - `id` (string, required)
  - `log` (string): Progress of the restore (the newest 64 KiB), one line per entry. Omitted from lists.
  - `maxUploadBytes` (integer (int64), required): The largest file the control plane accepts.
  - `mode` (enum: new | in_place, required)
  - `preBackupId` (string)
  - `sha256` (string): Checksum of the uploaded bytes, known once the upload completed.
  - `sizeBytes` (integer (int64), required): Bytes announced by the upload.
  - `sourceServiceId` (string, required)
  - `startedAt` (string (date-time))
  - `status` (enum: provisioning | pre_backup | awaiting_upload | uploading | restoring | succeeded | failed, required)
  - `targetServiceId` (string)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict; 413 Request Entity Too Large (`file_too_large`); 422 Unprocessable Entity (`unsupported_dump_format`); 500 Internal Server Error
