Skip to content
DocsAll docs
Console

Snapshots API

Lists the API routes that show and delete volumes, their lineage of snapshots, and their named versions.

Last updated

On this page

What a snapshot freezes

A snapshot records the definition of its source, and a restore defaults to it. A workspace snapshot freezes image, packages, locks, and resources; an environment snapshot freezes services. The environment a desk connects to is not frozen.

Sync points, which a clone cuts from its source, are not snapshots. They are never listed, never a restore target, and cannot be deleted by hand.

How long a snapshot lives

Every snapshot expires. A request to take a snapshot chooses when:

  • Absent ttl_secs and never: the region's default, 30 days unless an admin changed it.
  • ttl_secs: an integer from 3600 (one hour) to 31536000 (one year). Outside that is 422.
  • never: true: no expiry. Sending never with ttl_secs is 400.

The response returns expires_at, null for never. Only the versions routes of a named volume return it later.

Using a snapshot extends it. Restoring or cloning from it, or starting a capsule from it, moves its expiry to a full default life from now, if that is later. It never shortens an expiry and never touches a snapshot that never expires.

Every ten minutes the platform deletes the snapshots past their time, by the rules of a delete by hand. A snapshot that a running workspace or environment stands on is kept and tried again. Deleting the last snapshot of a detached volume deletes the volume.

GET /v1/volumes

The volumes you can read that have at least one snapshot, one row each, sorted by id. A volume with only sync points is not listed. Without owner, the list holds your personal volumes and those of every team you belong to. A device workspace has no volume and is not listed.

QueryMeaning
kindworkspace or environment: keep one kind
ownerone owner (you, or a team you belong to). Your handle keeps your personal volumes. A team keeps the team's volumes, your workspaces in that team included. It never widens your set

Response: 200, an array of volume rows.

json
[  {    "id": "env-1f2e3d4c5b6a7988",    "name": "go-base",    "latest_version": 2,    "kind": "environment",    "volume": "env-1f2e3d4c5b6a7988",    "display_name": "acme-dev",    "deleted": false,    "latest_ms": 1760779800000,    "snapshots": 3,    "last_push_at": "2026-10-18T09:30:00Z",    "usedBytes": 4194304000,    "usedAt": "2026-10-18T09:35:00Z"  }]
FieldTypeMeaning
idstringthe volume id, the id of the workspace or environment it was cut from. Use it as {key} below
namestring or nullthe name its first push gave it, or null for a volume never named
latest_versioninteger or nullthe highest version a push has numbered; null while the volume is unnamed
kindstringworkspace or environment
volumestringthe same as id
display_namestringwhat the source is called; once it is gone, the name its newest snapshot recorded, else the volume name
deletedbooleanthe workspace or environment is gone; its snapshots are not. false when the cluster could not be asked
latest_msinteger or nullepoch milliseconds of the newest record on the volume, sync points included; approximate
snapshotsintegerhow many snapshots, in any phase but error
last_push_atstring or nullRFC 3339; when the newest snapshot became ready; null while the only one is still being cut
used_byarrayEach live workspace or environment on the volume as {kind, name, id}; the one named like the volume comes first
usedBytesinteger or nullwhat the volume occupies on disk, as its node last measured it; null until a node has
usedAtstring or nullRFC 3339; when that measurement was taken. The number is as of then, not live

Errors:

  • 403 — a desk tool token asking for an owner outside its own team.
  • 404 — ?owner names an owner that you cannot read.

GET /v1/volumes/{key}/history

The snapshots of one volume, newest first.

Response: 200, an array of snapshot rows.

