Skip to main content
A volume is a block device that lives independently of any instance. You create it, attach it to an instance, and it keeps your data when that instance goes away. Volumes are regional: they are served from https://storage.<region>.basaltic.sh and can only attach to an instance in the same region.

Create a volume

The two tiers you can provision on, and what each one gives you.

Attach and grow

Single-attach, what survives instance deletion, and the one state an extend is accepted in.

Snapshots

Taking one, restoring from one, and why a volume with snapshots will not delete.

Snapshot policies

Schedules, the two retention bounds, and what retention will never touch.

Creating a volume

Creation is asynchronous. POST /v1/volumes answers 202 with the volume in creating; poll GET /v1/volumes/{volume_id} until status becomes available or error.
unique within your account
Matches ^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$. It appears in the CRN, so it has to be URL-safe. A duplicate name is 409 VOLUME_NAME_EXISTS.
1–16384
required
The ceiling matches what a single block device is capped at elsewhere in the industry. It is a per-volume sanity bound, not your quota — aggregate usage is gated separately.
ssd | nvme
required
See below. hdd exists as a stored tier but is rejected for new volumes: the spinning-disk pool is reserved for cold object storage.
Send an Idempotency-Key header. Retrying with the same key returns the original outcome instead of creating a second volume; reusing the key with a different body is rejected with 422.

Volume types

GET /v1/volume-types lists what you can provision on. The tier decides the IO ceiling your volume gets, and that ceiling scales with size rather than being flat — so the performance tracks what you paid for.

ssd

12 IOPS and 0.25 MiB/s per GB. Balanced cost against throughput.Floor 300 IOPS / 25 MiB/s, ceiling 16,000 IOPS / 250 MiB/s.

nvme

30 IOPS and 0.5 MiB/s per GB. Highest IOPS, lowest latency.Floor 1,000 IOPS / 50 MiB/s, ceiling 32,000 IOPS / 500 MiB/s.
Both tiers burst to 3× the sustained ceiling for 60 seconds. The burst budget refills while the volume sits under its baseline, so short spikes are absorbed without you tracking anything. A burst is clamped to the tier maximum too: bursting hides spikes, it does not hand a small volume more throughput than the largest one can sustain.
The floors exist so a small volume is still usable, and the ceilings exist so one large volume cannot consume a meaningful fraction of the tier on its own. A single guest issuing enough concurrent IO can otherwise reach a double-digit percentage of everything the tier serves.
The type is fixed at creation, and there is no call that changes it. Moving to a different tier means creating a volume of the type you want and copying the data across from inside the guest.

Attaching to an instance

Attachment is a compute operation — the device binding, the guest hot-plug and the boot order all live on the instance side:
The volume moves from available to in_use, and detaching puts it back to available. See compute for the device slots, mount options and the detach call.
A volume attaches to one instance at a time. There is no multi-attach: the binding is unique per volume, so a second attach is refused rather than handing two guests the same block device.

What happens when the instance is deleted

Each attachment carries a delete_on_termination flag, and the default differs by how the volume got there: PATCH /v1/instances/{instance_id}/volumes/{volume_id} changes the flag on an existing attachment. Set it deliberately on anything holding data you care about — the boot volume default is the one that deletes. The boot disk itself cannot be detached: unplugging it would take the guest’s root filesystem with it, so the call is refused. Everything a boot volume cannot do — extend included — follows from that.

Growing a volume

The call answers 202 with the volume in extending and returns to available at the new size. new_size_gb must be strictly greater than the current size; anything else is 400 VOLUME_SIZE_INVALID.
A volume cannot shrink. There is no call that reduces size_gb, and the quota you committed at the larger size stays committed. Grow in the steps you actually need.
Extend only accepts a detached volume. An extend against a volume in in_use is 409 VOLUME_NOT_AVAILABLE. The resize itself does not need the volume offline — this is an API-level gate on the volume’s state machine, not a storage limitation — but today it means: detach, extend, re-attach.
A boot volume cannot be extended. It is permanently in_use — detaching the boot disk is refused, because it would take the guest’s root filesystem with it — so an extend can never be accepted on one. Size the boot volume at instance creation, or keep growable data on a separate volume.
Growing the volume does not grow the filesystem inside it. Once the volume is bigger the guest sees the new size on its next device rescan, and expanding the partition and filesystem is yours to do in the guest.

