Cortex API
Cortex is a memory API. This page is the OpenAPI-level reference for the six core tools an agent or client actually calls, plus the one thing everyone gets wrong: there are two ways in, and they are not interchangeable transports. Read Two ways to reach Cortex first.
Two ways to reach Cortex
The same memory sits behind two endpoints with different wire protocols and different auth. Pick one deliberately.
/memory — MCP edge | /api/cortex/mcp — broker | |
|---|---|---|
| Wire protocol | Real MCP — JSON-RPC 2.0 (initialize, tools/list, tools/call) |
Flat JSON — {tool, arguments}. This is not MCP. |
| Auth accepted | Bearer JWT only (RS256 / JWKS) | Authorization: ApiKey cndk_v1_… or a signed-in session cookie |
| Rejects | Any non-Bearer credential → 401 (an ApiKey is not a valid RS256 JWT) |
A body that is not exactly {tool, arguments} → 400 "Only tool and arguments are accepted" |
| Tools exposed | 16 (full registry) | 19-entry browser allowlist — includes memory.pin, memory.crystals, memory.archive, memory.export/recover/load/get_pins and the maas.connection.* set; the effective set is whatever the live tools/list returns for your credential |
| Best for | An MCP client that already holds a Bearer JWT (Claude Code via its pre-registered client) | Portable, self-serve calls with a cndk_v1 key — curl, Gemini CLI, a ChatGPT Action |
/memory edge accepts a Bearer JWT and nothing
else. The broker accepts your ApiKey (or session), exchanges it server-side for a
short-lived Bearer via an OAuth client-credentials grant, then speaks MCP to /memory on
your behalf. Your raw ApiKey never reaches Cortex. The ApiKey is a broker-only
credential — presenting it directly to /memory returns 401.
The broker request contract
POST /api/cortex/mcpwith headerContent-Type: application/json(else415).- Body is exactly two keys:
{ "tool": "<name>", "arguments": { … } }. Any third key, or an identity / connection / backend / scope selector insidearguments, is rejected. toolmust be on the broker allowlist;argumentsis validated against the same schema the edge enforces.- Success →
200 { "result": <structuredContent> }. Failure →{ "error": { "type", "message" } }with a status in{400, 401, 403, 413, 429, 502, 503}.Cache-Control: no-storealways; a non-POSTmethod →405.
All curl examples below set CONDUIR_BASE to your deployment's origin and use a
placeholder key. Mint your own cndk_v1 key at
Your keys. Key format:
cndk_v1_<22 chars>_<43 chars>.
# set once — your deployment origin (no trailing slash)
export CONDUIR_BASE="https://your-conduir-host"
export CORTEX_KEY="cndk_v1_xxxxxxxxxxxxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
tools/list advertise
memory.recall, memory.remember, and so on, and the broker sends those dotted
names. Some MCP clients display them with underscores (memory_recall) — that is a client
display detail, not the API name. The graph tools cortex_search and
cortex_entity use underscores as their real wire names.
Response envelope
Every tool at the /memory edge returns the MCP tools/call envelope —
{ content: [{ type: "text", text: <json> }], structuredContent: <result>, isError: <bool> }.
isError is true only when the result carries an in-band error
field; a transport degraded is deliberately isError: false. The broker
unwraps this and returns the structuredContent under result. The
Response shape shown for each tool below is that structuredContent.
The six core tools
These are the agent-callable read/write surface, common to both endpoints. The further tools
(memory.export, memory.recover, memory.pin,
maas.connection.*) are reachable with an appropriately scoped key or
session — authentication with the right scope IS the authorization boundary; there is
no separate approval ceremony. What your credential can actually do = the live
tools/list — see Behaviour & scope.
Recall the most relevant memories for a query, ranked relevance-first. Time
words in the query are search text, not a date filter — pass a
session_id to switch to a deterministic session transcript instead.
| Name | Type | Required | Rules / enum |
|---|---|---|---|
query | string | required | Non-empty. Relevance search text; time words are not a date filter. |
max_results | integer | optional | 1–100. Default 10. |
session_id | string | optional | ≤256 chars, single-line. When set, returns a session transcript instead of a ranked recall. |
after_ts | number | optional | Unix seconds ≥0. Only meaningful with session_id. |
before_ts | number | optional | Unix seconds ≥0. Must be ≥ after_ts. Only with session_id. |
order | enum | optional | asc | desc. Default desc. Only with session_id. |
items, degraded, store_status)items[]— most-relevant first. Every item carries a stable trio on every tier:content(string),entry_id(string — pass tomemory.forget),source_tier(string, e.g.L1_pulse,L3_semantic,L3_archive). Optional per-tier fields:key,timestamp,category,confidence,outcome,action,access_count,context. Engine internals and any credential id are masked at the edge.degraded(bool) —truemeans retrieval did not complete reliably, so an emptyitemsis not "no memories".store_status∈{populated, empty, unknown}— an empty result with a status other thanpopulatedmay mean a wiped or absent store, not a genuine no-match.error(string, optional) — present on a 4xx / contract fault.
curl -sS "$CONDUIR_BASE/api/cortex/mcp" \
-H "Authorization: ApiKey $CORTEX_KEY" \
-H "Content-Type: application/json" \
-d '{"tool":"memory.recall","arguments":{"query":"pilot decision","max_results":5}}'
{ "result": {
"items": [
{ "content": "The FTSE100 pilot prospect is confirmed.",
"entry_id": "mcp_9f3c…", "source_tier": "L3_archive" }
],
"degraded": false,
"store_status": "populated"
} }
Write one durable checkpoint. Idempotent per idempotency_key. Store no
secrets and no raw transcripts.
| Name | Type | Required | Rules / enum |
|---|---|---|---|
content | string | required | Non-empty. No secrets, no raw transcripts. |
idempotency_key | string | required | Stable and unique per logical write. Reuse only for an exact retry. |
content_kind | enum | optional | decision | durable_lesson | fact | observation | session_summary. Default observation. |
session_id | string | optional | ≤256 chars correlation label. |
client_event_ts | number | optional | Advisory only. Never overrides the server write-time. |
sequence | integer | optional | ≥0. Same-tick ordering tiebreak. |
idempotency_key, status, memory_ids)status∈{persisted, rejected, unknown}.persistedonly when the store reports episodes stored and not partial; a partial / zero / 4xx / 5xx write isrejected; a post-send timeout or transport error isunknown(the write may still have landed).memory_ids[]: Cortex's write path does not echo stored ids, so the edge derives theentry_idfrom the idempotency key (mcp_+ sha256). That is the idmemory.recallreturns for the row.outcome_known(bool, onunknown),reason(string),replay(bool —truewhen an idempotency-cache hit replays a prior persisted result).
curl -sS "$CONDUIR_BASE/api/cortex/mcp" \
-H "Authorization: ApiKey $CORTEX_KEY" \
-H "Content-Type: application/json" \
-d '{"tool":"memory.remember","arguments":{
"content":"Cross-vendor memory proven via one key.",
"idempotency_key":"note-2026-08-21-1",
"content_kind":"decision"}}'
{ "result": {
"idempotency_key": "note-2026-08-21-1",
"status": "persisted",
"memory_ids": ["mcp_1a2b…"]
} }
Suppress specific entries from future recall. This is a reversible, tenant-scoped un-index tombstone — it does not destroy the underlying records. Physical hard-deletion is a separate operator-governed capability.
| Name | Type | Required | Rules / enum |
|---|---|---|---|
entry_ids | array<string> | required | 1–100 non-empty strings, as returned by memory.recall. |
reason | string | optional | ≤512 chars, single-line. Recorded on the tombstone. |
status)status∈{forgotten, not_forgotten}.forgottenonly when the store confirms; an unreachable / 4xx / 5xx Cortex isnot_forgotten, never laundered.- Per-id counters that sum to
requested:tombstoned,already_forgotten,not_found,unknown.results[]gives per-id{entry_id, status}in request order. activity_confirmed(bool, present whenstatus == forgotten) —true= tombstone committed and its audit receipt was durably recorded;false= the rows ARE forgotten but the receipt could not be written (the delete is authoritative regardless).
activity_confirmed:false
returning systematically live. The code permits both values, so treat "always false" as an
observed runtime state, not a contract guarantee — the forget itself is still authoritative.
curl -sS "$CONDUIR_BASE/api/cortex/mcp" \
-H "Authorization: ApiKey $CORTEX_KEY" \
-H "Content-Type: application/json" \
-d '{"tool":"memory.forget","arguments":{
"entry_ids":["mcp_9f3c…"],
"reason":"superseded"}}'
{ "result": {
"status": "forgotten",
"requested": 1, "tombstoned": 1,
"already_forgotten": 0, "not_found": 0, "unknown": 0,
"results": [ { "entry_id": "mcp_9f3c…", "status": "tombstoned" } ],
"activity_confirmed": false
} }
Reachability check only. It does not prove a recall result exists or a write persisted.
None. additionalProperties: false, empty properties.
status∈{ready, unavailable}— a bare reachability signal, no degraded or detail sub-state.
curl -sS "$CONDUIR_BASE/api/cortex/mcp" \
-H "Authorization: ApiKey $CORTEX_KEY" \
-H "Content-Type: application/json" \
-d '{"tool":"memory.health","arguments":{}}'
{ "result": { "status": "ready" } }
Search memory by time and session — the read memory.recall
cannot do, because recall ranks by relevance and treats time words as text. Truly read-only: it does
not bump access counts and records no activity receipt.
| Name | Type | Required | Rules / enum |
|---|---|---|---|
query | string | optional | Non-empty if given. |
limit | integer | optional | 1–100. Default 20. |
session_id | string | optional | ≤256 chars. |
after_ts | number | optional | Unix seconds ≥0. |
before_ts | number | optional | Unix seconds ≥0. |
as_of | number | optional | Unix seconds ≥0. |
include_superseded | boolean | optional | Default false. |
order | enum | optional | asc | desc. |
episodes, count, degraded)episodes[]— each carriesentryId,sessionId,agentName,action,content,outcome,timestamp,supersedesEntryId,originChannel. Credential ids are redacted at the edge.count,degraded, and an optionalerror.
content is truncated to the first 500 characters on this route.
Full fidelity is available only through memory.export (operator-scoped).
curl -sS "$CONDUIR_BASE/api/cortex/mcp" \
-H "Authorization: ApiKey $CORTEX_KEY" \
-H "Content-Type: application/json" \
-d '{"tool":"cortex_search","arguments":{"limit":10,"order":"desc"}}'
{ "result": {
"episodes": [
{ "entryId": "mcp_9f3c…", "sessionId": "sess-42",
"agentName": "assistant", "action": "remember",
"content": "Cross-vendor memory proven via one key.",
"outcome": "persisted", "timestamp": 1755734400,
"supersedesEntryId": null, "originChannel": "mcp" }
],
"count": 1,
"degraded": false
} }
Explore the entity relationship graph. Pure read — no reinforcement, no receipt.
| Name | Type | Required | Rules / enum |
|---|---|---|---|
query | string | required | Non-empty. |
limit | integer | optional | 1–100. Default 20. |
rel_type | string | optional | ≤128 chars. Restricts the neighbour edge type. |
top_n | integer | optional | 1–10. Default 3. How many matched entities to expand. |
related_limit | integer | optional | 1–100. Default 20. |
entities, relationships, degraded)entities[]—name,entity_type,mention_count,first_seen,last_seen,related_episodes.relationships[]—source,target,type,weight.degraded, and an optionalerror.
entity_type is flagged systematically unreliable (F-066). Display it if you
must, but do not branch program logic on it.
curl -sS "$CONDUIR_BASE/api/cortex/mcp" \
-H "Authorization: ApiKey $CORTEX_KEY" \
-H "Content-Type: application/json" \
-d '{"tool":"cortex_entity","arguments":{"query":"pilot","top_n":3}}'
{ "result": {
"entities": [
{ "name": "Pilot", "entity_type": "concept", "mention_count": 4,
"first_seen": 1755648000, "last_seen": 1755734400,
"related_episodes": ["mcp_9f3c…"] }
],
"relationships": [
{ "source": "Pilot", "target": "Prospect", "type": "associated_with", "weight": 0.72 }
],
"degraded": false
} }
Behaviour & scope
How the API behaves as shipped, so you build against the real contract.
forgetis a reversible un-index, not a delete. A tenant-scoped tombstone suppresses entries from recall; the underlying records survive. GDPR erasure is therefore not satisfied byforgetalone — hard-deletion is a separate operator capability.- Text-only. Every argument and every stored
contentfield is a string. The wire contract has no binary or object content field. - Operator scope is a ceiling. A self-serve
cndk_v1key is clamped to{memory:read, memory:write, offline_access}. Somemory.export,memory.recover, andmemory.pin(and themaas.connection.*tools) are operator-only and unreachable with an agent key — those succeed only under the signed-in session path. The nine read/write memory + graph tools are the agent-callable surface. exportis episodic-only, and there is no import.memory.exportreturns your own episodic memory at full fidelity but omits the persisted portable stores (preferences, procedures, pins, entities) and lists non-portable derived state (embeddings, the entity graph, crystals, deep archive, retrieval indexes) that re-derives on any restore. Noimport/uploadtool exists — cross-model migration is export-out only, and the receiving side re-derives, so recall results change after a restore.- Recall is ranked, and every read is checkable. Recall returns the most
relevant memories ranked relevance-first, not a keyed lookup. Every read carries
degradedand (on recall)store_status, so a consumer can separate a genuine no-match from a degraded read. An empty result is not a confirmed "nothing there".
Next: Quickstart to wire a client in ~60 seconds, or create a key.