File System

July 13, 2026 · View on GitHub

OpenViking provides Unix-like file system operations for managing context.

WebDAV (Phase 1)

OpenViking Server also exposes a minimal WebDAV adapter for resource files:

/webdav/resources

Phase 1 intentionally keeps the scope narrow:

  • Resources only. Memories, skills, sessions, and other namespaces are not exposed.
  • Text-first writes. PUT currently accepts UTF-8 text content only.
  • WebDAV subset only. OPTIONS, PROPFIND, GET, HEAD, PUT, DELETE, MKCOL, and MOVE are supported.
  • Semantic sidecars and internal system files stay internal. Derived or internal files such as .abstract.md, .overview.md, .relations.json, .path.ovlock, .redirect.json, and .sync_log.json are hidden from listings and cannot be accessed directly through WebDAV.

Behavior notes:

  • Creating a new file through WebDAV triggers OpenViking semantic generation for that file path.
  • Replacing an existing file through WebDAV refreshes related semantics and vectors, same as write().
  • PUT does not create parent collections automatically. Create missing directories with MKCOL first.
  • User-created dot-directories and dot-files remain visible unless they match one of the reserved internal filenames above.
  • When multi-write storage is enabled, files redirected to a backup are still exposed through the filesystem APIs as normal files; internal redirect and sync metadata never become visible to callers.

API Reference

abstract()

Read L0 abstract (~100 tokens summary).

Parameters

ParameterTypeRequiredDefaultDescription
uristrYes-Viking URI (must be a directory)

Python SDK (Embedded / HTTP)

abstract = client.abstract("viking://resources/docs/")
print(f"Abstract: {abstract}")
# Output: "Documentation for the project API, covering authentication, endpoints..."

TypeScript SDK

const abstract = await client.abstract("viking://resources/docs/");
console.log(abstract);

Go SDK

abstract, err := client.Abstract(ctx, "viking://resources/docs/")
if err != nil {
    return err
}
fmt.Println(abstract)

HTTP API

GET /api/v1/content/abstract?uri={uri}
curl -X GET "http://localhost:1933/api/v1/content/abstract?uri=viking://resources/docs/" \
  -H "X-API-Key: your-key"

CLI

openviking abstract viking://resources/docs/

Response

{
  "status": "ok",
  "result": "Documentation for the project API, covering authentication, endpoints...",
  "time": 0.1
}

overview()

Read L1 overview, applies to directories.

Parameters

ParameterTypeRequiredDefaultDescription
uristrYes-Viking URI (must be a directory)

Python SDK (Embedded / HTTP)

overview = client.overview("viking://resources/docs/")
print(f"Overview:\n{overview}")

TypeScript SDK

const overview = await client.overview("viking://resources/docs/");
console.log(overview);

Go SDK

overview, err := client.Overview(ctx, "viking://resources/docs/")
if err != nil {
    return err
}
fmt.Println(overview)

HTTP API

GET /api/v1/content/overview?uri={uri}
curl -X GET "http://localhost:1933/api/v1/content/overview?uri=viking://resources/docs/" \
  -H "X-API-Key: your-key"

CLI

openviking overview viking://resources/docs/

Response

{
  "status": "ok",
  "result": "## docs/\n\nContains API documentation and guides...",
  "time": 0.1
}

read()

Read L2 full content.

Parameters

ParameterTypeRequiredDefaultDescription
uristrYes-Viking URI
offsetintNo0Starting line number (0-indexed)
limitintNo-1Number of lines to read, -1 means read to end
rawboolNofalseReturn raw stored content without memory-field cleanup. HTTP API only (Python SDK does not expose it yet).

Notes

  • read() accepts file URIs only. Passing an existing directory URI returns INVALID_ARGUMENT (400), not NOT_FOUND. This error carries a structured details payload — details.expected is "file", details.actual is "directory", and details.resource is the offending URI (present on the HTTP path) — so clients can detect a file-vs-directory mismatch programmatically (for example, fall back to list) instead of string-matching the message.
  • Public URI parameters accept resources and user scopes. For session files, use viking://user/{user_id}/sessions/{session_id} or the backward-compatible viking://session/{session_id} alias. Internal scopes such as temp and queue return INVALID_URI.

Python SDK (Embedded / HTTP)

content = client.read("viking://resources/docs/api.md")
print(f"Content:\n{content}")

TypeScript SDK

const content = await client.read("viking://resources/docs/api.md", 0, -1);
console.log(content);

Go SDK

content, err := client.Read(ctx, "viking://resources/docs/api.md", 0, -1)
if err != nil {
    return err
}
fmt.Println(content)

HTTP API

