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

# Compute

> Launch virtual machine instances, and drive them through their lifecycle: start, stop, reboot, resize, reinstall.

Compute runs virtual machines. You pick a size, an image and a subnet, and the
service builds the boot disk, the network interfaces and the cloud-init seed,
then boots the guest.

The service is **regional** — `compute.sa-saopaulo-1.basaltic.sh`. An instance,
its image, its keypairs and its pools all live in one region and never cross
into another.

<CardGroup cols={2}>
  <Card title="Launch an instance" icon="server" href="#launching-an-instance">
    Flavors, images, interfaces, disks, keys and cloud-init — and the two
    fields whose absence quietly costs you a network.
  </Card>

  <Card title="Lifecycle" icon="refresh-cw" href="#the-instance-lifecycle">
    What start, stop, reboot, resize and reinstall each preserve, and what
    they destroy.
  </Card>

  <Card title="Disks and interfaces" icon="hard-drive" href="#volumes">
    Hot-plugging volumes and NICs, and which of them survive the instance.
  </Card>

  <Card title="Console access" icon="terminal" href="#console-access">
    The boot transcript, a screenshot, and an interactive serial session for
    when the network is gone.
  </Card>
</CardGroup>

## Launching an instance

<Tabs>
  <Tab title="Console">
    Go to **Compute → Instances** and choose **Create instance**. The form is
    one card per decision — **Details**, **Flavor**, **Image**, **Boot
    volume**, **Data volumes**, **Networking**, **SSH keys**, **IAM role**,
    **User data** — with a running summary alongside them.

    **Networking** is the card to slow down on. Every interface carries its own
    **VPC**, **Subnet**, **Security groups** and **Assign public IP**;
    **Add network interface** adds another, and the first in the list is the
    primary NIC.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST https://compute.sa-saopaulo-1.basaltic.sh/v1/instances
    {
      "name": "web-01",
      "flavor_id": "550e8400-e29b-41d4-a716-446655440000",
      "image_id": "debian-13",
      "key_names": ["my-keypair"],
      "networks": [
        { "subnet_id": "9b2e4f1a-3c5d-4e6f-8a90-1b2c3d4e5f60",
          "security_group_ids": ["c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f"],
          "assign_public_ip": true }
      ]
    }
    ```
  </Tab>
</Tabs>

The call answers **`202`** with the instance in `pending`. Building the boot
disk, the interfaces and the seed happens after the response, so poll
`GET /v1/instances/{instance_id}` and watch `vm_state`.

<Warning>
  **Security groups are per interface.** They belong in
  `networks[].security_group_ids`, not in a top-level field. A NIC provisioned
  with an empty list gets no per-interface filtering at all, which is not the
  same thing as a closed instance.
</Warning>

<Warning>
  **An instance with no `networks` entry boots with no networking.** Nothing
  fails and nothing warns you — the guest comes up with no interface, so
  nothing reaches it and it reaches nothing. Send at least one entry; index 0
  becomes the primary NIC, the one carrying the guest's default route and its
  route to the metadata service.
</Warning>

### Sizing: flavors

A flavor is CPU and memory, and nothing else. It carries **no disk size** —
the boot disk is a volume sized at launch — so `GET /v1/flavors` is a catalog
of compute shapes, not of machine types.

<ResponseField name="class" type="shared | dedicated">
  `shared` oversubscribes CPU for higher density. `dedicated` pins each vCPU
  1:1 to a physical core. Both classes run on the same hosts; the class decides
  how the instance draws on a host's threads.
</ResponseField>

<ResponseField name="family" type="general | loadbalancer | database">
  Only `general` flavors can run instances and instance pools. The other two
  are reserved for the managed products, whose nodes are operated and priced
  by the platform, and are refused here. Pass `?family=general` when listing.
</ResponseField>

<ResponseField name="cpu_baseline_pct / cpu_burst_pct" type="percent of one vCPU">
  The guaranteed floor and the ceiling. The instance is entitled to
  `vcpus × cpu_baseline_pct / 100` cores however busy its neighbours get, and
  may not exceed the burst ceiling even on an idle host. Absent means the
  flavor guarantees no floor, or imposes no ceiling beyond the vCPU count.
</ResponseField>

<ResponseField name="net_mbps" type="aggregate throughput">
  The instance's network ceiling, in megabits/s, across all its interfaces.
  Absent means uncapped. A [resize](#resize) moves this with the flavor.
</ResponseField>

The catalog is small and comes back in one page — `GET /v1/flavors` is not
paginated.

### Choosing an image

`image_id` takes three forms, and the difference matters for reproducibility:

<Tabs>
  <Tab title="A bare name">
    `"image_id": "debian-13"` follows the tag. You get whichever build is
    current at the moment the instance is created, so two launches a month
    apart can boot different bits.
  </Tab>

  <Tab title="name:version">
    `"image_id": "debian-13:20260807"` pins one build. This is how you opt out
    of the tag moving under you.
  </Tab>

  <Tab title="An image id">
    A UUID pins one build too, and is what the API returns to you.
  </Tab>
</Tabs>

Names resolve across your own images plus the public platform catalog, and
always at the `amd64` architecture. See [Images and keypairs](/compute/images)
for how a tag moves and what the listing does and does not show you.

### The boot disk

The boot volume is cloned from the image and sized by `boot_volume_size_gb`,
which defaults to the image's `min_disk_gb` and may not go below it. Anything
smaller is a `400` before the instance row exists, not a failed build.

`boot_volume_type` picks the tier — `ssd` or `nvme` — and defaults to the
region's. Both fields accept 1–16384 GB.

<Warning>
  **The boot volume is always created with `delete_on_termination: true`.** It
  is destroyed when the instance is deleted. If you want it to outlive the
  instance, flip the flag on the attachment after launch — see
  [what survives a delete](#what-survives-a-delete).
</Warning>

`data_volumes` creates blank volumes alongside the instance. Each takes a
`size_gb`, an optional `volume_type`, and an optional `mount_path` — with a
mount path set, the in-guest agent formats the disk (only if it is blank) and
mounts it there, using `fstype`, default `ext4`. These default to
`delete_on_termination: true`, the opposite of a volume you attach later.

### SSH keys and cloud-init

`key_names` authorizes existing keypairs on the instance. The public keys are
**baked into the seed at launch**, so deleting the keypair afterwards does not
remove access from an instance already running.

`user_data` is base64-encoded cloud-init, and the platform merges it over a
base configuration that creates the login user:

|                 |                                                 |
| --------------- | ----------------------------------------------- |
| Default user    | `basaltic`, in `sudo`, with `NOPASSWD:ALL`      |
| Authorized keys | the public keys of every keypair in `key_names` |
| Password login  | disabled (`ssh_pwauth: false`)                  |
| Root login      | disabled                                        |

<Warning>
  **Your user-data must decode to a document whose first line is
  `#cloud-config`.** Anything else — a `#!/bin/bash` script, a MIME multipart
  bundle — is ignored in silence: the instance boots, your script never runs,
  and nothing in the API says so. Wrap shell work in cloud-init's `runcmd`
  instead.
