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

# Restore a backup into this cluster, in place

> Overwrites this cluster's data from a backup, keeping its endpoint,
security groups, IAM role and parameter groups. Restore was previously a
create-time bootstrap only, so recovering from a bad migration meant
creating a second cluster and cutting over by hand — every connection
string, plus everything attached to the original. This keeps all of it.

**Destructive and irreversible.** `confirm` must equal the cluster's name.
A pre-restore backup is requested before anything is overwritten, so a
mistaken restore has a way back.

The source backup may belong to this cluster or to another in the same
account; a cross-cluster restore grants this cluster read on that backup
bucket for the duration and revokes it when the restore reports.

Asynchronous. The cluster reports `restoring` — it is neither active nor
building and must not be read as serving current data — and returns to
`active` when its members report. An HA cluster is restored as a WHOLE:
Patroni is paused, the leader is restored, and the replicas are
reinitialised from it. A failed restore leaves the cluster serving with a
fault recorded and the pre-restore backup still available.

Postgres only.




## OpenAPI

````yaml /api-reference/specs/database.yaml post /v1/clusters/{cluster_id}/restore
openapi: 3.0.3
info:
  title: Basaltic Database API
  version: 1.0.0
  description: |
    Managed database clusters — provision an engine, convert a single node to
    high availability, fail over between members and restore from a
    backup.
  contact:
    name: Basaltic Support
    email: ping@basaltic.sh
  license:
    name: Proprietary
    url: https://basaltic.sh/terms
servers:
  - url: https://database.{region}.basaltic.sh
    description: Regional API endpoint
    variables:
      region:
        default: sa-saopaulo-1
        description: Region code
security:
  - SignatureAuth: []