GET /api/v1/content/read?uri={uri}
curl -X GET "http://localhost:1933/api/v1/content/read?uri=viking://resources/docs/api.md" \
  -H "X-API-Key: your-key"

CLI

openviking read viking://resources/docs/api.md

Response

{
  "status": "ok",
  "result": "# API Documentation\n\nFull content of the file...",
  "time": 0.1
}

write()

Update an existing file, or create a new one when mode="create", and automatically refresh related semantics and vectors.

Parameters

ParameterTypeRequiredDefaultDescription
uristrYes-File URI to write. For mode="create", the file must not already exist
contentstrYes-New content to write
modestrNoreplacereplace, append, or create
waitboolNofalseWait for background semantic/vector refresh
timeoutfloatNonullTimeout in seconds when wait=true

Notes

  • replace and append require the file to exist; create targets a new file and returns 409 Conflict when the path already exists. Directories are always rejected.
  • create only accepts text-writable extensions: .md, .txt, .json, .yaml, .yml, .toml, .py, .js, .ts. Parent directories are created automatically.
  • Derived semantic files cannot be written directly: .abstract.md, .overview.md, .relations.json.
  • File content is updated before the API returns. wait only controls whether the call waits for semantic/vector refresh to finish.
  • The public API no longer accepts regenerate_semantics or revectorize; write always refreshes related semantics and vectors.

Python SDK (Embedded / HTTP)

result = client.write(
    "viking://resources/docs/api.md",
    "# Updated API\n\nFresh content.",
    mode="replace",
    wait=True,
)
print(result["root_uri"])

TypeScript SDK

await client.write("viking://resources/docs/new.md", "# New document\n", { wait: true });

Go SDK

result, err := client.Write(
    ctx,
    "viking://resources/docs/api.md",
    "# Updated API\n\nFresh content.",
    &openviking.WriteOptions{
        Mode: "replace",
        Wait: true,
    },
)
if err != nil {
    return err
}
fmt.Println(result["root_uri"])

HTTP API

POST /api/v1/content/write
curl -X POST "http://localhost:1933/api/v1/content/write" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{
    "uri": "viking://resources/docs/api.md",
    "content": "# Updated API\n\nFresh content.",
    "mode": "replace",
    "wait": true
  }'

CLI

openviking write viking://resources/docs/api.md \
  --content "# Updated API\n\nFresh content." \
  --wait

Response

{
  "status": "ok",
  "result": {
    "uri": "viking://resources/docs/api.md",
    "root_uri": "viking://resources/docs",
    "context_type": "resource",
    "mode": "replace",
    "written_bytes": 29,
    "content_updated": true,
    "semantic_status": "complete",
    "vector_status": "complete",
    "queue_status": {
      "Semantic": {
        "processed": 1,
        "error_count": 0,
        "errors": []
      },
      "Embedding": {
        "processed": 2,
        "error_count": 0,
        "errors": []
      }
    }
  }
}

ls()

List directory contents.

Parameters

ParameterTypeRequiredDefaultDescription
uristrYes-Viking URI
simpleboolNoFalseReturn only relative paths
recursiveboolNoFalseList all subdirectories recursively
outputstrNoHTTP: agent; SDKs: originalOutput format: agent or original
abs_limitintNo256Abstract length limit for agent output
show_all_hiddenboolNoFalseInclude hidden files like -a
node_limitintNo1000Maximum number of results
sort_bystrNoNoneSort directories and files within their groups by name or mtime before applying node_limit; directories remain first
sort_orderstrNoascSort direction: asc or desc

Entry Structure

{
    "name": "docs",           # File/directory name
    "size": 4096,             # Size in bytes
    "mode": 16877,            # File mode
    "modTime": "2024-01-01T00:00:00Z",  # ISO timestamp
    "isDir": True,            # True if directory
    "uri": "viking://resources/docs/",  # Viking URI
    "meta": {}                # Optional metadata
}

Python SDK (Embedded / HTTP)

entries = client.ls(
    "viking://resources/",
    node_limit=200,
    sort_by="mtime",
    sort_order="desc",
)
for entry in entries:
    type_str = "dir" if entry['isDir'] else "file"
    print(f"{entry['name']} - {type_str}")

TypeScript SDK

const entries = await client.list("viking://resources/docs/", { simple: true });
console.log(entries);

Go SDK

entries, err := client.List(ctx, "viking://resources/", nil)
if err != nil {
    return err
}
for _, entry := range entries {
    fmt.Println(entry)
}

HTTP API

GET /api/v1/fs/ls?uri={uri}&simple={bool}&recursive={bool}
# Basic listing
curl -X GET "http://localhost:1933/api/v1/fs/ls?uri=viking://resources/" \
  -H "X-API-Key: your-key"