</Warning>

<Warning>
  **The merge is by top-level key, not a deep merge.** A key you set replaces
  the base entirely. Sending your own `users:` block therefore replaces the
  `basaltic` user — and takes your keypair's authorized keys with it, because
  those live inside it. Add users under a different key, or restate the base
  user's `ssh_authorized_keys` yourself.
</Warning>

A `user_data` document that is not valid YAML fails the build, and the
instance lands in `error` rather than booting without it.

### Giving the instance an identity

`iam_role_id` attaches an IAM role. Software inside the guest then fetches
short-lived credentials from the metadata service at `169.254.169.254` — no
access key is written to the instance, and nothing has to be rotated.

<Note>
  Two conditions, and both are easy to miss. The role's **trust policy** must
  accept `crn:compute:*:*:instance/*` (or the specific instance CRN), and the
  principal making the launch call needs **`iam:PassRole`** on the role,
  separately from `compute:CreateInstance`. Without the trust policy the
  instance launches and the credentials never mint; without `iam:PassRole` the
  launch itself is denied. [Roles and instance identity](/iam/roles) walks
  through both.
</Note>

## The instance lifecycle

An instance carries two states and they answer different questions.

<Columns cols={2}>
  <Card title="vm_state" icon="target">
    What you asked for. It moves the moment the API accepts your call.
  </Card>

  <Card title="power_state" icon="activity">
    What the guest is actually doing, as last observed on the host.
  </Card>
