Context0 API Reference

Updated at:

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

http://<host>:4040 (data plane); some management APIs run on :8080

Authentication

Request header X-API-Key: <your-api-key>. In the examples in this topic, the key is passed in through the $API_KEY environment variable

Response format

{"code": 0, "msg": "ok", "data": <T>, "trace_id": "..."}

Error format

{"code": <non-zero>, "msg": "error message", "trace_id": "..."}

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)

Note
  • 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 *_at fields are ISO-8601 instants in UTC, ending with Z (for example 2026-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 time 2026-09-17 18:03:43.226352 (with no T and no time zone), which could not be reliably parsed by new 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):

  1. Public knowledge base default level raised to writable.visibility=workspace (alias public): the default role of any member in the workspace is editor. 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 to private and grant permissions as needed, or add an explicit viewer row for specific members (see "Knowledge base permissions (ACL)" in the Knowledge section).

  2. ACL row expiration is a hard invalidation. An authorization row with an expired expires_at is 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.

  3. Cross-workspace responses on the graph data plane changed from 403 to 404.GET /v1/knowledge-bases/{kbId}/graph/nodes|edges previously returned 403, and now returns 404 when 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.

  4. 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 granted owner received my_role=owner but still could not delete the knowledge base. This is now unified into the ACL decision chain: owner is 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

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 [A-Za-z0-9_\-.:@]

user_id

string

No

End user ID (mem0 user_id). Defaults to the member dimension

messages

array

Yes

Message list. Up to 100 messages per batch.

messages[].role

string

Yes

user / assistant / system / tool

messages[].content

string

Yes

Message text. Up to 65,535 characters.

messages[].tool_name

string

Conditional

Required only when role=tool.

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

  • eager mode: event snapshot

  • batch mode: stored into session.metadata_json

agent_id

string

No

Agent identifier, persisted to session.agent_id

extract_mode

string

No

Defaults to the global configuration.

  • eager: immediate extraction

  • batch: deferred until flush.

custom_instructions

string

No

Request-level distillation instruction (valid only in eager mode). Up to 8,192 characters.

timestamp

long

No

Time when the memory occurred (epoch seconds). Defaults to the current server time.

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_id)

session_internal_id

long

Internal database auto-increment ID (may be null, returned on first creation)

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 (eager mode: populated; batch mode: null)

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 is required

user_id

string

Conditional

At least one of this parameter and session_id is required

query

string

No

Query text. An empty value triggers memory_bootstrap zero-query cold start

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 null elements returns 400. ACL checks are performed per knowledge base (403 without permission).

knowledge_top_k

integer

No

Knowledge search top_k. Valid values: [1, 50].

include_multimodal

boolean

No

Specifies whether to inject multimodal PromptBlocks. Default: true

multimodal_max_images

integer

No

Maximum number of multimodal blocks injected per call. Valid values: [0, 50].

context_scope

string

No

full / minimal / memory_only / knowledge_only

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 wiki_pages). Disabled by default. Takes effect only when the server-side context.wiki.enabled=true and the request carries a non-empty knowledge_base_ids. When effective and hits exist, the knowledge budget (20% by default) is internally split into chunk 12% + wiki 8%

inherit_summary

boolean

No

Specifies whether to inherit the previous compact_summary

inherit_from_session_id

string

No

Explicitly specifies which session's compact_summary to inherit

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

system / user / assistant

prompt_blocks[].tag

string

Type tag (user_profile / memory_recall/ recent_history / knowledge / multimodal, and more)

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 memory:<id>, knowledge:<chunk_id>, multimodal:<resource_id>)

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)

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: [A-Za-z0-9_\-.:@]{1,128}

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 [A-Za-z0-9_\-.:@]{1,128}

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.

  • Used when an Owner Key closes a session of a specific end user. Use the same identity value as session.user_id in action=start.

  • Omitted or null means unspecified; when non-empty, it must match [A-Za-z0-9_\-.:@]{1,128}

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"}'
Note

An Owner Key represents the workspace; a specific end user cannot be inferred from the key alone.

  • If you used session.user_id at start, pass the same top-level user_id at end so that the close and flush operations land on the same end-user/member identity.

  • Only when start/end omit 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.

Note

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: fast / standard / deep / highest. Default: standard

top_k

integer