# Simple path list
curl -X GET "http://localhost:1933/api/v1/fs/ls?uri=viking://resources/&simple=true" \
  -H "X-API-Key: your-key"

# Recursive listing
curl -X GET "http://localhost:1933/api/v1/fs/ls?uri=viking://resources/&recursive=true" \
  -H "X-API-Key: your-key"

CLI

openviking ls viking://resources/ [--simple] [--recursive]

Response

{
  "status": "ok",
  "result": [
    {
      "name": "docs",
      "size": 4096,
      "mode": 16877,
      "modTime": "2024-01-01T00:00:00Z",
      "isDir": true,
      "uri": "viking://resources/docs/"
    }
  ],
  "time": 0.1
}

tree()

Get directory tree structure.

Parameters

ParameterTypeRequiredDefaultDescription
uristrYes-Viking URI
outputstrNoHTTP: agent; SDKs: originalOutput format: agent or original
abs_limitintNoHTTP: 256; SDKs: 128Abstract length limit for agent output
show_all_hiddenboolNoFalseInclude hidden files like -a
node_limitintNo1000Maximum number of results
level_limitintNo3Maximum directory depth to traverse

Python SDK (Embedded / HTTP)

entries = client.tree("viking://resources/")
for entry in entries:
    type_str = "dir" if entry['isDir'] else "file"
    print(f"{entry['rel_path']} - {type_str}")

TypeScript SDK

const tree = await client.tree("viking://resources/docs/", { nodeLimit: 100 });
console.log(tree);

Go SDK

entries, err := client.Tree(ctx, "viking://resources/", nil)
if err != nil {
    return err
}
for _, entry := range entries {
    fmt.Println(entry["rel_path"], entry["isDir"])
}

HTTP API

GET /api/v1/fs/tree?uri={uri}
curl -X GET "http://localhost:1933/api/v1/fs/tree?uri=viking://resources/" \
  -H "X-API-Key: your-key"

CLI

openviking tree viking://resources/my-project/

Response

{
  "status": "ok",
  "result": [
    {
      "name": "docs",
      "size": 4096,
      "isDir": true,
      "rel_path": "docs/",
      "uri": "viking://resources/docs/"
    },
    {
      "name": "api.md",
      "size": 1024,
      "isDir": false,
      "rel_path": "docs/api.md",
      "uri": "viking://resources/docs/api.md"
    }
  ],
  "time": 0.1
}

stat()

Get file or directory status information. For directories, returns the count of items under the directory.

Parameters

ParameterTypeRequiredDefaultDescription
uristrYes-Viking URI

Python SDK (Embedded / HTTP)

info = client.stat("viking://resources/docs/api.md")
print(f"Size: {info['size']}")
print(f"Is directory: {info['isDir']}")

# For directories, returns item count
dir_info = client.stat("viking://resources/docs")
if dir_info.get('isDir'):
    print(f"Item count: {dir_info.get('count')}")

TypeScript SDK

const metadata = await client.stat("viking://resources/docs/api.md");
console.log(metadata);

Go SDK

info, err := client.Stat(ctx, "viking://resources/docs/api.md")
if err != nil {
    return err
}
fmt.Println(info["size"], info["isDir"])

HTTP API

GET /api/v1/fs/stat?uri={uri}
curl -X GET "http://localhost:1933/api/v1/fs/stat?uri=viking://resources/docs/api.md" \
  -H "X-API-Key: your-key"

CLI

openviking stat viking://resources/my-project/docs/api.md
openviking stat viking://resources/my-project/docs

Response (File)

{
  "status": "ok",
  "result": {
    "name": "api.md",
    "size": 1024,
    "mode": 33188,
    "modTime": "2024-01-01T00:00:00Z",
    "isDir": false,
    "isLocked": false,
    "uri": "viking://resources/docs/api.md"
  },
  "time": 0.1
}

Response (Directory)

{
  "status": "ok",
  "result": {
    "name": "docs",
    "size": 4096,
    "mode": 16877,
    "modTime": "2024-01-01T00:00:00Z",
    "isDir": true,
    "isLocked": false,
    "uri": "viking://resources/docs",
    "count": 42
  },
  "time": 0.1
}

The isLocked field reports whether the path is currently held by a path lock: the path itself has a valid lock (including an exact-path lock for the target), or any ancestor directory holds a TreeLock. Returns false when the LockManager is unavailable or the lookup fails, so callers can avoid attempting a write only to observe ResourceBusyError.

The count field (directories only) contains the estimated number of items (files and subdirectories) under this directory (from vector index).


attrs()

Get logical extended attributes for a file or directory.

Parameters

ParameterTypeRequiredDefaultDescription
uristrYes-Viking URI

Python SDK (HTTP)