</Columns>

That split is why `POST /v1/instances/{instance_id}/start` leaves `vm_state`
reading `running` immediately, while the guest is still booting. **To know
whether an instance is really up, read `power_state`.**

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: create
    pending --> building: provisioning starts
    building --> running: guest boots
    running --> stopping: stop
    stopping --> stopped: guest is down
    stopped --> running: start
    running --> rebooting: reboot
    rebooting --> running: guest is back
    stopped --> stopped: resize / reinstall
    building --> error: provisioning failed
    error --> running: start
    running --> deleting: delete
    stopped --> deleting: delete
    deleting --> deleted: teardown complete
```

| `vm_state`  | Meaning                                                                              |
| ----------- | ------------------------------------------------------------------------------------ |
| `pending`   | The row exists; provisioning has not begun.                                          |
| `building`  | The boot disk, interfaces and seed are being built.                                  |
| `running`   | Requested to run. Read `power_state` for whether it is.                              |
| `stopping`  | A graceful shutdown is in flight.                                                    |
| `stopped`   | Down, and kept — this is the state resize and reinstall need.                        |
| `rebooting` | A reboot is in flight.                                                               |
| `deleting`  | Teardown started.                                                                    |
| `deleted`   | Teardown finished. The row is removed shortly after, so reads start answering `404`. |
| `error`     | A build or lifecycle step failed. `fault` carries `code`, `message` and `details`.   |

<Note>
  You may also see `migrating` on an instance the platform is moving between
  hosts. It is transient, nothing you asked for, and it returns to `running`.
  Treat an unrecognized state as "busy, do not act" rather than as a failure.
</Note>

### Start, stop, reboot

Each is a `202` with an empty body — poll the instance to see the result.

<Tabs>
  <Tab title="Console">
    Open the instance from **Compute → Instances**. The header offers only the
    actions its current state allows: **Start** while it is stopped, **Stop**
    and **Reboot** while it is running.

    A hard power cycle is not among them. It sits on the **Settings** tab as
    **Hard reboot**, described there as *Power-cycles the instance without a
    clean OS shutdown* and gated behind a typed confirmation.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/instances/{instance_id}/start
    POST /v1/instances/{instance_id}/stop
    POST /v1/instances/{instance_id}/reboot
    ```
  </Tab>
</Tabs>

<AccordionGroup>
  <Accordion title="Start" icon="play">
    `POST /v1/instances/{instance_id}/start` accepts an instance in `stopped`
    **or in `error`** — the second case is how you retry an instance that
    failed on the way up, without recreating it. Any other state is a `409`.
  </Accordion>

  <Accordion title="Stop" icon="square">
    `POST /v1/instances/{instance_id}/stop` requires `running`. Anything else
    is a `409` — including an instance already `stopped`, so this is not a
    "make it stopped" idempotent call.
  </Accordion>

  <Accordion title="Reboot" icon="rotate-cw">
    `POST /v1/instances/{instance_id}/reboot` requires `running`. The default
    is a graceful ACPI reboot the guest can act on; `{"hard": true}` is a power
    cycle — the reset button, with no chance to flush anything.
  </Accordion>
</AccordionGroup>

### Resize

A resize changes **vCPU and RAM, and nothing else.** Disks are untouched,
addresses are untouched, the guest's data is untouched.

