Content

August 13, 2026 · View on GitHub

The Content API reads L0/L1/L2 content, writes text, and maintains semantic and vector indexes for stored content.

API Reference

abstract()

Read L0 abstract (~100 tokens summary).

Parameters

ParameterTypeRequiredDefaultDescription
uristrYes-Viking URI (must be a directory)

Python SDK

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

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

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

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": []
      }
    }
  }
}

batch_write()

Apply a preconditioned set of file writes below one Resource or Memory directory, then refresh the affected semantic and vector indexes as one request.

Parameters

ParameterTypeRequiredDefaultDescription
root_uristringYes-Existing Resource or Memory directory containing every target
operationsarrayYes-File writes to validate and apply
waitbooleanNotrueWait for semantic/vector refresh
timeoutnumberNonullRefresh timeout in seconds when wait=true
telemetryboolean/objectNofalseInclude operation telemetry

Each operation contains:

FieldTypeRequiredDescription
uristringYesTarget file URI below root_uri
contentstringConditionalUTF-8 text; exactly one of content and content_base64 is required
content_base64stringConditionalBase64-encoded bytes; not supported for Memory targets
precondition.kindstringYescreate_if_absent or replace_if_hash
precondition.base_hashstringConditionalRequired for replace_if_hash, formatted as sha256:<lowercase-hex>

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.
  • Every non-idempotent precondition is checked under the target tree lock before the first new write. A mismatch returns 409 Conflict.
  • If a target already contains the requested bytes, it is reported as unchanged; retrying the same request can therefore repeat a failed refresh without rewriting matching content.
  • The API prevents precondition conflicts from causing new writes, but an underlying I/O failure can still leave writes completed earlier in the batch visible.

Python SDK

result = client.batch_write(
    root_uri="viking://resources/wiki",
    operations=[
        {
            "uri": "viking://resources/wiki/new.md",
            "content": "# New page\n",
            "precondition": {"kind": "create_if_absent"},
        },
        {
            "uri": "viking://resources/wiki/existing.md",
            "content": "# Updated page\n",
            "precondition": {
                "kind": "replace_if_hash",
                "base_hash": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
            },
        },
    ],
    wait=True,
)

HTTP API

POST /api/v1/content/batch-write
curl -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",
        "precondition": {"kind": "create_if_absent"}
      }
    ],
    "wait": true
  }'

Response

{
  "status": "ok",
  "result": {
    "root_uri": "viking://resources/wiki",
    "created": ["viking://resources/wiki/new.md"],
    "updated": [],
    "unchanged": [],
    "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.

ParameterTypeRequiredDescription
uristringYesFile 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.png

Response

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>

The public SDKs and CLI do not currently expose a dedicated raw-byte download method, so this section shows only the HTTP tab.


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(
    "viking://resources/project/",
    ["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/json
curl -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 \
  --recursive

Response

{
  "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

  • HTTP endpoint: requires admin/root role when authentication is enabled. In api_key mode, use an admin key for tenant content; a raw root key cannot access tenant-scoped data.
  • Python HTTP client / CLI: sends the current authenticated identity

Parameters

ParameterTypeRequiredDefaultDescription
uristrYes-Viking URI to reindex
modestrNovectors_onlyReindex mode: vectors_only, semantic_and_vectors, or prune_orphans
waitboolNotrueWhether to wait for completion
dry_runboolNofalseOnly valid with mode="prune_orphans"; report orphan vector records without deleting them
tagslist[str]NonullWrite tags to every successfully rebuilt vector record. Omit to preserve existing tags; an empty list with replace clears them
tag_modestrNoreplaceTag 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://user
  • viking://user/<user_id>
  • viking://resources
  • viking://resources/...
  • viking://user/<user_id>/memories/...
  • viking://user/<user_id>/skills
  • viking://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.md or .overview.md
  • semantic_and_vectors: regenerates semantic artifacts first, then rebuilds vectors from the refreshed semantic outputs
  • prune_orphans: deletes vector-store records under the requested URI whose source files no longer exist in the filesystem. With dry_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 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,
    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/reindex

There 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 \
  --tag team=search --tag env=prod --tag-mode replace

The CLI sends tag fields only when at least one --tag is provided. Use HTTP or an SDK to clear tags with tags: [].

openviking reindex viking://user/default/skills --mode semantic_and_vectors --wait false
openviking reindex viking://resources --mode prune_orphans --dry-run

Synchronous 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://resources

Task records are persisted under /local/{account_id}/_system/tasks/{user_id}/{task_id}.json and can be queried after restart.

Result fields

FieldDescription
statuscompleted for synchronous completion, accepted for background execution
uriRequested URI after path-variable resolution
object_typeInferred target type, such as resource, skill, memory, user_namespace, skill_namespace, or global_namespace
modeEffective reindex mode
scanned_recordsNumber of records or semantic sources considered
rebuilt_recordsNumber of vector records successfully rebuilt
deleted_recordsNumber of vector records deleted by prune_orphans; 0 for dry_run=true
would_delete_recordsNumber of vector records that would be deleted by prune_orphans in dry-run mode
unsupported_recordsNumber of records skipped because no usable vector source was available
failed_recordsNumber of records that failed while rebuilding
duration_msSynchronous run duration in milliseconds
warningsRecoverable per-record warnings
task_idBackground task ID, present only when wait=false

Behavior notes

  • vectors_only and semantic_and_vectors are non-destructive. They use rebuild/upsert behavior and do not require dropping the vector collection first.
  • prune_orphans is destructive unless dry_run=true: it removes vector records whose source files no longer exist.
  • viking:// reindex fans out to supported top-level namespaces and excludes session.
  • Namespace reindex operations such as viking://user propagate to supported child content types.
  • vectors_only is the right mode when only the embedding model or vector index needs to be refreshed.
  • semantic_and_vectors is the right mode when semantic artifacts themselves must be regenerated before re-vectorization.
  • prune_orphans is 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.