No

Return top-k [1, 20]. Default: 5.

scope

string

No

Reserved field (for future session/user/workspace dimension convergence)

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 /v1/context/assemble

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. [A-Za-z0-9_\-.:@]

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
  }
}
Note

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 (open/closed/deleted/flushed)

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 role / content

messages[].role

string

Yes

user / assistant / system / tool

messages[].content

string

Yes

Message text

messages[].toolName

string

Conditional

Required when role=tool .

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].

Note

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: true

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 (role + content)

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 member_id within the tenant before querying

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 snake_case/camelCase keys listed above; other keys return an error

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 person / project / technology card)

preferences

string[]

No

Preference list

attributes

object

No

Open attribute map. Keys depend on the card type (org / repo_url / version / domain, and more)

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 role / preferences / attributes)

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.

Note

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 (L1 / L0_ENTITY_CARD / L2)

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 true

scope

string

No

all (default) / before_date / by_session_ids

before_date

string

Conditional

Required when scope=before_date; ISO-8601

session_ids

string[]

Conditional

Required when scope=by_session_ids.

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

Note

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)

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 user_id / agent_id / run_id entity keys

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.

Note

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: null

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 (configured && enabled && rollout_enabled)

effective_source

string

Effective source: member_level / workspace_default / built_in

updated_at

string

Last update time (ISO-8601)

member_id

long

Owning member ID (workspace default behavior: null)

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 (configured / enabled / rollout_enabled)

effective_source

string

Effective source: member_level / workspace_default / built_in

updated_at

string

Last update time (ISO-8601)

member_id

long

Owning member ID (null for workspace default behavior)

Response example

{
  "code": 0, "msg": "ok",
  "data": {
    "configured": true,
    "custom_prompt": "Focus on extracting the user's tech stack preferences and workflow habits",
    "enabled": true,
    "rollout_enabled": true,
    "effective": true,
    "effective_source": "member_level",
    "updated_at": "2026-08-19T10:00:00Z",
    "member_id": 2
  }
}

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: true

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):

  • positive_explicit

  • negative_explicit

  • positive_implicit

  • negative_implicit

channel

string

No

Feedback channel identifier

session_id

string

No

Associated session ID

recall_path

string

No

Recall path: bm25 / vector / graph (used for WeightAdaptation)

corrected_content

string

No

Correction provided by the user

Signal type descriptions

Type

Effect

positive_explicit

Explicit thumbs-up from the user/agent. hotness +0.2

negative_explicit

Explicit thumbs-down from the user/agent. hotness -0.2 and triggers the Dream counter-evidence pipeline

positive_implicit

Implicit positive signal (reuse, dwell). hotness +0.1

negative_implicit

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: generating / pending_review / approved / rejected

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

/v1/memories/search

Disabled (L1-only)

Bare array

Mem0 baseline compatible

V2

/v2/memories/search

Enabled by default

Bare array

Recommended; supports source traceability

V3

/v3/memories/search

Enabled by default

{"results": [...]}

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

POST

/v1/knowledge/bases/{kbId}/acl

Grants an explicit permission row (repeated grants to the same subject return 409; not an overwrite)

GET

/v1/knowledge/bases/{kbId}/acl

Lists all explicit permission rows of this knowledge base (including expired rows)

GET

/v1/knowledge/bases/{kbId}/acl/candidates

Authorization candidates: members of this workspace plus their existing explicit row roles

DELETE

/v1/knowledge/bases/{kbId}/acl/{aclId}

Revokes a permission row

Three semantics you must understand first:

  • An explicit row is the final decision, not a lower bound. Writing a viewer row on a public knowledge base demotes the member from the default editor to read-only, rather than "at least viewer".

  • denied is 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 in my_role (when access is denied, my_role is null, role_source is denied).

  • 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 the GET .../acl results for easier cleanup; to determine whether a row has expired, compare expires_at with the current time.

POST /v1/knowledge/bases/{kbId}/acl

Request

Request parameters

Parameter

Type

Required

Description

subject_type

string

Yes

Fixed: member (other subjects such as workspace / role are not supported; other values return 400 VALIDATION_FAILED)

subject_id

string

Yes

Authorization subject ID, i.e., the members.id 's decimal string (taken from .../acl/candidates 's member_id). Writing a non-numeric value such as "m_42" returns 400 - the authorization side compares numerically, so such rows never take effect

