docs/en/api/11-snapshot.md
On top of VikingFS, OpenViking provides Git-based multi-version management, called Snapshots. It saves an account's entire resource tree as a series of immutable commits, letting you walk history, compare versions, and restore the workspace to any past state.
Snapshots are powered by gitoxide embedded in the Rust RAGFS layer, maintaining one logical Git repository per account_id. This is fully transparent to callers — you never touch a .ovgit directory, the object store, or ref internals.
The four core commands:
| Command | Purpose |
|---|---|
commit | Save the current workspace state as a new snapshot |
log | Walk commit history starting from the newest |
show | View a commit's metadata, or read a file's content from that commit |
restore | Restore a directory (or the whole account tree) to a past snapshot |
commit_oid. Most commands also accept an abbreviated OID prefix or a branch name (e.g. main).main. Unless you pass one explicitly, every command operates on main.restore does not rewind or rewrite history. It reads the content at source_commit, writes the diff back into the workspace, and creates a new commit on top of the current HEAD. The new commit's parent is therefore the HEAD that existed before the restore — not source_commit. HEAD always advances monotonically and history is never lost.commit can be limited to specific URIs via paths; restore can be limited to a subtree via project_dir, leaving files outside it untouched./api/v1/snapshot.client.snapshot.*.commit / restore / show / log in viking_fs.py.SnapshotCmd in main.rs, subcommands in snapshot.rs.Save the current workspace state as a new snapshot.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| message | str | Yes | - | Commit message |
| paths | List[str] | No | null | viking:// URIs to scope the snapshot to; entries may be files or directories. Directories are expanded recursively with the snapshot pruning rules applied. null snapshots the whole account tree. An empty list [] is forwarded as an explicit empty path set (no-op). A path that exists in neither the VFS nor the previous snapshot logs a warning and is treated as a no-op deletion |
| branch | str | No | main | Branch to advance |
| author_name | str | No | null | Override the default author name (default viking-bot) |
| author_email | str | No | null | Override the default author email |
Python SDK (Embedded / HTTP)
result = client.snapshot.commit(
message="v1 initial import",
paths=["viking://resources/my_md.md"],
)
print(result["commit_oid"])
HTTP API
POST /api/v1/snapshot/commit
curl -X POST "http://localhost:1933/api/v1/snapshot/commit" \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"message": "v1 initial import",
"paths": ["viking://resources/my_md.md"]
}'
CLI
ov snapshot commit -m "v1 initial import" --paths viking://resources/my_md.md -o json
Response
When a new snapshot is created:
{
"status": "ok",
"result": {
"result": "created",
"commit_oid": "3f2a1b9c4d5e6f70819293a4b5c6d7e8f9a0b1c2",
"changed": 3
}
}
When the workspace is unchanged relative to the last commit, the result is noop and commit_oid is the current HEAD:
{
"status": "ok",
"result": {
"result": "noop",
"commit_oid": "3f2a1b9c4d5e6f70819293a4b5c6d7e8f9a0b1c2"
}
}
Starting from a branch's HEAD, walk history along the first parent (parents[0]) and return commits newest-first.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| branch | str | No | main | Branch to walk |
| limit | int | No | 20 | Max commits to return. The HTTP endpoint clamps this to 1–500 |
Python SDK (Embedded / HTTP)
history = client.snapshot.log(limit=10)
for commit in history:
print(commit["oid"], commit["message"])
HTTP API
GET /api/v1/snapshot/log?branch={branch}&limit={limit}
curl -X GET "http://localhost:1933/api/v1/snapshot/log?branch=main&limit=10" \
-H "X-API-Key: your-key"
CLI
ov snapshot log --limit 10 -o json
Response
result is a list of commit metadata, each element having the same shape as the metadata returned by show():
{
"status": "ok",
"result": [
{
"oid": "9a0b1c2d3e4f5061728394a5b6c7d8e9f0a1b2c3",
"tree": "11223344556677889900aabbccddeeff00112233",
"parents": ["3f2a1b9c4d5e6f70819293a4b5c6d7e8f9a0b1c2"],
"author": {
"name": "viking-bot",
"email": "[email protected]",
"time_seconds": 1750300000,
"tz_offset_seconds": 28800
},
"committer": {
"name": "viking-bot",
"email": "[email protected]",
"time_seconds": 1750300000,
"tz_offset_seconds": 28800
},
"message": "v2 modify delete add"
}
]
}
When the branch has no commits yet, the HTTP endpoint returns
404 NOT_FOUND.
View a commit's metadata; if path is given, return that file's content from the commit instead.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| target_ref | str | Yes | - | Commit OID (abbreviated prefix allowed), branch name, or tag |
| path | str | No | null | viking:// URI of a single file; omit to return commit metadata |
Python SDK (Embedded / HTTP)
# View commit metadata
meta = client.snapshot.show("3f2a1b9c")
print(meta["message"], meta["parents"])
# Read a file's content from the commit
blob = client.snapshot.show("3f2a1b9c", path="viking://resources/my_project/guide.md")
Note: when reading a file (
pathgiven), the Embedded (local) client returns rawbytes, while the HTTP client returns a{"oid": str, "size": int, "bytes": bytes}dict.
HTTP API
GET /api/v1/snapshot/show?target_ref={ref}[&path={uri}]
# Commit metadata (returns JSON)
curl -X GET "http://localhost:1933/api/v1/snapshot/show?target_ref=3f2a1b9c" \
-H "X-API-Key: your-key"
# File content (returns a binary stream)
curl -X GET "http://localhost:1933/api/v1/snapshot/show?target_ref=3f2a1b9c&path=viking://resources/my_project/guide.md" \
-H "X-API-Key: your-key"
Without path, the response is commit metadata JSON. With path, the response is a raw byte stream (Content-Type: application/octet-stream) plus two headers:
X-Snapshot-Oid: the blob object's OIDX-Snapshot-Size: the blob size in bytesCLI
# Commit metadata
ov snapshot show 3f2a1b9c -o json
# Read file content (defaults to stdout; use --out-file to write to a local file)
ov snapshot show 3f2a1b9c --path viking://resources/my_project/guide.md --out-file ./guide.md
Response (commit metadata)
{
"status": "ok",
"result": {
"oid": "3f2a1b9c4d5e6f70819293a4b5c6d7e8f9a0b1c2",
"tree": "00112233445566778899aabbccddeeff00112233",
"parents": [],
"author": {
"name": "viking-bot",
"email": "[email protected]",
"time_seconds": 1750299000,
"tz_offset_seconds": 28800
},
"committer": {
"name": "viking-bot",
"email": "[email protected]",
"time_seconds": 1750299000,
"tz_offset_seconds": 28800
},
"message": "v1 initial import"
}
}
Restore a directory (or the whole account tree) to its state at source_commit.
This is a forward-commit restore: it computes the diff between source_commit and the current HEAD, writes it back into the workspace, and creates a new commit on top of the current HEAD. The new commit's parent is the pre-restore HEAD (not source_commit), so history is never rewritten. Files outside project_dir are left untouched.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| source_commit | str | Yes | - | What to restore from: commit OID (abbreviated prefix allowed), branch name, or tag |
| project_dir | str | No | null | viking:// URI of the subtree to restore; omit to restore the whole account tree |
| branch | str | No | main | Branch to advance |
| dry_run | bool | No | false | Compute and return the diff only; write nothing |
| message | str | No | null | Message for the new commit; auto-generated when omitted |
| author_name | str | No | null | Override the default author name |
| author_email | str | No | null | Override the default author email |
Python SDK (Embedded / HTTP)
result = client.snapshot.restore(
project_dir="viking://resources/my_project",
source_commit="3f2a1b9c",
message="restore to v1",
)
print(result["result"], result["new_commit_oid"])
# Preview which files would change first
plan = client.snapshot.restore(
project_dir="viking://resources/my_project",
source_commit="3f2a1b9c",
dry_run=True,
)
print(plan["diff"])
HTTP API
POST /api/v1/snapshot/restore
curl -X POST "http://localhost:1933/api/v1/snapshot/restore" \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"project_dir": "viking://resources/my_project",
"source_commit": "3f2a1b9c",
"message": "restore to v1"
}'
CLI
# Positional args are <source_commit> then <project_dir>
ov snapshot restore 3f2a1b9c viking://resources/my_project -m "restore to v1" -o json
# Dry run
ov snapshot restore 3f2a1b9c viking://resources/my_project --dry-run -o json
Response (applied)
On a successful write that produces a new commit, result is applied. Note parent_commit equals the old (pre-restore) HEAD, confirming the forward-commit semantics:
{
"status": "ok",
"result": {
"result": "applied",
"new_commit_oid": "c3d4e5f60718293a4b5c6d7e8f9a0b1c2d3e4f50",
"source_commit": "3f2a1b9c4d5e6f70819293a4b5c6d7e8f9a0b1c2",
"parent_commit": "9a0b1c2d3e4f5061728394a5b6c7d8e9f0a1b2c3",
"written": 1,
"deleted": 1,
"unchanged": 1,
"written_paths": ["resources/my_project/guide.md"],
"deleted_paths": ["resources/my_project/changelog.md"],
"task_id": "snapshot_restore_reindex-..."
}
}
When the restore has vector side effects (files written/deleted), the response carries a task_id you can poll via GET /api/v1/tasks/{task_id} to track the background vector rebuild.
Response (noop)
When the source is byte-identical to the current state, the result is noop and no new commit is created:
{
"status": "ok",
"result": {
"result": "noop",
"head": "9a0b1c2d3e4f5061728394a5b6c7d8e9f0a1b2c3",
"source": "3f2a1b9c4d5e6f70819293a4b5c6d7e8f9a0b1c2"
}
}
Response (dry_run)
With dry_run=true, only the planned diff is returned and nothing is written. Diff paths are relative to project_dir:
{
"status": "ok",
"result": {
"result": "dry_run",
"head": "9a0b1c2d3e4f5061728394a5b6c7d8e9f0a1b2c3",
"source": "3f2a1b9c4d5e6f70819293a4b5c6d7e8f9a0b1c2",
"diff": {
"to_write": [{"path": "guide.md", "oid": "..."}],
"to_delete": ["changelog.md"],
"unchanged": ["notes/todo.md"]
}
}
}
A complete "commit → modify → restore" flow (Python SDK):
import openviking as ov
client = ov.OpenViking()
client.initialize()
root = "viking://resources/my_project"
# 1. Write initial content and commit v1
client.write(f"{root}/guide.md", "# Guide\n\nv1 content\n", mode="create", wait=True)
v1 = client.snapshot.commit(message="v1 initial import")
# 2. Modify and commit v2
client.write(f"{root}/guide.md", "# Guide\n\nv2 content\n", mode="replace", wait=True)
v2 = client.snapshot.commit(message="v2 update")
# 3. Walk history
for c in client.snapshot.log(limit=10):
print(c["oid"][:8], c["message"])
# 4. Restore the workspace to v1 (creates a new commit on top of v2)
client.snapshot.restore(project_dir=root, source_commit=v1["commit_oid"], message="restore to v1")
client.close()
For more end-to-end examples, see the examples/snapshot/ directory in the repository, covering the SDK, HTTP, and CLI surfaces.
| Scenario | HTTP Status | Error Code |
|---|---|---|
Branch/commit not found, or show's path does not exist in that commit | 404 | NOT_FOUND |
| Branch concurrently advanced during restore (CAS conflict) | 409 | CONFLICT |
Request body contains an unknown field (request model is extra="forbid") | 400 | INVALID_ARGUMENT |
GET /api/v1/tasks/{task_id}