attrs = client.attrs("viking://resources/docs/api.md")
print(attrs["attrs"]["tags"])

TypeScript SDK

const attributes = await client.attrs("viking://resources/docs/api.md");
console.log(attributes);

Go SDK

attrs, err := client.Attrs(ctx, "viking://resources/docs/api.md")
if err != nil {
    return err
}
metadata := attrs["attrs"].(map[string]any)
fmt.Println(metadata["tags"])

HTTP API

GET /api/v1/fs/attrs?uri={uri}
POST /api/v1/fs/attrs/set_tags
curl -X GET "http://localhost:1933/api/v1/fs/attrs?uri=viking://resources/docs/api.md" \
  -H "X-API-Key: your-key"

curl -X POST "http://localhost:1933/api/v1/fs/attrs/set_tags" \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{"uri":"viking://resources/docs","tags":["team=search"],"mode":"append","recursive":true}'

CLI

openviking attrs get viking://resources/docs/api.md
openviking attrs get viking://resources/docs/api.md tags
openviking attrs get viking://user/alice/memories/experiences/foo.md memory.resource_refs
openviking attrs set-tags viking://resources/docs/api.md --tags team=search,env=prod
openviking attrs set-tags viking://resources/docs --tags team=search --mode append --recursive

Directory targets update the directory semantic records; recursive=true also updates existing descendant files and directory semantic records.

Response (Resource)

{
  "status": "ok",
  "result": {
    "uri": "viking://resources/docs/api.md",
    "context_type": "resource",
    "attrs": {
      "tags": ["team=search", "env=prod"]
    }
  }
}

Response (Memory)

{
  "status": "ok",
  "result": {
    "uri": "viking://user/alice/memories/experiences/foo.md",
    "context_type": "memory",
    "attrs": {
      "memory": {
        "memory_type": "experiences",
        "name": "foo",
        "tags": ["ui"],
        "resource_refs": ["viking://resources/docs/api.md"]
      },
      "tags": ["team=search"]
    }
  }
}

attrs.memory is parsed from MEMORY_FIELDS metadata with content removed. attrs.tags is the explicit retrieval tag list used by attrs set-tags and search filters.


mkdir()

Create a directory.

Parameters

ParameterTypeRequiredDefaultDescription
uristrYes-Viking URI for the new directory
descriptionstrNonullInitial directory description. When provided, it is written to .abstract.md and queued for L0 vectorization.

Python SDK (Embedded / HTTP)

client.mkdir("viking://resources/new-project/")
client.mkdir("viking://resources/new-project/", description="API docs directory")

TypeScript SDK

await client.mkdir("viking://resources/docs/guides/", "Project guides");

Go SDK

if err := client.Mkdir(ctx, "viking://resources/new-project/", "API docs directory"); err != nil {
    return err
}

HTTP API

POST /api/v1/fs/mkdir
curl -X POST http://localhost:1933/api/v1/fs/mkdir \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{
    "uri": "viking://resources/new-project/",
    "description": "API docs directory"
  }'

CLI

openviking mkdir viking://resources/new-project/
openviking mkdir viking://resources/new-project/ --description "API docs directory"

Response

{
  "status": "ok",
  "result": {
    "uri": "viking://resources/new-project/"
  },
  "time": 0.1
}

rm()

Remove file or directory. When removing directories recursively, returns the estimated number of items deleted.

rm is idempotent: removing a valid URI that does not exist still succeeds. Invalid URI formats, unsupported schemes, and non-public scopes return INVALID_URI.

Parameters

ParameterTypeRequiredDefaultDescription
uristrYes-Viking URI to remove
recursiveboolNoFalseRemove directory recursively

Python SDK (Embedded / HTTP)

# Remove single file
client.rm("viking://resources/docs/old.md")

# Remove directory recursively
client.rm("viking://resources/old-project/", recursive=True)

TypeScript SDK

await client.remove("viking://resources/docs/old.md", { wait: true });

Go SDK

err := client.Remove(ctx, "viking://resources/old-project/", &openviking.RemoveOptions{
    Recursive: true,
})
if err != nil {
    return err
}

HTTP API

DELETE /api/v1/fs?uri={uri}&recursive={bool}
# Remove single file
curl -X DELETE "http://localhost:1933/api/v1/fs?uri=viking://resources/docs/old.md" \
  -H "X-API-Key: your-key"

# Remove directory recursively
curl -X DELETE "http://localhost:1933/api/v1/fs?uri=viking://resources/old-project/&recursive=true" \
  -H "X-API-Key: your-key"

CLI

openviking rm viking://resources/old.md [--recursive]

Response (Single file)

{
  "status": "ok",
  "result": {
    "uri": "viking://resources/docs/old.md"
  },
  "time": 0.1
}

