Content
The Content API reads L0/L1/L2 content, writes text, and maintains semantic and vector indexes for stored content.
API Reference
abstract()
Read the L0 abstract (an approximately 100-token summary), excluding the OKF header.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| uri | str | Yes | - | Viking URI (must be a directory) |
Python SDK
abstract = client.abstract(uri="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 the L1 overview for a directory, excluding the OKF header.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| uri | str | Yes | - | Viking URI (must be a directory) |
Python SDK
overview = client.overview(uri="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 the complete text of an L0, L1, or L2 file.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| uri | str | Yes | - | Viking URI (e.g. viking://resources/docs/api.md) or a 32-character hex vector record id (returned by stat()) |
| offset | int | No | 0 | Starting line number (0-indexed) |
| limit | int | No | -1 | Number of lines to read, -1 means read to end |
| raw | bool | No | false | Return 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 returnsINVALID_ARGUMENT(400), notNOT_FOUND. This error carries a structureddetailspayload —details.expectedis"file",details.actualis"directory", anddetails.resourceis the offending URI (present on the HTTP path) — so clients can detect a file-vs-directory mismatch programmatically (for example, fall back tolist) instead of string-matching the message.- Instead of a Viking URI, you may pass the 32-character hex
idreturned bystat()for a file. The server looks up the URI via the vector index and applies the same permission checks. Because indexing is asynchronous, a newly returned ID might not be resolvable immediately; lookup also fails if the corresponding vector record has been deleted. In both cases, the server returnsNOT_FOUNDand indicates that the data may not have been indexed yet or may have been deleted. - Public URI parameters accept
resourcesanduserscopes. For session files, useviking://user/{user_id}/sessions/{session_id}or the backward-compatibleviking://session/{session_id}alias. Internal scopes such astempandqueuereturnINVALID_URI.
Python SDK
content = client.read(uri="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.mdResponse
{
"status": "ok",
"result": "# API Documentation\n\nFull content of the file...",
"time": 0.1
}write()
Write a file and automatically refresh related semantics and vectors.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| uri | str | Yes | - | File URI to write |
| content | str | Yes | - | New content to write |
| mode | str | No | replace | replace overwrites an existing file or creates a missing file; append appends to an existing file or creates a missing file; create creates only a missing file and returns 409 Conflict if it already exists |
| wait | bool | No | false | Wait for background semantic/vector refresh |
| timeout | float | No | null | Timeout in seconds when wait=true |
| tags | string[] | No | Unset | Explicit retrieval tags for the written file, for example ["team=search", "env=prod"] |
| tag_mode | string | No | replace | Tag update mode when tags is supplied: replace overwrites tags; append merges tags by key |
Notes
replaceandappendcreate a missing target file.appenduses the supplied content as the initial file content in that case.createtargets only a missing file and returns409 Conflictwhen the path already exists. Directories are always rejected.- Explicit
createonly accepts text-writable extensions:.md,.txt,.json,.yaml,.yml,.toml,.py,.js,.ts. Parent directories are created automatically for every write mode. - Existing
.abstract.mdand.overview.mdbodies may be updated, but public APIs cannot create them. A body-only request preserves stored OKF metadata; a full-OKF request must match the stored metadata. Unknown metadata fields are silently dropped. A sidecar body write rebuilds only the directory's existing L0/L1 vectors and does not regenerate semantics. - File content is updated before the API returns.
waitonly controls whether the call waits for semantic/vector refresh to finish. - The public API no longer accepts
regenerate_semanticsorrevectorize; write always refreshes related semantics and vectors. - When
tagsis supplied, tags are included in the file's first vector upsert rather than updated after processing. Omittingtagspreserves existing tags; explicittags: []withtag_mode: "replace"clears them.
Python SDK
result = client.write(
uri="viking://resources/docs/api.md",
content="# Updated API\n\nFresh content.",
mode="replace",
wait=True,
options={"tags": ["team=search", "env=prod"], "tag_mode": "replace"},
)
print(result["root_uri"])TypeScript SDK
await client.write("viking://resources/docs/new.md", "# New document\n", {
wait: true,
tags: ["team=search", "env=prod"],
tagMode: "replace",
});Go SDK
result, err := client.Write(
ctx,
"viking://resources/docs/api.md",
"# Updated API\n\nFresh content.",
&openviking.WriteOptions{
Mode: "replace",
Wait: true,
Tags: []string{"team=search", "env=prod"},
TagMode: "replace",
},
)
if err != nil {
return err
}
fmt.Println(result["root_uri"])HTTP API
POST /api/v1/content/writecurl -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,
"tags": ["team=search", "env=prod"],
"tag_mode": "replace"
}'CLI
openviking write viking://resources/docs/api.md \
--content "# Updated API\n\nFresh content." \
--tags team=search,env=prod \
--tag-mode replace \
--waitResponse
{
"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": []
}
}
}
}batch_write()
Write multiple files below one Resource or Memory directory, then refresh the affected semantic and vector indexes once after all writes finish.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
root_uri | string | Yes | - | Existing Resource or Memory directory containing every target |
operations | array | Yes | - | File writes to validate and apply |
wait | boolean | No | true | Wait for semantic/vector refresh |
timeout | number | No | null | Refresh timeout in seconds when wait=true |
telemetry | boolean/object | No | false | Include operation telemetry |
Each operation contains:
| Field | Type | Required | Description |
|---|---|---|---|
uri | string | Yes | Target file URI below root_uri |
content | string | Conditional | UTF-8 text; exactly one of content and content_base64 is required |
content_base64 | string | Conditional | Base64-encoded bytes; not supported for Memory targets |
mode | string | No | replace (default), append, create, or upsert |
Notes
- A request supports at most 256 operations, 8 MiB per file, and 16 MiB total.
- All targets must be files below
root_uri, use the same context type, and have unique canonical URIs. - Resource targets may use any safe file extension; Memory targets retain the text extension allowlist and do not accept binary content.
replace,append, andcreatematchwrite()semantics.upsertreplaces an existing file or creates a missing file.- The batch holds one target tree lock while writing. Semantic processing starts only after every file is written and the lock is released, so
.overview.mdand.abstract.mdare refreshed once for the batch. - An underlying I/O failure can still leave writes completed earlier in the batch visible.
- Existing
.abstract.mdand.overview.mdbodies may be replaced or appended. OpenViking preserves and validates protected OKF metadata and rebuilds only the directory's existing L0/L1 vectors for these operations. - In the response body,
semantic_status(queued,complete, ordeferred) reports the directory aggregation status, whilevector_statusreports vector maintenance for changed files.
Python SDK
result = client.batch_write(
root_uri="viking://resources/wiki",
operations=[
{
"uri": "viking://resources/wiki/new.md",
"content": "# New page\n",
"mode": "upsert",
},
{
"uri": "viking://resources/wiki/existing.md",
"content": "# Updated page\n",
"mode": "upsert",
},
],
wait=True,
)HTTP API
POST /api/v1/content/batch-writecurl -X POST http://localhost:1933/api/v1/content/batch-write \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"root_uri": "viking://resources/wiki",
"operations": [
{
"uri": "viking://resources/wiki/new.md",
"content": "# New page\n",
"mode": "upsert"
}
],
"wait": true
}'Response
{
"status": "ok",
"result": {
"root_uri": "viking://resources/wiki",
"created": ["viking://resources/wiki/new.md"],
"updated": [],
"unchanged": [],
"semantic_status": "complete",
"vector_status": "complete",
"queue_status": {
"Semantic": {
"processed": 1,
"error_count": 0,
"errors": []
}
}
}
}The TypeScript and Go SDKs and the CLI do not currently expose batch write directly.
download()
Download a file as raw bytes. This is intended for images, PDFs, and other non-text content. The response uses application/octet-stream and returns the filename through Content-Disposition.
| Parameter | Type | Required | Description |
|---|---|---|---|
uri | string | Yes | File URI to download |
HTTP API
GET /api/v1/content/download?uri={uri}curl --get http://localhost:1933/api/v1/content/download \
-H "X-API-Key: your-key" \
--data-urlencode "uri=viking://resources/images/logo.png" \
--output logo.pngCLI
ov get viking://resources/images/logo.png ./logo.pngResponse
On success, the endpoint returns HTTP 200 with the raw file bytes instead of the standard JSON envelope:
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Disposition: attachment; filename*=UTF-8''logo.png
<binary body>ov get <uri> <local-path> downloads through the HTTP API above and writes the file to a local path. The Python, TypeScript, and Go SDKs do not currently expose a dedicated raw-byte download method.
set_tags()
Set explicit k=v tags used by retrieval filters. replace replaces existing tags, while append adds tags. When the target is a directory, recursive=true applies the update to files below it.
Python SDK
result = client.set_tags(
uri="viking://resources/project/",
tags=["team=search", "env=prod"],
mode="replace",
recursive=True,
)TypeScript SDK
const result = await client.setTags(
"viking://resources/project/",
["team=search", "env=prod"],
{ mode: "replace", recursive: true },
);Go SDK
result, err := client.SetTags(
ctx,
"viking://resources/project/",
[]string{"team=search", "env=prod"},
&openviking.SetTagsOptions{Mode: "replace", Recursive: true},
)HTTP API
POST /api/v1/content/set_tags
Content-Type: application/jsoncurl -X POST http://localhost:1933/api/v1/content/set_tags \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"uri":"viking://resources/project/",
"tags":["team=search","env=prod"],
"mode":"replace",
"recursive":true
}'POST /api/v1/fs/attrs/set_tags is an equivalent compatibility path currently used by the Python, TypeScript, and Go SDKs and the CLI.
CLI
ov set-tags viking://resources/project/ \
--tags team=search,env=prod \
--mode replace \
--recursiveResponse
{
"status": "ok",
"result": {
"uri": "viking://resources/project/",
"updated_uris": [
"viking://resources/project/guide.md"
],
"root_uri": "viking://resources/project/",
"context_type": "resource",
"tags": [
"team=search",
"env=prod"
],
"mode": "replace",
"success_count": 1,
"skipped_count": 0,
"failed_count": 0,
"tags_updated": true
}
}updated_uris contains the semantic record URIs actually updated. For recursive directory updates, success_count, skipped_count, and failed_count summarize all targets.
reindex()
Reindex semantic and/or vector artifacts for existing content already stored in OpenViking. This is an operational maintenance API intended for scenarios such as embedding model changes, VLM changes, vector store rebuild, or post-upgrade repair of existing indexes.
This API operates on existing viking://... content. It does not import new files. For normal ingestion, use Resources.
Authentication
- In
api_keymode, sharedviking://resources/...targets require an admin key. A regular user key may reindex only its ownviking://user/<user_id>/...namespace, including the equivalentviking://~/...home alias. A root key cannot access tenant-scoped data APIs. - Python HTTP client / CLI: sends the current authenticated identity
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| uri | str | Yes | - | Viking URI to reindex |
| mode | str | No | vectors_only | Reindex mode: vectors_only, semantic_and_vectors, or prune_orphans |
| wait | bool | No | true | Whether to wait for completion |
| dry_run | bool | No | false | Only valid with mode="prune_orphans"; report orphan vector records without deleting them |
| recursive | bool | No | true | Whether to process descendants recursively; false applies only to semantic_and_vectors on a resource, memory, or skill directory |
| tags | list[str] | No | null | Write tags to every successfully rebuilt vector record. Omit to preserve existing tags; an empty list with replace clears them |
| tag_mode | str | No | replace | Tag write mode: replace or append |
The HTTP request body rejects unknown fields. uri may use OpenViking path variables accepted by other content APIs; it is resolved before validation.
Supported URI scopes
viking://viking://userviking://user/<user_id>viking://resourcesviking://resources/...viking://user/<user_id>/memories/...viking://user/<user_id>/skillsviking://user/<user_id>/skills/<skill_name>
Session namespaces are not supported by reindex(). Requests for viking://session/... or viking://user/<user_id>/sessions/... are rejected; when reindexing a broader user namespace, session subtrees are skipped.
Modes
vectors_only: rebuilds vector-store records from currently recoverable source data without rewriting.abstract.mdor.overview.mdsemantic_and_vectors: regenerates semantic artifacts first, then rebuilds vectors from the refreshed semantic outputsprune_orphans: deletes vector-store records under the requested URI whose source files no longer exist in the filesystem. Withdry_run=true, it only reports how many records would be deleted.
For resource and skill, semantic_and_vectors refreshes directory/file semantic artifacts, including .abstract.md and .overview.md. For memory, it rebuilds the current persisted memory subtree semantics and vectors, but it does not replay historical extraction order.
For semantic_and_vectors, semantic generation and vector rebuilding are sequenced by the reindex executor. The semantic refresh step does not enqueue its own background vectorization work; vectors are rebuilt by the reindex step so wait=true reflects the reindex operation itself.
For a resource or memory directory, recursive=false regenerates only the target directory's .abstract.md and .overview.md, then rebuilds only that directory's L0/L1 vectors. Child directories do not regenerate semantic artifacts, and neither child directories nor files are re-vectorized. The target aggregation still reads existing summaries from deterministically sampled child directories; sampled direct files are summarized as inputs to the target aggregation. For a skill target, recursive=false regenerates the skill directory's L0/L1 semantic artifacts and vectors from SKILL.md, but does not rebuild the SKILL.md L2 vector. This flag does not change existing behavior for vectors_only, prune_orphans, or namespace targets.
For prune_orphans, source existence is checked against the filesystem. If an entire directory is missing, vector records for files and semantic sidecars below that directory, such as .abstract.md and .overview.md, are pruned together. dry_run is rejected for other modes.
When tags is provided, tags are included in the same upsert as each vector record produced by reindex; reindex does not call set_tags afterwards. Directory and namespace reindex operations apply tags to successfully rebuilt directory L0/L1 and leaf L2 records. replace overwrites existing tags, while append merges by key. When tags is omitted, tag_mode is ignored and existing tags remain unchanged. prune_orphans produces no vectors and ignores both fields.
Subtree reindex is not transactional. Records skipped because no semantic source is available, or records whose embedding fails, do not receive the new tags.
Python SDK
result = client.reindex(
uri="viking://resources",
mode="vectors_only",
wait=True,
options={
"tags": ["team=search", "env=prod"],
"tag_mode": "replace",
},
)
print(result)result = client.reindex(
uri="viking://user/default/skills",
mode="semantic_and_vectors",
wait=False,
)
print(result["status"])result = client.reindex(
uri="viking://resources",
mode="prune_orphans",
dry_run=True,
)
print(result["would_delete_records"])TypeScript SDK
console.log(await client.reindex("viking://resources/docs/", {
tags: ["team=search"],
tagMode: "append",
}));Go SDK
When passing a non-nil ReindexOptions, set Wait explicitly. Go's zero value is false; only opts=nil applies the SDK default wait=true.
result, err := client.Reindex(ctx, "viking://resources", &openviking.ReindexOptions{
Mode: "vectors_only",
Wait: true,
Tags: []string{"team=search"},
TagMode: "replace",
})
if err != nil {
return err
}
fmt.Println(result["status"])result, err := client.Reindex(ctx, "viking://resources", &openviking.ReindexOptions{
Mode: "prune_orphans",
Wait: true,
DryRun: true,
})
if err != nil {
return err
}
fmt.Println(result["would_delete_records"])HTTP API
POST /api/v1/content/reindexThere is no /api/v1/maintenance/reindex endpoint. Use /api/v1/content/reindex.
curl -X POST http://localhost:1933/api/v1/content/reindex \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-H "X-OpenViking-Account: default" \
-d '{
"uri": "viking://resources",
"mode": "vectors_only",
"wait": true,
"tags": ["team=search", "env=prod"],
"tag_mode": "replace"
}'CLI
openviking reindex viking://resources --mode vectors_only \
--tags team=search,env=prod --tag-mode replaceThe CLI sends tag fields only when non-empty --tags is provided. Use HTTP or an SDK to clear tags with tags: [].
openviking reindex viking://user/default/skills --mode semantic_and_vectors --wait falseopenviking reindex viking://resources --mode prune_orphans --dry-runSynchronous response (wait=true)
{
"status": "ok",
"result": {
"uri": "viking://resources",
"mode": "vectors_only",
"status": "completed",
"object_type": "resource",
"scanned_records": 120,
"rebuilt_records": 118,
"deleted_records": 0,
"would_delete_records": 0,
"unsupported_records": 2,
"failed_records": 0,
"duration_ms": 1284,
"warnings": []
},
"time": 0.1
}Asynchronous response (wait=false)
{
"status": "ok",
"result": {
"uri": "viking://resources",
"mode": "vectors_only",
"object_type": "resource",
"status": "accepted",
"task_id": "task_xxx"
},
"time": 0.1
}Poll the returned task through the task API:
curl -X GET http://localhost:1933/api/v1/tasks/task_xxx \
-H "X-API-Key: your-key" \
-H "X-OpenViking-Account: default"Reindex background tasks use task_type="admin_reindex" and resource_id equal to the requested uri, so they can also be listed with:
GET /api/v1/tasks?task_type=admin_reindex&resource_id=viking://resourcesTask records are persisted under /local/{account_id}/_system/tasks/{user_id}/{task_id}.json and can be queried after restart.
Result fields
| Field | Description |
|---|---|
| status | completed for synchronous completion, accepted for background execution |
| uri | Requested URI after path-variable resolution |
| object_type | Inferred target type, such as resource, skill, memory, user_namespace, skill_namespace, or global_namespace |
| mode | Effective reindex mode |
| scanned_records | Number of records or semantic sources considered |
| rebuilt_records | Number of vector records successfully rebuilt |
| deleted_records | Number of vector records deleted by prune_orphans; 0 for dry_run=true |
| would_delete_records | Number of vector records that would be deleted by prune_orphans in dry-run mode |
| unsupported_records | Number of records skipped because no usable vector source was available |
| failed_records | Number of records that failed while rebuilding |
| duration_ms | Synchronous run duration in milliseconds |
| warnings | Recoverable per-record warnings |
| task_id | Background task ID, present only when wait=false |
Behavior notes
vectors_onlyandsemantic_and_vectorsare non-destructive. They use rebuild/upsert behavior and do not require dropping the vector collection first.prune_orphansis destructive unlessdry_run=true: it removes vector records whose source files no longer exist.viking://reindex fans out to supported top-level namespaces and excludessession.- Namespace reindex operations such as
viking://userpropagate to supported child content types. vectors_onlyis the right mode when only the embedding model or vector index needs to be refreshed.semantic_and_vectorsis the right mode when semantic artifacts themselves must be regenerated before re-vectorization.prune_orphansis the right mode when the filesystem has been changed outside normal APIs and the vector store may still contain records for deleted paths.- Only one reindex task can run for the same URI and owner at a time. A concurrent request for the same target returns a conflict.
- For resource files, text files can use file content when no summary is available. Non-text files require a generated summary or existing vector record fallback; otherwise they are counted as unsupported.
Current limitations
- Reindex uses the best currently recoverable source inputs. It is not guaranteed to replay the exact historical embedding input byte-for-byte in every case.
- Memory semantic reindex is based on the currently persisted memory tree. It does not reconstruct the original chronological memory-extraction pipeline.
Related Documentation
- File System - directory and file operations
- Retrieval - semantic and pattern search
- Background Tasks - track asynchronous reindex tasks