role

string

Yes

viewer / editor / owner / denied; other values return 400

expires_at

string

No

Expiration time (ISO-8601).null = valid indefinitely

granted_by

string

No

Grantor identifier; defaults to the caller's member_id decimal string

  • The kb_id in the request body is ignored; the kbId in the path always prevails.

  • (kb_id, subject_type, subject_id) is unique: a repeated POST for the same subject returns 409 (40901, msg is ACL already exists for (kb_id, subject_type, subject_id)), not an overwrite. To change the role, first DELETE .../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 (DELETE .../acl/{aclId})

kb_id / workspace_id

long

Owning knowledge base / workspace

subject_type

string

Fixed: member

subject_id

string

members.id decimal string

role

string

viewer / editor / owner / denied

granted_by

string

Grantor identifier

granted_at

string

Grant time (ISO-8601)

expires_at

string

Expiration time; null = valid indefinitely.hard invalidation semantics described above

subject_name

string

Subject member name (read-only display field; null when the member has been deleted: null)

subject_user_id

string

Subject member's external user_id (read-only display field; null when the member has been deleted: null)

Note

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 subject_id

user_id

string

Member's external user_id

name

string

Member name

current_role

string

This member's existing explicit row role on this knowledge base; null indicates no effective explicit row - the member then takes the knowledge base default level (public knowledge base: editor / private knowledge base: no access). does not mean no permission

Note

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).

Note

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

workspace (public knowledge base; workspace members default to editor, with read and write) / private (private knowledge base; accessible only to the creator and explicitly authorized users).public Yes workspace 's request-side alias, not a third level - storage and responses are always normalized to workspace. See above for default values

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.

Note
  • 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=true streamlined 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- denied explicit 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

workspace (public knowledge base) / private (private knowledge base). On the request side, public can be used as an alias for workspace, and responses are always normalized to workspace

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: owner / editor / viewer; null indicates no access (the knowledge base then does not appear in the list)

role_source

string

Source of the effective role: owner_key (Owner Key global pass-through) / owner_member (created by the caller) / acl (explicit ACL row, final decision) / kb_default (public knowledge base default level) / denied (explicitly denied)

can_manage_acl

boolean

Whether the caller can manage the ACL of this knowledge base (equivalent to my_role == "owner"). The console uses it to decide the visibility of permission entries instead of probing for 403.

Role capability comparison:

Role

Capabilities

viewer

Search, read documents, read Wiki, read graph nodes/edges

editor

viewer + upload/delete documents, rebuild indexes, and serve as the target knowledge base of memory promotions

owner

editor + quality review decisions, knowledge base settings and tuning, knowledge base deletion, and ACL management

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).

Note

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

No

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 it workspace_id.

  • knowledge_base_id must belong to the current workspace, and document_id must belong to this KB; crossing workspaces returns 404 and insufficient ACL returns 403.

  • expected_ingest_revision must use the value just read from the document list. On success the revision increments by one and the new event_id is 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 (type/block_type/tags/source_url/modality)

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 (text/title/list/table/code/image/formula)

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 context.knowledge.multi-hop.enabled, which defaults to false in production

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 rerank is enabled)

modality

string

Modality type (text/image/audio, populated for multimodal results)

block_type

string

Chunk type (text/title/list/table/code/image/formula, populated for multimodal results)

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

Note
  • Null fields are not output. Unknown request fields are strictly rejected (to prevent kb_id singular typos from being silently ignored and causing a workspace-wide search).

  • ACL semantics: kb_ids are validated individually for viewer permission — 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 use GET /v1/knowledge/bases to obtain the accessible list (this endpoint returns only knowledge bases you have access to) before retrieval. When the Owner Key omits kb_ids, it searches the entire workspace; when the User Key omits kb_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 40304 feature-disabled.

graph_enabled

boolean

Conditional

Specifies whether to enable knowledge graph construction (GraphRAG), with no additional gating

visibility

string

Conditional

workspace (alias public) / private.This is a permission operation: switching to workspace immediately gives all members without explicit ACL rows in the workspace editor (read and write); switching to private immediately revokes their access, leaving only the creator and explicit ACL rows

Note

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"}}
Note

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.

