Skip to content

Memory graph & Asset Browser API

The memory graph endpoint, the read-only Kumiho proxy, artifact serving, and the typed asset write API.

This page documents the gateway endpoints that expose Kumiho graph memory to clients: a single aggregated memory graph payload for the Memory Auditor, a read-only generic Kumiho proxy for arbitrary GET endpoints, artifact body serving for rendering files on disk, and the typed Asset Browser write API for mutating projects, spaces, items, revisions, artifacts, bundles, and edges.

Use these endpoints when you want to read or edit the knowledge graph programmatically instead of through the dashboard. The dashboard’s Assets & memory explorer is built entirely on top of them. For the underlying data model — spaces, items, revisions, krefs, edges, and provenance — see Graph model: spaces, items & provenance. For how agents read and write memory through MCP tools instead of REST, see Kumiho memory tools.

Every route on this page requires the pairing bearer token:

Authorization: Bearer <token>

Obtain the token through the pairing flow in Pairing & authentication. A missing or invalid token returns 401. Most routes share the gateway’s default 64 KiB JSON body cap and request timeout; the exceptions (the memory graph timeout, the artifact body and content-edit size caps) are called out below.

Revka does not store the graph itself — persistence is Kumiho’s job. Every route here delegates to a KumihoClient that reaches Kumiho over one of two transports, chosen automatically:

