> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dbhost.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Stage restore upload (limited rollout)

> Exact-target import contract for operator-approved rollouts; not generally available.

The staged upload contract sends dump bytes directly from the browser or CLI to
an isolated S3 prefix. The file does not transit Vercel, Caddy, or the agent
request body.

<Warning>
  Limited availability: this is not a normal customer or published CLI workflow.
  Upload initialization requires the exact restore rollout binding and agent
  capability. Other databases receive `FEATURE_NOT_AVAILABLE`, and uploading an
  artifact does not by itself authorize or start a restore.
</Warning>

The historical multipart
`POST /api/v1/databases/{id}/backups/restore` route remains only as a
fail-closed compatibility endpoint. It never reads the upload body or calls the
agent: exact rollout targets receive `PRESIGNED_UPLOAD_REQUIRED`, while all
other targets receive `FEATURE_NOT_AVAILABLE`.

## Supported files

| Extension | Description                                      |
| --------- | ------------------------------------------------ |
| `.sql`    | Plain SQL dump                                   |
| `.sql.gz` | Gzip-compressed plain SQL dump                   |
| `.dump`   | `pg_dump` custom format                          |
| `.backup` | `pg_dump` custom format with alternate extension |

Maximum object size is 512 MB. The client supplies the exact size and SHA-256
checksum during initialization and completion.

Plain `.sql` and `.sql.gz` input is limited to 512 MB after decompression and
16 MiB per physical SQL line, including one `COPY` row. Use custom-format
`.dump` or `.backup` when a single value requires a larger line. Restore staging
has a 30-minute execution deadline; the separate five-minute maintenance window
starts only when a validated staging database is ready to swap.

## 1. Initialize upload

`POST /api/v1/databases/{id}/backups/restore-upload/init`

Send a unique `Idempotency-Key` header and reuse it only when retrying this
exact database, filename, size, and checksum binding. The CLI generates one for
each new upload. The key must contain 8-128 characters.

```json theme={null}
{
  "filename": "mybackup.sql.gz",
  "sizeBytes": 1048576,
  "checksumSha256": "<64 lowercase hex characters>"
}
```

```json 202 Accepted theme={null}
{
  "message": "Restore upload initialized",
  "operation": {
    "id": "op_01J...",
    "type": "restore",
    "status": "running",
    "stage": "upload",
    "progress": 1
  },
  "object": {
    "id": "obj_01J...",
    "expiresAt": "2026-07-20T12:15:00Z",
    "maxSizeBytes": 536870912
  },
  "upload": {
    "method": "POST",
    "url": "<short-lived presigned URL>",
    "expiresInSeconds": 900,
    "fields": {
      "key": "<isolated operation/object key>",
      "policy": "<signed policy>",
      "x-amz-signature": "<signature>"
    }
  }
}
```

The presigned URL is bound to the operation, object ID, isolated prefix, size,
checksum, content type, encryption policy, and a 15-minute expiry. Treat it as a
temporary secret. Send every returned form field unchanged and append the file
as the final multipart field named `file`.

## 2. Upload directly

Send the file bytes to the returned S3 URL using the exact method and form
fields. Do not send the DBHost API key to S3. The dump never passes through a
DBHost API, Vercel, Caddy, or agent request body.

## 3. Complete upload

`POST /api/v1/databases/{id}/backups/restore-upload/complete`

```json theme={null}
{
  "operationId": "op_01J...",
  "objectId": "obj_01J...",
  "sizeBytes": 1048576,
  "checksumSha256": "<64 lowercase hex characters>"
}
```

```json 202 Accepted theme={null}
{
  "message": "Restore artifact queued for validation",
  "operation": {
    "id": "op_01J...",
    "type": "restore",
    "status": "queued",
    "stage": "validate_artifact",
    "progress": 5
  }
}
```

Completion verifies the object binding, size and checksum before format/schema
validation. Poll `GET /api/v1/operations/{id}` for further status.

## Errors

| Status | Code                           | Description                                          |
| ------ | ------------------------------ | ---------------------------------------------------- |
| 400    | `INVALID_FILENAME`             | Filename does not use a supported extension          |
| 400    | `INVALID_CHECKSUM`             | Checksum is missing or malformed                     |
| 401    | `UNAUTHORIZED`                 | Missing or invalid API key                           |
| 403    | `FORBIDDEN`                    | Caller can view backups but cannot manage them       |
| 404    | `DATABASE_NOT_FOUND`           | Database is missing or outside key scope             |
| 409    | `OPERATION_CONFLICT`           | Another destructive operation is active              |
| 409    | `UPLOAD_NOT_READY`             | The uploaded object is not visible in storage yet    |
| 410    | `UPLOAD_EXPIRED`               | Presigned upload or operation has expired            |
| 413    | `PAYLOAD_TOO_LARGE`            | Declared or stored object exceeds 512 MB             |
| 413    | `SIZE_MISMATCH`                | Stored object size differs from initialization       |
| 422    | `CHECKSUM_MISMATCH`            | Stored object differs from declared checksum         |
| 404    | `FEATURE_NOT_AVAILABLE`        | The database is outside the approved restore rollout |
| 503    | `AGENT_CAPABILITY_UNAVAILABLE` | Staged upload is unavailable for this agent          |
