Skip to main content
Version: 0.3.36

Branches API

Branch, tag and revision endpoints live under the management prefix and are addressed by tenant and repository. All requests need Authorization: Bearer <token>.

/api/management/repositories/{tenant}/{repo}/...

Branches

Create branch

POST /api/management/repositories/{tenant}/{repo}/branches
{
"name": "feature-xyz",
"upstream_branch": "main",
"from_revision": "1788719729588-0",
"created_by": "alice",
"protected": false,
"include_revision_history": true
}
FieldRequiredMeaning
nameyesBranch name
upstream_branchnoBranch to fork from and to compare against by default. With no from_revision, the fork happens at this branch's current head
from_revisionnoFork at this revision instead of the head
created_bynoActor recorded on the branch; default system
protectednoCreate the branch protected; default false
include_revision_historynoCopy the source's commit log in a background job; default true

With neither upstream_branch nor from_revision the branch is created empty with head 0-0. Returns 201 Created with the branch record:

{
"name": "feature-xyz",
"head": "1788719729588-0",
"created_at": "2026-09-06T18:35:41.856317Z",
"created_by": "alice",
"created_from": "1788719729588-0",
"upstream_branch": "main",
"protected": false,
"description": null
}

List branches

GET /api/management/repositories/{tenant}/{repo}/branches

Returns an array of branch records.

Get branch

GET /api/management/repositories/{tenant}/{repo}/branches/{name}

Returns the branch record, or 404 with code BRANCH_NOT_FOUND.

Delete branch

DELETE /api/management/repositories/{tenant}/{repo}/branches/{name}

Returns 204 No Content. A protected branch answers 403 with Cannot delete protected branch '{name}'.

Get and set head

GET /api/management/repositories/{tenant}/{repo}/branches/{name}/head
PUT /api/management/repositories/{tenant}/{repo}/branches/{name}/head

GET returns {"revision": "1788719765785-0"}. PUT takes {"revision": "1788719765785-0"} and moves the head to that revision unconditionally, forward or back; it returns 204. Use it for rollback. A protected branch returns 403.

Set upstream

PATCH /api/management/repositories/{tenant}/{repo}/branches/{name}/upstream

Body {"upstream_branch": "develop"}, or {"upstream_branch": null} to clear. Returns the updated branch record. The upstream must exist.

Compare branches

GET /api/management/repositories/{tenant}/{repo}/branches/{branch}/compare/{base_branch}
{ "ahead": 2, "behind": 1, "common_ancestor": "1788719729588-0" }

ahead counts commits on branch that base_branch lacks; behind the reverse.

Diff branches

Per-node changes of a branch relative to a base branch since they diverged. Cost scales with the size of the change, not the repository.

GET /api/management/repositories/{tenant}/{repo}/branches/{branch}/diff/{base_branch}
{
"common_ancestor": "1788719729588-0",
"added": [{ "node_id": "f29d5b20-...", "workspace": "content", "path": "/feature-only", "operation": "added" }],
"modified": [{ "node_id": "260584c1-...", "workspace": "content", "path": "/hello", "operation": "modified" }],
"deleted": []
}

operation is added, modified, reordered or deleted; reordered nodes are listed under modified. A change to a translation carries a translation_locale. path is null when only a tombstone remains.

Merge

POST /api/management/repositories/{tenant}/{repo}/branches/{target_branch}/merge
{
"source_branch": "feature-xyz",
"strategy": "ThreeWay",
"message": "Merge feature-xyz",
"actor": "alice"
}

All four fields are required. strategy is ThreeWay or FastForward.

{ "success": true, "revision": 1788719765785, "conflicts": [], "fast_forward": false, "nodes_changed": 2 }

revision is the millisecond part of the merge commit's revision. When a three-way merge finds conflicts, success is false, revision is null and conflicts lists them:

{
"node_id": "260584c1-...",
"path": "",
"conflict_type": "BothModified",
"base_properties": { "title": "Hello v3" },
"target_properties": { "title": "Title from main" },
"source_properties": { "title": "Title from feature" }
}