json
[  {    "id": "env-1f2e3d4c5b6a7988-8a7b6c91",    "version": 2,    "state": {      "kind": "environment",      "services": [        { "name": "db", "image": "postgres:16@sha256:5b1c…", "command": [], "env": { "POSTGRES_PASSWORD": "dev" }, "mounts": [{ "folder": "pgdata", "path": "/var/lib/postgresql/data" }], "ports": [5432] }      ]    },    "lineage": [],    "region": "",    "message": "before the schema change",    "createdAt": "2026-10-18T09:30:00Z",    "parent": "env-1f2e3d4c5b6a7988-3c2d1e4f",    "phase": "ready"  }]
FieldTypeMeaning
idstringthe snapshot id; what restore, clone, and delete take
versioninteger or nullthe number its push took under the volume name; null for an unnamed volume
stateobject or nullwhat was frozen, tagged by kind. {"kind": "workspace", "image", "packages", "locks", "resources"} or {"kind": "environment", "services"}. null for an old snapshot that froze nothing
lineagearrayalways []; kept for older clients
regionstringalways ""; kept for older clients
messagestring or nullthe note from the snapshot
createdAtstring or nullRFC 3339
parentstring or nullthe snapshot this one followed
phasestringpending, working while it is being cut, ready, or error

Sync points are not listed. Rows in error are listed and do not count in a volume's snapshots.

Query: include=working changes the response to {"history": [...], "working": [...]}. Without it, the response stays the bare array.

Each row of history then has working_copies, an array of the names of the workspaces and environments whose head is that snapshot. Each working entry shows one live workspace or environment on the volume.

FieldTypeMeaning
kindstringworkspace or environment
namestringthe name of the working copy
idstringthe id of the working copy
statestringrunning or stopped
basestring or nullthe snapshot the working copy was built from. For a clone, the newest push of the source at the time of the clone
headstring or nullthe latest push of the working copy

Errors:

  • 400 — invalid name: {key} is not a plain name.
  • 404 — the volume is unknown, has no snapshots, or is not yours.

GET /v1/volumes/{key}/refs

The newest push of the volume by time: the snapshot a clone or restore grafts onto by default. There is one ref, latest. It is not a branch tip, because clones fork the lineage.

Response: 200. A volume with no snapshot, and one you cannot read, both answer {"latest": null}, not 404.

json
{ "latest": "env-1f2e3d4c5b6a7988-8a7b6c91" }

Errors:

  • 400 — invalid name.

DELETE /v1/volumes/{key}/snapshots/{snapshot}

Deletes one snapshot. If it was the last snapshot of a volume that no workspace or environment is on any more, the volume is deleted with it. Takes no body.

Response: 204 with an empty body.

Errors:

  • 400 — invalid name.
  • 404 — the volume is not yours or has no snapshots, or the snapshot is not on it.
  • 409 — a sync point cannot be deleted by hand; this snapshot is the base of {kind} {display}: a workspace or environment, of any owner, was created from it; this snapshot is where {kind} {display} stands now (its latest push): it is that parent's newest push; version {n} of {name} is used by {readers}, see Readers.
  • 503 — the cluster could not be reached: the API cannot check whether anything is running on the volume, so nothing is deleted. pipelines cannot be read right now; retry: the API cannot read the readers.

DELETE /v1/volumes/{key}

Deletes a detached volume and every snapshot on it. Takes no body.

Response: 204 with an empty body.

Errors:

  • 400 — invalid name.
  • 404 — the volume is not yours, or does not exist.
  • 409 — the volume still has a workspace or environment; this volume also holds snapshots owned by someone else; delete your own snapshots instead; volume {name} is used by {readers}, see Readers.
  • 503 — the cluster could not be reached; pipelines cannot be read right now; retry.

Names and versions

A volume gets a name on its first push. The name is unique among the volumes of one owner, a person or a team. Each push of a named volume is a version, numbered 1, 2, 3, and so on. A number is never reused, so a deleted version leaves a gap.

The three routes below read a volume by name. In them, {key} is the owner: your handle, or a team you belong to. The same position holds a volume id in the routes above.

A volume of another owner answers 404, the same as a volume that does not exist. This holds for a name and for an id.

A reference is name, name@N, or a snapshot id. name is the newest ready version. name@N is version N, written without a leading zero. Restore, clone, and from_snapshot take a reference.