<Tabs>
  <Tab title="Console">
    **Resize** on the instance header opens **Resize instance**, a flavor
    picker that summarizes **Current** against **New flavor** before you commit
    and then asks you to confirm.

    <Warning>
      The console offers **Resize** while the instance is still running, but
      the resize itself needs it stopped — submitting from a running instance
      comes back as **Failed to resize instance**. Stop it first.
    </Warning>
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/instances/{instance_id}/resize
    { "flavor_id": "550e8400-e29b-41d4-a716-446655440000" }
    ```
  </Tab>
</Tabs>

Two things about it are worth knowing before you plan a resize window:

<Steps>
  <Step title="The instance must be stopped">
    A running guest's CPU and memory maximums cannot be changed underneath it,
    so a resize on a running instance is a `409`. Stop it, resize, start it.
  </Step>

  <Step title="The new size is applied at the next start">
    The call records the target and returns. The guest comes up on the new
    flavor when you start it — there is no separate confirm step, and no
    state in which the instance is half-resized.
  </Step>
</Steps>

<Warning>
  **A resize does not move the instance to another host.** Growing means the
  extra vCPU and RAM have to be free on the host it is already on, so a resize
  can be refused for capacity while the region as a whole has plenty. Moving to
  a `dedicated` flavor is the strictest case: it needs whole threads nothing
  else may run on, which a busy-but-not-full host may not have.
</Warning>

Two more refusals, both `400`: resizing to the flavor the instance already
uses, and resizing onto a `loadbalancer` or `database` flavor.

An instance's network ceiling moves with the flavor as part of the resize, so
a downsize gives up the larger flavor's bandwidth too.

### Reinstall

`POST /v1/instances/{instance_id}/reinstall` re-images the boot disk while
keeping the instance itself. Also stopped-only, and also applied at the next
start.

<Columns cols={2}>
  <Card title="Kept" icon="check">
    The instance id, name, CRN, IP addresses and MAC addresses, its network
    interfaces, its keypairs, its cloud-init seed, and **every attached data
    volume**.
  </Card>

  <Card title="Replaced" icon="triangle-alert">
    The boot volume. A fresh one is cloned from the image and swapped in, and
    **the old one is deleted.** Everything on the root filesystem is gone.
  </Card>
</Columns>

<Tabs>
  <Tab title="Console">
    The console calls this **Replace root volume**, and it is on the
    instance's **Settings** tab, in the danger zone. It opens an **Image** and
    a **Boot volume** card — *Operating system for the replacement root volume.
    The current one is deleted* — and confirms with the instance name typed
    back.

    <Note>
      Same operation, different name. Nothing in the console is labelled
      "reinstall": look for **Replace root volume**, which is the more literal
      description of what happens to the disk.
    </Note>
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/instances/{instance_id}/reinstall
    { "image_id": "debian-13:20260807", "size_gb": 40, "volume_type": "nvme" }
    ```
  </Tab>
</Tabs>

Every field is optional. Omit `image_id` and it reinstalls from the image the
instance already has; omit `size_gb` and the replacement is the image's
`min_disk_gb`; omit `volume_type` and it lands on the region default. The same
floor applies as at launch — `size_gb` must be at least the image's
`min_disk_gb`, and within 1–16384 GB.

Because the addresses survive, reinstall is the operation for "same machine,
clean OS" — a rebuild where anything pointing at the instance keeps working.

### Delete