Deleting a volume

DELETE /v1/volumes/{volume_id} answers 202, flips the row to deleting and tears the volume down asynchronously. Poll until the volume disappears. Two refusals to know about:
The volume is attached. Detach it from the instance first. Delete is also refused during the transient states — creating, extending, deleting — so a second operation cannot race the first.
A volume that still has snapshots will not delete, because those snapshots are children of the volume’s data and removing the parent underneath them is not possible. Delete the snapshots first, then the volume.This is a clean refusal rather than a partial teardown, so nothing is lost by trying.
Deleting a volume also removes its snapshot policy — the schedule has nothing left to snapshot. Your quota is released once the teardown finishes, not when the call is accepted, so capacity and your reported usage stay aligned while a delete is in flight.

Snapshots

A snapshot is a point-in-time copy of one volume:
1

Take it

Allowed against a volume in available or in_use — you do not have to detach to snapshot. The snapshot is created asynchronously (202, status creating).
2

Wait for available

Poll GET /v1/snapshots/{snapshot_id}. available means the snapshot is complete and can be restored from; error puts the reason in error_message.
A snapshot name is unique per volume, so nightly on two different volumes is fine. size_gb is the volume’s size frozen at the moment the snapshot was taken — the volume may have been extended since, so do not read it as the volume’s current size, and do not read it as the space the snapshot occupies.
Snapshotting an attached volume captures the device as it is at that instant, including anything the guest has buffered but not yet flushed. For a database or anything else with in-memory state, quiesce or flush inside the guest before taking the snapshot if you need it to be application-consistent.

Restoring from a snapshot

Restoring means creating a new volume from the snapshot. There is no in-place rollback:
The snapshot has to be available, and size_gb must be at least the snapshot’s frozen size — you can restore onto a larger volume, never a smaller one. The restored volume records source_snapshot_id, which is also what stops retention from reaping a snapshot something still depends on.

Snapshot policies

A policy is a schedule attached to one volume: take a snapshot every interval_minutes, then keep at most retention_count of the snapshots that policy created.
one policy per volume
required
Attaching a second policy to the same volume is a 409. Update the existing one instead.
1–43200
required
A minimum gap, not an exact cadence. A snapshot lands at or after the interval and never before, and can land a minute or two late when the scheduling pass is busy. The floor is one minute because that is how often the pass evaluates schedules; the ceiling is 30 days.
1–256
required
How many of this policy’s snapshots to keep. When a run takes the count past this, the oldest go first.
0–3650, default 0
An optional age bound applied on top of the count: a snapshot outside either window is reaped. 0 means no age bound.
The first snapshot lands one interval from now. Attaching a schedule is not itself a request for a snapshot — use POST /v1/snapshots if you want one immediately.
Scheduled snapshots are named <policy>-<UTC timestamp>, for example nightly-20260115T000000Z, and they inherit the policy’s tags so a listing tells you which schedule produced them without a second lookup.

What retention will never delete

A schedule is also an automatic deleter, so the limits on what it can remove matter more than the limits on what it keeps:

Snapshots you took by hand

Retention only matches snapshots carrying this policy’s snapshot_policy_id. A snapshot a person created has none and is never a candidate, whatever tags it has.

Snapshots something depends on

A snapshot a volume was created from — including a restore still running — is skipped and re-examined on a later run. It becomes reapable once the dependent volume is gone.

The single newest snapshot

The newest snapshot is exempt from the age bound. A volume that could not be snapshotted for longer than the window never loses its whole history that way.

Anything, while paused

