Skip to content

API overview

The REST API is what the web console and the CLI use. Everything they can do is available to your own scripts. The reference is split into pages:

https://<any node>:7080/v1

Every node serves the API on port 7080 with the same TLS certificate as the console. In a cluster, any node accepts any request and passes it on to the node that holds the resource. The certificate is self-signed; download it from https://<node>:7080/tls.pem and trust it, or pass -k to curl while you try things out.

Requests and responses are JSON (Content-Type: application/json). Field names are snake_case, times are RFC 3339 in UTC, sizes are bytes, and durations are Go-style strings such as 72h or 30m. A request body may be at most 2 MiB, and a field the API does not know is rejected with 400 invalid_request.

Names of branches and bases use lowercase letters, digits and single hyphens, start with a letter, and have at most 32 characters (feature-x). A runtime is written engine@version, for example postgres@18.6.

Three paths are outside /v1 and need no login: GET /healthz (always ok), GET /readyz (503 until the node is initialized and has a cluster leader) and GET /tls.pem.

Send an API token in the Authorization header:

Terminal window
curl -sk -H "Authorization: Bearer dbb_..." https://node1:7080/v1/branches

There are two kinds of token. Both start with dbb_, are shown once when they are created, and only their hash is stored.

Kind Created by Acts as Scope
Service token An admin: Settings → Service tokens, db-branch token create, or POST /v1/tokens The token itself (for CI and integrations) branches (default) or full
Personal token Any signed-in person: Account → MCP access, or POST /v1/account/tokens The person who created it branches

The scope limits what a token can do:

  • branches may create, use and delete branches and read most other resources. It cannot change bases, runtimes, settings or tokens, and it cannot read settings, tokens, route users, backups or a base’s refresh source, because those can hold secrets.
  • full may do everything an admin can.

A token that is invalid, expired or revoked gets 401 unauthenticated. A token cannot create other tokens.

The console does not use tokens. It signs in with POST /v1/auth/login and receives a dbb_session cookie (HttpOnly, Secure, SameSite=Strict). Every request other than GET must then carry the session’s X-CSRF-Token header, whose value is csrf_token in the login response, or it fails with 403 csrf_failed. Scripts should use tokens instead. The CSRF check does not apply to requests with a bearer token.

Without any credentials, only a few calls work: creating a branch from a public snapshot, reading and connecting to an open branch, reading an operation, and listing public bases. See Branches.

The db-branch command talks to the agent over a local UNIX socket instead. Whoever can open the socket (root or the db-branch group) acts as an admin, and a few calls work only there: POST /v1/auth/reset, POST /v1/cluster/token and POST /v1/backups/restore return 403 socket_only over HTTPS.

Which calls are allowed is decided per request by an action name (branch.create, base.delete, and so on), shown for each endpoint in this reference. The built-in login is a single admin, who may do everything. Paid modules such as auth add roles and map them to these actions.

A failed request returns a status code and a body of this shape:

{
"error": {
"code": "not_found",
"message": "branch feature-x: not found"
}
}

Branch on code. The message is for people and may change.

Status code Meaning
400 invalid_request Malformed JSON, an unknown or missing field, or an invalid name, runtime, TTL or setting
400 weak_password The new password is too weak
401 unauthenticated No session or token, or it has expired
401 invalid_credentials Wrong user name or password
403 forbidden The action is not allowed for this caller or token scope
403 csrf_failed Cookie session without a matching X-CSRF-Token
403 socket_only Works only on a node’s local socket
404 not_found No such resource
404 runtime_not_installed The runtime is not installed on that node
409 already_exists The name is taken
409 conflict The resource is not in a state that allows this (for example resetting a branch that is not ready)
409 in_use Something still uses it (a base with branches, a runtime in use)
409 unclean_shutdown The base server did not stop cleanly, so no snapshot was taken
409 other_node The work belongs to another node
409 rollout_running A cluster update is already in progress
422 runtime_unavailable The runtime version is not offered
422 query_failed The SQL statement failed
429 rate_limited Too many login attempts; see the Retry-After header
503 no_leader The cluster has no leader at the moment; retry after the Retry-After header
503 no_capacity No node has room for the branch
503 cluster_upgrading A cluster update is changing the protocol
503 not_initialized The node has not run db-branch init, or the feature is not set up on it
503 modules_pending This node does not yet run every module the cluster requires
500 internal Unexpected error