<Tabs>
  <Tab title="Console">
    **Delete instance** is on the instance's **Settings** tab, in the danger
    zone. The confirmation restates the rule below — *Volumes marked delete on
    termination are destroyed with it; others are detached and kept* — and
    needs the instance name typed back.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    DELETE /v1/instances/{instance_id}
    ```
  </Tab>
</Tabs>

It answers `202`. The instance moves to `deleting`, the guest is torn down, and
the row is removed once its interfaces and volumes have been reclaimed — so a completed delete stops answering reads
entirely rather than leaving a `deleted` row behind.

Deleting an instance already `deleting` is accepted and re-drives the teardown
rather than starting a second one.

#### What survives a delete

|                                          |                                                            |
| ---------------------------------------- | ---------------------------------------------------------- |
| Boot volume                              | **Destroyed.** Created with `delete_on_termination: true`. |
| Launch-time `data_volumes`               | Destroyed, unless you sent `delete_on_termination: false`. |
| A volume you attached later              | **Released back to `available`,** not destroyed.           |
| A floating IP from `assign_public_ip`    | Released — the launch allocated it.                        |
| A floating IP you allocated and attached | **Yours.** It is detached and stays allocated.             |
| Keypairs, images                         | Untouched. Neither is instance-scoped.                     |

<Tip>
  To keep a boot disk past its instance, flip the flag on the attachment:
  `PATCH /v1/instances/{instance_id}/volumes/{volume_id}` with
  `{"delete_on_termination": false}`. The volume is then unbound and returned
  to `available` on teardown instead of destroyed. This works on the boot
  volume like any other attachment.

  In the console it is the **Delete on termination** switch on the instance's
  **Volumes** tab, one per attachment.
</Tip>

## Volumes

`GET /v1/instances/{instance_id}/volumes` lists the attachments in boot order,
boot disk first, each resolved with the volume's current name, tier, size and
status.

<Tabs>
  <Tab title="Console">
    **Attach volume** on the instance header opens a dialog asking for the
    **Volume**, and optionally a **Device name** — *Leave blank to assign the
    next available name* — a **Mount path** and a **Filesystem**. Only volumes
    the instance can actually take are offered.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/instances/{instance_id}/volumes
    { "volume_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "mount_path": "/data", "fstype": "ext4" }
    ```
  </Tab>
</Tabs>

The volume must be in the same account and in status `available`, and the
instance must be `running` or `stopped`. The disk is hot-plugged; `device`
picks the next free slot (`vdb`, `vdc`, …) unless you name one.

With `mount_path` set, the in-guest agent formats the disk — **only if it is
blank** — and mounts it there. Leave it empty and you get the block device and
nothing else.

Detaching is `DELETE /v1/instances/{instance_id}/volumes/{volume_id}`, also
`202`, also requiring `running` or `stopped`. In the console it is the row
action on the instance's **Volumes** tab, confirmed as **Detach volume** — and
the boot disk has no such control there, because detaching it is refused
anyway.

## Network interfaces

<Warning>
  **`public_ip` on the instance reports the primary NIC alone.** Because
  `networks[].assign_public_ip` is per interface, an instance can perfectly
  well have its public address on a secondary NIC — and then `public_ip` is
  empty, and polling it will never surface the address.

  `GET /v1/instances/{instance_id}/nics` is the read that covers every
  interface. Each NIC carries its own `public_ip` and `floating_ip_id`.
</Warning>

That listing is ordered by boot index, primary first, and each entry resolves
the interface's current MAC, IPv4, IPv6, subnet and VPC.

### Attaching and detaching

`POST /v1/instances/{instance_id}/nics` does one of two different things, and
you pick by which field you send — exactly one of them:

<Tabs>
  <Tab title="subnet_id — a fresh NIC">
    Provisions a new interface in that subnet, allocating an address and
    attaching any `security_group_ids` you name. `ip_address` and `mac` are
    optional overrides; a MAC must be locally administered. Detaching this
    interface later **destroys** it along with its address.
  </Tab>

  <Tab title="interface_id — an existing interface">
    Attaches a standalone interface you already created. It keeps its address,
    MAC and security groups, and `ip_address`, `mac` and `security_group_ids`
    are refused here rather than silently ignored. Detaching returns it to
    standalone instead of destroying it.
  </Tab>
</Tabs>

The instance must be `running` or `stopped`; the device is hot-plugged, or
appears at the next start for a stopped instance.