Note

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}]}}
Note
  • 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 viewer or higher, and insufficient permission returns 403 (40303). Starting 2026-09-16, when kbId crosses 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}/graph still 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 /v1/knowledge-bases/{kbId}/graph

Current knowledge base owner (management permissions); data={"deleted":true}

POST /v1/knowledge-bases/{kbId}/graph/backfill

Current knowledge base owner (management permissions); data={"enqueued":3}, the actual count may be 0

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 /v1/knowledge-bases/{kbId}/graph/conflicts/detect

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 /v1/knowledge-bases/{kbId}/graph/conflicts?status=&page=&size=

viewer+

Review queue pagination; status defaults to returning all statuses (open/resolved/dismissed/stale)

GET /v1/knowledge-bases/{kbId}/graph/conflicts/{conflictId}

viewer+

Single entry details (payload parsed into candidate values + source document/chunk evidence chain, same structure as list items)

POST /v1/knowledge-bases/{kbId}/graph/conflicts/{conflictId}/decision

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: resolved means 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 turn stale, 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-enabled configuration name, but only allows candidates with identical names under existing normalization rules to be confirmed directly; mere substring containment (such as PolarDB / 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 /ops/knowledge/graph/nodes?kb_id=&page=&size=

GET /ops/knowledge-bases/{kbId}/graph/nodes

GET /ops/knowledge/graph/edges?kb_id=&node=&depth=

GET /ops/knowledge-bases/{kbId}/graph/edges

DELETE /ops/knowledge/graph/delete?kb_id=

DELETE /ops/knowledge-bases/{kbId}/graph

POST /ops/knowledge/graph/backfill?kb_id=

POST /ops/knowledge-bases/{kbId}/graph/backfill

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.

Note
  • 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 variable WIKI_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/wiki prefix: 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 parameter kb_id; mistakenly writing kb_ids or 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/read results 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_config is 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 /pages?kb_id=1&page=0&size=20

Knowledge-base-isolated paginated page list; optional status / type / folder_id

POST /pages

editor; manual page creation - kb_id goes in the body (not the query); required: slug / title, optional: page_type / content_markdown / summary / category / folder_id

GET /stats?kb_id=1

Statistics of this knowledge base; task counts do not mix in other KBs

PUT /pages/update?kb_id=1&page_id=101

editor; partially updates title / content_markdown / summary / category / folder_id / status and returns the page DTO

POST /pages/regenerate?kb_id=1&page_id=101

editor; no request body is needed; returns {"accepted":true}

GET /dead-letters?kb_id=1&page=0&size=20

viewer; paginated redacted dead-letter summaries

GET /log?kb_id=1&page=0&size=50

viewer; paginated redacted task log summaries

POST /dead-letters/retry?kb_id=1&id=7

editor; no request body is needed; returns {"retried":true}; the parameter is id, not dead_letter_id

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.

  • status and page_type are closed value domains, validated against a whitelist on the write side; any value outside the domain returns 400 40001:

    • status ∈ draft / published / archived. Search and recall index only published, the backlink graph recognizes only published and resolvable draft, /stats counts only published_pages / draft_pages, and the console's publish / archive / restore buttons render only under draft / 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 is entity. Title resolution and deduplication candidates look only at entity / concept.GET /pages?type= filter values are likewise limited to these four.

  • slug and title is required and cannot be blank; leading/trailing spaces are trimmed by the same rule as wikilink targets; slug is up to 256 characters and title is up to 512 characters; exceeding a limit returns 400 with the column budget in the message. A missing or blank slug is a validation error (40001) and no longer falls through to storage-layer constraints (the historical behavior was 40009 missing or invalid required column: slug).

  • The category request field for page updates is category, while the response still uses category_path; slug is not modified. Setting status=published publishes a draft or restores an archived page; setting status=archived carries the archive semantics of the UI "delete", not a physical deletion. Before editing the body, first request /pages/{pageId}?for_edit=true to 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 kb_ids / kbIds alias). When omitted, an Owner Key searches all Wikis in the entire workspace; a User Key must explicitly provide

top_k

integer

No

Number of entries to return, 1-100 (accepts the topK alias)

mode

string

No

keyword (default, BM25) or regex (literal matching)

Note

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

