API Reference
Base URL: https://core.mnemoverse.com/api/v1
All /memory/* endpoints require authentication via X-Api-Key header or Authorization: Bearer header. Health endpoints (/health, /health/ready) are public and unauthenticated. Internal administrative endpoints exist but use separate authentication and are not part of the public API.
Authentication
Include your API key in every request:
# Option 1: X-Api-Key header (recommended)
curl -H "X-Api-Key: mk_live_YOUR_KEY" ...
# Option 2: Bearer token
curl -H "Authorization: Bearer mk_live_YOUR_KEY" ...Endpoints
POST /memory/write
Store a single memory atom.
Request:
| Field | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The insight, pattern, or lesson to remember (1-10,000 chars) |
concepts | string[] | No | Key concepts for Hebbian associations |
domain | string | No | Namespace: general, user:X, project:Z (default: general) |
metadata | object | No | Arbitrary key-value metadata |
external_ref | string | No | Client-provided unique reference for idempotent writes |
Response:
| Field | Type | Description |
|---|---|---|
stored | boolean | True if the atom passed the importance gate |
atom_id | UUID | UUID of the stored atom, or null if filtered |
importance | float | Computed importance score [0, 1] |
reason | string | Why stored or filtered |
POST /memory/write-batch
Store up to 500 atoms in one request.
Request:
| Field | Type | Required | Description |
|---|---|---|---|
items | WriteRequest[] | Yes | Array of write requests (1-500) |
Response:
| Field | Type | Description |
|---|---|---|
total_count | int | Total atoms processed |
stored_count | int | Atoms that passed importance gate |
results | object[] | Per-atom results with index, stored, atom_id, importance, error |
POST /memory/read
Query memory with semantic search + Hebbian expansion.
Request:
| Field | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Natural language query (1-5,000 chars) |
top_k | int | No | Max results (1-100, default: 10) |
domain | string | No | Filter by domain (null = all) |
min_relevance | float | No | Minimum relevance threshold (0-1, default: 0.3) |
include_associations | bool | No | Expand via Hebbian associations (default: true) |
concepts | string[] | No | Concept hints to bias search |
Response:
| Field | Type | Description |
|---|---|---|
items | MemoryItem[] | Matching memories, ordered by relevance |
episodic_hit | boolean | True if exact fingerprint match found |
query_concepts | string[] | Concepts extracted from query |
expanded_concepts | string[] | Concepts after Hebbian expansion |
search_time_ms | float | Search duration in milliseconds |
MemoryItem fields:
| Field | Type | Description |
|---|---|---|
atom_id | UUID | Unique identifier |
content | string | Stored text content |
relevance | float | Final score (similarity * valence modulation) |
similarity | float | Raw cosine similarity |
valence | float | Outcome polarity [-1, +1] |
importance | float | Importance score [0, 1] |
source | string | Hit source: episodic, semantic, or hebbian |
concepts | string[] | Associated concepts |
domain | string | Domain namespace |
metadata | object | Arbitrary metadata |
POST /memory/read-batch
Batch query up to 50 queries in one request.
Request:
| Field | Type | Required | Description |
|---|---|---|---|
queries | ReadRequest[] | Yes | Array of read requests (1-50) |
Response:
| Field | Type | Description |
|---|---|---|
results | ReadResponse[] | Per-query results |
POST /memory/query
Advanced query with multi-domain and metadata filtering.
Request:
| Field | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Natural language query |
domains | string[] | No | Filter by multiple domains |
metadata_filter | Filter[] | No | JSONB metadata conditions (eq, contains, in) |
top_k | int | No | Max results (default: 10) |
min_relevance | float | No | Min relevance (default: 0.3) |
include_associations | bool | No | Hebbian expansion (default: true) |
concepts | string[] | No | Concept hints |
POST /memory/feedback
Report outcome (success/failure) for memories. Updates valence and Hebbian associations.
Request:
| Field | Type | Required | Description |
|---|---|---|---|
atom_ids | UUID[] | Yes | Atoms to update |
outcome | float | Yes | Outcome signal: -1.0 (failure) to +1.0 (success) |
concepts | string[] | No | Concepts to reinforce |
query_concepts | string[] | No | Original query concepts (enables co-activation learning) |
domain | string | No | Domain for the feedback |
Response:
| Field | Type | Description |
|---|---|---|
updated_count | int | Number of atoms updated |
avg_valence | float | Average valence after update |
coactivation_edges | int | Hebbian edges created/updated |
POST /memory/consolidate
Trigger sleep consolidation. Clusters similar memories into prototypes, protects distinctive ones as singletons.
Request:
| Field | Type | Required | Description |
|---|---|---|---|
domain | string | No | Domain to consolidate (null = general) |
Response:
| Field | Type | Description |
|---|---|---|
domain | string | Domain consolidated |
atoms_before | int | Atom count before |
atoms_after | int | Atom count after |
prototypes_created | int | New prototype atoms |
singletons_protected | int | Von Restorff protected atoms |
compression_ratio | float | atoms_before / atoms_after |
duration_ms | float | Duration in milliseconds |
GET /memory/stats
Get memory statistics for your tenant.
Response:
| Field | Type | Description |
|---|---|---|
total_atoms | int | Total atoms stored |
episodes | int | Episode-type atoms |
prototypes | int | Prototype atoms (from consolidation) |
singletons | int | Protected singleton atoms |
hebbian_edges | int | Concept-concept association edges |
episodic_fingerprints | int | Number of distinct episodic-content fingerprints across stored atoms |
domains | string[] | Active domain names |
avg_valence | float | Average outcome valence |
avg_importance | float | Average importance score |
GET /memory/atoms/
Fetch a single atom by ID. Returns the full atom envelope (content, concepts, importance, valence, domain, created_at, etc.). Returns 404 NOT_FOUND if the atom does not exist in your tenant.
DELETE /memory/atoms/
Permanently delete one atom. Idempotent: deleting an already-gone atom returns 200 with {"deleted": 0, "atom_id": "..."} rather than 404 — so retry loops are safe.
DELETE /memory/domain/
Wipe every atom in a domain (e.g. project:old-client). Returns the count of deleted atoms in {"deleted": <int>, "domain": "..."}. Note: the REST route itself currently has no confirm-style interlock — the safety gate (confirm: true) lives in the MCP memory_delete_domain tool schema, not the HTTP layer. Treat this endpoint as destructive on the client side.
DELETE /memory/reset
⚠️ Destructive — wipes ALL atoms in your tenant across every domain. Disabled by default: gated server-side and returns 403 FORBIDDEN unless explicitly enabled on the deployment. Intended for benchmark / staging tenants only; production deployments keep it disabled. Returns total deleted count.
GET /memory/domains
List active domain names in your tenant. Same data as the domains field of /memory/stats, but cheaper if you only need the list.
POST /memory/atoms/by-external-ref
Look up a single atom by its external_ref (the optional client-supplied identifier set during write). Useful when the client wants to recover an atom ID after restart. Request body: { "external_ref": "<string, 1-255 chars>" }. Response: a single atom envelope (same shape as GET /memory/atoms/{atom_id}). Returns 404 NOT_FOUND if no atom matches.
GET /memory/export
Stream every atom in the calling tenant as JSONL (one atom JSON per line). Always tenant-scoped — there is no cross-tenant export and no domain filter; if you need a specific domain, post-filter the stream client-side. For backup / migration.
Health Endpoints
Two monitoring probes for load balancers, uptime services, and post-deploy smokes. Both are public — no authentication required — and are the only non-authed routes under /api/v1/. Hit them as often as your monitoring allows; cost is a single DB SELECT 1 on /health/ready, nothing on /health.
GET /health
Liveness probe. Returns 200 as long as the process is serving. Does NOT check the database, embedding model, or any downstream — a 200 here only means "uvicorn is accepting connections".
Response:
| Field | Type | Description |
|---|---|---|
status | string | Always "ok" when the process is up. |
database | boolean | Always false — this endpoint intentionally skips the DB round-trip. Use /health/ready for DB status. |
version | string | Service version (matches the deployed container). |
Example:
curl https://core.mnemoverse.com/api/v1/health
# {"status":"ok","database":false,"version":"1.0.0"}When to use: load-balancer liveness probes, uptime monitors that only care "is the process alive?". Safe at high frequency.
GET /health/ready
Readiness probe. Verifies the service can actually handle traffic — database reachable, memory engine initialised, embedding backend ready. This is what the post-deploy smoke hits to confirm a Railway deploy rolled out successfully.
Response:
| Field | Type | Description |
|---|---|---|
ready | boolean | true only when every sub-check passes. |
checks.database | boolean | DB pool is connected and SELECT 1 succeeds. |
checks.engine | boolean | MemoryEngine initialised and wired to storage. |
checks.embedding | boolean | Local model loaded OR API backend responded to warm-up. |
version | string | Service version. |
Example:
curl https://core.mnemoverse.com/api/v1/health/ready
# {"ready":true,"checks":{"database":true,"engine":true,"embedding":true},"version":"1.0.0"}When to use:
- Kubernetes / Railway readiness probe — route traffic only after this returns 200 with every sub-check
true. - Post-deploy smoke tests. Reference retry shape: poll up to 8 × 15 s (2 min cap, enough for a typical Railway rollout), assert HTTP 200 AND
ready === trueAND everychecks.*field istrue. Anyfalse= deploy regression; fail the smoke. - Your own monitoring: alert when
ready=falsepersists for longer than a deploy window (~2 min).
Status code: always 200 while the process is up. Sub-check values live in the body — ready=false is still a 200 response. This keeps the health surface distinct from errors the retry-wrapping proxies would otherwise treat as transient.
Error Responses
All errors follow a consistent format:
{
"code": "UNAUTHORIZED",
"message": "Invalid or missing API key",
"requestId": "req_abc123",
"retryable": false,
"details": null
}| HTTP Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid request body / schema constraints violated (returned by core's MnemoError(VALIDATION_ERROR, ..., 400) path) |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 403 | FORBIDDEN | API key lacks permission for the requested action (NOT raised on cross-tenant reads — those return an empty result set) |
| 404 | NOT_FOUND | Referenced atom / domain does not exist |
| 422 | VALIDATION_ERROR | FastAPI/Pydantic-level validation failure (raised before reaching the route handler) |
| 429 | RATE_LIMITED | Too many requests. Check Retry-After and X-RateLimit-Reset headers |
| 500 | INTERNAL | Server-side error (may be retryable) |
The codes above are the exact strings the API returns in the code field of the error body — match them with === / == in agent code, not by substring. There is no AUTH_INVALID or INTERNAL_ERROR code; older docs that mentioned them are wrong.
Rate Limits
Rate limits depend on your plan:
| Plan | Requests/min | Queries/day | Atoms |
|---|---|---|---|
| Free | 60 | 1,000 | 10,000 |
| Pro | 600 | 50,000 | 500,000 |
| Team | 3,000 | 500,000 | 5,000,000 |
Rate limit headers are included in every response:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1712345678