<Tabs>
  <Tab title="Console">
    **Attach NIC** on the instance header opens **Attach network interface**,
    whose two options are the same two cases: **New interface** — *Create a NIC
    on a subnet, attached to this instance* — and **Existing interface** —
    *Attach a standalone interface you already created*.

    Detaching is the row action on the instance's **Networking** tab, confirmed
    as **Detach interface**.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/instances/{instance_id}/nics
    { "subnet_id": "9b2e4f1a-3c5d-4e6f-8a90-1b2c3d4e5f60" }
    ```

    ```bash theme={null}
    POST /v1/instances/{instance_id}/nics
    { "interface_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" }
    ```
  </Tab>
</Tabs>

<Warning>
  Detaching the **only** NIC on an instance is refused. There is no way to
  reach a guest with no interface, so the API will not leave you with one.
</Warning>

### Public addresses

`networks[].assign_public_ip` allocates a floating IP and attaches it to that
interface. Each one is a real allocation: it counts against your account's
`floating_ips` quota and is billed like any other.

The interface's subnet must already route `0.0.0.0/0` to an internet gateway.
Without that route the address would be allocated and silently unreachable, so
the launch fails instead.

<Note>
  The older top-level `assign_public_ip` still works and means the primary NIC
  — the only interface it could ever have addressed. It is folded into
  `networks[0]`, so sending it *and* `networks[0].assign_public_ip` asks for
  the same address once, not for two.
</Note>

## Console access

Three endpoints, for three different situations, and each requires its own IAM
action — reading a boot log, taking a picture of the screen, and holding a
keyboard inside the guest are deliberately not the same grant.

<Tabs>
  <Tab title="Console">
    **Connect** on the instance header offers three routes: **SSH** — *From
    your terminal*, which assembles the command for you — **Serial** — *Text
    console in the browser* — and **Screenshot** — *Graphical console*.

    <Warning>
      **The boot transcript has no console control.** Nothing in the web
      console reads console output; **Serial** attaches a live session, and
      that needs the instance running. For a guest that failed on the way up —
      the case the transcript exists for — reading it is API only.
    </Warning>
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    GET /v1/instances/{instance_id}/console/output
    GET /v1/instances/{instance_id}/console/screenshot
    GET /v1/instances/{instance_id}/console/serial
    ```
  </Tab>
</Tabs>

<AccordionGroup>
  <Accordion title="Console output — the boot transcript" icon="scroll-text">
    `GET /v1/instances/{instance_id}/console/output` returns what the guest
    wrote to its serial console during its **current boot**: a bad fstab, a
    wrong kernel, a cloud-init failure. Nothing has to be installed in the
    guest, and it works on a stopped instance — which is exactly when you need
    it.

    `max_bytes` is a ceiling you may lower, not raise: the maximum is 65536 and
    larger values are clamped to it. When the transcript is longer than what
    you asked for, its **beginning** is dropped and `truncated` is `true`. The
    end is always kept.

    The transcript resets at every start, so a crashed guest's last transcript
    is gone once it restarts — read it before you reboot. An instance that has
    never booted returns empty output, not an error.

    Requires `compute:GetConsoleOutput`.
  </Accordion>

  <Accordion title="Screenshot — what the screen shows" icon="image">
    `GET /v1/instances/{instance_id}/console/screenshot` returns a still image,
    normally `image/png`.

    This is the counterpart for everything a serial transcript cannot reach: a
    guest sitting in its boot manager, at a GRUB prompt, panicking before
    serial init, or booted from an image whose kernel was never told to log to
    the serial port. In those the transcript is empty and the screen holds the
    whole answer.

    A still, not a session — there is no remote desktop. The instance must be
    running; a stopped one has no display to capture and answers `409` rather
    than a blank frame.

    Requires `compute:GetConsoleScreenshot`.
  </Accordion>

  <Accordion title="Serial console — an interactive session" icon="terminal">
    `GET /v1/instances/{instance_id}/console/serial` upgrades to a WebSocket
    carrying an interactive session on the instance's serial port. This is the
    way in when the network is broken: a wrong kernel, a full disk, a security
    group that locked you out.

    Raw bytes in binary frames, both directions. It is a terminal, not a
    protocol — point a terminal emulator at it. Authentication rides on the
    upgrade request, signed like any other call, so there is no separate token
    step. A non-WebSocket request answers `426`.

    `backlog_bytes` replays already-written output before live output begins,
    so attaching to a quiet guest shows why it is quiet instead of an empty
    screen. Default 32768, maximum 65536, `0` disables it. Replay and live
    output are one stream read forward, so nothing is lost or duplicated at
    the join.

    **One session per instance:** opening a second disconnects the first,
    rather than interleaving two people's keystrokes. A session ends after 15
    minutes idle, or 4 hours regardless, and the close frame says which.

    Requires `compute:StartSerialConsole`, and the instance to be running.

    <Note>
      This drops you at the guest's own login prompt. It is not a backdoor —
      the guest's credentials are still required, and nothing here grants
      access past what the guest itself allows.
    </Note>
  </Accordion>
