Skip to main content
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 regionalcompute.sa-saopaulo-1.basaltic.sh. An instance, its image, its keypairs and its pools all live in one region and never cross into another.

Launch an instance

Flavors, images, interfaces, disks, keys and cloud-init — and the two fields whose absence quietly costs you a network.

Lifecycle

What start, stop, reboot, resize and reinstall each preserve, and what they destroy.

Disks and interfaces

Hot-plugging volumes and NICs, and which of them survive the instance.

Console access

The boot transcript, a screenshot, and an interactive serial session for when the network is gone.

Launching an instance

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

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.
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.
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.
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.
aggregate throughput
The instance’s network ceiling, in megabits/s, across all its interfaces. Absent means uncapped. A resize moves this with the flavor.
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:
"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.
Names resolve across your own images plus the public platform catalog, and always at the amd64 architecture. See Images and keypairs 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.
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.
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:
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.
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.
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.
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 walks through both.

The instance lifecycle

An instance carries two states and they answer different questions.

vm_state

What you asked for. It moves the moment the API accepts your call.

power_state

What the guest is actually doing, as last observed on the host.
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.
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.

Start, stop, reboot

Each is a 202 with an empty body — poll the instance to see the result.
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.
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.
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.

Resize

POST /v1/instances/{instance_id}/resize with a flavor_id changes vCPU and RAM, and nothing else. Disks are untouched, addresses are untouched, the guest’s data is untouched. Two things about it are worth knowing before you plan a resize window:
1

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

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

Kept

The instance id, name, CRN, IP addresses and MAC addresses, its network interfaces, its keypairs, its cloud-init seed, and every attached data volume.

Replaced

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

DELETE /v1/instances/{instance_id} 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

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.

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

Network interfaces

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.
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:
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.
The instance must be running or stopped; the device is hot-plugged, or appears at the next start for a stopped instance.
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.

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

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

Names, tags and metadata

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.
not supported
PATCH /v1/instances/{instance_id} edits description, metadata and tags. A name is fixed for the life of the instance.
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.
replaced, not merged
The map you PATCH becomes the whole set of your keys.
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.

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

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

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.
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 next — it works on a guest that never reached the network, which is precisely the case SSH cannot diagnose.
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”.
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.
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.
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.
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.

Next

Images and keypairs

Importing an image, how a name moves between builds, and what the listing hides.

Instance pools

A managed set of identical instances, with rolling replacement and a shared public address.

Roles and instance identity

Trust policies, iam:PassRole, and credentials from the metadata service.

API reference

Every compute operation, with request and response schemas.