Context0 API Reference
This document provides detailed descriptions of all Context0 data-plane and management-plane REST APIs, including endpoint paths, request parameters, request examples, and response examples.
Overview
Item | Description |
Base URL |
|
Authentication | Request header |
Response format |
|
Error format |
|
Key types | Admin Key (platform-level bootstrap credential, management plane only) / Owner Key (tenant administrator with full management permissions) / User Key (end user, isolated to their own data) |
Some Mem0-compatible endpoints (such as
/v1/memories,/v1/memories/search, and more) return bare JSON (without the Box wrapper) to stay wire-compatible with the Mem0 SDK.Time field convention: all
*_atfields are ISO-8601 instants in UTC, ending withZ(for example2026-09-17T18:03:43.226352Z), and the client is responsible for rendering them in the local time zone. The 2026-09-16 release fixed a set of fields that violated this convention: the Ontology catalog/version (created_at,generated_at), Ontology tasks (created_at), document revision history and revision artifacts (created_at), and review notification delivery (created_at,delivered_at) previously returned the database wall-clock time2026-09-17 18:03:43.226352(with noTand no time zone), which could not be reliably parsed bynew Date(). If your client applied "add T and add Z" compatibility handling for these fields, you can remove it.
Changelog
2026-09-16: Knowledge base permission refactoring: four behavior changes
This refactoring changes knowledge base authorization from "allow unless explicitly denied" to default-deny, with roles viewer < editor < owner. The following four changes affect the observable behavior of existing callers. Confirm them before you upgrade (items 1 and 4 are relaxations; items 2 and 3 are tightened restrictions or code changes):
Public knowledge base default level raised to writable.
visibility=workspace(aliaspublic): the default role of any member in the workspace iseditor. Members can not only search and read, but also upload and delete documents in the knowledge base (including documents uploaded by others). If this is not desired, change the knowledge base toprivateand grant permissions as needed, or add an explicitviewerrow for specific members (see "Knowledge base permissions (ACL)" in the Knowledge section).ACL row expiration is a hard invalidation. An authorization row with an expired
expires_atis treated as nonexistent: callers fall back directly to the knowledge base default level (public knowledge base:editor/ private knowledge base: no access), and no residual "once authorized" permissions are retained. Expired rows still appear in the ACL list for easier cleanup.Cross-workspace responses on the graph data plane changed from 403 to 404.
GET /v1/knowledge-bases/{kbId}/graph/nodes|edgespreviously returned403, and now returns404when the knowledge base ID (kbId) belongs to another workspace or does not exist (without leaking the existence of the knowledge base ID). Clients that fall back on the 403 branch must also accept 404.User Key knowledge base deletion no longer recognizes only the creator.
DELETE /v1/knowledge/bases/{id}previously applied a separate "creator" check for User Keys: a member explicitly grantedownerreceivedmy_role=ownerbut still could not delete the knowledge base. This is now unified into the ACL decision chain:owneris sufficient to delete.
Insufficient permissions uniformly returns 403 + code=40303 (ACL_FORBIDDEN). A knowledge base ID that belongs to another workspace or does not exist uniformly returns 404. Knowledge base detail and list responses add my_role / role_source / can_manage_acl, so callers can render entries accordingly instead of probing permissions by "trying once to see whether a 403 is returned".
API catalog
Strategy — includes policy hooks (Hooks)
Audit and compliance— audit logs, audit policies, data erasure
Context
POST /v1/context/ingest
Writes a batch of conversation messages to a specified session and triggers subsequent memory extraction and deduplication.
Request
Request parameters
Parameter | Type | Required | Description |
session_id | string | Yes | Session ID (external session_id), 1 to 128 characters |
user_id | string | No | End user ID (mem0 user_id). Defaults to the |
messages | array | Yes | Message list. Up to 100 messages per batch. |
messages[].role | string | Yes |
|
messages[].content | string | Yes | Message text. Up to 65,535 characters. |
messages[].tool_name | string | Conditional | Required only when |
messages[].metadata | object | No | Message-level metadata |
messages[].resource_ids | string[] | No | Bound multimodal resource IDs. Up to 10. |
messages[].timestamp | long | No | Time when the message occurred (epoch seconds) |
metadata | object | No |
|
agent_id | string | No | Agent identifier, persisted to session.agent_id |
extract_mode | string | No | Defaults to the global configuration.
|
custom_instructions | string | No | Request-level distillation instruction (valid only in |
timestamp | long | No | Time when the memory occurred ( |
Request example
curl -X POST http://<host>:4040/v1/context/ingest \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"session_id": "sess-2026-08-19-001",
"user_id": "alice",
"messages": [
{"role": "user", "content": "I have been learning Rust recently"},
{"role": "assistant", "content": "Rust is a systems programming language focused on safety and performance."}
],
"extract_mode": "eager"
}'Response
Response parameters
Field | Type | Description |
session_id | string | External session ID (the |
session_internal_id | long | Internal database auto-increment ID (may be |
message_ids | array | List of written message references |
message_ids[].id | long | Internal message ID |
message_ids[].seq | int | Message sequence number |
event_id | long | Memory extraction event ID ( |
Response example
{
"code": 0, "msg": "ok",
"data": {
"session_id": "sess-2026-08-19-001",
"session_internal_id": 42,
"message_ids": [
{"id": 1, "seq": 1},
{"id": 2, "seq": 2}
],
"event_id": 789
},
"trace_id": "trace-001"
}POST /v1/context/assemble
Assembles LLM-facing context PromptBlocks based on a query and returns recalled memories and source references. This is the core endpoint that agents call on every conversation turn.
Request
Request parameters
Parameter | Type | Required | Description |
session_id | string | Conditional | At least one of this parameter and |
user_id | string | Conditional | At least one of this parameter and |
query | string | No | Query text. An empty value triggers |
token_budget | integer | No | Token budget. Valid values: [1, 32000]. |
strategy_id | long | No | Overrides the workspace default policy |
recall.top_k | integer | No | Memory recall top_k. Valid values: [1, 50]. |
recall.filters | object | No | ES filter key-value pairs |
recall.cosine_threshold | double | No | Cosine similarity lower bound. Valid values: [0, 1]. |
include | string[] | No | PromptBlock types to forcibly include |
exclude | string[] | No | PromptBlock types to forcibly exclude |
recent_messages_fetch_limit | integer | No | Upper limit of recent messages to fetch. Valid values: [1, 1000]. |
bootstrap_top_l0 | integer | No | Cold-start L0 entity card top-N. Valid values: [0, 100]. |
bootstrap_top_l1 | integer | No | Cold-start L1 fact top-N. Valid values: [0, 100]. |
budget_allocator | object | No | Token budget allocation ratio overrides |
budget_allocator.memory_ratio | double | No | Memory share |
budget_allocator.knowledge_ratio | double | No | Knowledge base share |
budget_allocator.history_ratio | double | No | History share |
budget_allocator.system_ratio | double | No | System share |
knowledge_base_ids | long[] | No | List of knowledge base IDs that scope the knowledge search. Up to 100 IDs after deduplication. A list containing |
knowledge_top_k | integer | No | Knowledge search top_k. Valid values: [1, 50]. |
include_multimodal | boolean | No | Specifies whether to inject multimodal PromptBlocks. Default: |
multimodal_max_images | integer | No | Maximum number of multimodal blocks injected per call. Valid values: [0, 50]. |
context_scope | string | No |
|
include_memories | boolean | No | Fine-grained override: forcibly enable or disable the memory dimension |
include_knowledge | boolean | No | Fine-grained override: forcibly enable or disable the knowledge base dimension |
include_wiki | boolean | No | Specifies whether to inject the Wiki page lane (tag |
inherit_summary | boolean | No | Specifies whether to inherit the previous |
inherit_from_session_id | string | No | Explicitly specifies which |
Request example
curl -X POST http://<host>:4040/v1/context/assemble \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"session_id": "sess-2026-08-19-001",
"query": "What is Rust's ownership mechanism?",
"token_budget": 4096,
"recall": {"top_k": 10, "cosine_threshold": 0.3}
}'Response
Response parameters
Field | Type | Description |
prompt_blocks | array | PromptBlock list. The agent should concatenate the blocks into the LLM prompt in order. |
prompt_blocks[].role | string |
|
prompt_blocks[].tag | string | Type tag ( |
prompt_blocks[].content | string | Body text |
prompt_blocks[].tokens | int | Number of tokens consumed by this block |
prompt_blocks[].sources | string[] | List of source references (such as |
token_used | int | Total number of tokens actually consumed |
token_budget | int | Token budget passed in the request |
trace_id | string | Trace ID |
stats | object | Assembly statistics |
stats.recall_total | int | Total number of recalled memories |
stats.recall_kept | int | Number of memories retained after trimming |
stats.messages_total | int | Total number of messages |
stats.messages_kept | int | Number of messages retained after trimming |
stats.took_ms | long | Assembly latency (ms) |
stats.multimodal_kept | int | Number of multimodal blocks |
Response example
{
"code": 0, "msg": "ok",
"data": {
"prompt_blocks": [
{"role": "system", "tag": "user_profile", "content": "The user is learning Rust...", "tokens": 42, "sources": ["memory:mem_1_2_abc"]},
{"role": "system", "tag": "memory_recall", "content": "- The user likes systems programming\n- ...", "tokens": 128, "sources": ["memory:mem_1_2_def"]},
{"role": "system", "tag": "recent_history", "content": "...", "tokens": 256, "sources": []}
],
"token_used": 426,
"token_budget": 4096,
"trace_id": "trace-001",
"stats": {
"recall_total": 5,
"recall_kept": 3,
"messages_total": 20,
"messages_kept": 10,
"took_ms": 142,
"multimodal_kept": 0
}
}
}POST /v1/context/compact
Incrementally compresses the session context, folding the earliest active messages into leaf summaries by chunk_size.
Request
Request parameters
Parameter | Type | Required | Description |
session_id | string | Yes | Session ID |
keep_recent_n | integer | No | Number of recent active messages to retain. Default: 50. Valid values: [1, 500]. |
chunk_size | integer | No | Number of messages folded per operation. Default: 20. Valid values: [5, 200]. |
model_hint | string | No | Overrides the default LLM model |
Request example
curl -X POST http://<host>:4040/v1/context/compact \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"session_id": "sess-2026-08-19-001", "keep_recent_n": 30}'Response
Response parameters
Field | Type | Description |
session_id | string | External session ID |
session_internal_id | long | Internal database auto-increment ID |
summary_message_id | long | ID of the generated summary message |
compacted_message_count | int | Number of compressed messages |
summary_token_count | int | Number of summary tokens |
active_message_count | int | Number of messages still active after compression |
depth | integer | Compression depth (0 for leaf summaries) |
source_seq_range | int[] | Sequence range of compressed messages [firstSeq, lastSeq] |
Response example
{
"code": 0, "msg": "ok",
"data": {
"session_id": "sess-2026-08-19-001",
"session_internal_id": 42,
"summary_message_id": 101,
"compacted_message_count": 20,
"summary_token_count": 180,
"active_message_count": 50,
"depth": 0,
"source_seq_range": [1, 20]
}
}POST /v1/context/flush
Persists short-term context as long-term memories and events.
Request
Request parameters
Parameter | Type | Required | Description |
session_id | string | Yes | Session ID |
compact_first | boolean | No | Specifies whether to run compact before persisting. Default: true. |
keep_recent_n | integer | No | Number of recent messages retained during compact. Default: 20. Valid values: [1, 500]. |
Request example
curl -X POST http://<host>:4040/v1/context/flush \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"session_id": "sess-2026-08-19-001", "compact_first": true}'Response
Response parameters
Field | Type | Description |
session_id | string | External session ID |
session_internal_id | long | Internal database auto-increment ID |
status | string | Session status ( |
flushed_at | string | Persist time (ISO-8601) |
compact_summary | object | Summary of the compact run before persisting (populated when compact_first=true). Same structure as the compact response. |
evolve_event_id | long | Memory distillation event ID |
Response example
{
"code": 0, "msg": "ok",
"data": {
"session_id": "sess-2026-08-19-001",
"session_internal_id": 42,
"status": "flushed",
"flushed_at": "2026-08-19T10:30:00Z",
"compact_summary": {
"session_id": "sess-2026-08-19-001",
"compacted_message_count": 20,
"summary_token_count": 180,
"active_message_count": 30
},
"evolve_event_id": 1001
}
}POST /v1/context/lifecycle
Reports context lifecycle events and drives the session state machine by dispatching on action. action=start creates or opens a session and returns the first-screen assembled PromptBlocks; action=end closes the session and returns closing statistics.
Request
Request parameters
Parameter | Type | Required | Description |
action | string | Yes | start / end |
session | object | Yes (start) | Required when action=start. Describes the metadata of the new session. |
session.external_id | string | Yes (start) | External session ID. Required when action=start. Valid values: |
session.user_id | string | No | End user ID (mem0 user_id), stored in session.user_id. Omitted or null means unspecified; when non-empty, it must match |
session.agent_id | string | No | Agent identifier. Up to 128 characters. |
session.title | string | No | Session title. Up to 256 characters. |
session.metadata | object | No | Custom metadata |
session_id | string | Conditional | Required when action=end. Session ID. |
user_id | string | No | Optional when action=end.
|
query | string | No | Optional when action=start. Works with token_budget to determine the first prompt assembly. |
token_budget | integer | No | Optional when action=start. Token budget [1, 32000]. |
Request example
start
curl -X POST http://<host>:4040/v1/context/lifecycle \ -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \ -d '{ "action": "start", "session": {"external_id": "sess-2026-08-19-001", "user_id": "alice", "agent_id": "my-agent"}, "query": "Continue the previous Rust discussion", "token_budget": 4096 }'end
curl -X POST http://<host>:4040/v1/context/lifecycle \ -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \ -d '{"action": "end", "session_id": "sess-2026-08-19-001", "user_id": "alice"}'
An Owner Key represents the workspace; a specific end user cannot be inferred from the key alone.
If you used
session.user_idat start, pass the same top-leveluser_idat end so that the close and flush operations land on the same end-user/member identity.Only when
start/endomit this value do both actions fall back to the member bound to the key. User Keys remain subject to the own-user check; an empty string is not accepted as "missing".
Response
Response parameters
Field | Type | Description |
session_id | string | External session ID |
session_internal_id | long | Internal database auto-increment ID |
prompt_blocks | array | Returned only for action=start. Same structure as /v1/context/assemble. |
token_used / token_budget / trace_id / stats | - | Returned only for action=start. Same semantics as assemble. |
message_count | int | Returned only for action=end. Cumulative number of messages in the session. |
total_tokens | long | Returned only for action=end. Cumulative tokens of the session. |
event_id | long | Returned only for action=end. ID of the event generated at closing. |
Null fields are not output (NON_NULL): start returns only assembly-related fields, and end returns only closing-related fields.
Response example
start, including assembly results:
{ "code": 0, "msg": "ok", "data": { "session_id": "sess-2026-08-19-001", "session_internal_id": 42, "prompt_blocks": [ {"role": "system", "tag": "user_profile", "content": "The user is learning Rust...", "tokens": 42, "sources": ["memory:mem_1_2_abc"]} ], "token_used": 42, "token_budget": 4096, "trace_id": "trace-001", "stats": {"recall_total": 3, "recall_kept": 1, "messages_total": 0, "messages_kept": 0, "took_ms": 88} } }end, including closing statistics:
{ "code": 0, "msg": "ok", "data": {"session_id": "sess-2026-08-19-001", "message_count": 45, "total_tokens": 12800, "event_id": 789} }
POST /v1/context/dialectic
Performs dialectic analysis on context recall results and outputs multi-perspective evidence and judgment. Provide at least one of session_id or user_id (workspace-wide recall is prohibited).
Request
Request parameters
Parameter | Type | Required | Description |
query | string | Yes | Query text |
session_id | string | Conditional | Session ID (optional; binds the recall scope). At least one of session_id and user_id is required. |
user_id | string | Conditional | User ID (optional; limits recall to the user's isolation scope). At least one of session_id and user_id is required. |
depth | string | No | Analysis depth: |
top_k | integer | No | Return top-k [1, 20]. Default: 5. |
scope | string | No | Reserved field (for future |
Request example
curl -X POST http://<host>:4040/v1/context/dialectic \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"query": "What are the pros and cons of Rust's ownership model?",
"user_id": "alice",
"depth": "standard",
"top_k": 5
}'Response
Response parameters
Field | Type | Description |
depth | string | Analysis depth actually used |
hits_count | int | Number of recall hits |
latency_ms | long | Analysis latency (ms) |
prompt_blocks | array | PromptBlock list. Same structure as |
Response example
{
"code": 0, "msg": "ok",
"data": {
"depth": "standard",
"hits_count": 5,
"latency_ms": 230,
"prompt_blocks": [
{"role": "system", "tag": "dialectic_analysis", "content": "## Multi-perspective analysis\n\n**Positive evidence**: ...\n**Counter evidence**: ...", "tokens": 320, "sources": ["memory:mem_1_2_abc"]}
]
}
}Session
POST /v1/sessions
Creates a session or retrieves an existing one (idempotent). If the externalId already exists, the existing session is returned.
Request
Request parameters
Parameter | Type | Required | Description |
external_id | string | Yes | External session ID. Valid values: 1 to 128 characters. |
agent_id | string | No | Agent identifier. Up to 128 characters. |
title | string | No | Session title. Up to 256 characters. |
metadata | string | No | JSON-format metadata string |
user_id | string | No | End user ID. Up to 128 characters. |
session_key | string | No | Thread key used for the cross-session inheritance chain. Up to 64 characters. |
parent_session_id | string | No | external_id of the explicit parent session. Up to 128 characters. |
Request example
curl -X POST http://<host>:4040/v1/sessions \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"external_id": "sess-2026-08-19-001", "agent_id": "my-agent", "user_id": "alice"}'Response
Response example
{
"code": 0, "msg": "ok",
"data": {
"session": {
"id": 1,
"external_id": "sess-2026-08-19-001",
"agent_id": "my-agent",
"user_id": "alice",
"status": "open",
"title": null,
"message_count": 0,
"total_tokens": 0,
"session_key": null,
"parent_session_id": null,
"created_at": "2026-08-19T10:00:00Z",
"updated_at": "2026-08-19T10:00:00Z",
"closed_at": null
},
"created": true
}
}session.id is the internal database auto-increment primary key (Long type); session.external_id is the external session identifier passed in by the caller.
GET /v1/sessions
Lists sessions of the current tenant by page. A User Key sees only its own sessions; Owner Key sees the entire workspace.
Request
Request parameters
Parameter | Type | Required | Description |
agent_id | string | No | Filter by agent |
status | string | No | Filter by status ( |
page | integer | No | Page number (starting from 0). Default: 0. |
page_size | integer | No | Page size. Default: 20. Maximum: 200. |
Request example
curl "http://<host>:4040/v1/sessions?status=open&page_size=10" \
-H "X-API-Key: $API_KEY"Response notes: returns Box<List<SessionDto>>. Pagination information is exposed through the X-Total-Count, X-Page, X-Page-Size, X-Has-More response header.
GET /v1/sessions/{id}
Retrieves details of a specified session.{id} is the externalId or the internal ID.
Request
Request example
curl "http://<host>:4040/v1/sessions/sess-2026-08-19-001" \
-H "X-API-Key: $API_KEY"PATCH /v1/sessions/{id}
Partially updates session attributes (such as title or metadata).
Request
Request example
curl -X PATCH http://<host>:4040/v1/sessions/sess-2026-08-19-001 \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"title": "Rust study session"}'POST /v1/sessions/{id}/messages
Appends a batch of messages to a session.
Request
Request parameters
Parameter | Type | Required | Description |
messages | array | Yes | Message list. Each message contains |
messages[].role | string | Yes |
|
messages[].content | string | Yes | Message text |
messages[].toolName | string | Conditional | Required when |
Request example
curl -X POST http://<host>:4040/v1/sessions/sess-2026-08-19-001/messages \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "What is a borrow checker?"}]}'GET /v1/sessions/{id}/messages
Pulls session messages incrementally by since_seq or in reverse order by before_seq.
Request
Request parameters
Parameter | Type | Required | Description |
since_seq | integer | No | Start pulling after this sequence number. Default: 0. |
before_seq | integer | No | Pull messages before this sequence number |
order | string | No | asc (default) / desc |
limit | integer | No | Number of entries to return. Default: 50. Maximum: 200. |
page_size | integer | No | Compatible alias for limit. Valid values: [1, 200]. |
size | integer | No | Compatible alias for limit. Valid values: [1, 200]. |
limit, page_size, size Only one may be provided; providing multiple returns 400.
Request example
curl "http://<host>:4040/v1/sessions/sess-2026-08-19-001/messages?since_seq=0&limit=50" \
-H "X-API-Key: $API_KEY"Response
Returns Box<MessagePage>, MessagePage contains items (message array) and total (total count). The response headers contain X-Total-Count.
PUT /v1/sessions/{id}/close
Closes a session and optionally generates a handoff summary.
Request
Request parameters
Parameter | Type | Required | Description |
generate_summary | boolean | No | Specifies whether to generate a handoff summary. Default: |
Request example
curl -X PUT http://<host>:4040/v1/sessions/sess-2026-08-19-001/close \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"generate_summary": true}'DELETE /v1/sessions/{id}
Physically deletes a specified session and its messages.
Request
Request example
curl -X DELETE http://<host>:4040/v1/sessions/sess-2026-08-19-001 \
-H "X-API-Key: $API_KEY"GET /v1/sessions/{id}/inheritance
Returns the inheritance chain of a session, showing cross-session context transfer relationships.
Request
Request example
curl "http://<host>:4040/v1/sessions/sess-2026-08-19-001/inheritance" \
-H "X-API-Key: $API_KEY"GET /v1/sessions/stats
Returns aggregated session statistics (total/active/closed/inherited/cumulative tokens).
Request
Request example
curl "http://<host>:4040/v1/sessions/stats" -H "X-API-Key: $API_KEY"Response
Response example
{
"code": 0, "msg": "ok",
"data": {
"total": 5,
"open": 4,
"closed": 1,
"inherited": 0,
"total_tokens": 32
}
}GET /v1/sessions/{id}/token-stats
Returns token usage statistics of a specified session, including per-role token counts and the tokenizer used.
Request
Request example
curl "http://<host>:4040/v1/sessions/3/token-stats" -H "X-API-Key: $API_KEY"Response
Response example
{
"code": 0, "msg": "ok",
"data": {
"total_tokens": 26,
"message_count": 4,
"by_role": {
"user": 23,
"assistant": 3
},
"token_estimator": "cl100k_base"
}
}Memory
POST /v1/memories
Mem0-compatible memory creation API. Automatically triggers deduplication and distillation.
Request
Request parameters
Parameter | Type | Required | Description |
messages | array | Yes | Conversation message list ( |
user_id | string | No | End user ID. When omitted, it is derived from the API key identity. |
agent_id | string | No | Agent identifier |
run_id | string | No | Run instance identifier |
metadata | object | No | Custom metadata key-value pairs |
custom_instructions | string | No | Request-level distillation instruction. Up to 8,192 characters. |
timestamp | long | No | Occurrence time (epoch seconds) |
Request example
curl -X POST http://<host>:4040/v1/memories \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "user", "content": "I like using the Vim editor"},
{"role": "assistant", "content": "OK, I noted your preference for Vim."}
],
"user_id": "alice"
}'Response
Response example
{"results": [{"id": "mem_1_2_abc123", "memory": "User prefers using the Vim editor", "event": "ADD"}]}POST /v3/memories/add
V3 memory write. Returns a Box response containing memory_id / event_id / created. Request parameters: same as POST /v1/memories.
Request
Request example
curl -X POST http://<host>:4040/v3/memories/add \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "My tech stack is Java + Spring Boot"}],
"user_id": "alice"
}'Response
Response example
{"code": 0, "msg": "ok", "data": {"memory_id": "mem_1_2_def456", "event_id": "evt_789", "created": true}}POST /v3/memories
Mem0 v1.1 SDK-compatible memory list API. Entity filter parameters (user_id / agent_id / run_id) can be placed at the top level of the request body or in the filters map. Pagination is controlled through query parameters. Returns the same pagination envelope as Mem0 cloud (bare JSON, without the Box wrapper).
Request
Request parameters
Parameter | Position | Type | Required | Description |
user_id / userId | body | string | No | Top-level user filter. The server resolves it to a |
agent_id / agentId | body | string | No | Top-level Agent filter |
run_id / runId | body | string | No | Top-level Run filter |
filters | body | object | No | Entity filter map. Accepts the |
page | query | integer | No | Page number (starting from 1). Default: 1. |
page_size | query | integer | No | Page size. Default: 100. Valid values: [1, 200]. |
Request example
curl -X POST "http://<host>:4040/v3/memories?page=1&page_size=50" \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"user_id": "alice"}'Response
Response parameters
Field | Type | Description |
count | long | Total number of matched memories |
next | string | URL of the next page; null when there are no more pages |
previous | string | URL of the previous page; null on the first page |
results | array | List of memories on this page (Mem0 memory structure) |
Response example
{
"count": 156,
"next": "/v3/memories/?page=2&page_size=50",
"previous": null,
"results": [
{"id": "mem_1_2_abc123", "memory": "User prefers using the Vim editor", "metadata": {}}
]
}POST /v3/entity_cards/{id}/edit
Performs a partial update on the role / preferences / attributes of an L0 entity card. Only the fields provided are overwritten; fields not provided (null) remain unchanged.
Request
Request parameters
Parameter | Type | Required | Description |
role | string | No | Entity role (such as the role description of a |
preferences | string[] | No | Preference list |
attributes | object | No | Open attribute map. Keys depend on the card type ( |
Request example
curl -X POST http://<host>:4040/v3/entity_cards/card_1_2_alice/edit \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"role": "Senior backend engineer", "preferences": ["Java", "Spring Boot"]}'Response
Response parameters
Field | Type | Description |
card_id | string | Entity card ID |
metadata | object | Complete merged entity card metadata (including |
Response example
{
"code": 0, "msg": "ok",
"data": {
"card_id": "card_1_2_alice",
"metadata": {"role": "Senior backend engineer", "preferences": ["Java", "Spring Boot"], "attributes": {}}
}
}GET /v1/memories
Lists memories by page, filtered by user_id / agent_id / run_id. Mem0-compatible bare JSON array response.
Pagination window constraint: offset + limit must not exceed 10000; exceeding it returns 400.
Request
Request parameters
Parameter | Type | Required | Description |
user_id | string | No | End user filter |
agent_id | string | No | Agent filter |
run_id | string | No | Run instance filter |
doc_type | string | No | Filter by type ( |
limit | integer | No | Number of entries to return. Default: 100. Maximum: 200 (mutually exclusive with page_size). |
offset | integer | No | Offset. Default: 0 (mutually exclusive with page). |
page | integer | No | Page number (starting from 0). For backward compatibility. |
page_size | integer | No | Page size. For backward compatibility. |
start_date | string | No | Lower bound of update time (ISO-8601) |
end_date | string | No | Upper bound of update time (ISO-8601) |
Request example
curl "http://<host>:4040/v1/memories?user_id=alice&limit=20" \
-H "X-API-Key: $API_KEY"GET /v1/memories/{id}
Retrieves details of a single memory by memory_id.
Request
Request example
curl "http://<host>:4040/v1/memories/mem_1_2_abc123" -H "X-API-Key: $API_KEY"PUT /v1/memories/{id}
Updates the content or metadata of a specified memory.
Request
Request example
curl -X PUT http://<host>:4040/v1/memories/mem_1_2_abc123 \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"text": "User prefers using the Neovim editor"}'DELETE /v1/memories/{id}
Deletes a single memory.
Request
Request example
curl -X DELETE http://<host>:4040/v1/memories/mem_1_2_abc123 -H "X-API-Key: $API_KEY"Response
Response example
{"message": "memory deleted", "id": "mem_1_2_abc123"}DELETE /v1/memories/clear
Bulk cleanup of memories, deleting by scope. This is an irreversible operation and requires confirm=true.
Request
Request parameters
Parameter | Type | Required | Description |
confirm | boolean | Yes | Must be |
scope | string | No |
|
before_date | string | Conditional | Required when |
session_ids | string[] | Conditional | Required when |
Request example
curl -X DELETE http://<host>:4040/v1/memories/clear \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"confirm": true, "scope": "all"}'Response
Response example
{"message": "memories cleared", "deleted": 42, "scope": "all"}POST /v1/memories/search
V1 memory semantic search. L1-only (no L2 source-text BM25 recall), serving as the Mem0 baseline path.
Request
Request parameters
Parameter | Type | Required | Description |
query | string | Yes | Query statement |
user_id | string | No | End user filter |
agent_id | string | No | Agent filter |
run_id | string | No | Run instance filter |
filters | object | No | Additional ES filter |
limit | integer | No | Number of entries to return [1, 200]. Default: 10 (compatible with top_k). |
vector_only | boolean | No | true disables the BM25 lexical channel for pure vector kNN |
cosine_threshold | float | No | Cosine similarity lower bound [0, 1] (compatible with threshold). When omitted, the server injects the default threshold 0.2. |
exclude_l2 | boolean | No | true excludes L2 placeholders |
When an Owner Key calls the search endpoint, it must explicitly provide user_id; otherwise, 400 VALIDATION_FAILED is returned.
Request example
curl -X POST http://<host>:4040/v1/memories/search \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"query": "What editor does the user like?", "user_id": "alice", "limit": 5}'Response
Response example
[
{"id": "mem_1_2_abc123", "memory": "User prefers using the Vim editor", "score": 0.92, "metadata": {}},
{"id": "mem_1_2_def456", "memory": "The user uses Neovim plugins", "score": 0.85, "metadata": {}}
]POST /v2/memories/search
V2 memory search with L1 + L2 source-text BM25 recall. Compared with V1, it additionally performs BM25 search over L2 source texts, suitable for scenarios that require source traceability.
Request
Request parameters
Request parameters are the same as V1, with additional support for:
Parameter | Type | Required | Description |
include_l2 | boolean | No | Explicitly controls the L2 switch (enabled by default on the server when not provided) |
V2 is the recommended search endpoint.include_l2 When not provided, it is determined by the server include-l2-default configuration (default: true).cosine_threshold When omitted, the server injects the default threshold 0.2. Owner Key calls must explicitly provide user_id; otherwise, 400 VALIDATION_FAILED is returned.
Request example
curl -X POST "$HOST/v2/memories/search"POST /v3/memories/search
V3 search in the Mem0 v1.1 SDK-compatible format.user_id entity parameters are placed in the filters map.
Request
Request parameters
Parameter | Type | Required | Description |
query | string | Yes | Query statement |
filters | object | No | Accepts the |
limit | integer | No | Number of entries to return |
vector_only | boolean | No | Pure vector mode |
include_l2 | boolean | No | L2 switch |
cosine_threshold | float | No | Cosine lower bound. When omitted, the server injects the default threshold 0.1. |
When an Owner Key calls the search endpoint, it must explicitly provide user_id; otherwise, 400 VALIDATION_FAILED is returned. In V3, the user_id is carried in the filters map (filters.user_id, and also accepts filters.userId and the top-level user_id). When the top-level and filters values conflict, 400 VALIDATION_FAILED is also returned.
Request example
curl -X POST http://<host>:4040/v3/memories/search \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"query": "The user's tech stack", "filters": {"user_id": "alice"}, "limit": 10}'Response
Response example
{"results": [{"id": "mem_1_2_def456", "memory": "The user's tech stack is Java + Spring Boot", "score": 0.88}]}POST /v1/memories/{id}/pin
Pins a memory to raise its recall priority.
Request
Request example
curl -X POST http://<host>:4040/v1/memories/mem_1_2_abc123/pin -H "X-API-Key: $API_KEY"POST /v1/memories/{id}/unpin
Unpins a memory.
Request
Request example
curl -X POST http://<host>:4040/v1/memories/mem_1_2_abc123/unpin -H "X-API-Key: $API_KEY"POST /v3/memories/{id}/invalidate
Marks a memory as invalid (invalid_at=now()) so that it is no longer recalled by search.
Request
Request example
curl -X POST http://<host>:4040/v3/memories/mem_1_2_abc123/invalidate -H "X-API-Key: $API_KEY"Response
Response example
{"code": 0, "msg": "ok", "data": {"id": "mem_1_2_abc123", "invalid_at": "2026-08-19T10:30:00Z"}}POST /v3/memories/{id}/promote
Promotes session-scoped memories to user scope so that they persist across sessions.
Request
Request example
curl -X POST http://<host>:4040/v3/memories/mem_1_2_abc123/promote -H "X-API-Key: $API_KEY"GET /v1/memories/{id}/history
Returns all version change records of a specified memory. Mem0-compatible bare JSON array.
Request
Request example
curl "http://<host>:4040/v1/memories/mem_1_2_abc123/history" -H "X-API-Key: $API_KEY"Response
Response example
[
{"id": 1, "version": 1, "event_type": "ADD", "content_after": "User prefers Vim", "created_at": "2026-08-19T10:00:00Z"},
{"id": 2, "version": 2, "event_type": "UPDATE", "content_before": "User prefers Vim", "content_after": "User prefers Neovim", "created_at": "2026-08-19T11:00:00Z"}
]GET /v1/memories/{id}/source-texts
Views the L2 original conversation messages that produced this L1 memory (traceability).
Request
Request example
curl "http://<host>:4040/v1/memories/mem_1_2_abc123/source-texts" -H "X-API-Key: $API_KEY"GET /v1/memories/distill-prompt
Queries the distillation rule configuration and effective status of the current member. OWNER permissions are not required; every authenticated member can manage their own distillation rules.
Request
Request parameters
Parameter | Type | Description |
configured | boolean | Whether a configuration row exists |
custom_prompt | string | Configured custom distillation prompt. When not configured: |
enabled | boolean | Business switch of the configuration row |
rollout_enabled | boolean | Whether the feature flag is rolled out to this workspace |
effective | boolean | Whether it actually takes effect ( |
effective_source | string | Effective source: |
updated_at | string | Last update time (ISO-8601) |
member_id | long | Owning member ID (workspace default behavior: |
Request example
curl "http://<host>:4040/v1/memories/distill-prompt" -H "X-API-Key: $API_KEY"Response
Response parameters
Field | Type | Description |
configured | boolean | Whether a configuration row exists |
custom_prompt | string | Configured custom distillation prompt; null when not configured |
enabled | boolean | Business switch of the configuration row |
rollout_enabled | boolean | Whether the feature flag is rolled out to this workspace |
effective | boolean | Whether it actually takes effect ( |
effective_source | string | Effective source: |
updated_at | string | Last update time (ISO-8601) |
member_id | long | Owning member ID (null for workspace default behavior) |
PUT /v1/memories/distill-prompt
Creates or updates the distillation rules of the current member and returns the complete updated state.
Request
Request parameters
Parameter | Type | Required | Description |
custom_prompt | string | Yes | Custom distillation prompt. Up to 8,192 characters. |
enabled | boolean | No | Business switch. Default: |
Request example
curl -X PUT http://<host>:4040/v1/memories/distill-prompt \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"custom_prompt": "Focus on extracting the user's tech stack preferences and workflow habits", "enabled": true}'Response
Same as GET /v1/memories/distill-prompt.
DELETE /v1/memories/distill-prompt
Deletes the distillation rules of the current member (idempotent). After deletion, it falls back to the workspace default or built-in prompt.
Request
Request example
curl -X DELETE http://<host>:4040/v1/memories/distill-prompt -H "X-API-Key: $API_KEY"Response
Response example
{"code": 0, "msg": "ok", "data": null}POST /v3/memories/{id}/feedback
Submits memory feedback signals (thumbs up/down / explicit/implicit feedback). Negative feedback triggers the Dream counter-evidence pipeline.
Request
Request parameters
Parameter | Type | Required | Description |
signal_type | string | Yes | Feedback types (four):
|
channel | string | No | Feedback channel identifier |
session_id | string | No | Associated session ID |
recall_path | string | No | Recall path: |
corrected_content | string | No | Correction provided by the user |
Signal type descriptions
Type | Effect |
| Explicit thumbs-up from the user/agent. hotness +0.2 |
| Explicit thumbs-down from the user/agent. hotness -0.2 and triggers the Dream counter-evidence pipeline |
| Implicit positive signal (reuse, dwell). hotness +0.1 |
| Implicit negative signal (skip, reject). hotness -0.1 |
Request example
curl -X POST http://<host>:4040/v3/memories/mem_1_2_abc123/feedback \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"signal_type": "negative_explicit", "recall_path": "vector", "corrected_content": "The user has switched from Vim to Neovim"}'GET /v3/memories/{id}/feedback
Queries the feedback history of a specified memory by page.
Request
page (starting from 0; default: 0) and size (default: 20; maximum: 100).
Request example
curl -X GET "$HOST/v3/memories/{id}/feedback"GET /v1/memories/stats
Returns aggregated memory statistics (total/facts/entities/source texts). Box-wrapped response.
Request
Request parameters
Query parameters: user_id, agent_id, run_id (all optional).
Request example
curl "http://<host>:4040/v1/memories/stats?user_id=alice" -H "X-API-Key: $API_KEY"Response
Response example
{"code": 0, "msg": "ok", "data": {"total": 156, "facts": 120, "entities": 12, "sources": 24}}GET /v1/memories/relations
Queries the memory relationship graph (nodes + edges) of a specified entity for visualizing associations between memories. Returns the relationship network centered on entity_id within the specified depth.
Request
Request parameters
Parameter | Type | Required | Description |
entity_id | string | Yes | Starting entity ID |
depth | integer | No | Traversal depth. Default: 1. |
include_all_members | boolean | No | Specifies whether to include relationships of all workspace members (default: false; can be enabled only by Owner Key; User Key passing true returns 403) |
Request example
curl "http://<host>:4040/v1/memories/relations?entity_id=alice&depth=2" -H "X-API-Key: $API_KEY"Response
Response example
{
"code": 0, "msg": "ok",
"data": {
"nodes": [
{"id": "alice", "label": "alice"},
{"id": "rust-project", "label": "rust-project"}
],
"edges": [
{"from": "alice", "to": "rust-project", "label": "works_on"}
]
}
}GET /v1/memory_promotions
Lists memory promotion records of the current member by page (including manually initiated and scheduled promotions).
Request
Request parameters
Parameter | Type | Required | Description |
page | integer | No | Page number (starting from 0). Default: 0. |
pageSize | integer | No | Page size. Default: 20. |
status | string | No | Filter by status: |
memberId | long | No | Filter by member (Owner Key available; User Key automatically limited to self) |
Request example
curl "http://<host>:4040/v1/memory_promotions?page=0&pageSize=10" -H "X-API-Key: $API_KEY"Response
Response example
{
"code": 0, "msg": "ok",
"data": {
"content": [
{
"id": 1,
"topic": "Summarize this week's architecture decisions",
"title": null,
"status": "generating",
"source_type": "scheduled",
"target_member_id": 4,
"memory_count": 0,
"verification_degraded": false,
"created_at": "2026-09-16T01:03:20Z",
"updated_at": "2026-09-16T01:03:20Z"
}
],
"total_elements": 1,
"total_pages": 1
}
}status values:
generating(generating),pending_review(pending approval),approved(approved),rejected(rejected).source_type values:
manual(manually initiated),scheduled(triggered by a scheduled task).
POST /v1/memory_promotions/generate
Manually initiates a memory promotion run that distills the current member's memories into knowledge document candidates based on topic.
Request
Request parameters
Parameter | Type | Required | Description |
topic | string | Yes | Promotion topic / prompt |
target_kb_id | long | No | ID of the target knowledge base for automatic approval |
Request example
curl -X POST "http://<host>:4040/v1/memory_promotions/generate" \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"topic": "Summarize this week's architecture decisions", "target_kb_id": 3}'GET /v1/memory_promotions/{promotionId}
Retrieves details of a specified promotion record.
Request
Request example
curl "http://<host>:4040/v1/memory_promotions/1" -H "X-API-Key: $API_KEY"Appendix: V1/V2/V3 search version differences
Version | Path | L2 source-text recall | Response format | Use case |
V1 |
| Disabled (L1-only) | Bare array | Mem0 baseline compatible |
V2 |
| Enabled by default | Bare array | Recommended; supports source traceability |
V3 |
| Enabled by default |
| Mem0 v1.1 SDK compatible |
Knowledge
Knowledge base permissions (ACL)
All four endpoints require the owner permission of this knowledge base: Owner Key passes globally; the knowledge base creator and members granted the owner role are delegated administrators; other members get 403 (40303); a kbId in another workspace or nonexistent returns 404.
Method | Path | Description |
|
| Grants an explicit permission row (repeated grants to the same subject return 409; not an overwrite) |
|
| Lists all explicit permission rows of this knowledge base (including expired rows) |
|
| Authorization candidates: members of this workspace plus their existing explicit row roles |
|
| Revokes a permission row |
Three semantics you must understand first:
An explicit row is the final decision, not a lower bound. Writing a
viewerrow on a public knowledge base demotes the member from the defaulteditorto read-only, rather than "at least viewer".deniedis an action, not a permission level. It is used to completely turn off a member's default level on a public knowledge base; it does not appear inmy_role(when access is denied,my_roleisnull,role_sourceisdenied).expiration is a hard invalidation. An expired row is equivalent to no row at all, so the caller falls back to the knowledge base default level (public knowledge base:
editor/ private knowledge base: no access). Expired rows are still listed in theGET .../aclresults for easier cleanup; to determine whether a row has expired, compareexpires_atwith the current time.
POST /v1/knowledge/bases/{kbId}/acl
Request
Request parameters
Parameter | Type | Required | Description |
subject_type | string | Yes | Fixed: |
subject_id | string | Yes | Authorization subject ID, i.e., the |
role | string | Yes |
|
expires_at | string | No | Expiration time (ISO-8601). |
granted_by | string | No | Grantor identifier; defaults to the caller's |
The
kb_idin the request body is ignored; thekbIdin the path always prevails.(kb_id, subject_type, subject_id)is unique: a repeated POST for the same subject returns 409 (40901,msgisACL already exists for (kb_id, subject_type, subject_id)), not an overwrite. To change the role, firstDELETE .../acl/{aclId}and then POST.Parameter validation failures uniformly return 400
40001(VALIDATION_FAILED).
Request example
# Grant edit permission to member 42, expiring in 7 days
curl -X POST http://<host>:4040/v1/knowledge/bases/1/acl \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"subject_type": "member", "subject_id": "42", "role": "editor", "expires_at": "2026-09-23T00:00:00Z"}'
# Demote member 43 on a public knowledge base to read-only (an explicit row is the final decision, not "at least viewer")
curl -X POST http://<host>:4040/v1/knowledge/bases/1/acl \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"subject_type": "member", "subject_id": "43", "role": "viewer"}'
# Completely turn off the default level of member 44 on a public knowledge base
curl -X POST http://<host>:4040/v1/knowledge/bases/1/acl \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"subject_type": "member", "subject_id": "44", "role": "denied"}'Response
Response parameters
Field | Type | Description |
id | long | ACL row ID, used for revocation ( |
kb_id / workspace_id | long | Owning knowledge base / workspace |
subject_type | string | Fixed: |
subject_id | string |
|
role | string |
|
granted_by | string | Grantor identifier |
granted_at | string | Grant time (ISO-8601) |
expires_at | string | Expiration time; |
subject_name | string | Subject member name (read-only display field; null when the member has been deleted: |
subject_user_id | string | Subject member's external |
subject_name / subject_user_id are read-only display fields added by this addition, and the types and semantics of existing fields are unchanged. Authorization rows remain after a member is deleted (both fields are then null); an explicit DELETE is required for cleanup.
Response example
GET returns an array with the same structure:
{"code": 0, "msg": "ok", "data": {"id": 7, "kb_id": 1, "workspace_id": 1, "subject_type": "member", "subject_id": "42", "role": "editor", "granted_by": "2", "granted_at": "2026-09-16T10:00:00Z", "expires_at": "2026-09-23T00:00:00Z", "subject_name": "alice", "subject_user_id": "u-alice"}}GET /v1/knowledge/bases/{kbId}/acl/candidates
Request
Request example
curl "http://<host>:4040/v1/knowledge/bases/1/acl/candidates" -H "X-API-Key: $API_KEY"Response
Response parameters
Field | Type | Description |
member_id | long | Member ID, i.e., the |
user_id | string | Member's external |
name | string | Member name |
current_role | string | This member's existing explicit row role on this knowledge base; |
2026-09-16 fix: current_role previously counted expired rows (expires_at <= now) as the current role, which is the opposite of the authorization side's hard-expiration semantics that "an expired row is equivalent to no such row" — the same member shows role_source: kb_default in the knowledge base details, but here still carried a denied that had expired last month. Expired rows no longer appear in current_role (the field type and value range are unchanged; only the interpretation is now aligned with authorization). To view and clean up expired rows, use GET /v1/knowledge/bases/{kbId}/acl, which by design includes expired rows.
Response example
{"code": 0, "msg": "ok", "data": [
{"member_id": 42, "user_id": "u-alice", "name": "alice", "current_role": "editor"},
{"member_id": 43, "user_id": "u-bob", "name": "bob", "current_role": null}
]}DELETE /v1/knowledge/bases/{kbId}/acl/{aclId}
Request
Request example
curl -X DELETE http://<host>:4040/v1/knowledge/bases/1/acl/7 -H "X-API-Key: $API_KEY"After revocation, the member falls back to the knowledge-base default profile (public knowledge base editor / private knowledge base no permission) — revoking a denied row returns the default write permission of a public knowledge base to that member. Returns Box<Void> (data is null).
aclId must belong to the kbId in the URL, otherwise 404 (the same code as "the row does not exist", so as not to leak "this id exists but belongs to another knowledge base"). 2026-09-17 fix: previously the row was located only by (aclId, workspaceId), while authorization looked at kbId — so any member who was an owner on one knowledge base (even one just created) could use that kbId as a ticket to delete authorization rows of any other knowledge base in the same workspace, including other people's owner rows and denied rows that were currently locking themselves out. This is a tightening: cross-knowledge-base deletions that previously succeeded now return 404, and the behavior of a correctly paired kbId is unchanged.
POST /v1/knowledge/bases
Creates a knowledge base.visibility When not provided, the default value depends on the key type: User Key → private, Owner Key → workspace; when explicitly provided, the provided value takes precedence (User Key can also create public knowledge bases).
Request
Request parameters
Parameter | Type | Required | Description |
name | string | Yes | Knowledge base name |
description | string | No | Description |
visibility | string | No |
|
graph_enabled | boolean | No | Specifies whether to enable the knowledge graph |
wiki_enabled | boolean | No | Specifies whether to enable Wiki distillation. Default: false. |
tuning | object | No | Optional initial tuning parameters |
tuning.top_k | integer | No | Search top_k |
tuning.rerank_enabled | boolean | No | Specifies whether to enable reranking |
tuning.hybrid_weight | double | No | BM25 vs vector weight |
tuning.chunk_size | integer | No | Chunk size |
tuning.chunk_overlap | integer | No | Chunk overlap |
Request example
curl -X POST http://<host>:4040/v1/knowledge/bases \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"name": "Project documentation library", "description": "Stores project technical documentation"}'Response
Response example
{"code": 0, "msg": "ok", "data": {"id": 1, "name": "Project documentation library", "description": "Stores project technical documentation", "visibility": "workspace", "wiki_enabled": false, "created_at": "2026-08-19T10:00:00Z", "my_role": "owner", "role_source": "owner_member", "can_manage_acl": true}}GET /v1/knowledge/bases
In paginated mode, every row carries my_role / role_source / can_manage_acl (see the field table in the knowledge base details below for field meanings), so callers can directly render the availability of the "upload/delete/permission management" entries.compact=true streamlined catalog contains only name / description / doc_count / updated_at (also listing only accessible knowledge bases), without role fields.
Lists by page the knowledge bases that the current caller has access to. Knowledge bases without access do not appear in the results, rather than appearing and then failing with 403 on invocation. Supports
compact=truestreamlined catalog + ETag incremental sync.The criterion for "has access" is exactly the same as on the authorization side: knowledge bases created by the caller, plus knowledge bases with unexpired, non-
deniedexplicit rows, plus public knowledge bases where the caller has no explicit rows (when explicit rows exist, they take precedence; see Knowledge base permissions (ACL)).
Request
Request parameters
Parameter | Type | Required | Description |
page | integer | No | Page number (starting from 0). Default: 0. |
page_size | integer | No | Page size. Default: 20. Valid values: [1, 100]. |
compact | boolean | No | When true, returns a streamlined catalog (no pagination) with ETag support |
Request example
curl "http://<host>:4040/v1/knowledge/bases?page_size=10" -H "X-API-Key: $API_KEY"GET /v1/knowledge/bases/{id}
Retrieves details of a specified knowledge base. Requires viewer or higher permissions; insufficient permissions return 403 (40303). A kbId that belongs to another workspace or does not exist returns 404.
Request
Request example
curl "http://<host>:4040/v1/knowledge/bases/1" -H "X-API-Key: $API_KEY"Response
Response parameters
Field | Type | Description |
id | long | Knowledge base ID |
workspace_id | long | Owning workspace ID |
name | string | Knowledge base name |
description | string | Description |
visibility | string |
|
owner_member_id | long | Creator's member_id |
doc_count | int | Total number of documents (cached value maintained by the ingestion path) |
graph_enabled | boolean | Specifies whether to enable the knowledge graph |
wiki_enabled | boolean | Specifies whether to enable Wiki distillation. Default: false. |
created_at / updated_at | string | Creation and update time (ISO-8601) |
my_role | string | The caller's effective role: |
role_source | string | Source of the effective role: |
can_manage_acl | boolean | Whether the caller can manage the ACL of this knowledge base (equivalent to |
Role capability comparison:
Role | Capabilities |
| Search, read documents, read Wiki, read graph nodes/edges |
|
|
|
|
Response example
{"code": 0, "msg": "ok", "data": {"id": 1, "workspace_id": 1, "name": "Project documentation library", "description": "Stores project technical documentation", "visibility": "workspace", "owner_member_id": 2, "doc_count": 42, "graph_enabled": false, "wiki_enabled": false, "created_at": "2026-08-19T10:00:00Z", "updated_at": "2026-08-19T10:00:00Z", "my_role": "editor", "role_source": "kb_default", "can_manage_acl": false}}DELETE /v1/knowledge/bases/{id}
Deletes a knowledge base. Requires the owner permission of this knowledge base. The decision is fully consistent with my_role: Owner Key passes globally, the knowledge base creator is always owner, and members explicitly granted owner can also delete it. Insufficient permissions return 403 (40303); a knowledge base in another workspace or nonexistent returns 404 (a repeated delete returns 404 the second time).
Since 2026-09-16, User Key knowledge base deletion no longer recognizes only the "creator". Previously this used a separate ownerMemberId check, so a member granted owner received my_role=owner still could not delete the knowledge base. This is now unified into the ACL decision chain. This is a permission relaxation that only affects members explicitly granted owner.
Request
Request example
curl -X DELETE http://<host>:4040/v1/knowledge/bases/1 -H "X-API-Key: $API_KEY"POST /v1/knowledge/bases/{kbId}/documents/upload
Uploads a document to a specified knowledge base. Multimodal content is supported (text/PDF/image/audio). Identical content is idempotent. Requires editor or higher permissions (members of a public knowledge base are editor by default); insufficient permissions return 403 (40303); a kbId in another workspace or nonexistent returns 404.
Request
FormParameter (multipart/form-data)
Parameter | Type | Required | Description |
file | file | Yes | File to upload |
title | string | No | Document title |
external_id | string | No | External unique identifier (idempotency key) |
source_url | string | No | Source URL |
file_path | string | No | Sync source path |
source_updated_at | string | No | Source file update time (ISO-8601) |
folder_path | string | No | Folder path for batch upload |
images | file[] | No | Markdown sidecar image attachments |
metadata | string | No | JSON-format user metadata |
Request example
curl -X POST http://<host>:4040/v1/knowledge/bases/1/documents/upload \
-H "X-API-Key: $API_KEY" \
-F "file=@./docs/architecture.md" \
-F "title=Architecture design document"POST /v1/knowledge/documents
Creates a document from a URL or text content (flat API; knowledge_base_id is specified in the body). Requires editor or higher permissions of the target knowledge base; insufficient permissions return 403 (40303); a knowledge base in another workspace or nonexistent returns 404.
Request
Request parameters
Parameter | Type | Required | Description |
knowledge_base_id | long | Yes | Target knowledge base ID |
title | string | Document title | |
content | string | Conditional | Text content (choose either this or source_url) |
source_url | string | Conditional | Source URL |
external_id | string | No | External unique identifier |
Request example
curl -X POST http://<host>:4040/v1/knowledge/documents \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"knowledge_base_id": 1, "title": "API design guidelines", "content": "RESTful APIs should..."}'GET /v1/knowledge/bases/{kbId}/documents
Lists documents in a specified knowledge base by page. Requires viewer or higher permissions of the target knowledge base; insufficient permissions return 403 (40303); a kbId in another workspace or nonexistent returns 404.
Request
Request parameters
Query parameters: page (default: 0), size (default: 20; maximum: 100), q (optional; up to 200 characters; a literal substring of the title or external_id, not an SQL wildcard), ingest_status (optional: pending / processing / indexed / done / failed). Filtering, pagination, and totals are all computed server-side, not limited to the first 100 entries loaded by the browser. Existing response fields and unfiltered requests remain compatible. The flat document list entry supports the same filters.
Request example
curl "http://<host>:4040/v1/knowledge/bases/1/documents?size=10" -H "X-API-Key: $API_KEY"POST /v1/knowledge/documents/reindex
Re-queues an existing document through the normal knowledge.ingest flow, used for rebuilding existing documents after an embedding fingerprint / model, chunking, or index configuration release. Requires editor or higher permissions.
Safety and concurrency contract:
workspace/member is derived only from
X-API-Key; the request body does not provide itworkspace_id.knowledge_base_idmust belong to the current workspace, anddocument_idmust belong to this KB; crossing workspaces returns 404 and insufficient ACL returns 403.expected_ingest_revisionmust use the value just read from the document list. On success the revision increments by one and the newevent_idis returned; replaying an old revision returns 409 to avoid duplicate rebuilds.Only one document is rebuilt per call. The document status update and outbox enqueue happen within one short transaction; embedding / OSS / OpenSearch run asynchronously after the transaction commits.
Request
Request parameters
Parameter | Type | Required | Description |
knowledge_base_id | long | Yes | Target knowledge base ID |
document_id | long | Yes | Target document ID |
expected_ingest_revision | long | Yes | The current revision read from the list/details; must be >= 0 |
Request example
curl -X POST http://<host>:4040/v1/knowledge/documents/reindex \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"knowledge_base_id":1,"document_id":42,"expected_ingest_revision":3}'Response
Response notes
In a successful response, data.ingest_status is pending, data.ingest_revision is 4, data.event_id can be used for event tracking. For paginated rebuilds of an entire knowledge base / workspace, use scripts/reindex-knowledge.sh, and see the release steps in docs/operations/runbooks/embedding-dimension-change.md.
GET /v1/knowledge/documents/{id}
Retrieves document details. Requires viewer or higher permissions of the target knowledge base; insufficient permissions return 403 (40303); a document in another workspace or nonexistent returns 404.
Request
Request example
curl "http://<host>:4040/v1/knowledge/documents/42" -H "X-API-Key: $API_KEY"DELETE /v1/knowledge/documents/{id}
Deleting a document requires editor or higher permission on the knowledge base the document belongs to (it does not require being the uploader — in a public knowledge base any member can by default delete documents uploaded by others; see behavior change 1 in the overview "2026-09-16 knowledge base permission refactoring"). Insufficient permission returns 403 (40303); a document that crosses workspaces or does not exist returns 404.
Request
Request example
curl -X DELETE http://<host>:4040/v1/knowledge/documents/42 -H "X-API-Key: $API_KEY"POST /v1/knowledge/search
Performs hybrid search (BM25 + vector + RRF fusion) over knowledge bases accessible to the tenant.
Request
Request parameters
Parameter | Type | Required | Description |
query | string | Yes | Query statement. Up to 4,096 characters. |
kb_ids | long[] | Conditional | A User Key must specify this; an Owner Key may omit it (workspace-wide search). Up to 100 after deduplication |
top_k | integer | No | Number of entries to return [1, 100]. Default: 5. |
filters | object | No | Whitelist filter ( |
rrf_k | integer | No | RRF fusion constant K. Valid values: [1, 1000]. |
recall_multiplier | integer | No | Recall expansion factor. Valid values: [1, 20]. |
hybrid_weight | double | No | BM25 vs vector weight. Valid values: [0.0, 1.0]. |
rewrite | boolean | No | Specifies whether to enable LLM query rewriting |
type | string | No | Document type filter |
tags | string[] | No | Tag filter |
block_type | string | No | Chunk type filter ( |
metadata_condition | object | No | Structured metadata conditions |
decompose | boolean | No | Specifies whether to enable multi-hop query decomposition; by default determined by the deployment switch |
cross_languages | string[] | No | Cross-language variants generated by query rewriting (BCP-47 style language tags), up to 5; the original query is always kept |
Request example
curl -X POST http://<host>:4040/v1/knowledge/search \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"query": "How do I configure OpenSearch?", "kb_ids": [1, 2], "top_k": 5}'Response
Response parameters
Field | Type | Description |
chunk_id | string | Text chunk ID |
document_id | string | Document ID |
knowledge_base_id | string | Knowledge base ID |
title | string | Document title |
snippet | string | Content snippet (preview of the first ~4,096 characters) |
score | double | RRF fusion score (non-normalized) |
position | int | Result rank position (starting from 0) |
rerank_score | double | Rerank score (populated when |
modality | string | Modality type ( |
block_type | string | Chunk type ( |
section_path | string | Section path (populated for multimodal results) |
page_number | integer | Page number (populated for PDF documents) |
oss_url | string | Context0 short-lived signed download URL (validity determined by business configuration) |
graph_hit | boolean | Whether recalled by the knowledge graph |
graph_path | string | Graph entity chain path |
Null fields are not output. Unknown request fields are strictly rejected (to prevent
kb_idsingular typos from being silently ignored and causing a workspace-wide search).ACL semantics:
kb_idsare validated individually forviewerpermission — as long as one knowledge base in the list is inaccessible, the entire request returns 403 (40303); it does not "skip the inaccessible knowledge bases and return the remaining results". First useGET /v1/knowledge/basesto obtain the accessible list (this endpoint returns only knowledge bases you have access to) before retrieval. When the Owner Key omitskb_ids, it searches the entire workspace; when the User Key omitskb_ids, it returns 403 directly (no longer falling back to "search the entire workspace").
Response example
{
"code": 0, "msg": "ok",
"data": [
{
"chunk_id": "chunk_001",
"document_id": "42",
"knowledge_base_id": "1",
"title": "OpenSearch Configuration Guide",
"snippet": "OpenSearch requires configuring cluster.name, node.name...",
"score": 0.91,
"position": 0,
"rerank_score": 0.95,
"modality": "text",
"block_type": "text"
}
]
}GET /v1/knowledge/bases/{kbId}/tuning
Reads the search tuning parameters of a specified knowledge base (requires viewer or higher permissions). A null field means "use the global default" (top_k=5 / rerank=true / hybrid_weight=0.7 / graph_weight=0.3 / chunk_size=800 / chunk_overlap=100).
Request
Request example
curl "http://<host>:4040/v1/knowledge/bases/1/tuning" -H "X-API-Key: $API_KEY"Response
Response parameters
Field | Type | Description |
top_k | integer | Number of search results to return |
rerank_enabled | boolean | Specifies whether to enable reranking |
hybrid_weight | double | BM25 vs vector weight [0.0, 1.0] |
chunk_size | integer | Chunk size |
chunk_overlap | integer | Chunk overlap |
rrf_k | integer | RRF fusion constant K |
recall_multiplier | integer | Recall expansion factor |
query_rewrite_enabled | boolean | Specifies whether to enable LLM query rewriting |
enable_contextual_retrieval | boolean | Whether context-augmented retrieval is enabled |
graph_weight | double | Knowledge graph recall weight (GraphRAG) |
graph_skip_extensions | string[] | Graph extraction skipped extensions; null inherits the global value, an empty array disables this filter for the knowledge base, and a non-empty array fully replaces the global value |
enable_parent_child | boolean | Parent-child chunking and post-hit parent context completion; null inherits the global configuration (default: false) |
Response example
{
"code": 0, "msg": "ok",
"data": {
"top_k": 8,
"rerank_enabled": true,
"hybrid_weight": 0.5,
"chunk_size": 800,
"chunk_overlap": 100,
"rrf_k": 60,
"recall_multiplier": 3,
"query_rewrite_enabled": false,
"enable_contextual_retrieval": false,
"graph_weight": null,
"graph_skip_extensions": null,
"enable_parent_child": null
}
}PUT /v1/knowledge/bases/{kbId}/tuning
Partially updates knowledge base tuning parameters (patch semantics; only the provided keys are overwritten). Requires editor or higher permissions. The request body is any subset of the tuning fields, with field names consistent with the GET /v1/knowledge/bases/{kbId}/tuning response.
Request
Request example
curl -X PUT http://<host>:4040/v1/knowledge/bases/1/tuning \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"top_k": 8, "rerank_enabled": true}'Response
Response notes
Same structure as the GET response (returns the complete merged tuning).
PUT /v1/knowledge/bases/{kbId}/settings
Updates knowledge base settings - the quality review switch, the knowledge graph switch, and visibility. At least one of the three fields must be provided (requires the owner permission of this knowledge base).
Request
Request parameters
Parameter | Type | Required | Description |
review_enabled | boolean | Conditional | Specifies whether to enable quality review. Enabling requires both the deployment-level worker and the workspace feature flag to be on; otherwise returns |
graph_enabled | boolean | Conditional | Specifies whether to enable knowledge graph construction (GraphRAG), with no additional gating |
visibility | string | Conditional |
|
review_enabled / graph_enabled / visibility At least one must be provided. Changing visibility does not touch any explicit ACL row - explicit rows are final decisions; switching visibility only changes which default level people "without explicit rows" fall into.
Request example
curl -X PUT http://<host>:4040/v1/knowledge/bases/1/settings \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"review_enabled": true, "graph_enabled": false, "visibility": "private"}'Response
Response example
{"code": 0, "msg": "ok", "data": {"kb_id": 1, "review_enabled": true, "graph_enabled": false, "visibility": "private"}}GET /v1/knowledge/bases/{kbId}/index-stats
Views the index statistics of this knowledge base - document count, chunk count, index size, ingestion queue depth, and last ingestion time. Requires viewer or higher permissions of the target knowledge base; insufficient permissions return 403 (40303); a kbId in another workspace or nonexistent returns 404.
Request
Request example
curl "http://<host>:4040/v1/knowledge/bases/1/index-stats" -H "X-API-Key: $API_KEY"Response
Response example
{"code": 0, "msg": "ok", "data": {"kb_id": 1, "kb_name": "Project documentation library", "doc_count": 42, "chunks_count": 1380, "index_size_bytes": 10485760, "ingest_queue_depth": 0, "last_ingest_at": "2026-09-16T09:31:00Z"}}These counts leak the size characteristics of the knowledge base content, so they are in the same tier as "reading knowledge base content". Starting 2026-09-16, a viewer threshold was added: previously there was only the workspace fence, and any User Key in the same workspace could read the document count, chunk count, index size, and last ingestion time of a private knowledge base.
GET /v1/knowledge-bases/{kbId}/graph/nodes
Views the graph entity nodes of this knowledge base by page (pagerank descending). Requires viewer or higher permissions.
Path note: the graph data plane is mounted at /v1/knowledge-bases/... (with a hyphen), which differs from the /v1/knowledge/bases/... form used elsewhere in this chapter.
Request
Request parameters
Query parameters: page (default: 0), size (default: 50; maximum: 1000; exceeding the maximum is silently clamped to it).
Request example
curl "http://<host>:4040/v1/knowledge-bases/1/graph/nodes?page=0&size=50" -H "X-API-Key: $API_KEY"Response
Response example
{"code": 0, "msg": "ok", "data": {"page": 0, "size": 50, "nodes": [{"id": "…", "label": "PolarDB-X", "type": "product", "pagerank": 0.42}]}}GET /v1/knowledge-bases/{kbId}/graph/edges
Views the 1-hop adjacency subgraph of an entity (nodes + edges). Requires viewer or higher permissions.depth currently supports only 1 hop and is kept for API compatibility.
Request
Request example
curl "http://<host>:4040/v1/knowledge-bases/1/graph/edges?node=PolarDB-X&depth=1" -H "X-API-Key: $API_KEY"Response
Response example
{"code": 0, "msg": "ok", "data": {"nodes": [{"id": "…", "label": "PolarDB-X", "type": "product", "pagerank": 0.42}], "edges": [{"from": "PolarDB-X", "to": "CN", "predicate": "part_of", "weight": 0.8}]}}Permission and behavior change: graph entities/relationships are content summaries extracted from chunks, so these two endpoints are in the same tier as "reading knowledge base content" — they require
vieweror higher, and insufficient permission returns 403 (40303). Starting 2026-09-16, whenkbIdcrosses workspaces or does not exist, it returns 404 (previously 403), so that auto-increment kbIds cannot be probed for existence by enumeration.The graph adds the following knowledge base data-plane management APIs; the original management-plane
/ops/knowledge-bases/{kbId}/graphstill requires an Owner Key + OPS port 8080. A User Key of the knowledge base owner does not thereby gain global OPS permissions.
Graph data-plane clearing and rebuilding
Method / path | Permissions and response |
DELETE | Current knowledge base owner (management permissions); |
POST | Current knowledge base owner (management permissions); |
No request body is needed; both return Box; paths use knowledge-bases hyphens. A User Key with knowledge base owner permission can call them; viewer / editor cannot. Resources are validated by workspace/KB, reusing the original graph guard / barrier; the rebuild count indicates the number enqueued, not synchronous completion, and existing review is not bypassed.
Graph conflict detection and manual review (added 2026-09-16)
Two kinds of silently adjudicated signals during multi-document consolidation - cross-document type divergence of the same entity (type_conflict) and the same alias claimed by multiple entities (alias_conflict) - can be scanned and queued on demand for manual adjudication by editor+. Detection is a synchronous read-only scan (zero LLM, no graph writes); type / alias decisions and graph update events commit in the same transaction and take effect after background consolidation. The feature is gated by context.knowledge.graph.conflict-detection.enabled (default: true); when disabled, the endpoints return 404.
Method / path | Permission | Description |
POST | editor+ | Synchronously scans the live subgraph: new conflicts are queued, existing conflicts refresh detected_at, and open rows not reproduced in this round turn stale; idempotent, repeated clicks are harmless |
GET | viewer+ | Review queue pagination; |
GET | viewer+ | Single entry details (payload parsed into candidate values + source document/chunk evidence chain, same structure as list items) |
POST | editor+ | Manual adjudication; only open entries can be adjudicated |
Example:
# Trigger detection (editor+)
curl -X POST "http://<host>:4040/v1/knowledge-bases/1/graph/conflicts/detect" -H "X-API-Key: $API_KEY"{"code": 0, "msg": "ok", "data": {"scanned_documents": 12, "conflicts": 3, "opened": 2, "refreshed": 1, "staled": 0}}# Type conflict adjudication: choose the correct type (chosen_type must be in the candidate set, otherwise 400)
curl -X POST "http://<host>:4040/v1/knowledge-bases/1/graph/conflicts/11/decision" \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"action":"resolve","chosen_type":"project"}'
# Alias conflict adjudication: choose the alias owner / dismiss without further reminders
curl -X POST "http://<host>:4040/v1/knowledge-bases/1/graph/conflicts/12/decision" \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"action":"resolve","chosen_owner":"Alibaba Cloud"}'
curl -X POST "http://<host>:4040/v1/knowledge-bases/1/graph/conflicts/12/decision" \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" -d '{"action":"dismiss"}'Adjudication semantics:
resolvedmeans the decision and update events are committed, not that the graph update is complete. Both type / alias are updated asynchronously by the graph-consolidate worker; failures retry per the existing event policy, and a failed event enqueue rolls back the decision as well. Manual alias decisions take precedence over old mappings; type decisions apply to the final entity after alias merging and continue to replay in later consolidations.dismiss, a conflict with the same fingerprint (payload_hash) is not reopened; open rows whose signals disappear turnstale, and revive to open when reproduced; resolved/dismissed conflicts are not reopened by repeated detection. The conflict list is read-only awareness for viewer; detection and adjudication are write operations (403). Conflict records are cleaned up synchronously when a knowledge base or workspace is deleted.Entity auto-confirmation keeps the
context.knowledge.graph.consolidate.auto-confirm.containment-enabledconfiguration name, but only allows candidates with identical names under existing normalization rules to be confirmed directly; mere substring containment (such asPolarDB/PolarDB-X) still requires disambiguation. Jaccard auto-confirmation still needs explicit enablement and is disabled by default.
Graph management plane route convergence (updated 2026-09-16)
The management plane (OPS port 8080, Owner Key) graph routes now pass kb_id as a query param, in the same form as /ops/knowledge/wiki/**. The old path-parameter version remains available and will be removed after it enters the deprecation cycle.
New paths (recommended) | Old paths (deprecated, behavior unchanged) |
GET | GET |
GET | GET |
DELETE | DELETE |
POST | POST |
Both route groups share one implementation; request/response JSON, authentication (Owner Key), and the workspace fence (cross-workspace 403) are verbatim identical; size remains capped at 1000. The change follows the backend red line: "/ops/** routes must not contain @PathVariable; identifiers go through query params or the body".
The same convergence batch includes the promotion delete API: the new DELETE /ops/promotion/delete?promotionId=, while the old DELETE /ops/promotion/{promotionId} also remains available during the deprecation cycle.
Wiki data plane gating: the search, read-by-ID/slug, catalog read, source trace, and issues methods of the Search-Read-Expand controller are registered only when the server-side
context.wiki.enabled=true(environment variableWIKI_ENABLED, default true; an explicit false still takes effect); when disabled, the corresponding methods are unavailable (404, or 405 if the same path has other HTTP methods). This is not equivalent to disabling the entire/v1/knowledge/wikiprefix: the page list/update, single-page regeneration, dead-letter/log, and config/backfill data plane management endpoints are not gated by this read-side switch; generation is still limited by the worker and knowledge base configuration. The original OPS permissions and distillation pipeline gating remain unchanged.ACL: the User Key must explicitly list the knowledge base IDs (the search request body field is
knowledge_base_ids, and for read endpoints it is the query parameterkb_id; mistakenly writingkb_idsor other field names is treated as unauthorized and returns 403 because they are unrecognized), otherwise it returns 403 (workspace-level Wiki retrieval is exclusive to the Owner Key); the kb_id list in the request must not exceed 100 after deduplication (400 if exceeded), and read permission is validated per knowledge base.GET /v1/knowledge/wiki/readresults go through the server-side read cache by default (context.wiki.read-cache.*, enabled by default, TTL 300s, with workspaceId included in the cache key for isolation).
GET /v1/knowledge/wiki/config?kb_id=<id>
Views the generation switch and deployment capabilities of a single knowledge base. Requires knowledge base read permission. Returns Box<SettingsDto>:
Response
Response example
{"code":0,"msg":"ok","data":{"kb_id":1,"wiki_enabled":false,"build_available":true,"search_available":true,"wiki_config":{"wiki_language":"Chinese","extraction_granularity":"standard","content_instructions":""}}}build_available indicates the distill/finalize worker is enabled; search_available indicates the read-side service switch; neither implies that pages already exist.
PUT /v1/knowledge/wiki/config?kb_id=<id>
Sets the continuous generation switch and generation configuration for a single knowledge base. This requires the knowledge base owner (management permission); a User Key with the corresponding role is sufficient. At least one of wiki_enabled and wiki_config must be provided; the old switch-only request remains valid. In DATA, wiki_config is a JSON object:
Request
Request example
{"wiki_enabled":true,"wiki_config":{"wiki_language":"Chinese","extraction_granularity":"focused","content_instructions":"Focus on architecture decisions"}}Response
Response notes
Returns the settings structure above. Enabling requires the generation worker to be available; enabling does not backfill existing documents, disabling does not delete existing pages, and configuration changes do not immediately rebuild historical pages.
wiki_configis merged field by field rather than replacing the whole object: omitted fields are retained, and a field value of null clears the corresponding override and restores reading the default value.The canonical fields are:
wiki_language(Chinese / English / Japanese / Korean / French / German / Spanish)extraction_granularity(standard / focused / exhaustive)content_instructions(up to 8000 characters).
The read default values are Chinese, standard, and empty string, respectively.
Compatible mappings:
granularity→extraction_granularity,custom_instructions→content_instructions.Conflicting aliases within the same request are rejected. Newly passed unknown fields are rejected, while existing unknown configuration fields are retained; historical invalid JSON does not block reading the switch, but silently overwriting it is rejected when writing the configuration.
POST /v1/knowledge/wiki/backfill?kb_id=<id>
Backfills existing indexed documents for a knowledge base with Wiki enabled. Requires the knowledge base owner (management permissions). No request body is needed. Returns:
Response
Response example
{"code":0,"msg":"ok","data":{"kb_id":1,"status":"accepted","enqueued":3}}enqueued is the number of document operations enqueued, not the number of pages generated; it may be 0. Tasks are processed asynchronously and do not bypass review; there is no request-level deduplication guarantee - after a timeout, check the status before deciding whether to retry. This API does not turn on the knowledge base generation switch.
PUT /ops/knowledge/wiki/config?kb_id=<id> (advanced generation configuration)
Using the management plane address and the workspace Owner Key, you can set the wiki_config JSON string. This legacy OPS contract is retained and is not changed to the DATA object format; for knowledge base page configuration, prefer the data plane endpoint described above. Knowledge base owner identity is not the same as the workspace Owner Key.
Request
Request example
curl -X PUT "http://<host>:8080/ops/knowledge/wiki/config?kb_id=1" \
-H "X-API-Key: $OWNER_KEY" -H "Content-Type: application/json" \
-d '{"wiki_config":"{\"extraction_granularity\":\"focused\",\"wiki_language\":\"Chinese\",\"content_instructions\":\"Focus on architecture decisions\"}"}'Configuration changes do not imply immediate rebuilding of historical pages. For complete getting-started steps, see Build a RAG + Wiki enterprise knowledge base.
Wiki data-plane page management and task summaries
prefix /v1/knowledge/wiki, and all return Box. viewer is read-only; editor can edit / publish / archive / restore pages, regenerate a single page, and retry failed tasks of this knowledge base; configuration, ACL, and knowledge-base-wide backfill still require the knowledge base owner. Global ES repair remains OPS-only and is not open to ordinary knowledge base roles.
Method / relative path | Request and response |
GET | Knowledge-base-isolated paginated page list; optional status / type / folder_id |
POST | editor; manual page creation - |
GET | Statistics of this knowledge base; task counts do not mix in other KBs |
PUT | editor; partially updates title / content_markdown / summary / category / folder_id / status and returns the page DTO |
POST | editor; no request body is needed; returns |
GET | viewer; paginated redacted dead-letter summaries |
GET | viewer; paginated redacted task log summaries |
POST | editor; no request body is needed; returns |
Paging for pages, dead letters, and logs requires page to be non-negative and size to be 1-200; invalid values are rejected. The paged data contains pagination fields such as content / total_elements, not a bare array. Pages, target folders, dead letters, and their associated source documents must belong to an authorized workspace/KB; out-of-scope resources cannot be accessed by borrowing a valid kb_id. The parent_id when creating/updating a folder must also be in the same knowledge base.
statusandpage_typeare closed value domains, validated against a whitelist on the write side; any value outside the domain returns 40040001:status∈draft/published/archived. Search and recall index onlypublished, the backlink graph recognizes onlypublishedand resolvabledraft,/statscounts onlypublished_pages/draft_pages, and the console's publish / archive / restore buttons render only underdraft/archived- writing an out-of-domain value makes the page disappear from all four places at once.page_type∈entity/concept/summary/index(the four actually produced by the generation side). When not provided, the default isentity. Title resolution and deduplication candidates look only atentity/concept.GET /pages?type=filter values are likewise limited to these four.
slugandtitleis required and cannot be blank; leading/trailing spaces are trimmed by the same rule as wikilink targets;slugis up to 256 characters andtitleis up to 512 characters; exceeding a limit returns 400 with the column budget in the message. A missing or blankslugis a validation error (40001) and no longer falls through to storage-layer constraints (the historical behavior was40009 missing or invalid required column: slug).The category request field for page updates is
category, while the response still usescategory_path; slug is not modified. Settingstatus=publishedpublishes a draft or restores an archived page; settingstatus=archivedcarries the archive semantics of the UI "delete", not a physical deletion. Before editing the body, first request/pages/{pageId}?for_edit=trueto get the complete body; truncated content from a budgeted read must not be saved back.Regeneration first checks the ownership of all sources, then queues the still-valid sources of the same knowledge base; a manual page without valid sources or with all sources deleted returns 409 instead of pretending to be queued. Retrying a dead letter must verify the ownership of both the dead letter and the associated document; on success it is queued and the dead-letter record is removed. Neither accepted nor retried implies that model processing has completed.
Dead-letter summary fields:
id,knowledge_id,fail_count,created_at,error_code=WIKI_DISTILL_FAILED,last_error(fixed to "Distillation failed; please retry or contact the administrator"). Log summary fields:id,action,summary,created_at; action is only distill_batch / distill_coverage / link_validation / other, and summary is a fixed redacted message. Neither API returns raw model results or provider errors.
POST /v1/knowledge/wiki/search
Performs BM25 full-text search over Wiki distillation pages and returns the hit list (without full bodies). Unlike /v1/knowledge/search (chunk-level hybrid search), Wiki search targets distilled structured knowledge pages. Only status=published pages can be searched (enforced server-side); a draft page can be published by an editor through the data-plane page update or the original management-plane publish, and appears in search results only after index synchronization.
Request
Request parameters
Parameter | Type | Required | Description |
query | string | Conditionally required | A single query; provide at least one of query and a non-empty queries |
queries | string[] | No | Up to 10 queries; takes precedence over query when non-empty |
knowledge_base_ids | long[] | No | List of knowledge base IDs to scope the search (accepts the |
top_k | integer | No | Number of entries to return, 1-100 (accepts the |
mode | string | No | keyword (default, BM25) or regex (literal matching) |
Unknown fields are strictly rejected: a field in the request body other than those in the table above (such as writing kb_ids as the singular kb_id) directly returns 400 40001 unknown field '<name>', consistent with /v1/knowledge/search. Previously such typos were silently dropped, causing the request to degrade from "retrieve the specified KB" to "retrieve the entire workspace".
Request example
curl -X POST http://<host>:4040/v1/knowledge/wiki/search \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"query": "Deployment process", "knowledge_base_ids": [1], "top_k": 5}'Response
Response example
For the body, call GET /v1/knowledge/wiki/pages/{pageId} separately.
{
"code": 0, "msg": "ok",
"data": [
{
"id": "101",
"title": "Deployment Guide",
"slug": "deploy-guide",
"page_type": "concept",
"category": "Operations",
"summary": "Introduces Docker Compose and the image deployment process",
"snippet": "...prepare DASHSCOPE_API_KEY in .env before deployment...",
"score": 8.42
}
]
}GET /v1/knowledge/wiki/pages/{pageId}
Reads a Wiki page from MySQL by page ID (including the markdown body). A normal read checks read permission against the page's actual knowledge base and follows the output budget; for_edit=true requires editor write permission of the page's actual knowledge base, returns the complete body, and ignores the normal read budget. A client cannot bypass page ownership by substituting another kb_id.
Request
Request parameters
Parameter | Type | Required | Description |
max_output_chars | int | No | Body output budget for normal reads (counted in Unicode code points), 1000-200000; when the budget is exceeded, the first 70% + last 30% are kept with a truncation marker line inserted. Defaults to |
for_edit | boolean | No | Defaults to false; true requires write permission on the page's actual knowledge base and returns the complete body without applying max_output_chars or the deployment default budget |
When the budget takes effect (parameter or configuration > 0), the response data additionally echoes content_truncated (boolean, whether truncation occurred) and content_total_chars (int, total body characters before truncation); these two fields are absent when the budget is off.
Request example
curl "http://<host>:4040/v1/knowledge/wiki/pages/101" -H "X-API-Key: $API_KEY"Response
Response example
{
"code": 0, "msg": "ok",
"data": {
"id": 101,
"knowledge_base_id": 1,
"slug": "deploy-guide",
"title": "Deployment Guide",
"page_type": "concept",
"category_path": "Operations/Deployment",
"summary": "Introduces Docker Compose and the image deployment process",
"content_markdown": "# Deployment Guide\n...",
"source_refs": "[\"doc:42\"]",
"aliases": "deployment,deploy",
"status": "published",
"view_count": 12,
"created_at": "2026-08-19T10:00:00Z",
"updated_at": "2026-08-19T11:00:00Z"
}
}GET /v1/knowledge/wiki/pages/by-slug
Reads a Wiki page by knowledge base ID + slug (response structure same as above).
Request
Request parameters
Parameter | Type | Required | Description |
kb_id | long | Yes | Knowledge base ID |
slug | string | Yes | Page slug |
max_output_chars | int | No | Body output budget, same semantics as |
Request example
curl "http://<host>:4040/v1/knowledge/wiki/pages/by-slug?kb_id=1&slug=deploy-guide" \
-H "X-API-Key: $API_KEY"GET /v1/knowledge/wiki/folders
Lists the Wiki folder structure of a specified knowledge base, optionally filtered by parent folder.
Request
Request parameters
Parameter | Type | Required | Description |
kb_id | long | Yes | Knowledge base ID |
parent_id | long | No | Parent folder ID. Defaults to returning top-level folders |
Request example
curl "http://<host>:4040/v1/knowledge/wiki/folders?kb_id=1" -H "X-API-Key: $API_KEY"Response
Response example
{
"code": 0, "msg": "ok",
"data": [
{"id": 10, "knowledge_base_id": 1, "name": "Operations", "path": "/Operations", "parent_id": null, "sort_order": 0}
]
}Ordering: results are returned in ascending order of sort_order, and when sort_order values are equal, they fall back to ascending order of id (stable across DBs).
Folder write rules (POST /ops/knowledge/wiki/folders, PUT /ops/knowledge/wiki/folders/update)
The folder write side is on the management plane (/ops/knowledge/wiki/folders*, both requiring an Owner Key). The constraints are as follows:
Field | Rule |
| Required; leading and trailing spaces are trimmed, and the value must not be empty after trimming; at most 256 characters; must not contain |
| Read-only; derived by the server from |
| is optional and must be an integer (may be negative). A string / |
| Optional on creation; changing parent is not supported on update, and passing a value different from the current one returns |
Unique per parent and name: the same
kb_id+ the sameparent_iddoes not allow duplicate names; conflicts return409/40901a folder named 'X' already exists under the same parent. The same name under different parents is legal; renaming a folder to its own current name is also legal (a no-op).Renaming cascades path recalculation: after renaming a folder, the
pathprefix of its entire subtree is rewritten synchronously (a subfolder's ownnamestays unchanged). Folders that share the prefix but do not belong to that subtree (such as/Alphaand/AlphaOther) are unaffected.Delete semantics (
DELETE /ops/knowledge/wiki/folders/delete):When the folder still has subfolders, it returns
409/40901folder still has N subfolder(s); delete or empty them first— because changing parent is not supported, the subfolders of a deleted parent folder would become orphans that can be neither listed nor repaired.When the folder contains only pages, it still returns
200, and thefolder_idof those pages is cleared in place (returning to the unfiled state), leaving no dangling references.kb_idparticipates in validation: a folder that does not belong to thekb_idreturns404via query param, in the same shape as/folders/update,/pages/movebehaves consistently.
GET /v1/knowledge/wiki/read
The core endpoint of the Read step. Reads the full Wiki page by kb_id + slug and returns the out_links / in_links slug list and neighbor page summary cards (neighbors) so that agents can navigate and expand along the link graph (Expand).
Request
Request parameters
Parameter | Type | Required | Description |
kb_id | long | Yes | Knowledge base ID |
slug | string | Yes | Page slug |
max_output_chars | int | No |
|
Request example
curl "http://<host>:4040/v1/knowledge/wiki/read?kb_id=1&slug=deploy-guide" \
-H "X-API-Key: $API_KEY"Response
Response example
{
"code": 0, "msg": "ok",
"data": {
"page": {
"id": 101,
"knowledge_base_id": 1,
"slug": "deploy-guide",
"title": "Deployment Guide",
"page_type": "concept",
"content_markdown": "# Deployment Guide\n...",
"status": "published"
},
"out_links": ["env-config", "docker-compose"],
"in_links": ["getting-started"],
"neighbors": [
{
"id": 102,
"slug": "env-config",
"title": "Environment Variable Configuration",
"page_type": "entity",
"summary": "Introduces the key variables in .env (truncated to 150 characters)",
"direction": "out"
},
{
"id": 99,
"slug": "getting-started",
"title": "Getting Started",
"page_type": "concept",
"summary": "5-minute getting started guide",
"direction": "in"
}
]
}
}neighbors In the direction is out (the current page points to the neighbor) or in (the neighbor points to the current page); summary is truncated to 150 characters. Results hit the server-side read cache by default (TTL 300s).
GET /v1/knowledge/wiki/pages/{pageId}/sources
The source tracing endpoint. Lists the source documents of a Wiki page (including short-lived signed OSS download links, TTL 1800s) and, by default, 10 representative chunk snippets per document (configurable via context.wiki.source-query.preview-chunks) for checking distilled content against the sources.
Request
Request parameters (search mode)
Parameter | Type | Required | Description |
query | string | No | Search term, up to 256 characters; matches chunks and attaches adjacent context |
mode | string | No |
|
max_chunks | int | No | Maximum chunks returned per document. Default: 20. Valid values: 1-20 |
context_window | int | No | Context window. Default: 1. Valid values: 0-2 |
Search mode and range reading are mutually exclusive: query and start_chunk provided together returns 400.query When neither the search nor the range parameters are provided, a default overview is returned (representative chunk snippets per document).
Request parameters (range reading mode)
Parameter | Type | Required | Description |
start_chunk | int | No | Starting chunk position of the range (>= 0). Mutually exclusive with the |
end_chunk | int | No | Ending position of the range (inclusive). Providing only |
In the response, each source document echoes total_chunks (the total number of chunks of that document) so that agents can plan the next read range.
Request example
curl "http://<host>:4040/v1/knowledge/wiki/pages/101/sources" -H "X-API-Key: $API_KEY"Response
Response example
{
"code": 0, "msg": "ok",
"data": [
{
"document_id": 42,
"title": "deployment-manual.pdf",
"source_url": null,
"file_path": "docs/deploy.pdf",
"mime_type": "application/pdf",
"oss_presigned_url": "/v1/files/download?key=...&expires=...&signature=...",
"total_chunks": 128,
"chunks": [
{"chunk_id": "42_3", "position": 3, "content": "...prepare .env before deployment..."}
]
}
]
}POST /v1/knowledge/wiki/issues
Flags quality issues (outdated/inaccurate/missing/broken links) on Wiki page content for background review and re-distillation. Requires editor write permission of the page's real knowledge base; viewer cannot submit - this is an authorization fix for the read-only role. Success returns 200 (Box-wrapped).
Request
Request parameters
Parameter | Type | Required | Description |
page_id | long | Yes | Page ID |
issue_type | string | Yes |
|
description | string | No | Issue description, up to 2,048 characters |
Request example
curl -X POST http://<host>:4040/v1/knowledge/wiki/issues \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"page_id": 101, "issue_type": "outdated", "description": "The deployment port has changed from 8080 to 4040"}'Response
Response example
{"code": 0, "msg": "ok", "data": {"id": 7, "status": "open"}}GET /v1/knowledge/documents/{id}/images
Lists images extracted from a document (OSS originals + short-lived signed download links + Vision descriptions/OCR text). Requires reader or higher permissions. Crossing workspaces / nonexistent returns 404.
Request
Request parameters
Parameter | Type | Description |
oss_key | string | OSS object key |
signed_url | string | Short-lived pre-signed GET URL. When OSS is not connected, it is |
page_number | integer | Page number (populated for PDFs) |
block_type | string | Chunk type (usually image) |
section_path | string | Section path |
caption | string | Vision-generated description + OCR text (embedded in the vector store for retrieval) |
Request example
curl "http://<host>:4040/v1/knowledge/documents/42/images" -H "X-API-Key: $API_KEY"Response
Response parameters
Field | Type | Description |
oss_key | string | OSS object key |
signed_url | string | Short-lived pre-signed GET URL; null when OSS is not connected (oss_key is still returned) |
page_number | integer | Page number (populated for PDFs) |
block_type | string | Chunk type (usually image) |
section_path | string | Section path |
caption | string | Vision-generated description + OCR text (embedded in the vector store for retrieval) |
Response example
{
"code": 0, "msg": "ok",
"data": [
{
"oss_key": "ws1/doc42/img_001.png",
"signed_url": "https://oss.example.com/...&Signature=...",
"page_number": 3,
"block_type": "image",
"section_path": "Architecture/Storage layer",
"caption": "Storage trio architecture diagram: PolarDB-X + PolarDB-X Search + OSS"
}
]
}Document immutable source revisions (added 2026-09-16)
prefix: /v1/knowledge/bases/{kbId}/documents/{docId}/revisions. All responses follow Box<T> (business data is in data), and every call re-checks the current knowledge base viewer permissions. A revision ID that crosses the workspace / knowledge base / document returns 404; a historical ID is not an authorization credential. After the source storage read or PDF rendering finishes, the current permission state is queried again; an ACL downgrade or a public-to-private switch committed in the meantime will not return the body based on stale permissions cached within the request.
Method / relative path | Behavior |
GET | descending list; |
GET | Pins the source snapshot |
GET | original body; does not issue an OSS bearer URL and does not fall back to the current document |
GET | page of the PDF (starting from 1), rendered to PNG by server-side PDFBox; no browser PDF actions are performed |
GET | Reads the latest retained parsed text, chunks, and coverage of this source revision; returns 404 when absent and does not fall back to the current document |
POST | editor explicitly restores a pinned source; request |
Revision fields:
id(business UUID),number,snapshot_hash,source,created_at,author_member_id(may be null for old records).sourcecontains the source, original hash / OSS key, title, metadata, and document metadata snapshot; parsing coverage is provided through the separate/parsedAPI; no knowledge-base-wide release manifest is stored. The new revision produced by a restore records its origin insource.restored_from_revision_id; this audit note does not participate in thesnapshot_hashof the source content. The technicalingest_revision,projection_generationis not the business revision ID. Identical snapshots are not recreated; a metadata change or A→B→A produces a new business revision. restore keeps the document ID / external_id, first validates the original outside a transaction, then performs CAS within a short transaction and generates the new source revision and the existing ingest outbox; it does not restore old permissions, old review decisions, or Wiki / graph derivations, and it is not equivalent to a knowledge-base-wide release rollback. This API does not imply that content has been reviewed or published.New capture and restore are disabled by default:
context.knowledge.revisions.capture-enabled=false. Set it to true only after confirming that all readers / writers / workers / GC have been upgraded to a compatible version; old programs do not recognize historical references, and a mere epoch or switch cannot isolate an old GC. Disabling capture does not disable the protection or reading of existing historical objects. When an old original lacks a verifiable content_hash, no historical baseline is forged.Source fields:
revision_id,source_hash,mime_type,encoding(utf-8orbase64),content. Explicitly fails when the original SHA-256 check fails, the object is missing, or the 16 MiB online preview budget is exceeded; when uploading with attachments, the main file check uses the fixedmetadata_json.content_sha256. PDFs returnpage,total_pages,mime_type=image/png, base64content; each file has up to 1,000 pages, each page up to 1,200x1,600, and the server renders at most 2 pages concurrently. Binary originals and parsing evidence are provided separately.Parsed fields:
revision_id,source_hash,artifact_hash,text,blocks,coverage,created_at.blocksretains onlypage_number,block_type,section_path,textand does not persist signed URLs. On a successfully committed ingestion, an immutable parse is retained per(revision_id, ingest_revision); retries of the same ingestion do not overwrite evidence, and re-ingestion can add parses so that an initially partial parse can be repaired. Reads verify content digests; Ontology tasks pinartifact_hash, and later re-parsing does not change the evidence of old tasks. Coverage status is complete / partial / unknown; unknown must not be treated as complete. A single parse artifact is capped at 12,000,000 bytes, and a workspace defaults to at most 1 GiB (context.knowledge.revisions.max-parsed-bytes-per-workspace), counted against the workspace logical storage quota.The retention policy has two layers. Automatic trimming: each time a revision is added, the latest N revisions are kept according to
context.knowledge.revisions.max-kept-per-document(default 5), and the excess is physically deleted along with the parsing artifacts. Hard limits: by default a single document has at most 1,000 revisions (max-per-document), and the workspace historical source file bundle is at most 50 GiB (max-retained-bytes-per-workspace); when exceeded, new writes are rejected rather than history being silently deleted. Originals / sidecars that have historical references are not deleted when the current document is updated; the history of that scope is deleted only when the knowledge base / workspace is deleted. Logical historical bytes count toward the workspace storage quota. Currently it deduplicates by document/source_hash bundle and takes the maximum byte count in the records, rather than a per-OSS-object ledger; derived attachments generated later under the same source_hash cannot be precisely measured this way. This is not the final implementation of per-release reference counting / retention windows / physical billing.ctxdb0 read wiring:
knowledge documents versions <document-id> --source <name-or-id> --page 0 --limit 20 --jsonandknowledge documents source <document-id> --source <name-or-id> --revision <business-revision-uuid> --json. The latter keeps the source response JSON and encoding as-is; permission / missing / corruption errors fail directly without calling the current document detail as a substitute; no release pin or semantic verify capability is added. The single-document restore command isknowledge documents restore <document-id> --source <name-or-id> --revision <business-revision-uuid> --expected-revision <current-head-uuid> --json, using the same editor permission and concurrency check.
Ontology knowledge base (managing catalog, candidate versions, and validation)
prefix: /v1/knowledge/bases/{kbId}/ontology; all use the Box wrapper (code, msg, data, trace_id); the fields below all refer to data. An Ontology belongs to a single KB; reading requires viewer, while saving, structural validation, discovery, referencing, deactivation, and deletion require editor. viewer is read-only. All resources are restricted by the current workspace/member/KB permissions. Saving yields a candidate definition and does not automatically change existing Wiki / graphs; after an explicit apply, new graph and Wiki tasks pin the selected definition, and documents still follow existing review.
Management catalog and candidate appending
Method / relative path | Request and response |
GET |
|
POST |
|
DELETE | Returns |
Catalog row fields are id, kind, status, schema_version_id, version_number, source_kind, created_at, generated_at, author_member_id, type_count, relation_count, is_active, task_id, error_code.kind=version statuses are candidate / active; kind=discovery statuses are pending / processing / dead / expired. Sources are legacy / json_import / manual_edit / sample_discovery. The catalog does not return full schema text, sample text, or raw model requests. After merging versions and non-hidden tasks, the server paginates uniformly by created_at DESC,id DESC; a successful discovery shows only the generated version, without duplicating the task row. A catalog read error is not an empty catalog; on 404, check the actual request address and deployed version - it must not degrade to a successful empty list.
Candidate save contract:
request_idis required, non-empty, and up to 128 characters; the same ID with identical full request content returns the original version, while different content returns 409. A new request always appends a version after validation, even if the schema / instruction content is identical - old versions are never reused.source_kindaccepts only manual_edit / json_import.schema,instructionsis required;instructions'sontology_discovery_instructionsandgraph_extraction_instructionsare each up to 8,000 characters and are stored pinned with the new version.expected_head_version_idmust take the real historical head returned by the catalog (null for an empty history), not the current active or the selected edit source; a management tombstone also counts as a historical head.parent_version_idonly identifies the edit source and may be null; when non-null it must belong to the same knowledge base and still be selectable, and it does not carry CAS. When editing an old version, the latest head is still used for CAS.A head conflict returns 409. The client keeps the draft, and the user explicitly refreshes the head before saving again; automatic retry-overwrite is not allowed. Saving is not equivalent to referencing or publishing.
Schema field names are camelCase and strictly validated: the field names of the
schemafamily (types[],types[].attributes[],relations[]) are camelCase (valueType/allowedUnits/typeId), unlike the snake_case request bodies used elsewhere in this document. The presence of an undeclared field (for example writingaliasesasalias, or writingallowedUnitsasallowed_units) returns 40040001 unknown field '<name>' on OntologySchema…, andPOST /ontology/validatebehaves the same. Previously such typos were silently dropped, so the save succeeded butaliases/allowedUnitswere persisted as null, causing alias resolution and unit validation to silently fail. Note that the facts structure ofvalidate-facts(instances/links) remains lenient, because the same set of types also carries model extraction output.Deletion only hides the management catalog and new selections, keeping the schema, instructions, historical facts / tasks, and historical reads by ID; it does not roll back the head / version number or release historical version quota. Repeated deletion is idempotent, and deletion does not increase the active generation. The only reference switch is still through the
/activegeneration CAS described below.
Direct sample upload discovery
This is an independent sample protocol, not the revision discovery / extraction protocol of /tasks below.
Method / relative path | Request and response |
POST | multipart: repeated |
GET |
|
POST | The request must be an empty JSON object |
DELETE | Returns |
optionsis{request_id,ontology_discovery_instructions?,graph_extraction_instructions?,base_schema_version_id?,competency_questions?}.request_id is required, at most 128 characters. The same ID with the same valid input returns the original task; a different input returns 409.
The two instructions are each at most 8000 characters: when omitted, they inherit the corresponding value of the pinned baseline version, and are empty if there is no baseline; an explicit empty string means clearing the instruction, not inheriting it. The baseline must belong to the same knowledge base and be selectable. The instance extraction instructions are pinned and saved only with the task and candidate version, for use in subsequent graph extraction; the sample model prompts do not contain the extraction instructions and cannot be ghostwritten by the sample model.
competency_questions(V103, optional): competency question acceptance cases, a string array of at most 10 items, each 1..200 characters; when provided, they are evaluated together with the discovery call, and questions that the schema cannot answer produce, in the taskquality_report, acompetency_unsupportedwarning (does not block release); leaving it empty means no evaluation this time.
Example:
curl -X POST "http://<host>:4040/v1/knowledge/bases/1/ontology/discoveries" \
-H "X-API-Key: $API_KEY" \
-F 'files=@sample-a.pdf' -F 'files=@sample-b.txt' \
-F 'options={"request_id":"sample-request-001","ontology_discovery_instructions":"Focus only on products and terms","graph_extraction_instructions":"Keep source evidence during extraction"};type=application/json'Statuses are pending / processing / done / dead / expired. The first two display as "running" in the UI; candidates cannot be viewed, edited, referenced, or deleted, and are restored from the server catalog after a refresh. done atomically generates an unapplied version,
authority=unreviewed_candidate, without automatic referencing. The sample model outputs only schema / explanation, passes structural validation, does not fabricate revision source anchors, and does not imply that the old per-item evidence protocol has passed.Sample limits (configuration prefix
context.knowledge.ontology-samples): by default 1-4 files per batch, 10 MiB per file, 20 MiB total, PDF / DOCX / TXT / MD only; parsed text totals at most 32,000 UTF-16 characters. Exceeding a limit fails the whole batch - no truncation and no partial file acceptance. No OCR, image / embedded attachment processing, or external link fetching is performed; scanned files without text, encrypted, corrupted, empty, or unsupported formats fail explicitly. Defaults: 2 actual parsing threads, 30 seconds per file, and a 120-second batch parsing budget; models still use existing provider timeouts, and thread interruption is not promised to equal hard CPU isolation.Zero formal ingestion side effects: samples use independent private storage and are only parsed and discovered; no formal document upload is called, no document / revision is created, no INGEST is delivered, and no chunk / embedding / ES / Graph / Wiki / review writes are performed. The original is pinned by the SHA-256 of the raw bytes, and the artifact hash includes the parser version, source hash, and text; the task pins the baseline, the two instructions, the parser/template versions, and the model configuration. A retry reuses the pinned input that was successfully parsed; a retry is rejected when the input or run configuration becomes invalid, and re-upload is required.
Transient failures default to the first attempt plus up to 3 automatic retries (5 / 10 / 20 seconds); 429 is likewise bounded; structural, permission, and no-text errors are not treated as transient. New and old tasks combined default to at most 8 active tasks and 1,000 retained tasks per knowledge base; versions default to at most 1,000, and management deletion does not release quota.
filesSummary fields areid,filename,mime,bytes,source_hash,artifact_hash,parser_version,retain_until,source_available, without returning original URLs or full text. Sample originals and text are retained for at least 7 days; running tasks cannot be reclaimed. Reaching the retention period does not mean reclamation has happened;source_availablerelies on actual reclaim markers and object location information, not merely time comparison. Successful versions remain usable after sample reclamation; a dead task becomes expired after losing its sample or event and cannot be retried. Uncommitted orphan objects can be reclaimed only after 24 hours; nothing is deleted when database / object age is uncertain, and failed deletions keep the location for retry.
Compatible versions, fact validation, and apply protocol
Method / relative path | Request and response |
GET | 20 pinned versions per page, descending array |
GET |
|
PUT | editor; |
GET | viewer; the last 20 pinned-schema extraction records, including source revision, schema version, ingestion count, facts, model/evidence provenance, and the published flag |
GET | Reads the pinned version under current permissions |
POST |
|
POST | The request is a schema; returns |
POST | The request is facts; validated against the pinned schema with source anchor verification; returns the same Validation |
GET | An array of stable type IDs matched case-insensitively by name / alias; ambiguity returns all candidates without automatic merging |
schema / facts inner fields are camelCase; outer version, instruction, and task fields are snake_case. At most 100 types, 300 relations, and 50 properties per type; names / IDs are non-empty and up to 128 characters. Example definition:
{
"expected_version_id": null,
"schema": {
"types": [{"id":"indicator","name":"Indicator","aliases":["metric"],"attributes":[{"id":"value","name":"Value","valueType":"number","required":true,"allowedUnits":["u_test"]}]}],
"relations": []
},
"instructions": {"ontology_discovery_instructions":"Output only candidates with source evidence", "graph_extraction_instructions":"Strictly follow the fixed types and units"}
}Relation fields
{id,name,domain,range,min,max}; a null max means no upper bound. Facts fields{instances,links,complete}, instances are{id,typeId,properties,units,evidence}, edges are{id,typeId,from,to,evidence}; anchors are{revisionId,sourceHash,quote,page,start,end}. Positioning must providepageor provide bothstart/end; a quote without positioning returnsanchorissues. Text offsets use UTF-16, left-closed and right-open; when offsets are given they must exactly match the quote; plain-text page numbers support onlypage=1; retained PDF / Office parses use pinned chunk page numbers, and when both page numbers and offsets are provided they must agree. At most 5,000 instances / 20,000 edges; the complete set must satisfy required, numeric / string / boolean types, units, domain/range, direction, and cardinality. When a numeric property declares a non-emptyallowedUnits, a missing or mismatched unit returns structuredunitissues. Source verification allows at most 200 anchors / 16 originals; binary sources that are unknown or lack historical parses are explicitly unverifiable; only literal positioning, which is not equivalent to five-state semantic supportability validation or factual authority.complete=falseretains partial issues and does not claim a full pass.Applying is not equivalent to a knowledge-base-wide rebuild: existing documents need explicit reprocessing through the current document ingestion API. The Graph / Wiki switches still take effect separately; without a configured schema, the original processing is kept. Running tasks pin the applied generation and definition version; after a switch, old tasks cannot publish over new results. The graph validates type IDs, properties, units, relation constraints, and source anchors before entering the original graph publish/aggregation flow; only complete parsing and complete model output may replace a document's graph contribution. Automatic graph extraction is currently capped at 64,000 characters per document; exceeding the cap, partial/unknown parsing, or model truncation fails explicitly and keeps the old graph.
facts.publishedindicates that the result once passed graph publishing; the currently applied version is marked by/activein the path always prevails.Wiki uses the same pinned schema in three places - candidate page discovery, document summaries, and page merging - keeping existing slugs and references; the document generation and applied generation are checked separately before body and schema publishing. Existing Wiki logs
distill_coveragerecordontology_versionandingest_revision, making the processing configuration traceable. This guarantees that definitions enter real model requests; it does not promise the business accuracy of model-generated content.ctxdb0 provides
knowledge ontology versions|active|apply|save|discover|extract|tasks. Each command specifies--source <name-or-id>; apply uses--version <uuid|none> --expected-generation <n>, save reads the save request above from--stdin, and discover uses--revision <uuid>(optionally--schema-version); extract must specify both pinned version parameters.tasksLists recent tasks and their result statuses; use the APIs above for single task details and retries.
Approval notifications (added 2026-09-16, disabled by default)
prefix: /v1/knowledge/bases/{kbId}/review-notifications. All return Box<T>, and configuration, delivery records, and retries all require the current knowledge base owner.
Method / relative path | Request and response |
GET |
|
PUT |
|
GET | An array of 20 entries per page; |
POST | Only failed deliveries that are pending with a dead outbox can be retried; a changed configuration or an already sent/cancelled notification returns 409 |
The notification is written to the outbox within the committing transaction of the original review's final
accepted, shared by automatic and manual review.accepted_pending, rejection, or failed activation does not send an "approved" notification; a rolled-back transaction leaves no notification. Notifications are sent asynchronously; failures use the existing event retry mechanism and do not roll back the document review status. Notifications are generated only for new approvals after enablement; historical approvals are not backfilled.The webhook is a JSON POST containing
event_id(stable notification UUID),type=knowledge.document.review_approved,knowledge_base_id,document_id,review_id,revision_id(null when history is not captured),content_hash,approved_at. The document full text is not included. The request headersIdempotency-KeyandX-Context0-Event-Idare both this UUID; receivers should deduplicate by it. Delivery may repeat when sending succeeds but local acknowledgment fails; exactly-once is not promised. 2xx means success; other statuses mean failure.statusis pending / delivered / cancelled; pending withevent_status=deadmeans automatic retries are exhausted and the owner can retry manually. After disabling or changing the subscription, notifications of the old configuration that have not started sending are cancelled and will not be delivered to the new address; in-flight HTTP requests may still complete. Retries keep the same event number.Address restrictions are validated both at save time and at actual DNS connection time: redirects, URL credentials, fragments, cloud metadata addresses, link-local, and reserved addresses are prohibited. Private/loopback networks are denied by default; internal receivers must be configured one by one with exact hostnames by an administrator in
context.knowledge.review-notifications.allowed-internal-hosts, and cannot be bypassed via the API. The connection timeout is 5 seconds and the response timeout is 10 seconds; response bodies are not read, and URLs containing receiver tokens are not written to failure logs. Scheduled sending is enabled by default (context.knowledge.review-notifications.scheduled-enabled=true); nothing is sent without an enabled subscription.
Pinned-revision Ontology discovery / extraction async tasks (compatible protocol)
prefix: /v1/knowledge/bases/{kbId}/ontology. The server-side context.knowledge.ontology-tasks.enabled currently defaults to true; with an explicit false, old task creation / retries return a validation error and no worker is registered.context.knowledge.ontology-tasks.scheduled-enabled defaults to true and controls polling; both configurations should follow the actual deployment override values. New sample discovery shares this worker and switch; when explicitly disabled, sample creation / retries return a feature-disabled error. Existing event outbox / lease / retry queues are reused, with no new Agent runtime.
Method / relative path | Request and response |
POST |
|
GET | 20 tasks per page; requires viewer |
GET | Pinned task, input fingerprint, candidate results, and run status; requires viewer |
POST | The original author with editor permission explicitly retries a dead event that is still retained; pinned inputs are unchanged; non-dead / already cleaned returns 409; run configuration drift is still rejected |
extract must specify a pinned Ontology version; discover may optionally use an existing version, and instructions are pinned with that version. Each run takes 1-4 revisions of the same knowledge base, supporting retained text originals and PDF / Office files with existing historical parses, with a total length of at most 32,000 characters; each knowledge base has at most 8 queued / processing tasks. Source revision / original hash / parse artifact hash and coverage, schema / instructions, system templateVersion / hash, model configuration digest, and sampling budget form the input fingerprint; duplicate inputs return the same task. Failed tasks are not silently retried by repeated creation; the original author can explicitly retry, and changed input dependencies require creating a new task. By default each knowledge base retains at most 1,000 tasks and 1,000 Ontology versions (
context.knowledge.ontology-tasks.max-retained-per-kb/context.knowledge.ontology.max-versions-per-kb); the cap rejects new entries without automatically deleting history.Statuses use the existing event statuses (pending / processing / done / dead). Truncated model results, run configuration changes, or lost permissions cause failures; a worker that lost its lease must not write back. Results contain
candidate,issues,valid,authority=unreviewed_candidate,coverage.scope=selected_originals_only,coverage.corpus_complete=false. done only means the task finished, and valid only means structural and literal evidence passed; neither is formal publishing. Accepting a schema requires explicitly saving a candidate, then applying it through/active; the compatible task panel allows editing the candidate JSON while the original model results remain retained. The new Ontology catalog uses independent sample discovery and version disposition; it does not show the removed revision trial-run / canvas panels, nor equate version referencing, editing, or deletion with the old per-item candidate accept / merge / reject. Document review continues to use the existing review mechanism; knowledge-base-wide publishing, knowledge-base-wide version rollback, and expert double review are out of scope for this simplification.discover output is
{schema,candidates}. Every type, property, and relation must have a corresponding provenance candidate;pathuse respectivelytypes.TYPE_ID,types.TYPE_ID.attributes.ATTRIBUTE_ID,relations.RELATION_ID; each item contains an independentid, a finite number in 0-1confidence, and a non-emptyevidence. At most 100 items; missing items / duplicate paths / undeclared paths all return blocking issues; high confidence does not exempt anchor validation. extract output uses the facts format above. Task references may come only from the revisions selected in the request; other unselected sources of the same knowledge base cannot be introduced. After events are cleaned by the retention policy, tasks with results remain done and tasks without results show expired; retry cannot recreate lost events.
Search quality benchmark (gold standard + scoring runs)
Method | Path | Description |
|
| Submits a gold-standard sample. Request body: |
|
| Lists samples of this workspace |
|
| Runs a benchmark. Request body: |
|
| Lists historical scoring runs |
|
| Details of a single scoring run (including the metrics JSON) |
ACL semantics (starting 2026-09-16): a benchmark is essentially a controlled retrieval probe — a sample that says "expect chunk X to appear in the results of knowledge base N" can naturally serve as an existence oracle for "whether knowledge base N contains chunk X". Therefore, both seeding probes and running probes must pass the ACL:
POST .../gold-standards:kb_ids- every knowledge base in it requiresvieweror above; no permission returns 403 (40303); crossing workspaces / nonexistent returns 404.POST .../runs: first, each of the inputkb_idsis checked withrequireReadindividually (a 403 does not leave a failed record inbenchmark_runs— validation happens before the run record is created); then, sample by sample, it determines whether the caller has read permission on thekb_idsstored by the sample itself, and samples without permission are skipped entirely (rather than reduced to a readable subset, otherwise the reported recall would be inconsistent with the scope of the sample).kb_idsmust now be an array of numbers; a non-numeric element returns 400 (rather than being silently dropped). Note: previously thekb_idsfilter never actually took effect due to a type issue (small integers in the request body were parsed asIntegerand were never equal when compared with theLongin the samples), so benchmark runs always covered all samples in the workspace. After the fix,kb_idstruly takes effect, and historical benchmark numbers cannot be directly compared side by side with those after the fix.
Multimodal
Multimodal resources are written by /v1/context/ingest (messages[].resource_ids and the upload API. A User Key can access only its own resources; Owner Key can access the entire workspace. Cross-tenant queries uniformly return 404.
POST /v1/multimodal/upload
Uploads a multimodal resource (image/audio/document) and returns queued status; OCR/ASR/transcription is extracted later by the async scheduler.
The upload path is /v1/multimodal/upload (not /v1/multimodal; POST /v1/multimodal, which returns 405).
Request
Form parameter (multipart/form-data)
Parameter | Type | Required | Description |
file | file | Yes | File to upload |
modality | string | Yes | Modality type: |
session_id | string | No | Associated session ID |
metadata | string | No | JSON-format metadata string |
Request example
curl -X POST "http://<host>:4040/v1/multimodal/upload" \
-H "X-API-Key: $API_KEY" \
-F "file=@/path/to/image.png" \
-F "modality=image"Response
Response example
{"code": 0, "msg": "ok", "data": {"resource_id": "res_abc123", "ingest_status": "queued"}}GET /v1/multimodal
Lists multimodal resources of the current tenant by page, ordered by created_at descending. A lightweight projection without heavy fields such as OCR/ASR/transcription.
Request
Request parameters
Parameter | Type | Required | Description |
page | integer | No | Page number (starting from 0). Default: 0. |
size | integer | No | Page size. Default: 50. Maximum: 200. |
Request example
curl "http://<host>:4040/v1/multimodal?page=0&size=20" -H "X-API-Key: $API_KEY"Response
Response example
{
"code": 0, "msg": "ok",
"data": {
"content": [
{
"resource_id": "res_abc123",
"modality": "image",
"mime": "image/png",
"oss_url": "http://context0.example.com/v1/files/download?key=...&expires=...&signature=...",
"thumbnail_url": null,
"size_bytes": 20480,
"ingest_status": "done",
"memory_id": "mem_1_2_abc",
"session_id": "sess-2026-08-19-001",
"member_id": 2,
"created_at": "2026-08-19T10:00:00Z",
"updated_at": "2026-08-19T10:00:05Z"
}
],
"total_elements": 1,
"total_pages": 1,
"number": 0,
"size": 20
}
}data is a Spring Data Page structure: entries are in content, and pagination metadata is in total_elements / total_pages / number / size (the global SNAKE_CASE naming strategy applies).
GET /v1/multimodal/{resourceId}
Retrieves multimodal resource details, including OCR / ASR / transcription text and metadata. Cross-tenant access returns 404.
Request
Request example
curl "http://<host>:4040/v1/multimodal/res_abc123" -H "X-API-Key: $API_KEY"Response
Response parameters
Field | Type | Description |
resource_id | string | Resource ID |
modality | string | Modality ( |
mime | string | MIME type |
oss_url | string | Context0 short-lived signed download URL |
thumbnail_url | string | Thumbnail URL |
size_bytes | long | File size (bytes) |
transcribed_text | string | Transcription text (audio/video) |
ocr | object | OCR result (image/PDF) |
asr | object | ASR result (audio) |
metadata | object | User metadata |
memory_id | string | Associated memory ID |
session_id | string | Associated session ID |
member_id | long | Owning member ID |
ingest_status | string | Ingestion status |
Response example
{
"code": 0, "msg": "ok",
"data": {
"resource_id": "res_abc123",
"modality": "image",
"mime": "image/png",
"oss_url": "http://context0.example.com/v1/files/download?key=...&expires=...&signature=...",
"thumbnail_url": null,
"size_bytes": 20480,
"transcribed_text": null,
"ocr": {"text": "Architecture diagram annotation text"},
"asr": null,
"metadata": {},
"memory_id": "mem_1_2_abc",
"session_id": "sess-2026-08-19-001",
"member_id": 2,
"ingest_status": "done",
"created_at": "2026-08-19T10:00:00Z",
"updated_at": "2026-08-19T10:00:05Z"
}
}GET /v1/multimodal/{resourceId}/url
Generates a temporary pre-signed download URL for a resource (each call mints a new link; long-lived links are not stored). Cross-tenant access returns 404.
Request
Request parameters
Parameter | Type | Required | Description |
ttl | integer | No | Pre-sign validity (seconds); non-positive values fall back to the configured default; capped at 86400 (24h), exceeding it returns 400 |
Request example
curl "http://<host>:4040/v1/multimodal/res_abc123/url?ttl=3600" -H "X-API-Key: $API_KEY"Response
Response example
{
"code": 0, "msg": "ok",
"data": {
"signed_url": "https://oss.example.com/...&Expires=...&Signature=...",
"expires_at": "2026-08-19T11:00:00Z",
"oss_key": "ws1/res_abc123.png"
}
}Skill
GET /v1/skills
Lists Skills in the current workspace by page, ordered by updated_at descending.
Request
Request parameters
Parameter | Type | Required | Description |
page | integer | No | Page number (starting from 0). Default: 0. |
page_size | integer | No | Page size. Default: 20. Valid values: [1, 100]. |
Request example
curl "http://<host>:4040/v1/skills?page=0&page_size=10" -H "X-API-Key: $API_KEY"POST /v1/skills/search
Dedicated semantic search endpoint for Skills. Returns a relevance-ranked Skill list (L0/L1 layer information, used for trigger decisions).
Request
Request parameters
Field | Type | Required | Description |
query | string | Yes | Search text |
top_k | int | No | Maximum number of entries to return |
tags | string[] | No | Filter by tags |
Request example
curl -X POST http://<host>:4040/v1/skills/search \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"query": "code review", "top_k": 5, "tags": ["review"]}'Response
Response example
response (Box<List<SkillSearchResult>>):
{
"code": 0,
"msg": "ok",
"data": [
{"id": 1, "name": "code-review", "description": "...", "abstract": "...", "score": 0.87, "tags": ["review"]}
]
}To enumerate uploaded Skills, use GET /v1/skills (supports pagination, ordered by updated_at descending).
GET /v1/skills/{id}
Retrieves Skill metadata (L0 layer, most lightweight).
Request
Request example
curl "http://<host>:4040/v1/skills/1" -H "X-API-Key: $API_KEY"GET /v1/skills/{id}/overview
Retrieves the Skill summary/abstract (L1 layer), used to decide whether to load the full body after a trigger judgment.
Request
Request example
curl "http://<host>:4040/v1/skills/1/overview" -H "X-API-Key: $API_KEY"GET /v1/skills/{id}/content
Retrieves the complete Skill markdown/script body (L2 layer).
Request
Request example
curl "http://<host>:4040/v1/skills/1/content" -H "X-API-Key: $API_KEY"GET /v1/skills/{id}/package
Returns the Skill body + resource manifest (including pre-signed download URLs and SHA-256) in one call for the client to verify before installation.
Request
Request parameters
Query parameters: ttl (pre-sign validity in seconds, default: 3600).
Request example
curl "http://<host>:4040/v1/skills/1/package?ttl=7200" -H "X-API-Key: $API_KEY"Response
The client must verify the digests of the body and every resource separately before writing to disk; installation should be refused when a digest is missing or mismatched.download_url is data, not a trust authorization: the client should also restrict allowed schemes/origins, prevent credentials from leaking through redirects, and write to disk atomically only after integrity verification passes.
Response parameters
Field | Type | Description |
id | integer | Skill ID |
name | string | Skill package name |
version | integer | Skill version |
visibility | string | Visibility |
content | string | Installed as the |
content_sha256 | string |
|
resources[].rel_path | string | Relative path within the Skill package |
resources[].mime_type | string | Resource MIME type |
resources[].size_bytes | integer | Raw byte size of the resource |
resources[].sha256 | string | SHA-256 of the resource's raw bytes (64 hex characters) |
resources[].download_url | string | Context0 short-lived signed download URL; the backend streams from OSS, so the client does not need OSS access |
Response example
{
"code": 0,
"msg": "ok",
"data": {
"id": 1,
"name": "incident-response",
"version": 3,
"visibility": "workspace",
"content": "---\nname: incident-response\n---\n# Incident response\n",
"content_sha256": "<64-hex-sha256>",
"resources": [
{
"rel_path": "references/runbook.md",
"mime_type": "text/markdown",
"size_bytes": 1024,
"sha256": "<64-hex-sha256>",
"download_url": "http://context0.example.com/v1/files/download?key=redacted&expires=redacted&signature=redacted"
}
]
}
}POST /v1/skills
Creates a Skill by uploading metadata and the body (optionally with base64 binary resources). Requires OWNER permissions. There are three creation modes (in priority order: input_schema > content > body):
input_schema mode: provide
input_schema(JSON Schema) +name, and a Skill in MCP Tool format is generated automatically.body mode: provide
body(structured body, no frontmatter needed) +name(usually combined with metadata fields such asdescription/tags).content mode: provide
content(complete Skill Markdown, must start with---YAML frontmatter; name / description and other metadata are parsed from the frontmatter).
Request
Request parameters
Parameter | Type | Required | Description |
content | string | Conditional | Complete Skill Markdown; must start with |
name | string | Conditional | Skill name. Required in body mode ( |
body | string | Conditional | Structured body that works with name / description (no frontmatter needed); choose either this or content |
description | string | No | Skill description |
allowed_tools | string[] | No | Whitelist of tools allowed to be called |
tags | string[] | No | Tags, used for search filtering |
input_schema | object | No | Skill parameter JSON Schema |
visibility | string | No | Visibility, such as workspace / member |
metadata | object | No | Custom metadata |
resources | array | No | Resources uploaded with the Skill (base64) |
resources[].rel_path | string | Yes | Resource relative path |
resources[].content_base64 | string | Yes | Base64-encoded resource content |
resources[].mime | string | No | MIME type (such as image/png) |
Request example
body mode
curl -X POST http://<host>:4040/v1/skills \ -H "X-API-Key: $OWNER_KEY" -H "Content-Type: application/json" \ -d '{"name": "Code Review", "description": "PR Code review process", "body": "# Code review process\n...", "tags": ["review"]}'content mode, starting with frontmatter
curl -X POST http://<host>:4040/v1/skills \ -H "X-API-Key: $OWNER_KEY" -H "Content-Type: application/json" \ -d '{"content": "---\nname: Code Review\ndescription: PR Code review process\n---\n# Code review process\n...", "tags": ["review"]}'
Response
Response example
{
"code": 0, "msg": "ok",
"data": {
"id": 1,
"name": "Code Review",
"description": null,
"version": 1,
"source_format": "markdown",
"abstract": null,
"allowed_tools": [],
"tags": ["review"],
"visibility": "workspace",
"owner_member_id": 2,
"resource_count": 0,
"ingest_status": "pending",
"created_at": "2026-08-19T10:00:00Z",
"updated_at": "2026-08-19T10:00:00Z"
}
}GET /v1/skills/{id}/resources
Lists binary / text resources associated with a Skill (such as example scripts and screenshots).
Request
Request parameters
Parameter | Type | Description |
rel_path | string | Resource relative path |
mime_type | string | MIME type |
size_bytes | long | Resource size in bytes |
Request example
curl "http://<host>:4040/v1/skills/1/resources" -H "X-API-Key: $API_KEY"Response
Response example
{
"code": 0, "msg": "ok",
"data": [
{"rel_path": "scripts/run.sh", "mime_type": "text/x-shellscript", "size_bytes": 512}
]
}GET /v1/skills/{id}/resources/{relPath}
Generates an OSS pre-signed download URL for a specified Skill resource, allowing anonymous download within ttl seconds.{relPath} is the resource relative path (may contain /).
Request
Request parameters
Parameter | Type | Required | Description |
ttl | integer | No | Pre-signed URL validity (seconds). Default: 3600. |
Request example
curl "http://<host>:4040/v1/skills/1/resources/scripts/run.sh?ttl=3600" -H "X-API-Key: $API_KEY"Response
Response example
data is the pre-signed URL string.
{"code": 0, "msg": "ok", "data": "https://oss.example.com/...&Signature=..."}DELETE /v1/skills/{id}
Deletes a Skill and its resources by ID. Requires OWNER permissions.
Request
Request example
curl -X DELETE http://<host>:4040/v1/skills/1 -H "X-API-Key: $OWNER_KEY"Response
Response example
{"code": 0, "msg": "ok", "data": null}Strategy
Strategy management requires Owner Key.
GET /v1/strategies/presets
Returns the list of built-in strategy presets.
Request
Request example
curl "http://<host>:4040/v1/strategies/presets" -H "X-API-Key: $OWNER_KEY"GET /v1/strategies
Lists strategies of the current tenant. Supports filtering by preset / active.
Request
Request parameters
Query parameters: preset (string), active (boolean).
Request example
curl "http://<host>:4040/v1/strategies?active=true" -H "X-API-Key: $OWNER_KEY"POST /v1/strategies
Creates a custom strategy.
Request
Request parameters
Parameter | Type | Required | Description |
name | string | Yes | Strategy name, 1-128 characters, only |
config_json | object | Yes | Strategy configuration (JSON object) |
preset | string | No | Preset type: |
description | string | No | Description, up to 512 characters |
After creation, active defaults to true.
Request example
curl -X POST http://<host>:4040/v1/strategies \
-H "X-API-Key: $OWNER_KEY" -H "Content-Type: application/json" \
-d '{"preset": "coding", "name": "Coding assistant strategy", "config_json": {"recall_top_k": 15}}'GET /v1/strategies/{id}
Retrieves details of a specified strategy.
Request
Request example
curl "http://<host>:4040/v1/strategies/1" -H "X-API-Key: $OWNER_KEY"PUT /v1/strategies/{id}
Updates strategy fields or parameters. All fields are optional; only the provided fields are updated (name cannot be updated - delete and recreate instead).
Request
Request example
curl -X PUT http://<host>:4040/v1/strategies/1 \
-H "X-API-Key: $OWNER_KEY" -H "Content-Type: application/json" \
-d '{"config_json": {"recall_top_k": 20}}'DELETE /v1/strategies/{id}
Deletes a specified strategy.
Request
Request example
curl -X DELETE http://<host>:4040/v1/strategies/1 -H "X-API-Key: $OWNER_KEY"POST /v1/strategies/{id}/apply
Applies a strategy as the effective configuration of the current tenant.
Request
Request example
curl -X POST http://<host>:4040/v1/strategies/1/apply -H "X-API-Key: $OWNER_KEY"Response
Response example
{"code": 0, "msg": "ok", "data": {"strategy_id": 1, "applied_at": "2026-08-19T10:00:00Z"}}POST /v1/strategies/{id}/deactivate
Sets a strategy as inactive.
Request
Request example
curl -X POST http://<host>:4040/v1/strategies/1/deactivate -H "X-API-Key: $OWNER_KEY"Strategy Hooks
Strategy hooks trigger custom behavior at specific lifecycle points of strategy execution (hook_point). Hooks execute in priority ascending order. Strategy management requires Owner Key.
POST /v1/strategies/{strategyId}/hooks
Creates a hook for a specified strategy.
Request
Request parameters
Parameter | Type | Required | Description |
hook_point | string | Yes | Trigger point identifier. Valid values: |
hook_type | string | Yes | Hook type. Valid values: |
config_json | object | No | Hook configuration (JSON object, such as webhook URL and timeout) |
enabled | boolean | No | Specifies whether to enable. Default: true. |
priority | integer | No | Execution priority (ascending). Default: 0. |
Request example
curl -X POST "http://<host>:4040/v1/strategies/1/hooks" \
-H "X-API-Key: $OWNER_KEY" -H "Content-Type: application/json" \
-d '{"hook_point": "recall_pre", "hook_type": "script", "config_json": {"expr": "ctx.topK = 20"}, "enabled": true, "priority": 10}'GET /v1/strategies/{strategyId}/hooks
Lists all hooks of a specified strategy.
Request
Request example
curl "http://<host>:4040/v1/strategies/1/hooks" -H "X-API-Key: $OWNER_KEY"GET /v1/strategies/{strategyId}/hooks/{hookId}
Retrieves details of a specified hook.
Request
Request example
curl "http://<host>:4040/v1/strategies/1/hooks/1" -H "X-API-Key: $OWNER_KEY"PUT /v1/strategies/{strategyId}/hooks/{hookId}
Updates hook configuration. All fields are optional; only the provided fields are updated.
Request
Request example
curl -X PUT "http://<host>:4040/v1/strategies/1/hooks/1" \
-H "X-API-Key: $OWNER_KEY" -H "Content-Type: application/json" \
-d '{"enabled": false}'DELETE /v1/strategies/{strategyId}/hooks/{hookId}
Deletes a specified hook.
Request
Request example
curl -X DELETE "http://<host>:4040/v1/strategies/1/hooks/1" -H "X-API-Key: $OWNER_KEY"Auth
GET /v1/auth/whoami
Returns the identity resolved from the current API Key, used to verify key validity and view the current identity. No ACL check.
Request
Request example
curl "http://<host>:4040/v1/auth/whoami" -H "X-API-Key: $API_KEY"Response
Response example
{
"code": 0, "msg": "ok",
"data": {
"authenticated": true,
"workspace_id": 1,
"member_id": 2,
"key_type": "USER",
"bound_user_id": "alice"
}
}When unauthenticated, :
{"code": 0, "msg": "ok", "data": {"authenticated": false}}API Key management (management plane)
Management-plane endpoints; require Owner Key authentication. Prefix: /ops/workspaces/members/keys.
Method | Path | Description |
|
| Creates an API Key and returns the one-time plaintext secret |
|
| Lists API Keys by page; Keys created after V56 include |
|
| Revokes a specified API Key |
Create request parameters:
Parameter
Type
Required
Description
workspace_id
long
Yes
Workspace ID
member_id
long
No
Member ID; when omitted, the server infers it from the member bound to the Key
key_type
string
Yes
OWNER/USERCreate response (one-time plaintext):
{"code": 0, "msg": "ok", "data": {"id": 5, "key_prefix": "ctx0_", "key_type": "USER", "plain_key": "ctx0_abc123..."}}List response (
plain_keynon-null only for V56+ Keys):{"code": 0, "msg": "ok", "data": {"items": [{"id": 5, "workspace_id": 1, "member_id": 2, "key_prefix": "ctx0_", "plain_key": "ctx0_abc123...", "key_type": "USER", "status": "active", "created_at": "2026-08-19T10:00:00Z", "last_used_at": "2026-09-16T08:00:00Z"}], "total_elements": 1}}Revoke request:
{"id": 5}
Member management (management plane)
Management-plane endpoints; require Owner Key authentication. Prefix: /ops/workspaces/members.
Method | Path | Description |
|
| Creates a member |
|
| Lists members by page (up to 200 per page) |
|
| Updates member information |
|
| Deletes a member. Request body: |
List request example:
curl "http://<host>:8080/ops/workspaces/members?workspaceId=3&page=0&pageSize=20" -H "X-API-Key: $OWNER_KEY"List response example:
{ "code": 0, "msg": "ok", "data": { "content": [ {"id": 1, "workspace_id": 3, "user_id": "admin", "name": "Admin", "status": "active", "created_at": "2026-09-16T01:00:00Z", "updated_at": "2026-09-16T01:00:00Z"} ], "total_elements": 1, "total_pages": 1 } }NoteworkspaceIdmust equal the caller's own workspace; cross-tenant queries are not allowed.
Managed file download
GET /v1/files/download
Uniformly streams files from OSS / MinIO to clients. Skill package resources, single Skill resource URLs, sources/images hit by knowledge base search, document image lists, Wiki source documents, and download links in multimodal upload results, lists, details, resource URLs, and context are all delivered through this API. Storage pre-signed addresses used by background model calls keep their original purpose.
Business APIs first perform the existing tenant, member, and resource visibility checks, then issue a short-lived download link for the specific object. Existing download_url, oss_url, signed_url field names and string types are unchanged; clients should use the returned values directly and must not parse OSS domains or assemble object paths themselves.
Request
Request parameters
Parameter | Type | Required | Description |
key | string | Yes | Server-generated encoded object key |
expires | integer | Yes | Server-generated Unix expiration time (seconds) |
signature | string | Yes | Server-generated HMAC-SHA256 signature |
Response
Success response
HTTP 200 with the raw file bytes (not Box JSON), Content-Disposition: attachment, Cache-Control: no-store, X-Content-Type-Options: nosniff). The server streams and closes the storage stream without caching the whole file in memory; it does not redirect to OSS.
Effective condition: this endpoint is effective only when OSS storage is configured (
context.oss.access-key). Deployments without OSS configuration do not register this route.Authentication: a complete signed URL is a temporary download credential. Only the data-plane port (default 4040) supports key-free access; the management-plane port still requires
X-API-Key. The signature binds the storage namespace, object key, and validity; a tampered or expired signature returns 403, and an arbitrary OSS key cannot be used to download. Like original OSS pre-signed links, a signed link lets any holder read the object within its validity, so public sharing should be avoided. The validity follows the TTL rules of the issuing business API (the multimodal resource URL API is still capped at 86,400 seconds). After the object is deleted, the link cannot read the original object; permission changes do not revoke issued, unexpired links early.Deployment: by default the origin of the current API request is used and passed to async retrieval with the request context. If access goes through a reverse proxy and the backend sees an internal origin, configure
OSS_DOWNLOAD_ENDPOINT=https://<Context0 public domain>; this value is the Context0 service origin, not the OSS origin. Multiple replicas must use consistent OSS configuration and signing credentials.CLI:
ctxdb0 skills fetchContinue to verify the body/resource SHA-256, size, and path, and write to disk atomically; do not execute downloaded content. A configured Context0 origin can be downloaded over the original connection protocol (including authorized HTTP-hosted instances); additional cross-origin addresses still require an explicit HTTPS whitelist, prohibit redirects, and must not receive the API Key.
Model invocation usage (management plane)
The following endpoints run on the management-plane port (default 8080) and require Owner Key authentication, use the standard Box response, and query only the authenticated workspace; the optional memberId limits results to a member.
Method | Path | Description |
|
| Daily aggregated summary; returns |
|
| Trends by day, invocation type, and model |
|
| Trends by hour and invocation type; |
call_typeincludesllm,embedding,vision,ocr,rerank,dedup. Four new categories cover DashScope Vision, standalone OCR, DashScope/HTTP Rerank, and custom HTTP deduplication adjudication (single and batch); by defaultLlmProviderDedupAdjudicatoris included inllmand is not double-counted indedup.Usage is taken from the
usage.prompt_tokens/completion_tokensorinput_tokens/output_tokensof successful HTTP responses; the total preferstotal_tokens, and when no total exists, known input and output are summed. When Rerank returns only a total, it is recorded as input tokens; when a generative call returns only a total, no input/output split is guessed. Each response counts once, without repeated multiplication by batch adjudication pairs; when a new request follows a business parsing failure, each successful response is counted separately. Requests without a successful response (403, 429, timeouts, etc.) are not estimated from input length.Custom adjudication services must pass through the above
usageat the top level of the response; a batch response should give the cumulative usage of the whole request. When usage is missing or invalid, or only non-token billing units are returned (such assearch_units), only the successful call count increases, and tokens accumulate as 0 known values with amodel token usage unavailablewarning; 0 does not mean actually free.Data accumulates asynchronously into the
token_usage_daily/hourlyof the shared database, allowing cross-node aggregation; the existing best-effort recording mechanism is kept. Background calls may have a workspace but no member; such usage is excluded when filtering by member. New categories enter the existing overview and enabled monthly token quota decisions; existing API fields, table structures, and configurations are unchanged. This does not cover streaming answers or other model paths not yet integrated, and data missed before the upgrade cannot be backfilled.
Scheduled memory promotion (Scheduled Promotion)
A scheduled task follows the schedule_mode schedule configuration (daily / weekly / monthly / interval / cron, five trigger timings, plus the legacy compatibility mode described below) to periodically trigger a memory promotion run. When auto_approve=true (default) is set, the result is automatically approved and stored into the knowledge base specified by target_kb_id (in this case target_kb_id is required). The overall capability is controlled by the configuration context.promotion.scheduled.enabled (default: false) and the workspace-level feature flag scheduled_promotion_enabled; automatic approval is additionally controlled by the scheduled_promotion_auto_approve_enabled flag (kill-switch, allowed by default). Each workspace has at most 10 active tasks and 24 scheduled executions per day; manual triggers have a 5-minute cooldown.
Task object fields (snake_case wire format): {id, member_id, name, prompt, schedule_type, schedule_mode, schedule_hour, schedule_minute, schedule_day_of_week, schedule_day_of_month, schedule_interval_hours, schedule_cron, trigger_summary, auto_approve, target_kb_id, enabled, last_run_at, next_run_at, fire_count, created_at, updated_at}.
Schedule configuration (schedule_mode)
Field | Applicable mode | Values and description |
| — |
|
| daily / weekly / monthly | 0-23, trigger hour |
| daily / weekly / monthly | 0-59, trigger minute |
| weekly | 1-7, Monday=1 |
| monthly | 1-31; when it exceeds the last day of the month, it is clamped to the end of the month (for example, day 31 triggers at the end of February) |
| interval | >=1, interval in hours |
| cron | Spring 6-field cron expression (second minute hour day month weekday) |
All schedules trigger in the fixed time zone Asia/Shanghai, which is not configurable.
cron restrictions: besides validating the expression, the server samples the next 3 trigger times and requires adjacent triggers to be at least 1 hour apart, otherwise it rejects the expression; a cron with no future trigger time is also rejected.
trigger_summary: the response adds the read-only field
trigger_summary, a server-generated Chinese schedule summary (for example, a daily or weekly description) that can be shown to users directly.legacy compatibility: the old field
schedule_type(hourly/every_other_day/weekly) is still accepted - old clients that only passschedule_typeare handled inlegacymode, with the three levels mapped to 1h / 48h / 168h intervals (in the response,schedule_modeReturnslegacy); for tasks created with the new mode, theschedule_typefield is written with the value of the same name as the mode. This change is purely additive; oldschedule_typeclients are unaffected - there is no breaking change.
Data plane (User / Agent Key)
User Key can only see and operate tasks they created; creation requires a member context.
Method and path | Description |
| Lists tasks by page; returns |
| Creates a task. Request body: |
| Updates a task; all request body fields are optional, and |
| Deletes a task (idempotent: success is returned even if the task does not exist; repeated DELETE calls all succeed) |
| Manually triggers one run immediately (5-minute cooldown; rejected when the previous promotion is still |
Permission validation for target_kb_id (starting 2026-09-16): on creation and update,
target_kb_idmust be a knowledge base on which the member the task belongs to (not the requester of this call) haseditoror higher permission — no permission returns 403 (40303), and crossing workspaces / not existing returns 404. Previously it only validated "whether this knowledge base is in the current workspace", so any member could point the persist target of a scheduled task to any knowledge base in the same workspace (including private knowledge bases), after which every schedule would write documents into it unattended.The decision subject uses the task's owning
member_idrather than the identity of the current request: a task is long-lived, and at execution time the only meaningful subject is its owner.Existing tasks are not validated retroactively; but the same gate is applied at execution time — if the owner later loses write permission on the target knowledge base, that round of promotion fails and the reason is recorded in the task's most recent error, rather than writing anyway.
On update, validation happens only when the request body actually carries auto_approve or target_kb_id (corrected 2026-09-17). Previously it was validated unconditionally, so a pure rename request
PUT {"name": "new name"}would re-evaluate the long-existingtarget_kb_id; after the ACL of the target knowledge base was later tightened, a rename would inexplicably return 403 / 404 even though the task itself never touched that knowledge base. The object of evaluation is "the new state this request is about to establish", not the legacy state.
Request
Request example:
curl -X POST "http://<host>:4040/v1/scheduled_promotions" -H "X-API-Key: $USER_KEY" \
-H 'Content-Type: application/json' \
-d '{"name": "Weekly architecture digest", "prompt": "Summarize this week's architecture decisions", "schedule_mode": "weekly", "schedule_day_of_week": 1, "schedule_hour": 10, "schedule_minute": 0, "auto_approve": true, "target_kb_id": 3}'Request body examples for the five modes (all are
POST /v1/scheduled_promotions,auto_approve/target_kb_id; common fields such as these are the same as above and omitted here):daily - every day at 09:00:
{"name": "Daily digest", "prompt": "Summarize the day's key decisions", "schedule_mode": "daily", "schedule_hour": 9, "schedule_minute": 0}weekly - every Monday at 10:00:
{"name": "Weekly report digest", "prompt": "Summarize this week's architecture decisions", "schedule_mode": "weekly", "schedule_day_of_week": 1, "schedule_hour": 10, "schedule_minute": 0}monthly - day 15 of every month at 09:00:
{"name": "Monthly digest", "prompt": "Summarize this month's milestones", "schedule_mode": "monthly", "schedule_day_of_month": 15, "schedule_hour": 9, "schedule_minute": 0}interval - every 6 hours:
{"name": "High-frequency digest", "prompt": "Distill recent discussion highlights", "schedule_mode": "interval", "schedule_interval_hours": 6}cron - weekdays at 09:00:
{"name": "Weekday digest", "prompt": "Summarize weekday progress", "schedule_mode": "cron", "schedule_cron": "0 0 9 * * 1-5"}
Response
Response example:
{
"code": 0, "msg": "ok",
"data": {
"id": 12, "member_id": 7, "name": "Weekly architecture digest", "prompt": "Summarize this week's architecture decisions",
"schedule_type": "weekly", "schedule_mode": "weekly",
"schedule_hour": 10, "schedule_minute": 0, "schedule_day_of_week": 1,
"schedule_day_of_month": null, "schedule_interval_hours": null, "schedule_cron": null,
"trigger_summary": "Weekly, Monday 10:00",
"auto_approve": true, "target_kb_id": 3, "enabled": true,
"last_run_at": null, "next_run_at": "2026-09-14T02:00:00Z", "fire_count": 0,
"created_at": "2026-09-10T00:00:00Z", "updated_at": "2026-09-10T00:00:00Z"
}
}Management plane
Management-plane endpoints require Owner Key authentication and follow the /ops/** all-query-param convention (no path parameters); without memberId, they operate on all tasks in the workspace.
Method and path | Description |
| Paginated list (filterable by member) |
| Task details |
| Create (request body same as the data plane) |
| Update (request body same as the data plane) |
| Delete (idempotent, same semantics as the data plane) |
| Manual trigger |
Scheduled promotions appear in the existing lists (data plane GET /v1/memory_promotions, management plane GET /ops/promotion/list) as source_type=scheduled, and the manual approve / reject flow is unchanged; automatically approved promotions record the source scheduledTaskId.
Promotion approval and artifact visibility (added 2026-09-16)
Approval adjudicates the initiator, not the approver. When approving a promotion, what is checked is whether the promotion's
requested_bymember haseditoror higher permissions on the target knowledge base - otherwise 403 (40303) and the promotion status stays unchanged atpending_review(the check happens before the status CAS, so it cannot get stuck atapproving). This rule blocks the workaround of "I have no write permission, but I find someone who does to click confirm for me".A promotion artifact is just an ordinary knowledge base document. After being stored, it inherits the target knowledge base ACL and carries no extra visibility of its own:
viewerof the target knowledge base can search it, read it, and see it in the Wiki/graph; members without permission on the target knowledge base cannot get it on any read path (403), including the owner of the original memories.Drafts before approval do not exist on knowledge base paths.
pending_reviewpromotions appear only in the promotion list; they cannot be found on knowledge base paths such asGET /v1/knowledge/bases/{kbId}/documents- members of the target knowledge base will not see unapproved content in advance.
Provider management and deployment rule boundaries
Advanced model-request header/top-level field removal is loaded only from deployment files at startup (see the "standalone JSON for advanced model request configuration" section of the Configuration Reference document). Existing Provider management APIs still use Box<T>, config_json kept as strings; no new rule read/write APIs or response fields are added.
Existing API | Permissions and behavior |
| Global: platform ADMIN only; tenant: OWNER of this workspace only; responses do not merge deployment rules |
| Uses the existing active resolution and permissions; historical metadata is readable |
| Uses the existing chain ordering, permissions, and DTO; historical metadata is not activated as deployment rules |
| The main |
| Platform ADMIN; a non-empty DB chain of a managed kind returns 409. An empty chain keeps the in-process disable, the lock remains, and re-enabling requires a deployment restart |
All Provider PUT calls reject reserved namespaces requestOverrides / request-overrides (checked before unknown outer DTO fields are dropped and within config_json; empty values are also rejected). This is not expanded into a ban on historical extraHeaders / removeBodyFields metadata, and does not automatically migrate the database or change legitimate GET calls.
Compatibility impact: endpoints, response structures, existing field types, and the Embedding fingerprint are unchanged. Without configured rules, the original management/request/fallback behavior is kept; the explicit security changes are the new PUT namespace rejection above, audit body suppression for all Provider PUT calls (action/identity/result/trace are kept), and ASR result download authentication isolation with taskId path validation. Malformed body/config JSON errors on Provider PUT use a fixed message without echoing submitted values or underlying parse fragments. Tenant Provider configuration remains staged and does not overwrite global live Providers.
Agent usage reporting
Allows agent runtimes to report model invocation usage, making it easy to track token consumption by session/agent.
POST /v1/agent-usage
Reports a single Agent usage record.
Request
Request parameters (flat structure, not nested)
Parameter | Type | Required | Description |
session_id | string | No | Associated session ID |
agent_type | string | No | Agent type identifier |
model | string | No | Model name |
input_tokens | long | No | Number of input tokens (>=0) |
output_tokens | long | No | Number of output tokens (>=0) |
turns | integer | No | Number of conversation turns (>=0) |
duration_sec | integer | No | Duration (seconds, >=0) |
Request example
curl -X POST "http://<host>:4040/v1/agent-usage" \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"session_id": "sess-001", "agent_type": "chat", "model": "qwen-plus", "input_tokens": 1000, "output_tokens": 500, "turns": 3, "duration_sec": 120}'Response
Response example
{"code": 0, "msg": "ok", "data": null}POST /v1/agent-usage/batch
Reports multiple Agent usage records in a batch (up to 100).
Request
Request body
A JSON array; each item has the same structure as a single report.
Request example
curl -X POST "http://<host>:4040/v1/agent-usage/batch" \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '[{"session_id": "sess-001", "agent_type": "chat", "model": "qwen-plus", "input_tokens": 500, "output_tokens": 200, "turns": 1, "duration_sec": 30}, {"session_id": "sess-002", "agent_type": "rag", "model": "qwen-max", "input_tokens": 800, "output_tokens": 300, "turns": 2, "duration_sec": 60}]'Agent usage statistics (management plane)
The following endpoints run on the management-plane port (default 8080) and require Owner Key.
GET /ops/agent-usage/summary
Queries the Agent usage summary within a specified time range, grouped by agent_type + model.
Request
Query parameters
Parameter | Type | Required | Description |
from | date | Yes | Start date (ISO format |
to | date | Yes | End date (ISO format |
memberId | string | No | Filter by member |
Request example
curl "http://<host>:8080/ops/agent-usage/summary?from=2026-09-01&to=2026-09-19" -H "X-API-Key: $OWNER_KEY"Response
Response example
{"code": 0, "msg": "ok", "data": {"total_tokens": 15000, "total_sessions": 5, "items": [{"agent_type": "chat", "model": "qwen-plus", "input_tokens": 8000, "output_tokens": 4000, "sessions": 3}]}}GET /ops/agent-usage/trend
Queries the Agent usage trend within a specified time range, grouped by date.
Request
Query parameters
Same as summary.
Request example
curl "http://<host>:8080/ops/agent-usage/trend?from=2026-09-01&to=2026-09-19" -H "X-API-Key: $OWNER_KEY"Audit and compliance
Audit Logs
Audit logs automatically record API operations and can be read by both the Owner Key and the User Key. A User Key can view only its own operation records.
GET /v1/audit/logs
Lists audit logs by page, with filtering by action type, result, and time range.
Request
Query parameters
Parameter | Type | Required | Description |
action | string | No | Filter by action type (such as |
result | string | No | Filter by result: |
from | string | No | Start time (ISO-8601, inclusive) |
to | string | No | End time (ISO-8601, inclusive) |
page | integer | No | Page number (0-indexed). Default: 0. |
size | integer | No | Page size. Default: 50. Maximum: 200. |
Request example
curl "http://<host>:4040/v1/audit/logs?action=context.ingest&result=success&page=0&size=20" \
-H "X-API-Key: $API_KEY"Response
Response example
{
"code": 0, "msg": "ok",
"data": {
"content": [
{
"id": 42,
"workspace_id": 1,
"action": "context.ingest",
"subject_type": "user",
"subject_id": "4",
"result": "success",
"path": "/v1/context/ingest",
"created_at": "2026-09-16T01:00:00Z",
"last_error_short": null
}
],
"total_elements": 1,
"total_pages": 1
}
}GET /v1/audit/logs/{id}
Retrieves details of a single audit log entry, including complete request/response information.
Request
Request example
curl "http://<host>:4040/v1/audit/logs/42" -H "X-API-Key: $API_KEY"GET /v1/audit/stats
Aggregates audit statistics by a specified dimension (action type distribution, success rate, etc.).
Request
Query parameters
Parameter | Type | Required | Description |
group_by | string | Yes | Aggregation dimension: |
action | string | No | Filter by action type |
from | string | No | Start time (ISO-8601) |
to | string | No | End time (ISO-8601) |
Request example
curl "http://<host>:4040/v1/audit/stats?group_by=action" -H "X-API-Key: $API_KEY"Response
Response example
{
"code": 0, "msg": "ok",
"data": {
"groups": [
{"key": {"action": "context.ingest"}, "count": 10, "success_rate": 0.9},
{"key": {"action": "session.create"}, "count": 25, "success_rate": 0.8}
]
}
}Audit Policies
Managing audit policies requires the Owner Key (a high-impact operation that can control the audit pipeline switch).
GET /v1/audit/policies
Lists audit policies of the current workspace.
Request
Request example
curl "http://<host>:4040/v1/audit/policies" -H "X-API-Key: $OWNER_KEY"POST /v1/audit/policies
Creates an audit policy.
Request
Request example
curl -X POST "http://<host>:4040/v1/audit/policies" \
-H "X-API-Key: $OWNER_KEY" -H "Content-Type: application/json" \
-d '{"name": "Record all write operations", "action_pattern": "*.create|*.update|*.delete", "enabled": true}'GET /v1/audit/policies/{id}
Retrieves details of a specified audit policy.
Request
Request example
curl "http://<host>:4040/v1/audit/policies/1" -H "X-API-Key: $OWNER_KEY"PUT /v1/audit/policies/{id}
Updates an audit policy.
Request
Request example
curl -X PUT "http://<host>:4040/v1/audit/policies/1" \
-H "X-API-Key: $OWNER_KEY" -H "Content-Type: application/json" \
-d '{"enabled": false}'DELETE /v1/audit/policies/{id}
Deletes a specified audit policy.
Request
Request example
curl -X DELETE "http://<host>:4040/v1/audit/policies/1" -H "X-API-Key: $OWNER_KEY"Data erasure
All data erasure endpoints require the Owner Key (a high-impact, irreversible cascading delete, not open to the User Key). A User Key call returns a port/permission error.
POST /v1/audit/gdpr/erasure
Initiates a GDPR "right to be forgotten" data erasure job (queued asynchronously; a background worker cascade-deletes subject data across the whole pipeline).
Request
Request parameters
Parameter | Type | Required | Description |
subjectType | string | Yes | Subject type: |
subjectId | string | Yes | Subject identifier ( |
workspaceId | string | No | Target workspace; defaults to the current one (an OWNER can target a non-host workspace through |
reason | string | No | Erasure reason (retained for audit) |
scope | string[] | No | Limits the erasure targets; defaults to a full-pipeline cascade |
A subject resolution failure does not silently succeed: when subject_id cannot be resolved to a corresponding member within the workspace (a user has no user_id of the same name, or a member is not numeric), the job's final state is failed and last_error indicates subject unresolved, with deleted_counts being {}; it does not report done + all-zero counts.
Request example
curl -X POST http://<host>:4040/v1/audit/gdpr/erasure \
-H "X-API-Key: $OWNER_KEY" -H "Content-Type: application/json" \
-d '{"subjectType": "user", "subjectId": "alice", "reason": "user request"}'Response
Response example
{"code": 0, "msg": "ok", "data": {"jobId": 1001, "status": "queued"}}GET /v1/audit/gdpr/jobs
Lists erasure jobs of the current workspace by page (limited to the caller's workspace).
Request
Request parameters
Parameter | Type | Required | Description |
status | string | No | Filter by status (such as |
page | integer | No | Page number (starting from 0). Default: 0. |
size | integer | No | Page size. Default: 50. Maximum: 200. |
Request example
curl "http://<host>:4040/v1/audit/gdpr/jobs?status=done&size=20" -H "X-API-Key: $OWNER_KEY"Response
Returns Box<Page<DetailResponse>>, entries are in content.
Response example
{
"code": 0, "msg": "ok",
"data": {
"content": [
{"id": 1001, "workspace_id": "1", "subject_type": "user", "subject_id": "alice", "status": "done", "attempt_count": 1, "submitted_at": "2026-08-19T10:00:00Z", "completed_at": "2026-08-19T10:00:30Z"}
],
"total_elements": 1,
"total_pages": 1,
"number": 0,
"size": 20
}
}GET /v1/audit/gdpr/jobs/{id}
Retrieves details of a single erasure job, including per-target deletion counts and error messages on failure. Cross-workspace access returns an error.
Request
Request example
curl "http://<host>:4040/v1/audit/gdpr/jobs/1001" -H "X-API-Key: $OWNER_KEY"Response
Response parameters
Parameter | Type | Description |
id | long | Job ID |
workspaceId | string | Owning workspace |
subjectType / subjectId | string | Erasure subject type and identifier |
reason | string | Erasure reason |
scope | string[] | Limited targets; for a full-pipeline cascade it is |
status | string | Job status |
attemptCount | int | Number of attempts |
lastError | string | Error message of the last failure |
deletedCounts | string | Per-target deletion counts (JSON string) |
submittedBy | string | Submitter identifier |
submittedAt / completedAt | string | Submit / completion time (ISO-8601) |
Response example
{
"code": 0, "msg": "ok",
"data": {
"id": 1001,
"workspace_id": "1",
"subject_type": "user",
"subject_id": "alice",
"reason": "user request",
"scope": null,
"status": "done",
"attempt_count": 1,
"last_error": null,
"deleted_counts": "{\"memories\":42,\"sessions\":3}",
"submitted_by": "owner-key-2",
"submitted_at": "2026-08-19T10:00:00Z",
"completed_at": "2026-08-19T10:00:30Z"
}
}Appendix: Common error codes
Error codes | Name | HTTP Status | Description |
0 | OK | 200 | Success |
40001 | VALIDATION_FAILED | 400 | Parameter validation failed |
40002 | FILTER_SYNTAX_INVALID | 400 | Invalid filter syntax |
40009 | DATA_INTEGRITY_VIOLATION | 400 | Data integrity conflict (NOT NULL / FK / UK) |
40010 | SOURCE_CHUNK_READ_ONLY | 400 |
|
40011 | QUOTA_EXCEEDED | 429 | Quota exceeded (OSS storage / monthly tokens) |
40012 | UNSUPPORTED_CHARACTER | 400 | The text contains characters that the storage-layer character set cannot hold |
40101 | AUTH_INVALID_KEY | 401 | Authentication failed (the API Key is invalid or missing) |
40301 | AUTH_PORT_MISMATCH | 403 | The key type does not match the port (for example a USER key calling the management plane) |
40302 | AUTH_REVOKED | 403 | The API Key has been revoked |
40303 | ACL_FORBIDDEN | 403 | Knowledge ACL validation failed (insufficient knowledge base permission). Note: when kbId crosses workspaces or does not exist, it returns |
40304 | FEATURE_DISABLED | 403 | The capability is disabled by a configuration switch |
40401 | RESOURCE_NOT_FOUND | 404 | The resource does not exist |
40901 | CONFLICT | 409 | Resource conflict |
40902 | SESSION_CLOSED | 409 | The session is closed |
42901 | RATE_LIMITED | 429 | Request rate exceeded |
50001 | INTERNAL_ERROR | 500 | Internal service error |
50301 | EMBEDDING_UNAVAILABLE | 503 | The Embedding service is unavailable |
50302 | LLM_UNAVAILABLE | 503 | The LLM service is unavailable |
50303 | OSS_UNAVAILABLE | 503 | The OSS service is unavailable |
50304 | SERVICE_UNAVAILABLE | 503 | A required optional runtime component is unavailable in the current deployment mode |
Legacy documentation error code migration reference: the error code table in earlier public documentation did not match the server's actual enumeration. If you wrote error handling or alerting rules against the old codes, replace them according to the following mapping:
Old
40001(authentication failed) →40101 AUTH_INVALID_KEY; the current40001now means VALIDATION_FAILED (parameter validation failed)Old
40003(insufficient permission) →40301 AUTH_PORT_MISMATCH(the key type does not match the port) /40302 AUTH_REVOKED(the key has been revoked) /40303 ACL_FORBIDDEN(ACL validation failed)Old
40004(resource does not exist) →40401 RESOURCE_NOT_FOUNDOld
40010(parameter validation failed) →40001 VALIDATION_FAILED; the current40010now means SOURCE_CHUNK_READ_ONLYOld
42900(request rate exceeded) →42901 RATE_LIMITED; quota exceeded separately uses40011 QUOTA_EXCEEDED(HTTP 429)Old
50000(internal service error) →50001 INTERNAL_ERROR
The old codes 40003 / 40004 / 42900 / 50000, as well as the "parameter validation failed" semantics of 40010 in the old table, were never returned by the server; the code value and semantics of 40009 (data integrity conflict) are unchanged.
Appendix: Parameter alias conventions
The API accepts both snake_case and camelCase:
session_id↔sessionIduser_id↔userIdagent_id↔agentIdtop_k↔topKkb_ids↔kbIdstoken_budget↔tokenBudgetkeep_recent_n↔keepRecentNinclude_l2↔includeL2vector_only↔vectorOnlycosine_threshold↔cosineThreshold↔threshold