</AccordionGroup>

## Names, tags and metadata

<ResponseField name="name" type="unique per account">
  1–128 characters, DNS-safe: letters, digits, dot, dash, underscore, starting
  and ending alphanumeric. A name that collides with another instance in the
  account is a `409`.
</ResponseField>

<ResponseField name="renaming" type="not supported">
  `PATCH /v1/instances/{instance_id}` edits `description`, `metadata` and
  `tags`. A name is fixed for the life of the instance.

  In the console those three are the **Details**, **Tags** and **Metadata**
  cards on the instance's **Settings** tab, and there is no name field among
  them.
</ResponseField>

<ResponseField name="tags" type="IAM and cost">
  Read by IAM policy conditions as `basalt:RequestTag/<key>` on the create and
  `basalt:ResourceTag/<key>` afterwards, and used for cost attribution. See
  [policies](/iam/policies).
</ResponseField>

<ResponseField name="metadata" type="replaced, not merged">
  The map you `PATCH` becomes the whole set of your keys.
</ResponseField>

<Note>
  Metadata keys under the `basalt:` namespace are control-plane state — how an
  instance proves which pool or managed resource it belongs to. You cannot set
  them, and you cannot remove them: they are preserved through a `PATCH`
  whether or not you echo them back, and proposing a *different* value for one
  is a `400` rather than a silent drop. Read-modify-write against the whole
  metadata map is therefore safe.
</Note>

### Instances you did not create

Managed products run on instances too. A load balancer's replicas and a
database cluster's nodes appear in `GET /v1/instances` with `managed_by` set.

They are read-only here: reads and console access work, and **every lifecycle
and mutation call answers `404`** — stop, resize, delete, NIC and volume
changes alike. Act on them through the resource that owns them.

## Quotas

An instance holds three regional counters at once, all reserved together at
create and released at delete: `instances`, `vcpus` and `ram_mb`. That is why
a small `instances` limit is not the whole story — a handful of large flavors
can exhaust `vcpus` first.

`volumes_per_instance` caps the boot disk plus every data volume on one
instance, and is checked before the instance row exists, so exceeding it is a
plain `4xx` rather than an instance that provisions partway and parks in
`error`. Floating IPs come out of the network service's `floating_ips`.

## CRNs

| Resource         | CRN                                                  |
| ---------------- | ---------------------------------------------------- |
| Instance         | `crn:compute:<region>:<account>:instance/<id>`       |
| Instance pool    | `crn:compute:<region>:<account>:instance-pool/<id>`  |
| Image (yours)    | `crn:compute:<region>:<account>:image/<id>`          |
| Image (platform) | `crn:compute:<region>::image/<id>` — no account slot |
| Keypair          | `crn:compute:<region>:<account>:keypair/<name>`      |
| Flavor           | `crn:compute:<region>::flavor/<id>`                  |

Keypair CRNs are built from the **name**, so an IAM policy can wildcard a
naming convention: `crn:compute:sa-saopaulo-1:my-account:keypair/ci-*`.

## Troubleshooting

