> ## 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.

# Create snapshot policy

> Attach a snapshot schedule to a volume. A volume has at most one
policy; attaching a second is a 409.

The first snapshot lands one `interval_minutes` from now —
attaching a schedule is not itself a request for a snapshot. Use
POST /v1/snapshots for one now.

What retention can delete, since a schedule is also an automatic
deleter: only the snapshots this policy itself took. A snapshot
taken by hand is never reaped, and neither is one something
depends on — a snapshot a volume was created from, including a
restore still running, is skipped and looked at again later.
Pausing the policy stops the deleting as well as the taking, and
deleting the policy keeps every snapshot it already took.




## OpenAPI

````yaml /api-reference/specs/storage.yaml post /v1/snapshot-policies
openapi: 3.0.3
info:
  title: Basaltic Storage API
  version: 1.0.0
  description: |
    Block volumes with their snapshots and snapshot policies, plus bucket
    management for object storage. The S3 wire protocol itself is served
    separately at `objects.{region}.basaltic.cloud`.
  contact:
    name: Basaltic Support
    email: ping@basaltic.sh
  license:
    name: Proprietary
    url: https://basaltic.sh/terms
servers:
  - url: https://storage.{region}.basaltic.sh
    description: Regional API endpoint
    variables:
      region:
        default: sa-saopaulo-1
        description: Region code
security:
  - SignatureAuth: []
