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

# Update member scale-in protection

> Protect or unprotect one member of a customer instance pool from manual and automatic scale-in. Already-retiring members return 409. Protection is scoped to this VM and does not prevent an explicit refresh, replacement of a failed VM, instance deletion, or deletion of the pool. Replacement VMs start unprotected.

> [!NOTE]
>
> Requires the IAM action **`compute:UpdateInstancePool`**. See [COMPUTE permissions](https://docs.basaltic.sh/compute/permissions.md) for the full list, what each one covers, and an example policy.


## OpenAPI

````yaml /api-reference/specs/compute.yaml patch /v1/instance-pools/{pool_id}/instances/{instance_id}
openapi: 3.0.3
info:
  title: Basaltic Compute API
  version: 1.0.0
  description: |
    Virtual machine instances, and the images, flavors and
    instance pools they are built from. Covers the whole instance lifecycle:
    start, stop, reboot, resize and reinstall.
  contact:
    name: Basaltic Support
    email: ping@basaltic.sh
  license:
    name: Proprietary
    url: https://basaltic.sh/terms
servers:
  - url: https://compute.{region}.basaltic.sh
    description: Regional API endpoint
    variables:
      region:
        default: sa-saopaulo-1
        description: Region code
security:
  - BearerAuth: []
paths:
  /v1/instance-pools/{pool_id}/instances/{instance_id}:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: instance_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    patch:
      tags:
        - Compute
      summary: Update member scale-in protection
      description: >-
        Protect or unprotect one member of a customer instance pool from manual
        and automatic scale-in. Already-retiring members return 409. Protection
        is scoped to this VM and does not prevent an explicit refresh,
        replacement of a failed VM, instance deletion, or deletion of the pool.
        Replacement VMs start unprotected.
      operationId: updateInstancePoolMemberProtection
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InstancePoolMemberProtectionRequest'
      responses:
        '200':
          description: Updated pool, including protected member UUIDs.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InstancePoolResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
      security:
        - BearerAuth: []
components:
  schemas:
    InstancePoolMemberProtectionRequest:
      type: object
      additionalProperties: false
      required:
        - scale_in_protected
      properties:
        scale_in_protected:
          type: boolean
          description: >-
            Protect this member from manual and automatic scale-in. Remove
            protection before selecting it for scale-in. Cannot change a
            retiring member. Explicit refresh, failed-instance replacement,
            instance deletion and pool deletion are unaffected. Replacement
            instances start unprotected.
    InstancePoolResponse:
      type: object
      properties:
        instance_pool:
          $ref: '#/components/schemas/InstancePool'
      required:
        - instance_pool
    InstancePool:
      type: object
      required:
        - faults
        - subnets
        - vpc
      description: >-
        A launch template plus a desired count. Creating a pool spawns
        desired_count instances; a reconciler converges member_count toward
        desired_count as it changes. member_count is how many members the pool
        holds; live_count is how many of them are running.

        A pool carries two tag sets and they answer different questions. `tags`
        labels the pool resource — that is what an IAM condition reads as
        `basalt:ResourceTag/<key>` and what a cost report groups by, and it
        reaches no instance. `template.tags` is the set stamped on every replica
        the pool launches.
      properties:
        subnets:
          type: array
          items:
            $ref: '#/components/schemas/ResourceRef'
          description: Eligible placement subnets in the same account, region and VPC.
        vpc:
          anyOf:
            - $ref: '#/components/schemas/ResourceRef'
            - type: object
              nullable: true
              enum:
                - null
        autoscaling:
          $ref: '#/components/schemas/AutoscalingPolicy'
        autoscaling_status:
          $ref: '#/components/schemas/AutoscalingStatus'
        rollout_surge:
          type: boolean
          readOnly: true
          description: Temporary rollout capacity; desired_count remains the steady target.
        scale_in_protected_instances:
          type: array
          readOnly: true
          items:
            type: string
            format: uuid
          description: >-
            Instance UUIDs protected from manual and automatic scale-in.
            Protection does not prevent refresh, failed-instance replacement, or
            explicit deletion.
        scale_in_blocked_reason:
          type: string
          readOnly: true
          description: >-
            Why scale-in protection currently prevents the requested reduction.
            Omitted when not blocked.
        retiring_instances:
          type: array
          readOnly: true
          items:
            $ref: '#/components/schemas/RetiringPoolMember'
        id:
          type: string
          format: uuid
          readOnly: true
          example: 8f2a1c3d-4e5b-4a6f-9c0d-1e2f3a4b5c6d
        crn:
          type: string
          readOnly: true
          description: >-
            Cloud Resource Name. This is the value an IAM policy statement must
            name to scope a permission to this pool alone; a policy written
            against anything else will not match.
          example: crn:compute:sa-saopaulo-1:my-account:instance-pool/web-pool
        name:
          description: >-
            Resource names must not start with the literal crn: prefix or be
            UUIDs (canonical, compact, braced, or urn:uuid: forms, in either
            case).
          type: string
          example: web-asg
        description:
          type: string
          example: Front-end autoscaling group
        desired_count:
          type: integer
          minimum: 0
          maximum: 100
          example: 2
        min_count:
          type: integer
          minimum: 0
          maximum: 100
          example: 1
        max_count:
          type: integer
          minimum: 0
          maximum: 100
          description: >-
            A value of 0 means the pool holds no members until max_count is
            raised.
          example: 3
        live_count:
          type: integer
          readOnly: true
          description: |
            How many members are UP — bound instances whose current_state is
            `running`.
          example: 2
        member_count:
          type: integer
          readOnly: true
          description: >-
            How many instances the pool holds, running or not. This is what the
            reconciler converges toward desired_count and what `status`
            reflects, so member_count == desired_count with live_count below it
            means the pool has the members it was asked for and some of them are
            not up.
          example: 2
        refresh_in_progress:
          type: boolean
          readOnly: true
          description: >-
            True while a rolling replacement requested through POST
            /v1/instance-pools/{pool_id}/refresh is still running. It clears
            itself once every member is on the current template. The pool reads
            `scaling` for the duration, since it runs one instance over its
            target while a replacement comes up.
          example: false
        stale_instance_count:
          type: integer
          readOnly: true
          description: >-
            How many members were launched from a template other than the pool's
            current one — that is, how many a refresh would replace. Non-zero
            after editing `template` and before refreshing, which is the signal
            that a template change has not been rolled out yet.
          example: 0
        status:
          type: string
          enum:
            - active
            - scaling
            - error
            - deleting
          readOnly: true
          description: >
            Where the pool is against its target.


            `active` means member_count == desired_count — the pool holds the

            members it was asked for. It is not a claim that all of them are up;

            read live_count for that.


            `scaling` means it does not, and the reconciler is converging it:

            after a create, after a desired_count change, and for the length of
            an

            instance refresh, which runs the pool one instance over its target

            while a replacement comes up.


            `error` means an active error fault exists. Capacity failures remain

            eligible for reconciliation; failed deletion retains its teardown
            intent

            and never recreates members. `deleting` is teardown without an
            active error.
          example: active
        faults:
          type: array
          readOnly: true
          description: >-
            Active faults, newest first. Empty for a healthy pool. Recovery
            resolves only the successful operation's codes.
          items:
            $ref: '#/components/schemas/Fault'
        managed_by:
          type: string
          readOnly: true
          example: customer
        tags:
          allOf:
            - $ref: '#/components/schemas/Tags'
          description: >-
            Labels on the POOL itself, for IAM conditions
            (`basalt:ResourceTag/<key>`) and cost attribution. They are attached
            to nothing else: no instance the pool launches carries them.

            The tags a replica is launched with are `template.tags`. Unlike the
            other top-level fields beside this one, `tags` is not a projection
            of the launch template — it is the pool's own set, and PATCHable on
            its own.
        template:
          allOf:
            - $ref: '#/components/schemas/InstancePoolTemplate'
          readOnly: true
          description: |
            The pool's launch config, in the shape instance create takes. The
            only place it appears: a flat copy of it beside this was two
            spellings of one thing, and two spellings drift.
    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
            params:
              type: object
              additionalProperties: true
              description: >-
                Optional non-sensitive values for interpolation of localized
                errors, keyed by error code. Never present for server errors.
              example:
                instances: 2
                pools: 0
            request_id:
              type: string
              format: uuid
              description: Request ID for debugging
              example: 550e8400-e29b-41d4-a716-446655440000
    ResourceRef:
      type: object
      required:
        - id
        - crn
      properties:
        id:
          type: string
          format: uuid
          example: 288aa1e9-bbf5-5584-bce9-6a688f06d8a1
        crn:
          type: string
          example: crn:catalog::platform:country/288aa1e9-bbf5-5584-bce9-6a688f06d8a1
        name:
          type: string
          description: >-
            Display label at response time — the resource's name where it has
            one, otherwise its human label (an invoice number, an account
            display name). Absent when the target is gone. Not a lookup key.
          example: Brazil
    AutoscalingPolicy:
      type: object
      additionalProperties: false
      required:
        - enabled
        - metrics
      description: >
        Target tracking shared by instance pools and load balancers. Updates
        replace

        the policy. Set enabled=false to retain settings and use manual sizing.

        Each metric recommends a desired count; the largest recommendation wins.

        Missing, stale or incomplete observations prevent scale-in but do not
        block

        scale-out recommended by another valid metric. Decisions obey the
        resource's

        min_count/max_count, warmup, cooldown, stabilization, step limits and
        quotas.

        State survives controller restarts. Active policies own desired_count;

        manual changes are accepted and automatic evaluation resumes after
        cooldown.

        Custom telemetry requires telemetry:ReadMetrics in the same account.
      properties:
        enabled:
          type: boolean
          example: true
        metrics:
          type: array
          minItems: 1
          maxItems: 5
          items:
            $ref: '#/components/schemas/ScalingMetric'
        warmup_seconds:
          type: integer
          minimum: 30
          maximum: 3600
          default: 180
        cooldown_seconds:
          type: integer
          minimum: 30
          maximum: 3600
          default: 60
        scale_down_stabilization_seconds:
          type: integer
          minimum: 60
          maximum: 3600
          default: 300
        max_scale_out_step:
          type: integer
          minimum: 1
          maximum: 100
          default: 4
        max_scale_in_step:
          type: integer
          minimum: 1
          maximum: 100
          default: 1
        drain_seconds:
          type: integer
          minimum: 30
          maximum: 3600
          default: 120
          description: >-
            Grace period after route withdrawal and proxy acknowledgements,
            before deleting a retiring member. Long-lived TCP/UDP sessions may
            end at the deadline; arbitrary application shutdown hooks are not
            supported.
    AutoscalingStatus:
      type: object
      readOnly: true
      required:
        - status
        - reason
        - history
      properties:
        status:
          type: string
          enum:
            - pending
            - disabled
            - stable
            - scaling
            - waiting
            - warming_up
            - metrics_unavailable
            - stabilizing
            - cooldown
            - draining
            - scale_in_blocked
        reason:
          type: string
          example: waiting for sustained lower demand
        evaluated_at:
          type: string
          format: date-time
        last_scaled_at:
          type: string
          format: date-time
        history:
          type: array
          maxItems: 50
          items:
            type: object
            required:
              - at
              - from
              - to
              - reason
            properties:
              at:
                type: string
                format: date-time
              from:
                type: integer
                minimum: 0
              to:
                type: integer
                minimum: 0
              reason:
                type: string
    RetiringPoolMember:
      allOf:
        - $ref: '#/components/schemas/Retirement'
        - type: object
          required:
            - instance_id
          properties:
            instance_id:
              type: string
              format: uuid
    Fault:
      type: object
      required:
        - code
        - severity
        - message
        - details
        - first_at
        - last_at
        - occurrences
      properties:
        code:
          type: string
          description: Stable machine-readable code owned by the reporting operation.
          example: BACKUP_FAILED
        severity:
          type: string
          enum:
            - error
            - warning
        message:
          type: string
          example: Backup upload failed.
        details:
          type: object
          nullable: true
          additionalProperties: true
          description: Structured context; legacy strings are preserved in legacy_text.
        first_at:
          type: string
          format: date-time
          description: First observation in this active occurrence series.
        last_at:
          type: string
          format: date-time
          description: Latest observation in this active occurrence series.
        occurrences:
          type: integer
          minimum: 1
          example: 1
    Tags:
      type: object
      additionalProperties:
        type: string
      example:
        environment: production
        team: backend
    InstancePoolTemplate:
      type: object
      description: >-
        Stored launch configuration with ResourceRef relationships. Use a
        reference id or crn as the corresponding string selector when replacing
        the template. Image identities remain pinned until template replacement.
      properties:
        addresses:
          type: array
          items:
            $ref: '#/components/schemas/AddressRequest'
          description: >-
            Address families allocated on the member's sole interface. Fixed
            addresses are not allowed.
        security_groups:
          type: array
          items:
            $ref: '#/components/schemas/ResourceRef'
        floating_ip_assignment:
          type: string
          enum:
            - none
            - ipv4
            - ipv6
            - dual_stack
            - auto
          default: none
          description: >-
            Per-member public floating IP assignment. Explicit families require
            addresses and internet routes in every selected subnet.
        user_data:
          type: string
          format: byte
          description: Base64-encoded user data (cloud-init), stamped on every replica.
        metadata:
          $ref: '#/components/schemas/Metadata'
        tags:
          allOf:
            - $ref: '#/components/schemas/Tags'
          description: >-
            Tags stamped on every instance this template launches. These are the
            replicas' tags, not the pool's — the pool's own labels are the
            top-level `tags`, and the two are independent.

            Changing them affects FUTURE launches only. The instances already
            running keep the tags they were launched with, so between the change
            and a refresh the pool holds members carrying two different tag
            sets; `stale_instance_count` is how many are still on the old one.
            POST /v1/instance-pools/{pool_id}/refresh rolls them onto the
            current template.
        iam_role:
          anyOf:
            - $ref: '#/components/schemas/ResourceRef'
            - type: object
              nullable: true
              enum:
                - null
          description: Role assigned to replicas, or null when none is configured.
          example:
            id: 550e8400-e29b-41d4-a716-446655440005
            crn: crn:iam:::role/550e8400-e29b-41d4-a716-446655440005
            name: instance-role
        volumes:
          type: array
          items:
            $ref: '#/components/schemas/InstanceVolume'
          description: >
            Per-replica disks, the boot disk included — mark it with

            `boot: true`. Each new replica receives the configured provisioned

            performance. Omitted performance uses the included allowance.
            Existing

            volumes and snapshot schedules are not supported in pool templates.
        flavor:
          allOf:
            - $ref: '#/components/schemas/ResourceRef'
          example:
            id: 550e8400-e29b-41d4-a716-446655440003
            crn: crn:compute:sa-saopaulo-1:platform:flavor/s1.small
            name: s1.small
        image:
          anyOf:
            - $ref: '#/components/schemas/ResourceRef'
            - type: object
              nullable: true
              enum:
                - null
          description: >-
            Source image pinned until the template is replaced, or null when no
            image is selected.
          example:
            id: 550e8400-e29b-41d4-a716-446655440004
            crn: >-
              crn:compute:sa-saopaulo-1:platform:image/ubuntu/architecture/amd64/version/24.04
            name: ubuntu
      required:
        - flavor
        - image
        - iam_role
        - security_groups
        - floating_ip_assignment
    ScalingMetric:
      type: object
      additionalProperties: false
      required:
        - source
        - target_type
        - target_value
      description: >
        CPU uses source=cpu, target_type=utilization and a percentage target
        <=100.

        Utilization is CPU seconds per second divided by allocated vCPUs across
        all

        ready members. Enabled CPU scaling requires min_count>=1. Other selector
        and

        aggregation fields are not allowed for CPU.


        Custom demand uses source=telemetry and target_type=average_value.
        Metric

        name and labels select series in the resource's account, organization
        and

        region. Temporal aggregation is applied within each series before
        combining

        series; repeated gauge samples are never summed as extra demand. The
        desired

        count is ceil(combined value / target_value): 750 pending jobs at a
        target of

        100 per instance recommends 8. Custom demand can scale a customer pool
        from 0.

        Producers must publish fresh zeroes for idle queues; absent data is not
        zero.
      properties:
        source:
          type: string
          enum:
            - cpu
            - telemetry
          example: telemetry
        target_type:
          type: string
          enum:
            - utilization
            - average_value
          example: average_value
        target_value:
          type: number
          minimum: 0
          exclusiveMinimum: true
          example: 100
        name:
          type: string
          pattern: ^[a-zA-Z_:][a-zA-Z0-9_:]{0,199}$
          example: queue_depth
        labels:
          type: object
          maxProperties: 10
          additionalProperties:
            type: string
            maxLength: 256
          description: Exact-match labels; tenancy labels and __name__ cannot be supplied.
          example:
            queue: orders
        sample_aggregation:
          type: string
          enum:
            - last
            - avg
            - max
            - rate
          default: last
          description: >-
            Use last for queue gauges; rate for monotonically increasing
            counters, with reset handling.
        series_aggregation:
          type: string
          enum:
            - sum
            - avg
            - max
          default: sum
        expected_series:
          type: integer
          minimum: 1
          maximum: 1000
          default: 1
          description: >-
            Exact expected cardinality; incomplete or ambiguous selectors are
            unavailable.
        window_seconds:
          type: integer
          minimum: 30
          maximum: 3600
          default: 120
        max_age_seconds:
          type: integer
          minimum: 30
          maximum: 3600
          default: 90
          description: >-
            Actual newest observation age per series; must not exceed
            window_seconds. Defaults to the smaller of 90 and the window.
    Retirement:
      type: object
      readOnly: true
      required:
        - requested_at
        - drain_seconds
      properties:
        requested_at:
          type: string
          format: date-time
        drain_seconds:
          type: integer
          minimum: 30
          maximum: 3600
        agent_acknowledged_at:
          type: string
          format: date-time
        drain_until:
          type: string
          format: date-time
          description: Earliest deletion time; absent while withdrawal is pending.
    AddressRequest:
      type: object
      properties:
        family:
          type: string
          enum:
            - ipv4
            - ipv6
        address:
          type: string
          description: >-
            Optional fixed address when creating an interface or instance NIC.
            For IPv6, use the first address of an aligned /96 inside the subnet
            /64 (last 32 bits zero); the first and last /96 ranges are reserved.
            Omit for automatic allocation. Managed database nodes and the
            add-address operation require automatic allocation.
      required:
        - family
      additionalProperties: false
    Metadata:
      type: object
      additionalProperties:
        type: string
      example:
        environment: production
        team: backend
    InstanceVolume:
      type: object
      required:
        - size_gb
      description: |
        One disk created with the instance. `boot: true` marks the one cloned
        from image_id; every other entry is a blank volume the in-guest agent
        formats and mounts.
      properties:
        boot:
          type: boolean
          default: false
          example: false
          description: |
            Marks the boot disk. It takes no mount_path or fstype —
            both come from the image — and sending either is refused
            rather than ignored.
        size_gb:
          type: integer
          minimum: 1
          example: 20
        volume_type:
          type: string
          example: nvme
          description: Tier; omitted = the region default.
        performance:
          $ref: '#/components/schemas/VolumePerformanceRequest'
        mount_path:
          type: string
          example: /data
        fstype:
          type: string
          example: ext4
          description: Filesystem the in-guest agent formats the volume with.
        delete_on_termination:
          type: boolean
          default: true
          example: true
          description: Destroyed with the instance unless set false.
    VolumePerformanceRequest:
      type: object
      additionalProperties: false
      description: >
        Independently provision total sustained, combined read/write
        performance.

        Omitted dimensions retain their current value (included allowance on
        create).

        SSD permits up to 8000 IOPS and 250 MiB/s; NVMe up to 12000 and 500
        MiB/s.

        The minimum is the volume's included allowance; higher grandfathered
        allowances

        remain available free of charge. Throughput is whole MiB/s, except an
        exact

        fractional legacy allowance may be selected to remove the paid add-on.

        Increases require account quota, regional capacity, and healthy storage.

        Applied extras bill by elapsed duration, including while detached or
        stopped.
      properties:
        iops:
          type: integer
          minimum: 1
          example: 6000
        throughput_mib_s:
          type: number
          minimum: 0
          example: 250
  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
    Conflict:
      description: Resource conflict (e.g., already exists, invalid state)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: CONFLICT
              message: Resource with this name already exists
              request_id: 550e8400-e29b-41d4-a716-446655440000
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        An OAuth 2.0 bearer token, sent as `Authorization: Bearer <token>`.
        This is the recommended way to authenticate.

        Get one by exchanging a service account's access key pair at
        `POST /v1/oauth/token` with `grant_type=client_credentials`. It is the
        standard client-credentials grant, so any OAuth-aware library will
        obtain and refresh it for you.

        ```
        curl -s -u "$KEY_ID:$SECRET" -d grant_type=client_credentials \
          https://iam.basaltic.sh/v1/oauth/token
        ```

        Tokens last an hour by default. The same access key pair is separately
        your AWS SigV4 credential for the S3-compatible object endpoint, which
        speaks nothing else.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.