Response (Recursive delete)

{
  "status": "ok",
  "result": {
    "uri": "viking://resources/old-project/",
    "estimated_deleted_count": 42
  },
  "time": 0.1
}

The estimated_deleted_count field (for recursive deletes) contains the estimated number of items (files and directories) deleted (from vector index). The CLI will display this information in output.

When deleting viking://resources/..., the response may include memory_cleanup, indicating that user memories referencing that resource URI were cleaned up before deletion.


mv()

Move file or directory.

Parameters

ParameterTypeRequiredDefaultDescription
from_uristrYes-Source Viking URI
to_uristrYes-Destination Viking URI

Python SDK (Embedded / HTTP)

client.mv(
    "viking://resources/old-name/",
    "viking://resources/new-name/"
)

TypeScript SDK

await client.move(
  "viking://resources/docs/old.md",
  "viking://resources/docs/new.md",
);

Go SDK

if err := client.Move(ctx, "viking://resources/old-name/", "viking://resources/new-name/"); err != nil {
    return err
}

HTTP API

POST /api/v1/fs/mv
curl -X POST http://localhost:1933/api/v1/fs/mv \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{
    "from_uri": "viking://resources/old-name/",
    "to_uri": "viking://resources/new-name/"
  }'

CLI

openviking mv viking://resources/old-name/ viking://resources/new-name/

Response

{
  "status": "ok",
  "result": {
    "from": "viking://resources/old-name/",
    "to": "viking://resources/new-name/"
  },
  "time": 0.1
}

grep()

Search content by pattern.

Parameters

ParameterTypeRequiredDefaultDescription
uristrYes-Viking URI to search in
patternstrYes-Search pattern (regex)
case_insensitiveboolNoFalseIgnore case
exclude_uristrNoNoneURI prefix to exclude from search
node_limitintNo256Maximum number of results. Omitted requests default to 256; pass a larger integer when you need more results
level_limitintNoPython SDK: 5; HTTP API / CLI / Go SDK: 10Maximum directory depth to traverse. The Go SDK currently uses the HTTP API default.

Python SDK (Embedded / HTTP)

results = client.grep(
    "viking://resources/",
    "authentication",
    case_insensitive=True,
    node_limit=1024,
)

print(f"Found {results['count']} matches")
for match in results['matches']:
    print(f"  {match['uri']}:{match['line']}")
    print(f"    {match['content']}")

TypeScript SDK

const matches = await client.grep("viking://resources/docs/", "authentication");
console.log(matches);

Go SDK

nodeLimit := 1024
result, err := client.Grep(ctx, "viking://resources/", "authentication", &openviking.GrepOptions{
    CaseInsensitive: true,
    NodeLimit:       &nodeLimit,
})
if err != nil {
    return err
}
fmt.Println(result["matches"])

HTTP API

POST /api/v1/search/grep
curl -X POST http://localhost:1933/api/v1/search/grep \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{
    "uri": "viking://resources/",
    "pattern": "authentication",
    "case_insensitive": true
  }'

CLI

openviking grep "authentication" --uri viking://resources/ [--ignore-case]

Response

{
  "status": "ok",
  "result": {
    "matches": [
      {
        "uri": "viking://resources/docs/auth.md",
        "line": 15,
        "content": "User authentication is handled by..."
      }
    ],
    "count": 1
  },
  "time": 0.1
}

glob()

Match files by pattern.

Parameters

ParameterTypeRequiredDefaultDescription
patternstrYes-Glob pattern (e.g., **/*.md)
uristrNo"viking://"Starting URI
node_limitintNo256Maximum number of results

Python SDK (Embedded / HTTP)

# Find all markdown files
results = client.glob("**/*.md", "viking://resources/")
print(f"Found {results['count']} markdown files:")
for uri in results['matches']:
    print(f"  {uri}")

# Find all Python files
results = client.glob("**/*.py", "viking://resources/")
print(f"Found {results['count']} Python files")

TypeScript SDK

const matches = await client.glob("**/*.md", "viking://resources/docs/");
console.log(matches);

Go SDK

result, err := client.Glob(ctx, "**/*.md", "viking://resources/", &openviking.GlobOptions{
    NodeLimit: openviking.Int(1024),
})
if err != nil {
    return err
}
fmt.Println(result["matches"])

HTTP API

POST /api/v1/search/glob
curl -X POST http://localhost:1933/api/v1/search/glob \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{
    "pattern": "**/*.md",
    "uri": "viking://resources/"
  }'

CLI