Note

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 context.wiki.read-budget.default-max-output-chars (default: 0 = no truncation)

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 GET /pages/{pageId} (1000-200000; defaults to context.wiki.read-budget.default-max-output-chars, default: 0 = no truncation). When the budget takes effect, the response echoes content_truncated / content_total_chars

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

name

Required; leading and trailing spaces are trimmed, and the value must not be empty after trimming; at most 256 characters; must not contain / (otherwise it would forge a non-existent level). Violations return 400 / 40001, such as name is required, name must not contain '/', name must be at most 256 characters

path

Read-only; derived by the server from parent folder path + "/" + name and not accepted from the client; the total length limit is 1024 characters, and exceeding it returns 400 / 40001 folder path must be at most 1024 characters

sort_order

is optional and must be an integer (may be negative). A string / null / boolean / object always returns 400 / 40001 sort_order must be an integer

parent_id

Optional on creation; changing parent is not supported on update, and passing a value different from the current one returns 400 changing folder parent is not supported

  • Unique per parent and name: the same kb_id + the same parent_id does not allow duplicate names; conflicts return 409 / 40901 a 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 path prefix of its entire subtree is rewritten synchronously (a subfolder's own name stays unchanged). Folders that share the prefix but do not belong to that subtree (such as /Alpha and /AlphaOther) are unaffected.

  • Delete semantics (DELETE /ops/knowledge/wiki/folders/delete):

    • When the folder still has subfolders, it returns 409 / 40901 folder 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 the folder_id of those pages is cleared in place (returning to the unfiled state), leaving no dangling references.

    • kb_id participates in validation: a folder that does not belong to the kb_id returns 404 via query param, in the same shape as /folders/update, /pages/move behaves 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

page.content_markdown Output budget, same semantics as GET /pages/{pageId} (1000-200000; defaults to context.wiki.read-budget.default-max-output-chars, default: 0 = no truncation). When the budget takes effect, page echoes content_truncated / content_total_chars; truncation happens after the read cache and does not affect cached content

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"
      }
    ]
  }
}
Note

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

keyword (default) / regex, other values return 400

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 query search parameters (provided together returns 400)

end_chunk

int

No

Ending position of the range (inclusive). Providing only start_chunk defaults to a window of 10 (i.e., [start, start+9]); the range span is capped at 50 (configurable via context.wiki.source-query.max-range-span), exceeding it returns 400;

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

outdated / inaccurate / missing / broken_link

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 null (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)

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; page=0, size=20, size up to 100; data is an array

GET /{revisionId}

Pins the source snapshot

GET /{revisionId}/source

original body; does not issue an OSS bearer URL and does not fall back to the current document

GET /{revisionId}/source/pages/{page}

page of the PDF (starting from 1), rendered to PNG by server-side PDFBox; no browser PDF actions are performed

GET /{revisionId}/parsed

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 /{revisionId}/restore

editor explicitly restores a pinned source; request {expected_revision_id:"UUID of the current latest business revision"}, conflict or identical content returns 409; returns the original KnowledgeDocumentDto and the newly stored event_id

  • Revision fields: id (business UUID), number, snapshot_hash, source, created_at, author_member_id (may be null for old records).source contains the source, original hash / OSS key, title, metadata, and document metadata snapshot; parsing coverage is provided through the separate /parsed API; no knowledge-base-wide release manifest is stored. The new revision produced by a restore records its origin in source.restored_from_revision_id; this audit note does not participate in the snapshot_hash of the source content. The technical ingest_revision, projection_generation is 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-8 or base64), 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 fixed metadata_json.content_sha256. PDFs return page, total_pages, mime_type=image/png, base64 content; 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.blocks retains only page_number, block_type, section_path, text and 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 pin artifact_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 --json and knowledge 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 is knowledge 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 /catalog?page=0&size=20

{items,page,size,total_elements,head_version_id,active}; page is non-negative, size is 1-100; active is {generation,schema_version_id}

POST /candidates

{request_id,expected_head_version_id,parent_version_id,source_kind,schema,instructions}; success returns the new Version without automatically referencing it

DELETE /versions/{id}?expected_generation=G

