API Reference
Base URL: http://localhost:3030 (or your deployment URL)
Authentication: When the environment variable named by server.auth_token_env (default LOOMEM_AUTH_TOKEN) contains a nonempty API key, send Authorization: Bearer <token> to protected routes, including the /v1, /api, /admin and /mcp endpoints. /health, OAuth discovery and /oauth/* routes, and the static dashboard are outside the Bearer middleware; OAuth routes apply their own protocol checks. Without a key, protected routes accept requests with admin privileges. The server refuses a non-loopback bind without a key unless LOOMEM_ALLOW_UNAUTH=1 is explicitly set.
When no stream is specified, data is read from and written to the default stream __user_default__.
Storage
POST /v1/store
Store a memory chunk.
Request:
{
"content": "User prefers dark mode in all tools",
"stream": "100",
"level": 0,
"metadata": { "source": "user-stated" },
"user_id": "alice",
"app_id": "claude"
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
content |
string | yes | — | Memory text (1 – 1M chars) |
stream |
string | no | __user_default__ |
Namespace ID |
level |
int | no | 0 | Memory tier (0 = raw, 1 = consolidated) |
metadata |
object | no | null | Custom JSON metadata |
user_id |
string | no | null | Creator identifier |
app_id |
string | no | null | Application identifier |
Response:
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "stored"
}
Side effects: - Pre-ingestion sanitization (HTML stripping, instruction injection detection) - PII redaction (phones, emails, PESEL, blocklist words) - Entity extraction (dictionary + LLM queue) - Embedding generation (async queue) - Tantivy indexing - Graph population (stream-scoped) - Surprise scoring (importance adjustment) - Contradiction detection against existing memories
LLM-based knowledge extraction from full conversation transcripts is available via the memory_ingest MCP tool — see MCP Tools Reference.
Search
POST /v1/search
Hybrid search across all memory tiers.
Request:
{
"query": "What IDE does the user prefer?",
"top_k": 5,
"stream": "100",
"date_from": "2026-01-01",
"date_to": "2026-04-03",
"trace": true,
"dry_run": false
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
query |
string | yes | — | Search query |
top_k |
int | no | 10 | Max results |
stream |
string | no | __user_default__ |
Namespace filter |
streams |
string[] | no | null | Multi-namespace search |
date_from |
string | no | null | ISO date lower bound |
date_to |
string | no | null | ISO date upper bound |
entity |
string | no | null | Filter by entity name |
trace |
bool | no | false | Include debug trace info |
dry_run |
bool | no | false | Skip implicit boost / access tracking |
valid_at |
int (unix sec) | no | null | Bitemporal time-travel: return only chunks whose [valid_from, valid_until] covers this timestamp. Open intervals (None) are unbounded on that side. |
include_superseded |
bool | no | false | Include old versions |
fact_type |
string | no | null | Filter: preference, project, fact |
subject |
string | no | null | Filter by subject entity |
min_confidence |
f64 | no | null | Minimum extraction confidence |
Response:
{
"results": [
{
"chunk_id": "abc-123",
"content": "User switched from VSCode to Cursor (reason: speed)",
"score_final": 0.87,
"trace_info": {
"level": "L1",
"source": "consolidation",
"is_latest": true,
"created_at": 1743638400,
"memory_type": "static",
"importance": 1.2,
"access_count": 3,
"version": 2,
"superseded_by": null
}
}
],
"trace_metadata": {
"total_results_before_topk": 42,
"dedup_removed": 3,
"search_latency_us": 1200,
"query_complexity": "medium"
}
}
Evidence object (experimental, opt-in): when [search] evidence_fields = true in config.toml, every hit (cold and cached) carries an additive evidence key. The key is omitted entirely while the flag is off, so default responses are unchanged. The object has exactly seven keys, all always present; an unknown value is null, never a substituted date.
"evidence": {
"author": "user",
"conversation_date": "2026-04-02",
"event_date": "2026-03-26",
"event_date_provenance": "resolved",
"source_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"byte_start": 0,
"byte_end": 142
}
| Key | Type | Description |
|---|---|---|
author |
string? | Normalised author role: user, assistant, system or tool. Anything else (a person's name, free text) is null |
conversation_date |
string? | Date the conversation took place (YYYY-MM-DD), as stored |
event_date |
string? | Date the described event happened (YYYY-MM-DD), as extracted. Only calendar-valid ISO dates are emitted |
event_date_provenance |
string? | resolved when the engine recorded the temporal cue the date was resolved from, unknown otherwise; null exactly when event_date is null. explicit is reserved and not emitted today |
source_id |
string? | Canonical source chunk this memory was cut or extracted from |
byte_start, byte_end |
int? | Byte range of a source fragment within its source (see [fragments]); both set or both null |
Stage rejections (traced requests): with trace: true, trace_metadata also carries rejections — one entry per candidate dropped between the traced post-rank stages — next to the existing evidence stage snapshots. stage is the first stage the id is absent from (after_soft_delete, after_filters_and_dedup or response); reason is one of soft_deleted, event_date_out_of_range, bitemporal_out_of_range, superseded, extraction_meta_missing, fact_type_filter, subject_filter, min_confidence, content_prefix_dedup, aggregation_subject_dedup, source_agent_filter, truncated_top_k, operation_finalization. The response snapshot is captured after operation finalization and the final event-date check, so its IDs and order match the returned search results. A record restored into the final response is not listed as rejected; operation_finalization identifies a primary result displaced by the operation policy. The four evidence-stage names and their order are unchanged. The MCP max_context_tokens trim happens after the traced stages, so a result it drops is not listed here.
"trace_metadata": {
"rejections": [
{ "chunk_id": "def-456", "stage": "after_filters_and_dedup", "reason": "superseded" },
{ "chunk_id": "ghi-789", "stage": "response", "reason": "truncated_top_k" }
]
}
POST /v1/context-pack
Smart context packing for system prompt injection. Assembles a token-budgeted context window from profile, relevant memories, and recent activity.
Request:
{
"query": "working on dashboard project",
"stream": "100",
"budget_tokens": 2000,
"sections": ["profile", "relevant", "recent"]
}
| Field | Type | Default | Description |
|---|---|---|---|
query |
string | null | Topic focus |
stream |
string | __user_default__ |
Namespace |
budget_tokens |
int | 4000 | Max tokens in response |
sections |
string[] | all | Which sections to include |
Section token allocation:
- profile: 20% of budget
- relevant: 50% of budget
- recent: 30% of budget
Response:
{
"context": "## Profile\nUser is a software engineer...\n\n## Relevant\n...\n\n## Recent\n...",
"sources": [
{ "chunk_id": "abc-123", "score": 0.87, "section": "relevant" }
],
"total_tokens": 1856,
"sections_included": ["profile", "relevant", "recent"],
"sections_truncated": []
}
Memory management
POST /v1/delete
Delete a single chunk by ID.
{ "id": "abc-123" }
Response: { "status": "deleted", "id": "abc-123" }
DELETE /api/memories/{id}
REST-compliant delete. Optional ?ns=<namespace> query parameter.
Response: { "deleted": true, "id": "abc-123" }
GET /api/memories/{id}
Returns one memory item (id, content, layer, confidence, decay, event_date, created_at, category, entity_ids, version, is_latest, superseded_by, source_stream, source, source_agent). 404 for unknown or soft-deleted ids; non-admin callers may only read chunks in their own stream.
When [search] evidence_fields = true, the item also carries the same additive evidence object as a /v1/search hit (see above). The dashboard list endpoint does not carry it.
POST /v1/purge-namespace
Delete all chunks in a stream.
{
"stream": "100",
"dry_run": true,
"confirmed": false
}
Response:
{
"status": "ok",
"stream": "100",
"dry_run": true,
"deleted_count": 42,
"deleted_ids": ["abc-123", "def-456", "..."]
}
Set dry_run: false and confirmed: true to actually delete.
Graph
GET /v1/graph/entity/{name}?stream={stream_id}
Get entity node with edges and chunk references. Requires stream parameter — graph is per-stream isolated.
{
"entity": {
"id": "ent-123",
"name": "Cursor",
"type": "TECHNOLOGY",
"aliases": ["cursor IDE"],
"chunk_count": 2
},
"neighbors": [
{ "entity": "Alice", "entity_type": "PERSON", "relation": "uses", "direction": "incoming" }
],
"chunk_ids": ["abc-123", "def-456"]
}
GET /v1/graph/stats
{
"total_entities": 42,
"total_edges": 87,
"avg_chunks_per_entity": 3.2
}
POST /v1/build-graph
Rebuild entity graph from all stored entities. Useful after bulk ingestion.
POST /v1/extract-entities
Trigger LLM NER backfill on chunks missing entity extraction. Runs asynchronously.
Synthesis
Version history of a chunk is available via the memory_history MCP tool; the synthesized user profile via the memory_profile MCP tool. See MCP Tools Reference.
GET /v1/generate-memory-md
Generate a MEMORY.md proposal from top chunks.
{
"proposal": "# Memory\n\n## Identity\n- Software engineer...\n\n## Preferences\n...",
"metadata": { "chunks_considered": 200, "sections": 15 }
}
Consolidation & maintenance
POST /v1/dream
Trigger dream consolidation on current stream.
{
"stream": "100",
"chunks_processed": 50,
"groups_found": 12,
"facts_merged": 8,
"contradictions_resolved": 2,
"cost_usd": 0.04,
"duration_ms": 3200
}
Memory quality analysis is available via the memory_reflect MCP tool.
POST /v1/boost
Boost a chunk's importance to 1.5.
{ "id": "abc-123" }
POST /v1/embed-missing
Backfill embeddings for chunks that don't have them.
{
"status": "ok",
"total_missing": 15,
"embedded": 15,
"failed": 0
}
POST /v1/retag-all
Re-extract entities on all chunks.
POST /v1/score-all
Recompute importance scores for all chunks via embedding similarity.
POST /v1/re-embed-all
Regenerate all embeddings (useful when switching providers).
POST /v1/reset-importance
Reset all accumulated implicit boosts back to defaults.
POST /v1/reset-backfill
Clear LLM entity extraction markers to allow re-processing.
Admin
GET /v1/status
Engine health and statistics.
{
"status": "ok",
"uptime_secs": 86400,
"config_summary": {
"vector_enabled": true,
"tantivy_enabled": true,
"scheduler_enabled": true,
"rocksdb_keys": 311,
"tantivy_docs": 115,
"embeddings_count": 112
}
}
GET /v1/namespaces
{
"namespaces": {
"personal": "100",
"work": "110"
}
}
GET /v1/whoami
Returns the auth context for the current caller (active stream, role, accessible streams).
GET /health
Liveness check. No authentication required.
{ "status": "ok" }
Ambient memory
POST /v1/ambient returns a small, token-budgeted set of plain-fact memory snippets for injection into an agent's context at the start of a turn. See the ambient endpoint contract.
Background workers
POST /admin/workers/pause
Pause all background workers (consolidation, decay, compaction, backup, clustering, purge, stats). Useful for eval runs where you need a frozen database.
Requires admin token.
Response:
{
"paused": true,
"message": "All workers paused"
}
POST /admin/workers/resume
Resume all background workers.
Response:
{
"paused": false,
"message": "All workers resumed"
}
GET /admin/workers/status
Check if workers are paused.
Response:
{
"paused": false
}
Pause state does not survive server restart — workers resume automatically on startup.
MCP endpoint
POST /mcp
MCP JSON-RPC 2.0 endpoint. Used by Claude Desktop, claude.ai connectors, and other MCP clients. The server identifies itself as loomem-memory.
See MCP Tools Reference for the 15 available memory_* tools.
OAuth endpoints
For MCP Remote Connector authorization:
POST /oauth/register Dynamic Client Registration (RFC 7591)
GET /oauth/authorize Authorization page (user enters API key)
POST /oauth/authorize Submit authorization
POST /oauth/token Exchange code for access token