Bases API
A base is the source a branch is cloned from. See the API overview for authentication, errors and operations. Calls for a base that lives on another node are passed on to that node automatically.
The base object
Section titled “The base object”{ "name": "prod", "description": "Masked production data, refreshed every Monday", "runtime": "postgres@18.6", "status": "ready", "status_reason": null, "node": "node1", "port": 40100, "created_at": "2026-11-01T09:00:00Z", "latest_snapshot": "s20261109", "snapshots": [ { "name": "s20261109", "created_at": "2026-11-09T03:00:00Z", "public": false } ], "replicas": { "s20261109": ["node2"] }, "replica_nodes": null, "holders": [ { "node": "node1", "state": "held", "origin": true }, { "node": "node2", "state": "held", "origin": false } ], "operation": null, "can": { "base.snapshot": true, "base.delete": true }}| Field | Meaning |
|---|---|
status |
provisioning, ready, maintenance or failed |
node |
The node the base lives on. Its branches start from here or from a replica |
snapshots[].public |
Whether anyone may create a branch from the snapshot without logging in |
snapshots[].mask |
Present on a snapshot made by masking: how it was made |
replicas |
For each snapshot, the other nodes that hold a copy |
replica_nodes |
The setting: null keeps a copy on every node, a list names the other nodes that keep one |
holders[].state |
held, pulling (copying now) or draining (giving up its copy) |
can |
Which base actions the caller may do on this base. Used by the console to show buttons |
GET /v1/bases
Section titled “GET /v1/bases”Action base.read. Lists bases, sorted by name. GET /v1/bases/{name} (same action) returns one.
GET /v1/public/bases
Section titled “GET /v1/public/bases”Action base.public. Lists the bases that have a public snapshot. Works without a login.
POST /v1/bases
Section titled “POST /v1/bases”Action base.create. Creates a base and takes its first snapshot. Answers 202, operation kind base.create.
| Field | Type | Meaning |
|---|---|---|
name |
string, required | Base name |
runtime |
string, required | engine@version, installed on the node |
dump |
string | A .sql or .sql.gz dump to load: an upload ID (upload:..., see below) or a path on the node. Omit for an empty base |
description |
string | Free text, up to 1000 characters |
node |
string | Node to create it on. The path in dump is read there. Default: the node you called |
replica_nodes |
array or null | null or absent: every node keeps a copy. []: only the base’s node. A list: those nodes as well |
Errors: 409 already_exists, 404 runtime_not_installed.
PATCH /v1/bases/{name}
Section titled “PATCH /v1/bases/{name}”Action base.describe. Changes the description. Answers 200 with the base.
{ "description": "Masked production data" }DELETE /v1/bases/{name}
Section titled “DELETE /v1/bases/{name}”Action base.delete. Answers 204. A base that branches still use returns 409 in_use.
POST /v1/bases/{name}/start and /stop
Section titled “POST /v1/bases/{name}/start and /stop”Actions base.start and base.stop. Start or stop the base’s database server. Answer 204.
POST /v1/bases/{name}/snapshots
Section titled “POST /v1/bases/{name}/snapshots”Action base.snapshot. Stops the base server cleanly, takes a new snapshot, starts it again, and copies the snapshot to the nodes that keep replicas. Answers 202, operation kind base.snapshot. If the server does not stop cleanly, the call fails with unclean_shutdown and no snapshot is taken.
PATCH /v1/bases/{name}/snapshots/{snapshot}
Section titled “PATCH /v1/bases/{name}/snapshots/{snapshot}”Action base.publish. Makes a snapshot public or private. Answers 200 with the base.
{ "public": true }DELETE /v1/bases/{name}/snapshots/{snapshot}
Section titled “DELETE /v1/bases/{name}/snapshots/{snapshot}”Action base.snapshot_delete. Answers 204. The latest snapshot, and snapshots that branches use, cannot be deleted (409 conflict for the latest, 409 in_use for the others). Older unused snapshots are also pruned automatically when the Prune old snapshots setting is on.
POST /v1/bases/{name}/mask
Section titled “POST /v1/bases/{name}/mask”Action base.mask. Writes a masked copy of another base into this (empty) base and snapshots it. The masking is done by a masking tool (a module such as Greenmask), which must be installed. Answers 202, operation kind base.mask.
| Field | Type | Meaning |
|---|---|---|
source |
string, required | Base to copy from |
snapshot |
string | Snapshot of the source. Default: its latest |
masker |
string, required | Name of the masking tool module, for example greenmask |
rules |
string | Masking rules, in the format the masking tool expects |
POST /v1/bases/{name}/sql
Section titled “POST /v1/bases/{name}/sql”Action base.sql. Runs SQL as the administrator on the base server (started if it is stopped) to change data before the next snapshot. Answers 204. A failing statement returns 422 query_failed.
{ "sql": "INSERT INTO shop.products (name) VALUES ('new item');" }POST /v1/bases/{name}/load
Section titled “POST /v1/bases/{name}/load”Action base.load. Loads a dump on top of the base’s current data. Answers 202, operation kind base.load.
| Field | Type | Meaning |
|---|---|---|
dump |
string, required | An upload ID or a path on the base’s node |
snapshot |
boolean | Take a snapshot after loading |
Existing branches are not affected.
Uploads
Section titled “Uploads”Dumps can be sent over the API instead of copying them to a node.
POST /v1/uploads
Section titled “POST /v1/uploads”Action base.upload. The request body is the file itself (not JSON). It needs a Content-Length header and the query parameters name (the file name) and, optionally, node. The upload is stored on that node, and the ID refers to it until it is used by POST /v1/bases or /load.
curl -sk -H "$AUTH" -H 'Content-Type: application/gzip' \ --data-binary @prod.sql.gz "$DBB/uploads?name=prod.sql.gz"# {"id":"upload:01J...","name":"prod.sql.gz","size":104857600,"node":"node1"}Errors: 411 length_required, 413 too_large (above the Largest dump upload setting), 507 no_space (not enough room on the node’s ZFS pool).
GET /v1/uploads/room
Section titled “GET /v1/uploads/room”Action base.upload. Returns how large an upload this node accepts now: {"node": "node1", "max_bytes": 21474836480, "reason": "limit"}. reason is limit (the setting) or pool (free space).
Scheduled refresh
Section titled “Scheduled refresh”A base can be refreshed from a source database on a schedule: dump the source, load it and take a snapshot. every must be at least 1h.
GET /v1/bases/{name}/refresh
Section titled “GET /v1/bases/{name}/refresh”Action base.refresh.read. Returns the schedule. 404 not_found if none is set. The source password is never returned.
{ "base": "prod", "engine": "postgres", "source": { "host": "db.internal", "port": 5432, "user": "reader", "password_set": true, "databases": ["app"] }, "every": "24h0m0s", "updated_at": "2026-11-09T12:00:00Z", "last_run": { "started_at": "2026-11-10T03:00:00Z", "finished_at": "2026-11-10T03:12:00Z", "ok": true, "error": null }, "next_run": "2026-11-11T03:00:00Z", "operation": null}PUT /v1/bases/{name}/refresh
Section titled “PUT /v1/bases/{name}/refresh”Action base.refresh. Sets or changes the schedule. Answers 200 with the schedule above. Without password, the stored password is kept.
{ "source": { "host": "db.internal", "port": 5432, "user": "reader", "password": "...", "databases": ["app", "billing"] }, "every": "24h"}DELETE /v1/bases/{name}/refresh
Section titled “DELETE /v1/bases/{name}/refresh”Action base.refresh. Turns the schedule off. Answers 204.
POST /v1/bases/{name}/refresh/run
Section titled “POST /v1/bases/{name}/refresh/run”Action base.refresh. Refreshes now. Answers 202, operation kind base.refresh.
Replicas
Section titled “Replicas”PUT /v1/bases/{name}/replicas
Section titled “PUT /v1/bases/{name}/replicas”Action base.replicas. Chooses which nodes keep a copy of the base. The body must contain replica_nodes: null for every node, [] for the base’s own node only, or a list of node names. Answers 200 with the base. Nodes that are dropped give up their copy once no branch on them uses the base.
{ "replica_nodes": ["node2"] }