Snapshots
The golden-snapshot catalog: registering immutable versions, sharing, and deleting golden lineages. See Goldens and imports for the deeper mechanics and the exact 409 wording this page documents from the code.
Registering a version directly (POST /v1/snapshots/versions) is a
lower-level primitive than adj import run — the import pipeline (see
Sources & imports) calls it internally after a
successful dump. Most admins never call it directly.
POST /v1/snapshots/versions — register a snapshot version
Auth: admin.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | The golden lineage name (created on first use). |
substrate |
string | yes | e.g. "postgres". Must match the substrate of any existing version of the same name. |
source_ref |
string | no | Free-form provenance note. |
vsc_handle |
string | yes | The volume-snapshot-controller handle the data-plane operator clones from. |
size_bytes |
integer | no | Reported size, for adj org usage / adj catalog ls display. |
$ curl -s https://api.stagdb.com/v1/snapshots/versions -X POST -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name":"prod-pg16","substrate":"postgres","vsc_handle":"vsc-prod-pg16-v3","size_bytes":45097156608}'
Response 201 Created
{"snapshot_id": "5f2a9c11-3d7e-4b8a-9c1d-2e3f4a5b6c7d", "version": 3}
version is the next sequential integer for this lineage (1 on first
registration). Versions are immutable — nothing in this API ever mutates
an existing version's data.
| Status | When |
|---|---|
| 400 | name empty or substrate invalid — wire body is the generic {"error": "invalid input"}. |
| 403 | Caller is not an admin. |
| 409 | substrate doesn't match the existing lineage's substrate — wire body is the generic {"error": "conflict"}. |
GET /v1/snapshots — list visible goldens
Auth: bearer.
$ curl -s https://api.stagdb.com/v1/snapshots -H "Authorization: Bearer $TOKEN"
Response 200 OK
{
"snapshots": [
{
"id": "5f2a9c11-3d7e-4b8a-9c1d-2e3f4a5b6c7d",
"name": "prod-pg16",
"substrate": "postgres",
"share_scope": "org",
"latest_version": 3,
"created_at": "2026-06-10T08:00:00Z"
}
]
}
share_scope is "admin" (visible only to admins) or "org" (shared,
visible to everyone). Non-admin callers only ever see "org"-scoped
goldens — admin-private ones are filtered out of this response entirely for
them, not just hidden in the UI. latest_version is the newest ready
version (0 if none yet).
| Status | When |
|---|---|
| 401 | Missing/invalid/expired bearer token. |
POST /v1/snapshots/{id}/share — set sharing
Auth: admin.
Request body
| Field | Type | Required |
|---|---|---|
shared |
boolean | yes |
$ curl -s https://api.stagdb.com/v1/snapshots/5f2a9c11-3d7e-4b8a-9c1d-2e3f4a5b6c7d/share -X POST \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"shared":true}'
shared: true moves the golden to share_scope: "org" (visible to
developers); shared: false reverts it to "admin". Existing clones are
unaffected either way — sharing only changes future visibility.
Response: 204 No Content.
| Status | When |
|---|---|
| 400 | {id} not a valid UUID. |
| 403 | Caller is not an admin. |
| 404 | No snapshot with that id in this org. |
DELETE /v1/snapshots/{id} — delete a golden
Auth: admin.
Query parameter
| Param | Notes |
|---|---|
force |
?force=true cascades the delete through live child instances. Omit (or any other value) for the safe, blocked-by-default behavior. |
Without ?force=true — blocked if clones exist
$ curl -s https://api.stagdb.com/v1/snapshots/5f2a9c11-3d7e-4b8a-9c1d-2e3f4a5b6c7d -X DELETE \
-H "Authorization: Bearer $TOKEN"
If live instances were cloned from this golden, the delete is refused:
{
"error": "golden has live child instances; re-run with --force",
"children": [
{"id": "7a1e9c3d-5b2f-4c8a-9d0e-2f3a4b5c6d7e", "owner": "3e6f0a2b-7c8d-4e1f-9a2b-3c4d5e6f7a8b", "phase": "Ready"}
]
}
children lists every live clone as {id, owner, phase} so the caller (the
CLI prints this as a table) knows exactly what --force would destroy.
Nothing is deleted in this response — the golden and its clones are
untouched. 409 Conflict.
With ?force=true — cascades clones, never import history
$ curl -s "https://api.stagdb.com/v1/snapshots/5f2a9c11-3d7e-4b8a-9c1d-2e3f4a5b6c7d?force=true" -X DELETE \
-H "Authorization: Bearer $TOKEN" -o /dev/null -w '%{http_code}\n'
202
force=true destroys the golden and every live clone of it,
asynchronously. One thing it never cascades: import history. If the
golden lineage was built by adj import run (or has a clone pool pinning
it), the delete is refused outright — --force does not bypass this,
because it's a foreign-key restrict, not a live-children check:
{"error": "golden is still referenced by an import record or clone pool"}
409 Conflict. This is a different 409 from the live-children one
above — same status code, different body, different meaning. Remove the pool
first (DELETE /v1/pools/{id}) if that's what's blocking it; an import
record blocking it means the lineage is meant to stay, and per-version delete
isn't exposed by this API.
Response on success: 202 Accepted, empty body — the CR delete and clone
teardown proceed asynchronously on the data plane.
| Status | When |
|---|---|
| 400 | {id} not a valid UUID. |
| 403 | Caller is not an admin. |
| 404 | No snapshot with that id in this org. |
| 409 | Live child instances exist and force was not true (body includes children); or force=true was passed but an import record or clone pool still references the golden ("golden is still referenced by an import record or clone pool" — never bypassed by force). |
Next
- Sources & imports — how versions actually get built from a real database.
- Pools — pools pin a version and are one of the two things that block a force-delete.