openviking glob "**/*.md" [--uri viking://resources/]

Response

{
  "status": "ok",
  "result": {
    "matches": [
      "viking://resources/docs/api.md",
      "viking://resources/docs/guide.md"
    ],
    "count": 2
  },
  "time": 0.1
}

Create relations between resources.

Parameters

ParameterTypeRequiredDefaultDescription
from_uristrYes-Source URI
to_urisstr or List[str]Yes-Target URI(s)
reasonstrNo""Reason for the link

Python SDK (Embedded / HTTP)

# Single link
client.link(
    "viking://resources/docs/auth/",
    "viking://resources/docs/security/",
    reason="Security best practices for authentication"
)

# Multiple links
client.link(
    "viking://resources/docs/api/",
    [
        "viking://resources/docs/auth/",
        "viking://resources/docs/errors/"
    ],
    reason="Related documentation"
)

TypeScript SDK

await client.link(
  "viking://resources/docs/api/",
  [
    "viking://resources/docs/auth/",
    "viking://resources/docs/errors/",
  ],
  "Related documentation",
);

HTTP API

POST /api/v1/relations/link
# Single link
curl -X POST http://localhost:1933/api/v1/relations/link \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{
    "from_uri": "viking://resources/docs/auth/",
    "to_uris": "viking://resources/docs/security/",
    "reason": "Security best practices for authentication"
  }'

# Multiple links
curl -X POST http://localhost:1933/api/v1/relations/link \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{
    "from_uri": "viking://resources/docs/api/",
    "to_uris": ["viking://resources/docs/auth/", "viking://resources/docs/errors/"],
    "reason": "Related documentation"
  }'

CLI

openviking link viking://resources/docs/auth/ viking://resources/docs/security/ --reason "Security best practices"

Response

{
  "status": "ok",
  "result": {
    "from": "viking://resources/docs/auth/",
    "to": "viking://resources/docs/security/"
  },
  "time": 0.1
}

relations()

Get relations for a resource.

Parameters

ParameterTypeRequiredDefaultDescription
uristrYes-Viking URI

Python SDK (Embedded / HTTP)

relations = client.relations("viking://resources/docs/auth/")
for rel in relations:
    print(f"Related: {rel['uri']}")
    print(f"  Reason: {rel['reason']}")

TypeScript SDK

const relations = await client.relations("viking://resources/docs/auth/");
console.log(relations);

HTTP API

GET /api/v1/relations?uri={uri}
curl -X GET "http://localhost:1933/api/v1/relations?uri=viking://resources/docs/auth/" \
  -H "X-API-Key: your-key"

CLI

openviking relations viking://resources/docs/auth/

Response

{
  "status": "ok",
  "result": [
    {"uri": "viking://resources/docs/security/", "reason": "Security best practices"},
    {"uri": "viking://resources/docs/errors/", "reason": "Error handling"}
  ],
  "time": 0.1
}

Remove a relation.

Parameters

ParameterTypeRequiredDefaultDescription
from_uristrYes-Source URI
to_uristrYes-Target URI to unlink

Python SDK (Embedded / HTTP)

client.unlink(
    "viking://resources/docs/auth/",
    "viking://resources/docs/security/"
)

TypeScript SDK

await client.unlink(
  "viking://resources/docs/auth/",
  "viking://resources/docs/security/",
);

HTTP API

DELETE /api/v1/relations/link
curl -X DELETE http://localhost:1933/api/v1/relations/link \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{
    "from_uri": "viking://resources/docs/auth/",
    "to_uri": "viking://resources/docs/security/"
  }'

CLI

openviking unlink viking://resources/docs/auth/ viking://resources/docs/security/

Response

{
  "status": "ok",
  "result": {
    "from": "viking://resources/docs/auth/",
    "to": "viking://resources/docs/security/"
  },
  "time": 0.1
}

export_ovpack

Export a resource tree as a .ovpack file.

1. API Implementation Overview

Packages all resources under the specified URI into a .ovpack file for backup or migration. Available to ROOT, ADMIN, and USER roles; normal URI access controls still apply.

Processing Flow:

  1. Verify user permissions
  2. Traverse resources under the specified URI
  3. Write content files and the OVPack manifest
  4. Package into zip format (.ovpack)
  5. Return as file stream

Format Notes:

  • The exported ZIP stores user content unchanged under <root>/files/ and internal metadata under <root>/_ovpack/.
  • The manifest is stored at <root>/_ovpack/manifest.json.
  • entries[].path is relative to the exported root; "" means the root directory itself.
  • File entries include size and sha256; content_sha256 covers the sorted file list of path, size, and sha256.
  • _ovpack/index_records.jsonl stores portable index scalar fields. With include_vectors=true, _ovpack/dense.f32 stores a pure-dense float32 vector snapshot plus embedding metadata; vector indexes whose VectorIndex.IndexType is hybrid do not support vector snapshot export.
  • Runtime fields such as id, uri, account_id, created_at, updated_at, and active_count are regenerated in the target environment and are not restored from the package.
  • OVPack does not add package-size, file-count, or directory-depth limits; the practical limit comes from ZIP, the storage backend, and the runtime environment.