paths:
  /v1/snapshot-policies:
    post:
      tags:
        - Storage
      summary: Create snapshot policy
      description: |
        Attach a snapshot schedule to a volume. A volume has at most one
        policy; attaching a second is a 409.

        The first snapshot lands one `interval_minutes` from now —
        attaching a schedule is not itself a request for a snapshot. Use
        POST /v1/snapshots for one now.

        What retention can delete, since a schedule is also an automatic
        deleter: only the snapshots this policy itself took. A snapshot
        taken by hand is never reaped, and neither is one something
        depends on — a snapshot a volume was created from, including a
        restore still running, is skipped and looked at again later.
        Pausing the policy stops the deleting as well as the taking, and
        deleting the policy keeps every snapshot it already took.
      operationId: createSnapshotPolicy
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SnapshotPolicyCreateRequest'
      responses:
        '201':
          description: Snapshot policy created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SnapshotPolicyResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: |
            The volume already has a snapshot policy, or a policy with
            this name already exists in the account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
        - SignatureAuth: []
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      description: >-
        Optional client-generated key that makes a create replay-safe. Retrying
        a request with the same key returns the original outcome verbatim
        instead of creating a duplicate resource. Reusing a key with a different
        request body is rejected (422); a request whose key is still being
        processed returns 409. Records are honored for 24 hours. Use a UUID or
        similarly unique token.
      required: false
      schema:
        type: string
        maxLength: 255
      example: 550e8400-e29b-41d4-a716-446655440000
  schemas:
    SnapshotPolicyCreateRequest:
      type: object
      required:
        - volume_id
        - name
        - interval_minutes
        - retention_count
      properties:
        volume_id:
          type: string
          format: uuid
          example: 5f8d2c1a-9b3e-4d7a-8c6f-1e2a3b4c5d6e
        name:
          type: string
          minLength: 1
          maxLength: 128
          description: |
            Unique within the account — it names the policy in its CRN.
            Scheduled snapshots are named `<policy>-<UTC timestamp>`.
          example: nightly
        description:
          type: string
          example: Nightly snapshots of the database volume
        interval_minutes:
          $ref: '#/components/schemas/SnapshotIntervalMinutes'
        retention_count:
          $ref: '#/components/schemas/SnapshotRetentionCount'
        retention_days:
          $ref: '#/components/schemas/SnapshotRetentionDays'
        enabled:
          type: boolean
          default: true
          description: |
            Defaults to true. Set false to attach a paused schedule. Pausing
            stops the whole policy — no snapshots are taken and none are
            deleted, because a paused schedule that kept reaping would
            delete history while you were looking at it.
          example: true
        tags:
          $ref: '#/components/schemas/Tags'
    SnapshotPolicyResponse:
      type: object
      properties:
        snapshot_policy:
          $ref: '#/components/schemas/SnapshotPolicy'
    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
    SnapshotIntervalMinutes:
      type: integer
      minimum: 1
      maximum: 43200
      description: |
        Minutes between snapshots — a minimum gap, not an exact cadence. A
        periodic pass takes whatever has come due and re-bases each policy's
        next run off the moment it ran, so a snapshot lands at or after
        `interval_minutes` and never before, and can land a minute or two
        later when the pass is busy. A window the pass misses costs one
        snapshot rather than producing a catch-up burst afterwards.

        The floor is one minute, because that pass is what evaluates the
        schedule and nothing finer can be honoured; the ceiling is 30 days.
        Sub-hourly intervals multiply Ceph snapshot churn and count against
        the `snapshots` quota, so pick the largest interval that meets your
        recovery point objective.
      example: 1440
    SnapshotRetentionCount:
      type: integer
      minimum: 1
      maximum: 256
      description: |
        How many of this policy's snapshots to keep. When a fire takes the
        count past this, the oldest go first.
      example: 7
    SnapshotRetentionDays:
      type: integer
      minimum: 0
      maximum: 3650
      default: 0
      description: |
        Optional age bound, applied on top of `retention_count`: a
        snapshot outside EITHER window is reaped. 0 means no age bound.
        The single newest snapshot is exempt from the age bound, so a
        volume that could not be snapshotted for longer than the window
        never loses its whole history.
      example: 30
    Tags:
      type: object
      additionalProperties:
        type: string
      example:
        environment: production
        team: backend
    SnapshotPolicy:
      type: object
      description: |
        A schedule attached to one volume: take a snapshot every
        `interval_minutes`, then keep at most `retention_count` of the
        snapshots this policy created.

        Retention only ever deletes snapshots the policy itself created
        (those carrying its `snapshot_policy_id`) — a snapshot taken by
        hand is never reaped. It also never deletes a snapshot something
        depends on: a snapshot a volume was created from, including a
        restore still in progress, is skipped and re-examined later.
      properties:
        id:
          type: string
          format: uuid
          readOnly: true
          example: 3c9f1b2e-6d4a-4e8b-9f1c-2a3b4c5d6e7f
        crn:
          type: string
          readOnly: true
          description: Cloud Resource Name (name-based, region-scoped).
          example: crn:storage:sa-saopaulo-1:my-account:snapshot-policy/nightly
        volume_id:
          type: string
          format: uuid
          description: >-
            The volume this schedule is attached to. A volume has at most one
            policy.
          example: 5f8d2c1a-9b3e-4d7a-8c6f-1e2a3b4c5d6e
        name:
          type: string
          maxLength: 128
          example: nightly
        description:
          type: string
          example: Nightly snapshots of the database volume
        enabled:
          type: boolean
          description: |
            Disabling pauses the whole policy — no scheduled snapshots and
            no retention. A paused schedule that kept reaping would delete
            history while you were looking at it.
          example: true
        interval_minutes:
          $ref: '#/components/schemas/SnapshotIntervalMinutes'
        retention_count:
          $ref: '#/components/schemas/SnapshotRetentionCount'
        retention_days:
          $ref: '#/components/schemas/SnapshotRetentionDays'
        next_run_at:
          type: string
          format: date-time
          readOnly: true
          description: |
            When the next snapshot is due. Re-stamped to
            `now + interval_minutes` each time the policy fires — never to
            `previous + interval` — so a window missed while the region was
            busy costs one snapshot, not one per window missed.
          example: '2026-01-16T00:00:00Z'
        last_run_at:
          type: string
          format: date-time
          readOnly: true
          description: When the policy last fired. Absent until the first fire.
          example: '2026-01-15T00:00:00Z'
        last_error:
          type: string
          readOnly: true
          description: |
            Why the most recent fire produced no snapshot (quota exhausted,
            volume mid-extend, …). Absent when the last fire succeeded.
          example: ''
        tags:
          $ref: '#/components/schemas/Tags'
        created_at:
          type: string
          format: date-time
          readOnly: true
          example: '2026-01-15T09:30:00Z'
        updated_at:
          type: string
          format: date-time
          readOnly: true
          example: '2026-01-15T09:30:00Z'
  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
    UnprocessableEntity:
      description: |
        The request is well-formed but cannot be processed as sent. On the
        operations that accept `Idempotency-Key` this is the key-reuse case: the
        key was first seen with a different request payload, so replaying the
        stored outcome would answer a question the caller did not ask.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: IDEMPOTENCY_KEY_REUSED
              message: >-
                This Idempotency-Key was already used with a different request
                payload
              request_id: 550e8400-e29b-41d4-a716-446655440000
    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: INTERNAL_ERROR
              message: An internal error occurred
              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.

````