enabled: false pauses the entire policy — no snapshots taken and none deleted. A paused schedule that kept reaping would delete history while you were looking at it.
Resuming a paused policy applies the retention window again on its next run. If you lowered retention_count while it was paused, everything now outside the window is reaped on that run.

Reading a schedule’s state

GET /v1/snapshot-policies/{policy_id} returns where the policy is in its cycle: last_error is the field to check on a schedule that has stopped producing snapshots — it carries reasons like an exhausted snapshots quota, or a volume that was mid-extend when the window came around.
A missed window costs one snapshot, not one per window missed. next_run_at is re-stamped to now + interval_minutes each time the policy fires, never to previous + interval, so a schedule that could not run for six hours takes a single snapshot when it resumes rather than a catch-up burst. Changing interval_minutes re-bases the next run off now too, so shortening a daily schedule to hourly takes effect within the hour.
Deleting a policy detaches the schedule and keeps every snapshot it already took — they become ordinary snapshots you own outright and are never reaped again. Delete the snapshots themselves if that is what you meant.

Volume statuses

Snapshots use a shorter set: creating, available, deleting, error.

Quotas

Four storage counters, all scoped to your organization within a region:
Quota is consumed for the lifetime of a row, not just while it is healthy. A volume parked in error still counts against volumes and volume_storage_gb — which mirrors what you can see — so delete failed volumes rather than leaving them.
An extend reserves the delta before it starts, so an extend that would cross volume_storage_gb is refused up front rather than half-applied.

Naming, CRNs and tag-conditioned access

Volume, snapshot and snapshot-policy CRNs are built from the name, not the id, and carry the region and your account:
Because the CRN is name-based, an IAM policy can gate a naming convention directly — crn:storage:*:my-account:volume/prod-* covers both the volumes that exist and the ones that will. All three resources accept a tags map, and the tag keys are policy context. basalt:ResourceTag/<key> fences access to volumes already labelled a certain way; basalt:RequestTag/<key> fences what a caller may label a volume as, which is what makes a create authorizable before the volume exists:

Listing and pagination

Volumes, snapshots and policies are listed newest-first and paginated with an opaque cursor. Pass marker from the previous page’s meta to get the next one:
Do not infer the end of a list from a short page. Read meta.has_more, and keep passing meta.marker until it is false — a page can come back smaller than the limit you asked for.
Volume and snapshot listings also take name (case-insensitive substring) and status; snapshots take volume_id to narrow to one volume, and policies take volume_id and enabled.

Troubleshooting

hdd is a real tier but not a creatable one — its pool is reserved for cold object storage. Use ssd or nvme. GET /v1/volume-types always lists exactly what you may create on.
The volume is not in available. Attached volumes (in_use) are the common case — detach, extend, re-attach. A volume mid-creating, extending or deleting also refuses, and settles on its own.A boot volume can never reach available while its instance exists, so an extend on one has no path to success. Move the data onto a separate volume, or rebuild the instance with a larger boot volume.
List them with GET /v1/snapshots?volume_id=<id> and delete them first. If a snapshot policy is still attached it may be creating new ones behind you — delete the policy, or pause it with enabled: false, before clearing the backlog.
Read last_error on the policy. The usual causes are an exhausted snapshots quota — clear space by lowering retention_count or deleting old snapshots — and a volume that was in a transient state when the window came around, which resolves itself on the next run.A policy with enabled: false produces nothing at all, and next_run_at tells you whether the schedule believes it is due.
A restore clones the snapshot and then grows to the size_gb you asked for, so it is never smaller than the snapshot’s frozen size. The filesystem inside it is still the size it was when the snapshot was taken — expand it in the guest.
error_message carries the last failure. The row keeps its quota while it exists, so delete a volume or snapshot you do not intend to retry.

Next

Object storage

Buckets, and the S3-compatible endpoint that serves them.

Compute

Attaching volumes, boot volumes and device slots.

Writing policies

Tag conditions and CRN patterns for storage resources.

API reference

Every storage operation, with request and response schemas.