Returns {id,deleted:true}; returns 409 on an active generation conflict or when the version is currently referenced or used by a running task

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_id is 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_kind accepts only manual_edit / json_import.schema, instructions is required; instructions 's ontology_discovery_instructions and graph_extraction_instructions are each up to 8,000 characters and are stored pinned with the new version.

  • expected_head_version_id must 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_id only 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 schema family (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 writing aliases as alias, or writing allowedUnits as allowed_units) returns 400 40001 unknown field '<name>' on OntologySchema…, and POST /ontology/validate behaves the same. Previously such typos were silently dropped, so the save succeeded but aliases / allowedUnits were persisted as null, causing alias resolution and unit validation to silently fail. Note that the facts structure of validate-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 /active generation 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 /discoveries

multipart: repeated files file fields; the options part is application/json; HTTP 202, returns {task_id,status,schema_version_id}, initially pending / null

GET /discoveries/{id}

{task_id,status,schema_version_id,error_code,error,created_at,completed_at,files,can_retry,authority,quality_report}; errors are redacted; quality_report (V103, may be null) is the quality gate report: {metrics,issues[]}, issues contain path/rule/severity(error|warning)/message, strict-mode blocks (error code QUALITY_GATE) are equally visible for diagnosis

POST /discoveries/{id}/retry

The request must be an empty JSON object {}; returns the task status; only the original author with editor permission on a still-active version can retry a recoverable dead task

DELETE /discoveries/{id}

Returns {task_id,deleted:true}; hides only dead / expired; running or successful tasks return 409 - this is not a cancel API

  • options is {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 task quality_report, a competency_unsupported warning (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.

  • files Summary fields are id, 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_available relies 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 /versions?page=0

20 pinned versions per page, descending array

GET /active

{generation,schema_version_id}; when not applied it is {generation:0,schema_version_id:null}

PUT /active

editor; {expected_generation,schema_version_id}, CAS conflict returns 409; setting null deactivates the Ontology and returns the new Binding

GET /documents/{docId}/facts

viewer; the last 20 pinned-schema extraction records, including source revision, schema version, ingestion count, facts, model/evidence provenance, and the published flag

GET /versions/{id}

Reads the pinned version under current permissions

POST /versions

{expected_version_id, schema, instructions}; CAS within the knowledge base row lock, conflict returns 409; same-hash reuse semantics are kept, but hitting a management tombstone appends a new version instead of returning the tombstone as a new candidate

POST /validate

The request is a schema; returns {valid, issues:[{path,rule,message}]}

POST /versions/{id}/validate-facts

The request is facts; validated against the pinned schema with source anchor verification; returns the same Validation

GET /versions/{id}/resolve?term=...

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 provide page or provide both start/end; a quote without positioning returns anchor issues. 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 only page=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-empty allowedUnits, a missing or mismatched unit returns structured unit issues. 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=false retains 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.published indicates that the result once passed graph publishing; the currently applied version is marked by /active in 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_coverage record ontology_version and ingest_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.tasks Lists 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 /

{generation,enabled,webhook_url}; default {generation:0,enabled:false,webhook_url:null}

PUT /

{expected_generation,enabled,webhook_url}; concurrent configuration changes return 409; one HTTP(S) receiver address per knowledge base

GET /deliveries?page=0

An array of 20 entries per page; id,document_id,review_id,status,event_status,error,created_at,delivered_at,payload

POST /deliveries/{id}/retry

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 headers Idempotency-Key and X-Context0-Event-Id are 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.

  • status is pending / delivered / cancelled; pending with event_status=dead means 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 /tasks

{mode:"discover" or "extract",schema_version_id,revision_ids:[...]}; requires editor

GET /tasks?page=0

20 tasks per page; requires viewer

GET /tasks/{id}

Pinned task, input fingerprint, candidate results, and run status; requires viewer

POST /tasks/{id}/retry

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; path use respectively types.TYPE_ID, types.TYPE_ID.attributes.ATTRIBUTE_ID, relations.RELATION_ID; each item contains an independent id, a finite number in 0-1 confidence, and a non-empty evidence. 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

POST

/v1/knowledge/benchmark/gold-standards

Submits a gold-standard sample. Request body: {query_text, kb_ids?, expected_chunk_ids, expected_document_ids?, notes?} (query_text and expected_chunk_ids are required; missing returns 400)

GET

/v1/knowledge/benchmark/gold-standards?page=0&size=50

Lists samples of this workspace

POST

/v1/knowledge/benchmark/runs

Runs a benchmark. Request body: {triggered_by?, kb_ids?}

GET

/v1/knowledge/benchmark/runs

Lists historical scoring runs

GET

/v1/knowledge/benchmark/runs/{id}

Details of a single scoring run (including the metrics JSON)

Note

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 requires viewer or above; no permission returns 403 (40303); crossing workspaces / nonexistent returns 404.

  • POST .../runs: first, each of the input kb_ids is checked with requireRead individually (a 403 does not leave a failed record in benchmark_runs — validation happens before the run record is created); then, sample by sample, it determines whether the caller has read permission on the kb_ids stored 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_ids must now be an array of numbers; a non-numeric element returns 400 (rather than being silently dropped). Note: previously the kb_ids filter never actually took effect due to a type issue (small integers in the request body were parsed as Integer and were never equal when compared with the Long in the samples), so benchmark runs always covered all samples in the workspace. After the fix, kb_ids truly 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.

Note

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: image / audio / video / doc

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
  }
}
Note

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 (image / audio / video / doc, and more)

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 SKILL.md UTF-8 body