GET /v1/volumes/{key}/{name}

One named volume.

Response: 200.

json
{  "id": "ws-0a1b2c3d4e5f6a7b",  "name": "go-base",  "owner": "acme",  "kind": "workspace",  "latest_version": 4,  "version_count": 3,  "deleted": false,  "used_bytes": 4194304000,  "used_at": "2026-10-18T09:35:00Z"}
FieldTypeMeaning
idstringthe volume id
namestringthe volume name
ownerstring{key}, in lowercase
kindstringworkspace or environment
latest_versioninteger or nullthe highest number a push took; null before the first numbered push
version_countintegerhow many versions exist now
deletedbooleanno workspace or environment uses the volume
used_bytes, used_atinteger or null, string or nullas in GET /v1/volumes

Errors:

  • 404 — volume {name} does not exist: the name is unknown, or the volume is not yours.

GET /v1/volumes/{key}/{name}/versions

The versions of the volume, newest first.

Response: 200, an array of version rows.

json
[  {    "version": 4,    "snapshot": "ws-0a1b2c3d4e5f6a7b-8a7b6c91",    "phase": "ready",    "parent_version": 3,    "message": null,    "created_at": "2026-10-18T09:30:00Z",    "ready_at": "2026-10-18T09:30:12Z",    "expires_at": "2026-11-17T09:30:00Z",    "saved_by": { "pipeline": "build", "run": "1760779800000-a1b2c3", "job": "base" }  }]
FieldTypeMeaning
versionintegerthe number of the push
snapshotstringthe snapshot id; what restore and clone take
phasestringpending, working, ready, or error
parent_versioninteger or nullthe version of the snapshot that this one followed. A version does not contain the version of another run that has the same parent
messagestring or nullthe note from the push
created_at, ready_atstring or nullRFC 3339
expires_atstring or nullRFC 3339; null when the version never expires
saved_byobject or null{pipeline, run, job} when a pipeline job saved it, else null

Sync points and unnumbered pushes are not listed. Versions in error are listed.

Errors:

  • 404 — volume {name} does not exist.

GET /v1/volumes/{key}/{name}/versions/{n}

One version row, as above.

Response: 200.

Errors:

  • 404 — volume {name} does not exist; volume {name} has no version {n}.

DELETE /v1/volumes/{key}/{name}

Deletes a named volume and every snapshot on it. The rules are those of DELETE /v1/volumes/{key}. Takes no body. When the volume is gone, the name is free again.

Response: 204 with an empty body.

Errors:

  • 404 — volume {name} does not exist.
  • 409 — the volume still has a workspace or environment; volume {name} is used by {readers}, see Readers.
  • 503 — the cluster could not be reached; pipelines cannot be read right now; retry.

DELETE /v1/volumes/{key}/{name}/versions/{n}

Deletes one version. Takes no body. Deleting the last version deletes the volume.

Response: 204 with an empty body.

Errors:

  • 404 — volume {name} does not exist; volume {name} has no version {n}.
  • 409 — a sync point cannot be deleted by hand; this snapshot is the base of {kind} {display}; this snapshot is where {kind} {display} stands now (its latest push); version {n} of {name} is used by {readers}, see Readers.
  • 503 — the cluster could not be reached; pipelines cannot be read right now; retry.

Readers

A delete is refused with 409 while a pipeline reads what it removes. The sentence lists the readers, for example volume go-base is used by pipeline build, run build #12.

  • A pipeline reads a volume when the latest version of its definition names it in fromSnapshot. A queued or running run reads it when its version does.
  • A pipeline that pins name@N blocks the delete of version N.
  • A pipeline that names the volume without a version blocks the delete of the last push of the volume.
  • A job that only has saveTo for the volume does not block a delete. The next run makes the volume again.
  • A version that a running or queued run started from is also kept.

Expiry follows the same rules. The platform skips a version that a reader needs and tries it again later.