> ## 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 an instance pool's size, tags or launch template

> Change desired_count (bounded by the pool's min_count/max_count), the
pool's own tags, and/or the launch template. The reconciler scales the
live instance set to match a new desired_count. min_count and max_count
are fixed at create.

`tags` relabels the POOL and nothing else: it takes effect immediately,
no instance is touched, and the new set is what a later IAM condition
reads as `basalt:ResourceTag/<key>`. It replaces the whole set — an empty
object clears it, an omitted field leaves it alone.

A new `template` replaces the stored one wholesale and changes what the
pool launches NEXT; the instances already running keep what they booted
with, because a live VM cannot change flavor, tier, subnet or tags in
place. So a `template.tags` edit leaves the pool holding members with two
different tag sets until it is rolled. The pool reports
`stale_instance_count` — how many members are on the old template — and
POST /v1/instance-pools/{pool_id}/refresh rolls them.




## OpenAPI

````yaml /api-reference/specs/compute.yaml patch /v1/instance-pools/{pool_id}
openapi: 3.0.3
info:
  title: Basaltic Compute API
  version: 1.0.0
  description: |
    Virtual machine instances, and the images, flavors, SSH keypairs 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:
  - SignatureAuth: []
paths:
  /v1/instance-pools/{pool_id}:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          example: 8f2a1c3d-4e5b-4a6f-9c0d-1e2f3a4b5c6d
    patch:
      tags:
        - Compute
      summary: Update an instance pool's size, tags or launch template
      description: >
        Change desired_count (bounded by the pool's min_count/max_count), the

        pool's own tags, and/or the launch template. The reconciler scales the

        live instance set to match a new desired_count. min_count and max_count

        are fixed at create.


        `tags` relabels the POOL and nothing else: it takes effect immediately,

        no instance is touched, and the new set is what a later IAM condition

        reads as `basalt:ResourceTag/<key>`. It replaces the whole set — an
        empty

        object clears it, an omitted field leaves it alone.


        A new `template` replaces the stored one wholesale and changes what the

        pool launches NEXT; the instances already running keep what they booted

        with, because a live VM cannot change flavor, tier, subnet or tags in

        place. So a `template.tags` edit leaves the pool holding members with
        two

        different tag sets until it is rolled. The pool reports

        `stale_instance_count` — how many members are on the old template — and

        POST /v1/instance-pools/{pool_id}/refresh rolls them.
      operationId: updateInstancePool
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InstancePoolUpdateRequest'
      responses:
        '200':
          description: The updated instance pool.
          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'
      security:
        - SignatureAuth: []
components:
  schemas:
    InstancePoolUpdateRequest:
      type: object
      description: >
        Every field is optional; an omitted one is left alone, and sending none
        of

        them is a 400 rather than a silent no-op.


        `min_count` and `max_count` are fixed at create and are not patchable.


        The two tag sets move independently. `tags` relabels the pool itself and

        takes effect immediately, touching no instance. `template.tags` — like
        the

        rest of `template` — does NOT touch the instances already running: a
        live

        VM cannot change flavor, tier, subnet or its tags in place. It changes
        what

        the pool launches NEXT, so until you roll the pool it holds members

        carrying two different tag sets, and `stale_instance_count` is how many
        are

        on the older one. Bring them onto the current template with

        POST /v1/instance-pools/{pool_id}/refresh.
      properties:
        tags:
          allOf:
            - $ref: '#/components/schemas/Tags'
          description: >-
            REPLACES the pool's labels: the map you send becomes the whole set,
            an empty object clears them, and omitting the field leaves them
            alone. Replacement rather than a merge because a merge leaves no way
            to say a key should be removed.

            These label the pool, not its instances. To change what future
            replicas are tagged with, send `template.tags`.
        desired_count:
          type: integer
          minimum: 0
          maximum: 100
          description: >-
            New target size, bounded by the pool's min_count/max_count and the
            hard platform cap of 100.
          example: 3
        template:
          allOf:
            - $ref: '#/components/schemas/InstancePoolTemplate'
          description: >-
            Replaces the launch config WHOLESALE — the object you send is what
            the pool launches next, and anything you leave out is cleared rather
            than kept. Replacement rather than a deep merge so a shorter
            `networks` or `data_volumes` cannot be read as a truncation and
            silently drop an interface or a disk.
    InstancePoolResponse:
      type: object
      properties:
        instance_pool:
          $ref: '#/components/schemas/InstancePool'
    Tags:
      type: object
      additionalProperties:
        type: string
      example:
        environment: production
        team: backend
    InstancePoolTemplate:
      type: object
      description: >-
        The pool's launch config, in the shape a standalone instance create
        takes: same field names, same types, same meanings, so a client that can
        build an instance can build a pool of them without a second, narrower
        contract to learn.

        It belongs to the pool. There is no separate launch-template resource to
        create, version or share between pools.

        Networking is one ordered `networks` list, index 0 being the primary
        NIC, replacing the flat `subnet_id` + `extra_nics` split. `ip_address`
        and `mac` are part of that shared NIC shape but are refused here: every
        replica launches from this one template, so a fixed address would have
        the second replica ask for one the first already holds.
      properties:
        flavor_id:
          type: string
          format: uuid
        image_id:
          type: string
          description: >-
            Image to clone each replica's boot disk from. Accepts the same three
            forms instance create does: an image id, `name:version`, or a bare
            `name`.

            Unlike instance create, the reference is resolved ONCE, when the
            pool is created, and the resulting image id is what every replica
            boots — including replacements spawned months later. A tag
            re-resolved per replica would let a heal boot a newer build than its
            siblings, and a pool whose members are quietly not identical is the
            premise of the primitive breaking silently. To move a pool to a new
            build, change the template.
        boot_volume_size_gb:
          type: integer
          minimum: 1
          maximum: 16384
        boot_volume_type:
          type: string
          enum:
            - ssd
            - nvme
          description: Boot disk tier for every replica; omitted = the region default.
        networks:
          type: array
          description: >-
            Per-replica interfaces. Index 0 is the primary NIC and is required;
            the rest are extras.
          items:
            $ref: '#/components/schemas/NetworkConfig'
        data_volumes:
          type: array
          description: >-
            Blank per-replica data volumes, created and reclaimed with each
            replica.
          items:
            $ref: '#/components/schemas/PoolTemplateVolume'
        key_name:
          type: string
          description: SSH keypair name to authorize on every replica.
        key_names:
          type: array
          items:
            type: string
          description: SSH keypair names to authorize on every replica.
        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_id:
          type: string
          format: uuid
          description: >-
            IAM role attached to every replica, reachable from its IMDS
            endpoint.
        assign_public_ip:
          type: boolean
          description: >-
            The older spelling of `networks[0].assign_public_ip`: each replica
            gets a floating IP on its PRIMARY NIC, allocated by the reconciler
            and released on scale-in. Per-NIC flags live on `networks[]`, and a
            replica can be public on a secondary interface while its primary
            stays private.
    InstancePool:
      type: object
      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:
        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/8f2a1c3d-4e5b-4a6f-9c0d-1e2f3a4b5c6d
        account_id:
          type: string
          format: uuid
          readOnly: true
        name:
          type: string
          example: web-asg
        description:
          type: string
          example: Front-end autoscaling group
        flavor_id:
          type: string
          format: uuid
        image_id:
          type: string
          format: uuid
        subnet_id:
          type: string
          format: uuid
        user_data:
          type: string
          format: byte
        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: 1
          maximum: 100
          example: 3
        live_count:
          type: integer
          readOnly: true
          description: >-
            How many members are UP — bound instances in vm_state `running`.
            This is the number to alert or scale on. It can sit below
            member_count while a replica boots, and below desired_count on an
            `active` pool whose members have stopped.
          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` carries the last failure in error_message and is still

            reconciled — the pool keeps being retried. `deleting` is a teardown
            in

            flight.
          example: active
        error_message:
          type: string
          readOnly: true
          description: >-
            The last failure the reconciler recorded, cleared when the pool
            reaches its target. Set alongside status `error`, and left in place
            through a later resize — a pool that failed to spawn and is being
            scaled again has not yet proved the failure is behind it.
        managed_by:
          type: string
          readOnly: true
          example: customer
        security_group_ids:
          type: array
          items:
            type: string
            format: uuid
        keypair_names:
          type: array
          items:
            type: string
        iam_role_id:
          type: string
          format: uuid
        boot_volume_size_gb:
          type: integer
          minimum: 1
          maximum: 16384
        boot_volume_type:
          type: string
          enum:
            - ssd
            - nvme
        extra_nics:
          type: array
          items:
            $ref: '#/components/schemas/PoolTemplateNIC'
        data_volumes:
          type: array
          items:
            $ref: '#/components/schemas/PoolTemplateVolume'
        assign_public_ip:
          type: boolean
          description: >-
            Each replica gets a floating IP on its PRIMARY NIC, allocated by the
            reconciler and released on scale-in. Secondary interfaces carry
            their own flag — read `template.networks[]` or `extra_nics[]` for
            those.
        metadata:
          $ref: '#/components/schemas/Metadata'
        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 same launch config as the flat fields above, rendered in
            instance-create's shape. Both are always emitted and they cannot
            disagree — this is a projection of the one stored template, not a
            second copy of it. Read this one; the flat fields are kept for
            clients written before it existed.
    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
    NetworkConfig:
      type: object
      required:
        - subnet_id
      properties:
        subnet_id:
          type: string
          format: uuid
          description: Subnet to attach the NIC to (required).
          example: 9b2e4f1a-3c5d-4e6f-8a90-1b2c3d4e5f60
        ip_address:
          type: string
          description: |
            Optional fixed IP. Must be in the subnet's CIDR and not
            currently allocated to another interface. An address is
            picked automatically when omitted.
          example: 10.0.1.42
        mac:
          type: string
          description: |
            Optional MAC address. Must be locally-administered (`X2:`,
            `X6:`, `XA:`, `XE:` in the first octet). Generated when
            omitted.
          example: 02:1a:2b:3c:4d:5e
        security_group_ids:
          type: array
          items:
            type: string
            format: uuid
            example: c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f
          description: |
            Security groups to attach to this NIC at provision time.
            Each must be owned by the same account. Empty list = no
            per-NIC ACLs (the platform's default-allow stays in
            force).
        assign_public_ip:
          type: boolean
          default: false
          description: |
            Allocate a floating IP and attach it to THIS interface once it
            exists. Per NIC, so a secondary interface can carry the public
            address while the primary stays private, and an instance with
            several public interfaces gets one address each.

            Each address is a separate floating-IP allocation: it counts
            against the account's floating_ips quota and is billed like any
            other. It is released when the instance is torn down — an
            address you allocated yourself and attached to the same NIC is
            not, and survives the instance.

            The interface's subnet must already route 0.0.0.0/0 to an
            internet gateway. Without that the address would be silently
            unreachable, so the launch fails instead.
    PoolTemplateVolume:
      type: object
      description: >-
        One blank per-replica data volume, created with each replica and deleted
        with it. A mount_path makes the in-guest agent format (fstype, default
        ext4, only if blank) and mount it.
      required:
        - size_gb
      properties:
        size_gb:
          type: integer
          example: 100
        volume_type:
          type: string
          enum:
            - ssd
            - nvme
        mount_path:
          type: string
          example: /data
        fstype:
          type: string
          example: ext4
        delete_on_termination:
          type: boolean
          default: true
          description: >-
            Whether the volume is destroyed with the instance (default) or
            released back to available on teardown. Honoured on a pool template
            too: a replica scaled in, replaced or torn down with the pool
            releases the volume instead of destroying it when this is false.
    Metadata:
      type: object
      additionalProperties:
        type: string
      example:
        environment: production
        team: backend
    PoolTemplateNIC:
      type: object
      description: One extra per-replica network interface.
      required:
        - subnet_id
      properties:
        subnet_id:
          type: string
          format: uuid
        security_group_ids:
          type: array
          items:
            type: string
            format: uuid
        assign_public_ip:
          type: boolean
          default: false
          description: >-
            Give THIS interface a floating IP on every replica, independently of
            the primary's. One allocation per replica per interface, each
            counted against the account's floating_ips quota and released on
            scale-in.
  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.

````