Code Entry Points:

  • openviking/server/routers/pack.py:export_ovpack - HTTP router
  • openviking/service/pack_service.py - Core service implementation
  • crates/ov_cli/src/handlers.rs:handle_export - CLI handler

2. Interface and Parameter Description

Parameters

ParameterTypeRequiredDefaultDescription
uristringYes-Viking URI to export
include_vectorsbooleanNofalseInclude a pure-dense vector snapshot; hybrid index types are rejected

Permission Requirements: ROOT, ADMIN, or USER

3. Usage Examples

HTTP API

POST /api/v1/pack/export
Content-Type: application/json
curl -X POST http://localhost:1933/api/v1/pack/export \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-admin-key" \
  -d '{
    "uri": "viking://resources/my-project/",
    "include_vectors": false
  }' \
  --output my-project.ovpack

Python SDK

import openviking as ov

client = ov.SyncHTTPClient(url="http://localhost:1933", api_key="your-admin-key")
client.initialize()

# Export to local file (HTTP SDK automatically handles download)
# Note: Export functionality is primarily used via CLI

TypeScript SDK

const outputPath = await client.exportOVPack(
  "viking://resources/docs/",
  "./exports/docs.ovpack",
  true,
);
console.log(outputPath);

Go SDK

outPath, err := client.ExportOVPack(
    ctx,
    "viking://resources/my-project/",
    "./exports/my-project.ovpack",
    &openviking.PackOptions{IncludeVectors: false},
)
if err != nil {
    return err
}
fmt.Println(outPath)

CLI

# Export resource
ov export viking://resources/my-project/ ./exports/my-project.ovpack

# Export with a dense vector snapshot
ov export viking://resources/my-project/ ./exports/my-project.ovpack --include-vectors

Response Example

This endpoint directly returns a file stream (Content-Type: application/zip), does not return a JSON envelope.


import_ovpack

Import a .ovpack file.

1. API Implementation Overview

Imports a .ovpack file to a specified location for restoring or migrating data. Available to ROOT, ADMIN, and USER roles; normal URI access controls still apply.

Processing Flow:

  1. Verify user permissions
  2. Parse uploaded .ovpack file
  3. Validate manifest metadata, paths, file and directory sets, file sizes, and checksums
  4. Apply on_conflict
  5. Import resources to target location and rebuild vectors

Code Entry Points:

  • openviking/server/routers/pack.py:import_ovpack - HTTP router
  • openviking/service/pack_service.py - Core service implementation
  • crates/ov_cli/src/handlers.rs:handle_import - CLI handler

2. Interface and Parameter Description

Parameters

ParameterTypeRequiredDefaultDescription
temp_file_idstringYes-Temporary upload file ID (obtained via temp_upload)
parentstringYes-Target parent URI (import to this location)
on_conflictstringNofailConflict policy: fail, overwrite, or skip
vector_modestringNoautoVector handling: auto, recompute, or require

Permission Requirements: ROOT, ADMIN, or USER

Behavior Notes:

  • The API no longer accepts vectorize or force.
  • vector_mode=auto restores a compatible dense snapshot when present, otherwise recomputes vectors. recompute always ignores package vectors. require fails unless a compatible dense snapshot is present.
  • Dense snapshot compatibility checks compare embedding provider, model, input mode, query/document parameters, and dimensions.
  • Session files are part of the user namespace (viking://user/{user_id}/sessions/...) and do not trigger vectorization.
  • on_conflict=fail returns a structured 409 CONFLICT when the target root already exists.
  • on_conflict=overwrite replaces the existing target root. on_conflict=skip keeps the existing target root and returns it without writing package contents. skip is root-level, not file-level.
  • Packages without a manifest are rejected by default because they cannot provide content integrity guarantees.
  • Packages with manifest entries are rejected if content files or directories are missing, extra files or directories are present, file sizes differ, per-file sha256 differs, or content_sha256 is missing or differs.
  • Packages whose manifest format_version is not the current supported version (3) are rejected.
  • .abstract.md and .overview.md are restored as semantic sidecars. .relations.json and OVPack internals are excluded.
  • Manifest context_type, when present in index scalar metadata, must match the final import path semantics.
  • Top-level scope packages such as viking://resources/ must be imported to viking://.
  • OVPack does not add import package-size, file-count, or directory-depth limits; the practical limit comes from ZIP, the storage backend, and the runtime environment.

3. Usage Examples

HTTP API

POST /api/v1/pack/import
Content-Type: application/json
# Step 1: Upload .ovpack file
TEMP_FILE_ID=$(
  curl -s -X POST http://localhost:1933/api/v1/resources/temp_upload \
    -H "X-API-Key: your-admin-key" \
    -F "file=@./exports/my-project.ovpack" \
  | jq -r '.result.temp_file_id'
)

# Step 2: Import
curl -X POST http://localhost:1933/api/v1/pack/import \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-admin-key" \
  -d "{
    \"temp_file_id\": \"$TEMP_FILE_ID\",
    \"parent\": \"viking://resources/imported/\",
    \"on_conflict\": \"overwrite\",
    \"vector_mode\": \"auto\"
  }"