Some endpoints add their own codes, noted where they appear.

List endpoints return {"items": [...], "next_cursor": null}. All items come in one response; there are no limit or cursor parameters yet, and next_cursor is always null. Branches and bases are sorted by name, operations newest first.

Creating a branch, resetting or deleting one, creating or snapshotting a base, loading a dump, refreshing a base and installing a runtime take from seconds to minutes. These calls return 202 Accepted at once, with a Location header and the operation:

{
"id": "op_01JC8Z6Q4M7V3X2N9K5R8T1W0B",
"kind": "branch.create",
"target": { "resource": "branch", "name": "feature-x" },
"node": "node1",
"state": "running",
"phase": "cloning",
"created_at": "2026-11-09T12:00:00Z",
"updated_at": "2026-11-09T12:00:03Z",
"seq": 1762689600000000000
}

state is running, succeeded or failed. While it runs, phase says where it is. When it fails, error holds the reason; finished_at is set once it ends. The resource itself (the branch, for example) is visible right away with a status, so you can also poll it.

Poll GET /v1/operations/{id} until state is no longer running, or follow the event stream. An operation keeps running if the client disconnects. The newest 200 finished operations are kept.

Quick changes, such as starting a base or extending a branch, finish within the request and return 200 or 204.

GET /v1/events is a Server-Sent Events stream of changes to branches, bases, operations, runtimes, nodes, settings and modules, so a client does not have to poll.

Terminal window
curl -skN -H "Authorization: Bearer dbb_..." https://node1:7080/v1/events
id: 18343
event: branch.updated
data: {"name":"feature-x","status":"ready", ...}
id: 18344
event: operation.updated
data: {"id":"op_...","state":"succeeded", ...}
Event Data
branch.updated, base.updated The same object GET returns (a base without the can field)
branch.deleted, base.deleted {"name": "..."}
operation.updated The operation
runtime.updated, runtime.deleted A runtime object, or {"runtime": "..."} when deleted
node.updated A node changed; read GET /v1/cluster
setting.updated The settings scope that changed
module.updated, update.updated A change in modules or in a cluster update; fetch the resource again

Each event has an id. After a dropped connection, reconnect with Last-Event-ID: <last id> to receive what you missed. If that is too old to replay, the server sends event: resync first and the client should reload the lists. A comment line (: ping) arrives every 15 seconds. A client that reads too slowly is disconnected and should reconnect. There is no way to filter by event type yet.

The design notes mention a few things the API does not do yet: Idempotency-Key headers, ETag and If-Match, cursor pagination, and cancelling an operation. Do not rely on them.

Terminal window
export DBB=https://node1:7080/v1
export AUTH="Authorization: Bearer $DBB_TOKEN"
# 1. Create the branch. The answer is the operation.
OP=$(curl -sk -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"name":"feature-x","base":"prod","ttl":"72h"}' \
$DBB/branches | jq -r .id)
# 2. Wait until it finishes.
while :; do
STATE=$(curl -sk -H "$AUTH" $DBB/operations/$OP | jq -r .state)
[ "$STATE" != running ] && break
sleep 1
done
echo "operation: $STATE"
# 3. Get the connection URL (includes the password).
curl -sk -H "$AUTH" $DBB/branches/feature-x/dsn
# {"dsn":"postgres://feature-x:...@node1:41203/postgres"}
# 4. Delete it when done.
curl -sk -X DELETE -H "$AUTH" $DBB/branches/feature-x

A branches token is enough for all four steps.