Reference
API
A dozen JSON endpoints over bearer auth, four scopes, and one table from domain error to HTTP status — so the shapes here are exhaustive rather than typical.
On this page
The shape of a call
Every endpoint is JSON in and JSON out. Authenticate with Authorization: Bearer <key>. There is no versioning negotiation and no envelope — the response body is the resource.
curl -X POST $COGNITIVE_MEMORY_URL/api/v1/context \ -H "Authorization: Bearer $COGNITIVE_MEMORY_KEY" \ -H "content-type: application/json" \ -d '{"userMessage":"deploy ZQ7X4M2K to staging"}'Paths are mounted under /api on this deployment. A hosted deployment serving the SDK from the same origin may present them at the root, which is why the client takes a base URL rather than hardcoding one.
Every endpoint
| Method | Path | Does | Needs |
|---|---|---|---|
| POST | /v1/context | Build the prompt block. Returns text, entries with reasons, and the token cost. | memories:read |
| POST | /v1/recall | Deterministic ranked lookup. Returns empty:true when nothing matched. | memories:read |
| POST | /v1/turns | Learn from a completed turn. | memories:write |
| POST | /v1/memories | Store facts outright. Restatements are folded in. | memories:write |
| GET | /v1/memories | List what is held, with tier counts. | memories:read |
| GET | /v1/memories/:id | One memory in full. | memories:read |
| PATCH | /v1/memories/:id | Move between tiers. | memories:write |
| DELETE | /v1/memories/:id | Forget one memory. | memories:write |
| GET | /v1/tensions | Contradictions, filterable by status. | memories:read |
| POST | /v1/tensions | Record a contradiction. | memories:write |
| POST | /v1/tensions/:id | Resolve one, keeping the pattern. | memories:write |
| GET | /v1/self-model | Reliability per domain. | memories:read |
| POST | /v1/self-model/outcome | Record how a domain went. | memories:write |
| GET | /v1/stats | Counts, weak domains, active tensions. | stats:read |
| GET | /api/v1/health | Liveness, limits, extractor mode, and unusable env values. | — |
| GET | /v1/keys | List your keys by prefix. | session |
| POST | /v1/keys | Mint a key. The secret is returned once. | session |
| DELETE | /v1/keys/:id | Revoke a key. | session |
The last three are session-authenticated, not key-authenticated — minting a credential requires the human. Credentials explains why that is not an inconsistency.
The endpoints you will actually call
The table above is the whole surface, which is the right shape for “does this exist”. These four are what an integration is actually made of, and each is here with its real request and its real response — because the two things you need in order to call something are what to send and what comes back, and neither is guessable from a method and a path.
/v1/contextmemories:readuserMessage is what drives the triggers; forceFull pins specific memories into the block whatever their tier; maxTokens overrides the ceiling for this call.const { text, entries, totalTokens, truncated } = await memory.context.build({ userMessage: "deploy ZQ7X4M2K to staging" })// text goes in front of your system prompt.// entries accounts for every line in it.curl -X POST $COGNITIVE_MEMORY_URL/api/v1/context \ -H "Authorization: Bearer $COGNITIVE_MEMORY_KEY" \ -H "content-type: application/json" \ -d '{"userMessage":"deploy ZQ7X4M2K to staging"}'{ "text": "### Memory index — established earlier in this project- Deploy target is ZQ7X4M2K (infra)", "entries": [ { "id": "mem_8fa21c", "tier": "L1", "reason": "index", "gist": "Deploy target is ZQ7X4M2K", "tokens": 9 }, { "id": "mem_1b40de", "tier": "L1", "reason": "trigger", "gist": "Staging host is internal-hbr-2291.pineapple.example", "body": "The staging host for the billing service is internal-hbr-2291.pineapple.example, owned by platform.", "tokens": 31 } ], "totalTokens": 40, "truncated": false}A memory body is included only when the message named something concrete and absent from the transcript — here, ZQ7X4M2K. The full rules are on what goes into the prompt.
/v1/turnsmemories:writeconst result = await memory.turns.learn({ userMessage, assistantResponse, sessionId})result.counts // { stored, merged, rejected, tensions, promoted }result.rejected // [{ content, reason }] — never silentcurl -X POST $COGNITIVE_MEMORY_URL/api/v1/turns \ -H "Authorization: Bearer $COGNITIVE_MEMORY_KEY" \ -H "content-type: application/json" \ -d '{"userMessage":"we deploy on Fridays","assistantResponse":"Noted."}'{ "stored": [ { "id": "mem_9c31af", "content": "we deploy on Fridays", "gist": "Deploys happen on Fridays", "tier": "L1", "domains": [], "accessCount": 0, "source": "rules", "createdAt": 1767225600000, "lastAccessedAt": 1767225600000 } ], "mergedInto": [], "counts": { "stored": 1, "merged": 0, "rejected": 0, "tensions": 0, "promoted": 0 }, "rejected": []}A turn whose user message contains a question is a lookup, not a lesson, and returns everything empty with the candidates under rejected. Why.
/v1/recallmemories:readrecallOrExplain wraps this and phrases the result for a prompt.const { results, empty } = await memory.recall.search({ query: "where do we deploy", limit: 8})if (empty) return "If you were not told, say so rather than guessing."curl -X POST $COGNITIVE_MEMORY_URL/api/v1/recall \ -H "Authorization: Bearer $COGNITIVE_MEMORY_KEY" \ -H "content-type: application/json" \ -d '{"query":"where do we deploy","limit":8}'{ "results": [ { "memory": { "id": "mem_8fa21c", "content": "Deploy target is ZQ7X4M2K", "tier": "L1", "domains": ["infra"], "accessCount": 4, "createdAt": 1767225600000, "lastAccessedAt": 1767312000000 }, "score": 0.62 } ], "empty": false}empty: true is not an empty array — it means the search ran and found nothing, which is a fact about your store and not the same as a failure. Recall.
/v1/self-model/outcomememories:writesuccess is false whenever a failurePattern is present; there is no third state. Patterns and strategies accumulate on the domain and become the guardrail injected into later prompts.await memory.selfModel.record({ domain: "database", success: false, failurePattern: "migrated the schema without a dry run"})curl -X POST $COGNITIVE_MEMORY_URL/api/v1/self-model/outcome \ -H "Authorization: Bearer $COGNITIVE_MEMORY_KEY" \ -H "content-type: application/json" \ -d '{"domain":"database","success":false,"failurePattern":"migrated without a dry run"}'{ "calibrationFactor": 1, "activeDomains": ["database"], "domains": { "database": { "reliabilityScore": 0.533, "sampleCount": 1, "knownFailurePatterns": ["migrated the schema without a dry run"], "recommendedStrategies": [] } }, "weakDomains": []}reliabilityScore: 0.533 after one failure, not 0 — the moving average carries a prior of two samples, so one bad turn cannot pin a warning into every prompt for ever. The self-model.
Scopes
Four, and a key must hold the one an endpoint needs. A 403 names the missing scope, so a caller can widen a key without guessing.
| Scope | Grants |
|---|---|
memories:read | Context builds, recall, reads, and the self-model. |
memories:write | Learning, storing, promoting, forgetting, resolving. |
stats:read | GET /v1/stats. |
keys:manage | Rotate sibling keys for the same organisation. Cannot create an organisation or widen its own scopes. |
A newly minted key gets memories:read, memories:write and stats:read by default. An agent that only reads should be issued only the read scope.
Errors
A failure carries a tag, a message, and whatever is actionable. A caller can therefore tell a wrong key from a wrong scope, and a malformed body from an outage, without parsing prose.
{ "error": "Forbidden", "message": "This key lacks the memories:write scope.", "requiredScope": "memories:write"}| Status | error | Means |
|---|---|---|
| 400 | InvalidRequest | The body or a parameter was wrong. issues carries field paths. |
| 401 | Unauthorized | No key, or a key that is revoked, expired or unknown. |
| 403 | Forbidden | Valid key, wrong scope. requiredScope says which. |
| 404 | NotFound | No such memory or tension. resource and id say which. |
| 500 | StorageFailure | The database failed. Retry, and do not treat the body as actionable. |
| 502 | ModelFailure | A configured model provider failed. The deterministic path is unaffected. |
| 503 | BootstrapDisabled | The service is up but has been configured not to serve this. |
Anything not in this table is a bug or an outage, and surfaces as a 500 with the cause logged server-side and nothing about internals in the body. A tidy 400 for a driver crash would hide it.
The two calls that matter
If you are integrating rather than reading, this is the whole API. Build context before the model runs; record the turn after it finishes.
# what the agent should know before it answerscurl -X POST $COGNITIVE_MEMORY_URL/api/v1/context \ -H "Authorization: Bearer $COGNITIVE_MEMORY_KEY" \ -H "content-type: application/json" \ -d '{"userMessage":"deploy ZQ7X4M2K to staging"}'# what it should remember from the finished turncurl -X POST $COGNITIVE_MEMORY_URL/api/v1/turns \ -H "Authorization: Bearer $COGNITIVE_MEMORY_KEY" \ -H "content-type: application/json" \ -d '{"userMessage":"...","assistantResponse":"..."}'POST /v1/context returns the block to prepend plus the entries that produced it; POST /v1/turns returns what was stored, merged and rejected, with reasons.
Health
Unauthenticated on purpose: an orchestrator has to reach it without holding a key, and nothing in it is tenant data.
{ "ok": true, "service": "cognitive-memory", "extractor": "rules-only", "limits": { "maxTotalTokens": 2000, "maxIndexItems": 60, "defaultRecallLimit": 8 }, "problems": []}extractor is rules-only or rules+model, and it is the first field to check when memory is not learning. problems is empty in the healthy case, so an operator can alert on it without a special case — it names environment values that were present but unusable, and a missing BETTER_AUTH_SECRET in production.