content_sha256

string

content SHA-256 (64 hex characters)

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 as description / 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 --- YAML frontmatter (containing name / description and other metadata); choose either this or body

name

string

Conditional

Skill name. Required in body mode (content mode parses it from frontmatter)

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 [A-Za-z0-9_\-.]

config_json

object

Yes

Strategy configuration (JSON object)

preset

string

No

Preset type: chat / agent / rag-only / coding / custom. Default: custom

description

string

No

Description, up to 512 characters

Note

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: recall_pre / recall_post / assemble_pre / assemble_post / compact_pre / compact_post / dream_pre / dream_post / write_pre / write_post / dedup_pre / dedup_post

hook_type

string

Yes

Hook type. Valid values: llm / script / condition

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

POST

/ops/workspaces/members/keys

Creates an API Key and returns the one-time plaintext secret

GET

/ops/workspaces/members/keys

Lists API Keys by page; Keys created after V56 include plain_key in the response (the plaintext can be re-obtained); for earlier Keys this field is null

POST

/ops/workspaces/members/keys/revoke

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 / USER

  • Create response (one-time plaintext):

    {"code": 0, "msg": "ok", "data": {"id": 5, "key_prefix": "ctx0_", "key_type": "USER", "plain_key": "ctx0_abc123..."}}
  • List response (plain_key non-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

POST

/ops/workspaces/members

Creates a member

GET

/ops/workspaces/members?workspaceId=&page=&pageSize=

Lists members by page (up to 200 per page)

PATCH

/ops/workspaces/members

Updates member information

DELETE

/ops/workspaces/members

Deletes a member. Request body: {"id": N}

  • 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
      }
    }
    Note

    workspaceId must 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 fetch Continue 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

GET

/ops/token-usage/summary?from=YYYY-MM-DD&to=YYYY-MM-DD

Daily aggregated summary; returns total_tokens, total_calls, and items grouped by call_type and model

GET

/ops/token-usage/trend?from=YYYY-MM-DD&to=YYYY-MM-DD

Trends by day, invocation type, and model

GET

/ops/token-usage/hourly-trend?days=7

Trends by hour and invocation type; days supports 1-30

  • call_type includes llm, 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 default LlmProviderDedupAdjudicator is included in llm and is not double-counted in dedup.

  • Usage is taken from the usage.prompt_tokens/completion_tokens or input_tokens/output_tokens of successful HTTP responses; the total prefers total_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 usage at 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 as search_units), only the successful call count increases, and tokens accumulate as 0 known values with a model token usage unavailable warning; 0 does not mean actually free.

  • Data accumulates asynchronously into the token_usage_daily/hourly of 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

schedule_mode

—

daily / weekly / monthly / interval / cron / legacy; when create provides no schedule configuration, the default is daily 09:00

schedule_hour

daily / weekly / monthly

0-23, trigger hour

schedule_minute

daily / weekly / monthly

0-59, trigger minute

schedule_day_of_week

weekly

1-7, Monday=1

schedule_day_of_month

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)

schedule_interval_hours

interval

>=1, interval in hours