conflict_type is BothModified, BothAdded, DeletedBySourceModifiedByTarget or ModifiedBySourceDeletedByTarget. A fast-forward that is not possible returns a validation error; a protected target returns 403.

Resolve merge

POST /api/management/repositories/{tenant}/{repo}/branches/{target_branch}/resolve-merge
{
"source_branch": "feature-xyz",
"resolutions": [{
"node_id": "260584c1-...",
"resolution_type": "keep-theirs",
"resolved_properties": { "title": "Title from feature" },
"translation_locale": null
}],
"message": "Merge feature-xyz (resolved)",
"actor": "alice"
}

resolution_type is keep-ours, keep-theirs or manual; resolved_properties are the node's final properties. Returns the same merge result shape with success: true. A resolution for a node that was not changed on both branches is rejected.

Compare, diff, merge, resolve-merge and upstream require the RocksDB storage backend (the default).

Tags

Create tag

POST /api/management/repositories/{tenant}/{repo}/tags
{ "name": "v1.0", "revision": "1788719765785-0", "message": "First release", "created_by": "alice", "protected": false }

name and revision are required. Returns 201 with the tag record:

{ "name": "v1.0", "revision": "1788719765785-0", "created_at": "2026-09-06T18:36:05.935331Z",
"created_by": "alice", "message": "First release", "protected": false }

List, get, delete

GET    /api/management/repositories/{tenant}/{repo}/tags
GET /api/management/repositories/{tenant}/{repo}/tags/{name}
DELETE /api/management/repositories/{tenant}/{repo}/tags/{name}

Delete returns 204, or 404 when the tag does not exist. A protected tag cannot be deleted. Read content at a tag through the rev/{revision} node endpoints below.

Revisions

List revisions

GET /api/management/repositories/{tenant}/{repo}/revisions?limit=50&offset=0&branch=main&include_system=false

Returns commits newest first. branch keeps only commits made on that branch and at or below its head; include_system=true adds system commits.

{
"revisions": [{
"revision": "1788719765785-0",
"parent": "1788719744056-0",
"merge_parent": "1788719742050-0",
"branch": "main",
"timestamp": "2026-09-06T18:36:05.785549Z",
"actor": "alice",
"message": "Merge feature",
"is_system": false,
"changed_nodes": [{ "node_id": "260584c1-...", "workspace": "content", "operation": "modified" }],
"changed_node_types": [], "changed_archetypes": [], "changed_element_types": []
}],
"total": 1,
"has_more": false
}

Get revision and its changes

GET /api/management/repositories/{tenant}/{repo}/revisions/{revision}
GET /api/management/repositories/{tenant}/{repo}/revisions/{revision}/changes

The first returns one revision record; the second returns its node changes as [{ "node_id": "...", "operation": "added" }]. The revision must be the full timestamp-counter string, otherwise 400 with Invalid revision.

Reading content on a branch or at a revision

These are content endpoints, outside the management prefix. The branch is the second segment; head reads the branch head and rev/{revision} reads a point in time.

GET /api/repository/{repo}/{branch}/head/{ws}/{path}
GET /api/repository/{repo}/{branch}/head/{ws}/$ref/{id}
GET /api/repository/{repo}/{branch}/rev/{revision}/{ws}/
GET /api/repository/{repo}/{branch}/rev/{revision}/{ws}/{path}
GET /api/repository/{repo}/{branch}/rev/{revision}/{ws}/$ref/{id}

Node writes (POST, PUT, DELETE on .../head/{ws}/{path}) and SQL (POST /api/sql/{repo}/{branch}) take the branch the same way.

Node history

GET /api/history/{repo}/{branch}/{ws}/{path}?limit=50
GET /api/history/{repo}/{branch}/{ws}/by-id/{id}?limit=50

Returns a node's revisions newest first, each with revision, updated_at, updated_by, deleted, message and is_system. The same listing is available over WebSocket as node_history and in the JavaScript client as nodes().history() / nodes().historyByPath().