paths:
  /v1/clusters/{cluster_id}/restore:
    post:
      tags:
        - Database
      summary: Restore a backup into this cluster, in place
      description: >
        Overwrites this cluster's data from a backup, keeping its endpoint,

        security groups, IAM role and parameter groups. Restore was previously a

        create-time bootstrap only, so recovering from a bad migration meant

        creating a second cluster and cutting over by hand — every connection

        string, plus everything attached to the original. This keeps all of it.


        **Destructive and irreversible.** `confirm` must equal the cluster's
        name.

        A pre-restore backup is requested before anything is overwritten, so a

        mistaken restore has a way back.


        The source backup may belong to this cluster or to another in the same

        account; a cross-cluster restore grants this cluster read on that backup

        bucket for the duration and revokes it when the restore reports.


        Asynchronous. The cluster reports `restoring` — it is neither active nor

        building and must not be read as serving current data — and returns to

        `active` when its members report. An HA cluster is restored as a WHOLE:

        Patroni is paused, the leader is restored, and the replicas are

        reinitialised from it. A failed restore leaves the cluster serving with
        a

        fault recorded and the pre-restore backup still available.


        Postgres only.
      operationId: restoreCluster
      parameters:
        - name: cluster_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RestoreClusterRequest'
      responses:
        '202':
          description: Restore accepted; the pre-restore backup has been requested.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClusterResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: A restore is already in progress, or the cluster is not active.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    RestoreClusterRequest:
      type: object
      description: >
        Restore a backup INTO THIS CLUSTER, overwriting its data. Destructive
        and

        irreversible.


        A pre-restore backup is taken automatically before anything is
        overwritten,

        so there is a way back from a mistaken restore.
      required:
        - backup_id
        - confirm
      properties:
        backup_id:
          type: string
          format: uuid
          description: >
            The backup to restore. May be a backup of THIS cluster (the common
            "undo a bad migration" case) or of another cluster in the same
            account, in which case this cluster is granted read on that backup
            bucket for the duration and it is revoked afterwards. Must satisfy
            the same rules as a create-time restore — see
            restore_from.backup_id.
          example: 3c4d5e6f-7081-4293-a4b5-c6d7e8f9a0b1
        recovery_target_time:
          type: string
          format: date-time
          description: >
            RFC3339 timestamp for point-in-time recovery — WAL is replayed
            forward from the backup until this time, then stops.
          example: '2026-01-15T08:00:00Z'
        confirm:
          type: string
          description: >
            Must equal the cluster's name. Restoring overwrites live data and
            cannot be undone, so consent has to be something that cannot arrive
            by accident — a retried request, a stale tab, a script looping over
            ids.
          example: prod-orders-db
    ClusterResponse:
      type: object
      properties:
        cluster:
          $ref: '#/components/schemas/Cluster'
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - request_id
          properties:
            code:
              type: string
              description: Error code identifying the type of error
              example: INVALID_INPUT
            message:
              type: string
              description: Human-readable error message
              example: Invalid request parameters
            request_id:
              type: string
              format: uuid
              description: Request ID for debugging
              example: 550e8400-e29b-41d4-a716-446655440000
    Cluster:
      type: object
      required:
        - id
        - crn
        - name
        - engine_type
        - engine_version
        - flavor_id
        - storage_gb
        - instance_count
        - assign_public_ip
        - status
        - tags
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
          example: 9b2e4c7a-1f3d-4a8e-bc25-6d0f1a2b3c4d
        crn:
          type: string
          description: IAM resource CRN
          example: >-
            crn:database:sa-saopaulo-1:my-account:cluster/9b2e4c7a-1f3d-4a8e-bc25-6d0f1a2b3c4d
        name:
          type: string
          example: prod-orders-db
        description:
          type: string
          example: Primary orders database
        engine_type:
          type: string
          enum:
            - postgres
            - valkey
          example: postgres
        engine_version:
          type: string
          description: e.g. '17' for postgres
          example: '17'
        flavor_id:
          type: string
          format: uuid
          description: >-
            Compute flavor each cluster member runs on. Must be a
            database-family flavor.
          example: e5f6a7b8-c9d0-4123-e4f5-a6b7c8d9e0f1
        storage_gb:
          type: integer
          minimum: 1
          example: 100
        instance_count:
          type: integer
          minimum: 1
          description: >
            Node count. Postgres: 1 = single-node; 2+ adds Patroni HA members
            (max 10). Valkey: 1 = single-node; 3 = primary + replicas with
            Sentinel (2 is rejected; max 3). Does not guarantee a completed
            failover drill for every topology.
          example: 1
        assign_public_ip:
          type: boolean
          description: >
            Whether the cluster's endpoints are backed by floating IPs and
            reachable from the internet. False means every endpoint resolves to
            a member's in-VPC address and the cluster is reachable only from the
            VPC.
          example: true
        patroni_managed:
          type: boolean
          description: >
            Whether postgres here runs under Patroni. Not the same question as
            instance_count >= 2: a cluster converted from single-node is
            Patroni-managed with one member — able to take a replica, not yet
            HA. Adding a replica requires this, not the node count.
          example: false
        parameter_group_id:
          type: string
          format: uuid
          example: 1a2b3c4d-5e6f-4071-8293-a4b5c6d7e8f9
        status:
          type: string
          description: >
            Lifecycle status of the cluster. `converting` is a single-node
            postgres cluster being adopted into Patroni — the node restarts
            under a new supervisor, so replica adds, failovers and further
            conversions are refused while it holds. `restoring` is a cluster
            whose data is being overwritten in place: it is neither active nor
            building, and must not be read as serving current data.
          enum:
            - pending
            - building
            - active
            - modifying
            - converting
            - restoring
            - failing-over
            - deleting
            - deleted
            - error
          example: active
        fault:
          $ref: '#/components/schemas/Fault'
        endpoints:
          type: array
          items:
            $ref: '#/components/schemas/Endpoint'
        instances:
          type: array
          items:
            $ref: '#/components/schemas/Instance'
        admin_user:
          type: string
          description: Bootstrap admin/superuser role name.
          pattern: ^[a-zA-Z_][a-zA-Z0-9_]{0,63}$
          example: admin
        admin_secret_id:
          type: string
          format: uuid
          description: secrets service id holding the admin password.
          example: 2b3c4d5e-6f70-4182-93a4-b5c6d7e8f9a0
        default_database:
          type: string
          pattern: ^[a-zA-Z_][a-zA-Z0-9_]{0,63}$
          example: default
        metadata:
          $ref: '#/components/schemas/Metadata'
        tags:
          $ref: '#/components/schemas/Tags'
        created_at:
          type: string
          format: date-time
          example: '2026-01-15T09:30:00Z'
        updated_at:
          type: string
          format: date-time
          example: '2026-01-15T09:30:00Z'
        deleted_at:
          type: string
          format: date-time
          example: '2026-01-16T09:30:00Z'
    Fault:
      type: object
      description: Error detail attached to a resource in status=error.
      required:
        - code
        - message
        - at
      properties:
        code:
          type: string
          example: provision_failed
        message:
          type: string
          example: compute instance failed to boot
        details:
          type: string
          example: quota exceeded in subnet
        at:
          type: string
          format: date-time
          example: '2026-01-15T09:31:00Z'
    Endpoint:
      type: object
      description: One writer/reader/admin connection endpoint for a cluster.
      required:
        - id
        - kind
        - dns_name
        - port
      properties:
        id:
          type: string
          format: uuid
          example: a7b8c9d0-e1f2-4345-a6b7-c8d9e0f1a2b3
        kind:
          type: string
          description: Which role this endpoint targets.
          enum:
            - writer
            - reader
            - admin
          example: writer
        floating_ip_id:
          type: string
          format: uuid
          description: >
            The endpoint's public floating IP. Absent on clusters created with
            assign_public_ip=false, whose endpoints resolve to a member's in-VPC
            address instead. Connect by dns_name in both cases.
          example: f6a7b8c9-d0e1-4234-f5a6-b7c8d9e0f1a2
        ip_address:
          type: string
          description: >
            The address `dns_name` currently answers with — the floating IP on a
            public endpoint, or the serving member's in-VPC address on a private
            one. Absent while it cannot be resolved, e.g. an endpoint whose
            member is still provisioning. It is resolved per request and a
            private endpoint's changes on failover, so connect by `dns_name` and
            use this for the things a name cannot express, such as a
            security-group rule.
          example: 203.0.113.50
        dns_name:
          type: string
          example: orders-db.my-account.db.sa-saopaulo-1.basaltic.sh
        port:
          type: integer
          minimum: 1
          maximum: 65535
          example: 5432
    Instance:
      type: object
      description: One VM-shaped member of a cluster.
      required:
        - id
        - role
        - status
      properties:
        id:
          type: string
          format: uuid
          example: b8c9d0e1-f2a3-4456-b7c8-d9e0f1a2b3c4
        member_name:
          type: string
          description: >-
            Cluster member name — pass as failover target_member to promote this
            node.
          example: dbaas-b8c9d0e1-0
        compute_instance_id:
          type: string
          format: uuid
          example: c9d0e1f2-a3b4-4567-c8d9-e0f1a2b3c4d5
        boot_volume_id:
          type: string
          format: uuid
          example: d0e1f2a3-b4c5-4678-d9e0-f1a2b3c4d5e6
        role:
          type: string
          description: Member role within the cluster.
          enum:
            - primary
            - replica
          example: primary
        status:
          type: string
          example: running
        agent_version:
          type: string
          description: >
            Version of the in-VM management agent this member reported in its
            last heartbeat. Absent when the member has not reported one — it has
            not beat yet, or it runs an agent released before the field existed.
            Absent means unknown: never assume it matches another member's.
          example: 1.7.12-1
    Metadata:
      type: object
      additionalProperties:
        type: string
      example:
        environment: production
        team: backend
    Tags:
      type: object
      additionalProperties:
        type: string
      example:
        environment: production
        team: backend
  responses:
    BadRequest:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: INVALID_INPUT
              message: Invalid request parameters
              request_id: 550e8400-e29b-41d4-a716-446655440000
    Unauthorized:
      description: Authentication required or token invalid
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: UNAUTHORIZED
              message: Authentication required
              request_id: 550e8400-e29b-41d4-a716-446655440000
    Forbidden:
      description: Insufficient permissions
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: ACCESS_DENIED
              message: You don't have permission to perform this action
              request_id: 550e8400-e29b-41d4-a716-446655440000
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: NOT_FOUND
              message: Resource not found
              request_id: 550e8400-e29b-41d4-a716-446655440000
  securitySchemes:
    SignatureAuth:
      type: apiKey
      in: header
      name: Authorization
      description: >
        Request signing with an access key issued to a service account. An

        HMAC-SHA256 over a canonical form of the request, close to AWS SigV4.

        The `basaltic` CLI signs for you.


        Send `Authorization`, `X-Date` (UTC, `YYYYMMDDTHHMMSSZ`) and `X-Nonce`

        (random per request); add `X-Content-Sha256` to bind a body, and

        `X-Amz-Security-Token` when using temporary credentials.


        ```

        Authorization: BASALTIC-HMAC-SHA256
        Credential=<access_key_id>/<date>/<region>/basaltic/basaltic_request,
        SignedHeaders=host;x-date;x-nonce, Signature=<hex>

        ```


        `<region>` is the region code you are calling, or `global` for the
        global

        services. A signature is valid for 5 minutes from `X-Date`, and mutating

        requests are replay-guarded on the nonce.


        **Full signing procedure, including a working implementation:**

        https://docs.basaltic.sh/authentication


        ## Rate limits

        There is no global request budget. A limit applies only where an

        operation documents a `429`, and that operation says what it counts.

        Those responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining`,

        `X-RateLimit-Reset` and, on a `429`, `Retry-After` — read them rather

        than hard-coding a number. Retrying before `Retry-After` is refused and

        extends the window. Everything else is bounded by quota, not by request

        rate.

````