TransportWhat it isWhen it’s used
SDK bridgeA loopback-only Python sidecar (kumiho_sdk_bridge.py) that exposes the Kumiho Python SDK over 127.0.0.1 for lower-latency readsPreferred when the Kumiho venv is present
Hosted FastAPIDirect HTTP to the Kumiho control plane at [kumiho] api_url (default https://api.kumiho.cloud)Fallback when the bridge is unavailable, returns 501 kumiho_sdk_bridge_unsupported, or returns a 5xx

The bridge is enabled by default and disabled by setting REVKA_KUMIHO_SDK_BRIDGE=0 (also accepts false, no, off). It is materialized to ~/.revka/kumiho/kumiho_sdk_bridge.py on first use, listens on an ephemeral loopback port, restarts automatically if it exits, and logs to ~/.revka/logs/kumiho-sdk-bridge.stdout.log and kumiho-sdk-bridge.stderr.log. Health-check and per-request timeouts are 10 seconds each. If the venv at ~/.revka/kumiho/venv/ is absent, the bridge simply fails to start and all reads fall back to hosted FastAPI — there is no hard error. See Kumiho setup for installing the sidecar.

Read responses are served from a token-scoped cache with a 10-second fresh TTL and a 120-second stale window: a cached value can be up to 10 s old, and a stale value keeps serving for 2 minutes after Kumiho goes offline. Write methods (POST/PUT/DELETE) are never retried — Kumiho has no idempotency keys, so a retried create could duplicate data.

GET /api/memory/graph returns nodes, edges, spaces, and stats in a single payload, ready for the dashboard’s Obsidian-style force-directed memory explorer.

GET /api/memory/graph?project=CognitiveMemory&limit=100&kinds=decision,fact&space=CognitiveMemory/Skills&sort=recent&search=gRPC
Authorization: Bearer <token>
ParamTypeDefaultMeaning
projectstring[kumiho] memory_project (CognitiveMemory)Kumiho project to read
limitint100 (max 500)Maximum number of items to include
kindsstringComma-separated kind filter, e.g. decision,fact,preference
spacestringSpace-path prefix filter
sortstringrecentrecent (newest first) or name
searchstringFulltext query; restricts the result set to matching items
{
"nodes": [
{
"id": "CognitiveMemory/Decisions/chose-grpc",
"name": "chose-grpc",
"kind": "decision",
"space": "CognitiveMemory/Decisions",
"created_at": "2026-06-18T09:12:00Z",
"title": "Chose gRPC over REST",
"summary": "...",
"revision_kref": "kref://CognitiveMemory/Decisions/chose-grpc?r=2"
}
],
"edges": [
{ "source": "...", "target": "...", "edge_type": "DERIVED_FROM", "metadata": {} }
],
"spaces": ["CognitiveMemory", "CognitiveMemory/Skills"],
"stats": { "total_items": 42, "total_edges": 18, "kinds": { "decision": 10, "fact": 32 } }
}

stats.total_items reflects the full filtered set before the limit truncation, so it can exceed nodes.length.

Edges only appear when both endpoints are already present in the returned node set; self-edges and cross-set edges are filtered out, and duplicate (source, edge_type, target) triples are deduplicated. This keeps the graph renderable without dangling references.

GET /api/kumiho/{*path} is a transparent read-only proxy to any Kumiho FastAPI GET endpoint. Query parameters are forwarded verbatim; the response body is returned as JSON.

GET /api/kumiho/items?space_path=/CognitiveMemory/Skills
GET /api/kumiho/revisions/latest?item_kref=kref://CognitiveMemory/my-skill
Authorization: Bearer <token>

Only GET is proxied — there is no write path here by design. To mutate the graph, use the typed Asset Browser write API or the resource-specific routes documented in Agents, skills & teams API.

Revka adds two observability headers so clients can see how the request was served:

HeaderValuesMeaning
X-Revka-Cachehit · staleWhether the response came from cache, and whether it was the stale fallback
X-Revka-Kumiho-Transportsdk-bridge · fastapiWhich transport answered

GET requests make up to 3 attempts (2 retries) with 500 ms / 1500 ms jittered backoff on transient upstream errors (502, 503, 504, 520, 522, 524) within a 15-second budget. Upstream HTML error pages (for example a Cloudflare 502 splash) are stripped before reaching the client.

GET /api/artifact-body streams the raw bytes of the local file referenced by a Kumiho artifact’s location field. The Asset Browser and the Workflow Runs artifact viewer use it to render text, image, and video artifacts without re-implementing file I/O.

GET /api/artifact-body?location=file:///home/user/.revka/workspace/artifacts/kumiho/report.md
Authorization: Bearer <token>
ParamTypeRequiredMeaning
locationstringyesThe artifact’s location value: an absolute path, optionally file://-prefixed; ~ is expanded

The response carries best-effort Content-Type (guessed from the extension), Content-Disposition: inline, and Cache-Control: private, max-age=60. The maximum served size is 256 MiB; larger files return 413.

The path is checked against the workspace security policy: the location must be absolute and resolve inside the workspace or an allowed root, otherwise the request returns 403. Relative paths return 400.

If no file is found on disk, the gateway attempts a revision-metadata fallback: it looks up the artifact by location in Kumiho and reconstructs the body from revision metadata — either a base64 payload (content_base64, data_base64, payload_base64, body_base64) or a text field (content, body, text, markdown, …) for text-like artifacts. When this path is taken, the response includes an X-Revka-Artifact-Source: revision-metadata header. Workflow YAML definitions are deliberately excluded from this fallback so they are never served as artifact bodies.

The /api/assets/* routes are the typed write surface behind the dashboard’s Assets page. Because the generic proxy is read-only, every mutation — creating spaces, adding revisions, editing artifact content — goes through these routes. They share a 2 MiB body limit (larger than the default 64 KiB) and require the bearer token.

POST /api/assets/projects
POST /api/assets/spaces
POST /api/assets/items
POST /api/assets/items/deprecate
POST /api/assets/revisions
POST /api/assets/revisions/deprecate
POST /api/assets/revisions/publish
POST /api/assets/revisions/tags — tag a revision
DELETE /api/assets/revisions/tags — untag a revision
GET /api/assets/bundles
POST /api/assets/bundles
GET /api/assets/bundles/members
POST /api/assets/bundles/members
DELETE /api/assets/bundles/members
POST /api/assets/edges
GET /api/assets/dependency-graph
POST /api/assets/artifacts
POST /api/assets/artifacts/deprecate
PUT /api/assets/artifacts/content — edit artifact content

The create routes mirror the Kumiho hierarchy: a project contains spaces, a space contains items, an item has a series of revisions, and a revision can carry artifacts. Each create returns the created entity wrapped under a key named for its type ({"item": {...}}, {"revision": {...}}, and so on).

POST /api/assets/projects
{ "name": "CognitiveMemory", "description": "optional" }
POST /api/assets/spaces
{ "parent_path": "/CognitiveMemory", "name": "Decisions" }
POST /api/assets/items
{ "space_path": "/CognitiveMemory/Decisions", "item_name": "chose-grpc", "kind": "decision", "metadata": {} }
POST /api/assets/revisions
{ "item_kref": "kref://CognitiveMemory/Decisions/chose-grpc", "metadata": { "title": "Chose gRPC" } }
POST /api/assets/edges
{ "source_kref": "<rev kref>", "target_kref": "<rev kref>", "edge_type": "DERIVED_FROM", "metadata": {} }

Edges connect revision krefs, not item krefs, and the edge_type is one of the provenance types described in Graph model: spaces, items & provenance (DERIVED_FROM, DEPENDS_ON, REFERENCED, CONTAINS, CREATED_FROM, BELONGS_TO).

POST /api/assets/artifacts attaches an artifact to a revision. It can register an existing file by location, validate that the file exists, or write the content to disk for you:

POST /api/assets/artifacts
{
"revision_kref": "kref://CognitiveMemory/Decisions/chose-grpc?r=2",
"name": "NOTES.md",
"location": "",
"content": "# Notes\n...",
"write_file": true,
"overwrite": false,
"validate_exists": false
}
FieldTypeMeaning
revision_krefstringRevision the artifact belongs to (must include an exact ?r=N selector)
namestringArtifact filename
locationstringAbsolute path or file:// URI; leave empty with write_file to auto-generate one under workspace/artifacts/kumiho/...
contentstringBody to write (when write_file is true); capped at 1 MiB
write_fileboolWrite content to location on disk before registering
overwriteboolAllow overwriting an existing file; otherwise an existing file returns 409
validate_existsboolFor link-only artifacts, fail with 400 if the file is missing

Any path written or linked is checked against the workspace security policy; paths outside the workspace and allowed roots return 403.

PUT /api/assets/artifacts/content edits an artifact’s body in place and is capped at 1 MiB:

PUT /api/assets/artifacts/content
{ "artifact_kref": "<artifact kref>", "revision_kref": "<revision kref>", "content": "updated body" }

The behavior depends on whether the revision is published:

  • Unpublished revision — the file is rewritten in place. The response has "created_revision": false.
  • Published revision — published history is immutable, so the gateway creates a new revision, writes the edited content to a new versioned file (name.rN.ext), and copies the other artifacts forward. The response has "created_revision": true and a copied_artifacts count.
{
"revision": { "...": "the (possibly new) revision" },
"artifact": { "...": "the edited artifact" },
"created_revision": true,
"copied_artifacts": 3
}
POST /api/assets/revisions/publish { "kref": "<revision kref>" }
POST /api/assets/revisions/tags { "kref": "<revision kref>", "tag": "milestone" }
DELETE /api/assets/revisions/tags { "kref": "<revision kref>", "tag": "milestone" }

Publish tags the revision published. Tag and untag accept any tag except current — moving or removing the current tag is high-risk and rejected with 403 (it must be done through kumiho_patch_apply).

Bundles are Kumiho’s grouping construct (a team, a canon set, a session’s captures). List them, list their members with resolved item detail, and add or remove members:

GET /api/assets/bundles?project=Revka&space_path=/Revka/Teams
GET /api/assets/bundles/members?bundle_kref=<bundle kref>
POST /api/assets/bundles/members { "bundle_kref": "...", "item_kref": "...", "metadata": {} }
DELETE /api/assets/bundles/members { "bundle_kref": "...", "item_kref": "..." }

GET /api/assets/dependency-graph walks edges outward from a revision for the canvas dependency view:

GET /api/assets/dependency-graph?revision_kref=<kref>&direction=both&depth=2&edge_type=all&node_limit=100
ParamTypeDefaultMeaning
revision_krefstringrequiredCenter node to walk from
directionstringbothupstream/outgoing, downstream/incoming, or both
depthint1 (max 3)How many edge hops to traverse
edge_typestringallRestrict to one edge type; all or empty means no filter
node_limitint100 (max 200)Stop expanding after this many nodes; sets truncated: true when hit

The response includes the resolved nodes (with their artifacts and incoming/outgoing edges), the deduplicated edge list, and the effective direction, depth, node_limit, and truncated flag.

The transport split described in How requests reach Kumiho is implemented by the SDK bridge (a process-local Python sidecar) and the KumihoClient, which together back every route on this page plus the agents, skills, teams, and workflow routes elsewhere in the API. They are internal — you never call them directly — but their behavior is worth knowing because it shapes what you observe:

  • Authentication uses two tokens. KUMIHO_SERVICE_TOKEN authenticates hosted FastAPI calls; KUMIHO_AUTH_TOKEN authenticates the bridge. They may differ. revka onboard writes both to ~/.revka/.env. See Kumiho setup.
  • Reads retry; writes do not. The retry and stale-cache behavior above applies to GET. Create/update/delete are issued once.
  • The cache is token-scoped. Cache keys are (token_hash, url), so one account’s cached reads can never leak to another.

The KumihoClient wraps the full Kumiho REST surface that these endpoints compose: items (create_item, list_items, search_items, deprecate_item), revisions (create_revision, get_latest_revision, tag_revision, batch_get_revisions), spaces and projects (ensure_space, create_project), edges (create_edge, list_edges), artifacts (create_artifact, get_artifacts, get_artifacts_by_location), and bundles (create_bundle, add_bundle_member, list_bundle_members). The /api/assets/* routes are thin, validated wrappers over exactly these calls, which is why creating an entity through the API behaves identically to creating it through an agent’s MCP tools.