schedule_cron

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 pass schedule_type are handled in legacy mode, with the three levels mapped to 1h / 48h / 168h intervals (in the response, schedule_mode Returns legacy); for tasks created with the new mode, the schedule_type field is written with the value of the same name as the mode. This change is purely additive; old schedule_type clients 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

GET /v1/scheduled_promotions?page=0&pageSize=20

Lists tasks by page; returns Box<Page<Response>>

POST /v1/scheduled_promotions

Creates a task. Request body: {name, prompt, schedule configuration fields (see the table above)?, schedule_type?, auto_approve?, target_kb_id?}; without any schedule configuration the default is daily at 09:00

PUT /v1/scheduled_promotions/{taskId}

Updates a task; all request body fields are optional, and enabled is also supported; any change to a schedule configuration field reschedules from the current time next_run_at

DELETE /v1/scheduled_promotions/{taskId}

Deletes a task (idempotent: success is returned even if the task does not exist; repeated DELETE calls all succeed)

POST /v1/scheduled_promotions/{taskId}/trigger

Manually triggers one run immediately (5-minute cooldown; rejected when the previous promotion is still generating/pending_review)

Note
  • Permission validation for target_kb_id (starting 2026-09-16): on creation and update, target_kb_id must be a knowledge base on which the member the task belongs to (not the requester of this call) has editor or 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_id rather 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-existing target_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

GET /ops/scheduled-promotions/list?memberId=&page=&pageSize=

Paginated list (filterable by member)

GET /ops/scheduled-promotions/detail?task_id=

Task details

POST /ops/scheduled-promotions/create

Create (request body same as the data plane)

PUT /ops/scheduled-promotions/update?task_id=

Update (request body same as the data plane)

DELETE /ops/scheduled-promotions/delete?task_id=

Delete (idempotent, same semantics as the data plane)

POST /ops/scheduled-promotions/trigger?task_id=

Manual trigger

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_by member has editor or higher permissions on the target knowledge base - otherwise 403 (40303) and the promotion status stays unchanged at pending_review (the check happens before the status CAS, so it cannot get stuck at approving). 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: viewer of 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_review promotions appear only in the promotion list; they cannot be found on knowledge base paths such as GET /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

GET /ops/providers?workspaceId=&kind=

Global: platform ADMIN only; tenant: OWNER of this workspace only; responses do not merge deployment rules

GET /ops/providers/active?workspaceId=&kind=

Uses the existing active resolution and permissions; historical metadata is readable

GET /ops/providers/chain?workspaceId=&kind=

Uses the existing chain ordering, permissions, and DTO; historical metadata is not activated as deployment rules

PUT /ops/providers?kind=

The main llm / embedding when non-empty deployment rules exist, a global save returns HTTP 409 / CONFLICT (40901) with zero database writes; the permission check precedes this check

POST /ops/providers/reload?kind=

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 YYYY-MM-DD, inclusive)

to

date

Yes

End date (ISO format YYYY-MM-DD, inclusive)

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 context.ingest, session.create, kb.read, memory.create)

result

string

No

Filter by result: success / failure

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 / result

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: user / member

subjectId

string

Yes

Subject identifier (user_id or member_id)

workspaceId

string

No

Target workspace; defaults to the current one (an OWNER can target a non-host workspace through X-Workspace-Id)

reason

string

No

Erasure reason (retained for audit)

scope

string[]

No

Limits the erasure targets; defaults to a full-pipeline cascade

Note

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 queued / running / done / failed)

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 null

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

l2:<chunk_id> is a read-only projection; reading, writing, or deleting by that ID is not supported

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 40401/404 rather than 403, so as not to leak existence

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 current 40001 now 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_FOUND

  • Old 40010 (parameter validation failed) → 40001 VALIDATION_FAILED; the current 40010 now means SOURCE_CHUNK_READ_ONLY

  • Old 42900 (request rate exceeded) → 42901 RATE_LIMITED; quota exceeded separately uses 40011 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 ↔ sessionId

  • user_id ↔ userId

  • agent_id ↔ agentId

  • top_k ↔ topK

  • kb_ids ↔ kbIds

  • token_budget ↔ tokenBudget

  • keep_recent_n ↔ keepRecentN

  • include_l2 ↔ includeL2

  • vector_only ↔ vectorOnly

  • cosine_threshold ↔ cosineThreshold ↔ threshold