<AccordionGroup>
  <Accordion title="vm_state is error right after create" icon="triangle-alert">
    Read `fault`. It carries a `code`, a `message` and free-form `details` from
    the step that failed, plus the time it happened.

    `POST /v1/instances/{instance_id}/start` accepts an instance in `error`, so
    a failure that happened while the guest was coming up can be retried
    without recreating anything.

    It does not rebuild what was never built. A failure earlier than that — the
    boot volume or the interfaces — leaves nothing for a start to bring up, and
    the instance is best deleted and created again once the cause is fixed. The
    usual causes are in the request: a subnet that does not route to an
    internet gateway while a NIC asked for a public address, a `user_data`
    document that is not valid YAML, or a boot size below the image's floor.
  </Accordion>

  <Accordion title="The instance says running but nothing answers" icon="activity">
    `vm_state` is what you asked for; `power_state` is what was observed. A
    `running` instance whose `power_state` is `shutdown`, `crashed` or
    `nostate` has not come up.

    Read the [console output](#console-access) next — it works on a guest that
    never reached the network, which is precisely the case SSH cannot
    diagnose.
  </Accordion>

  <Accordion title="My cloud-init never ran" icon="file-code">
    Two causes, both silent.

    Your `user_data` has to decode to a document starting with
    `#cloud-config`. A shell script is not merged and is not executed — put
    the commands in `runcmd`.

    Or the merge replaced something. Top-level keys replace wholesale, so a
    `users:` block of your own removes the default `basaltic` user and the
    authorized keys inside it, which reads from outside as "the instance
    booted and I cannot log in".
  </Accordion>

  <Accordion title="I cannot SSH in" icon="key-round">
    The default login user is **`basaltic`**, not `root` and not the image's
    own default — password login and root login are both disabled in the base
    configuration.

    Check that `key_names` named a keypair that existed at launch, and that the
    NIC's security group admits your source on port 22. Confirm the address
    from `GET /v1/instances/{instance_id}/nics` rather than from `public_ip`,
    which only reports the primary interface.
  </Accordion>

  <Accordion title="Resize is refused with a capacity error" icon="cpu">
    An instance does not move hosts when it resizes, so the growth has to fit
    where it already is. A `dedicated` flavor needs whole free threads, not
    merely an undersubscribed host.

    The way around it is to launch a new instance on the target flavor — the
    scheduler is free to place that one anywhere — and move the workload, or
    to reinstall onto a fresh instance and reattach the data volumes.
  </Accordion>

  <Accordion title="A 409 on start, stop or reboot" icon="circle-x">
    Each action accepts a narrow set of states: start needs `stopped` or
    `error`, stop and reboot need `running`, resize and reinstall need
    `stopped`. Anything mid-transition — `pending`, `building`, `stopping`,
    `rebooting`, `deleting` — is refused until it settles. Poll `vm_state` and
    retry.
  </Accordion>

  <Accordion title="An operation on an instance answers 404 but I can see it" icon="lock">
    Check `managed_by`. An instance owned by a load balancer or a database
    cluster is read-only through this API, and every mutation answers `404`
    rather than `403` — the customer surface does not admit it is actionable.

    A `403` is the other case, and a different one: the instance is yours and
    the IAM check on that specific action failed. Actions are per-operation, so
    a principal may read an instance and still not be allowed to stop it.
  </Accordion>
</AccordionGroup>

## Next

<CardGroup cols={2}>
  <Card title="Images and keypairs" icon="disc" href="/compute/images">
    Importing an image, how a name moves between builds, and what the listing
    hides.
  </Card>

  <Card title="Instance pools" icon="layers" href="/compute/instance-pools">
    A managed set of identical instances, with rolling replacement and a
    shared public address.
  </Card>

  <Card title="Roles and instance identity" icon="shield" href="/iam/roles">
    Trust policies, `iam:PassRole`, and credentials from the metadata service.
  </Card>

  <Card title="API reference" icon="code" href="/api-reference/introduction">
    Every compute operation, with request and response schemas.
  </Card>
</CardGroup>