Python SDK

import openviking as ov

client = ov.SyncHTTPClient(url="http://localhost:1933", api_key="your-admin-key")
client.initialize()

# Import .ovpack file (HTTP SDK automatically handles upload)
# Note: Import functionality is primarily used via CLI

TypeScript SDK

const uri = await client.importOVPack(
  "./exports/docs.ovpack",
  "viking://resources/",
  {
    onConflict: "overwrite",
    vectorMode: "auto",
  },
);
console.log(uri);

Go SDK

uri, err := client.ImportOVPack(
    ctx,
    "./exports/my-project.ovpack",
    "viking://resources/imported/",
    &openviking.ImportPackOptions{
        OnConflict: "overwrite",
        VectorMode: "auto",
    },
)
if err != nil {
    return err
}
fmt.Println(uri)

CLI

# Import .ovpack file
ov import ./exports/my-project.ovpack viking://resources/imported/

# Explicit conflict policy
ov import ./exports/my-project.ovpack viking://resources/imported/ --on-conflict overwrite

# Require restoring a compatible dense vector snapshot
ov import ./exports/my-project.ovpack viking://resources/imported/ --vector-mode require

Response Example

{
  "status": "ok",
  "result": {
    "uri": "viking://resources/imported/my-project/"
  },
  "telemetry": {
    "operation_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}

Conflict Error Example

{
  "status": "error",
  "error": {
    "code": "CONFLICT",
    "message": "Resource already exists at viking://resources/imported/my-project. Use on_conflict='overwrite' to replace it.",
    "details": {
      "resource": "viking://resources/imported/my-project"
    }
  }
}

backup_ovpack

Back up public scope roots as a restore-only .ovpack file. The backup includes resources and user; sessions are included through the user namespace under user/{user_id}/sessions. It excludes internal runtime data such as temp and queue. Set include_vectors=true to include compatible pure-dense vector snapshots; hybrid index types reject vector snapshot export.

POST /api/v1/pack/backup
curl -X POST http://localhost:1933/api/v1/pack/backup \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-admin-key" \
  -d '{"include_vectors":false}' \
  --output openviking-backup.ovpack

Go SDK:

outPath, err := client.BackupOVPack(
    ctx,
    "./backups/openviking.ovpack",
    &openviking.PackOptions{IncludeVectors: true},
)
if err != nil {
    return err
}
fmt.Println(outPath)

CLI:

ov backup ./backups/openviking.ovpack
ov backup ./backups/openviking.ovpack --include-vectors

restore_ovpack

Restore a backup package created by backup_ovpack to the original public scope roots. Regular import rejects backup packages. Vector handling follows vector_mode; session files under the user namespace are restored without vectorization.

ParameterTypeRequiredDefaultDescription
temp_file_idstringYes-Temporary upload file ID
on_conflictstringNofailConflict policy: fail, overwrite, or skip
vector_modestringNoautoVector handling: auto, recompute, or require
POST /api/v1/pack/restore
Content-Type: application/json
TEMP_FILE_ID=$(
  curl -s -X POST http://localhost:1933/api/v1/resources/temp_upload \
    -H "X-API-Key: your-admin-key" \
    -F "file=@./backups/openviking.ovpack" \
  | jq -r '.result.temp_file_id'
)

curl -X POST http://localhost:1933/api/v1/pack/restore \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-admin-key" \
  -d "{\"temp_file_id\":\"$TEMP_FILE_ID\",\"on_conflict\":\"overwrite\",\"vector_mode\":\"auto\"}"

Go SDK:

uri, err := client.RestoreOVPack(
    ctx,
    "./backups/openviking.ovpack",
    &openviking.ImportPackOptions{
        OnConflict: "overwrite",
        VectorMode: "require",
    },
)
if err != nil {
    return err
}
fmt.Println(uri)

CLI:

ov restore ./backups/openviking.ovpack --on-conflict overwrite
ov restore ./backups/openviking.ovpack --on-conflict overwrite --vector-mode require