Memories API
Create, read, update, and delete memories via the REST API.
Create a memory
POST /v1/memoriesRequest body
Send the target hub in the X-Hub-ID request header (or omit
for the caller's personal hub):
POST /v1/memories
Content-Type: application/json
Authorization: Bearer mxk_...
X-Hub-ID: hub_abc{
"content": "The auth system uses JWT with short-lived access tokens...",
"title": "Auth architecture",
"hint": "architecture documentation for the auth system",
"tags": ["auth", "jwt", "security"],
"hub_reason": "shared across the platform team",
"source_agent": "claude-code",
"project_context": { "repo": "github.com/memaxlabs/memax", "branch": "main" }
}| Field | Type | Required | Description |
|---|---|---|---|
content | string | yes | The memory content (markdown / text / code / structured). |
title | string | Auto-derived if omitted. | |
hint | string | Context hint for AI processing (improves classification). | |
kind | string | Override auto-classification: episodic, semantic, procedural, rationale. | |
stability | string | Override auto-classification: volatile, evolving, stable. | |
tags | string[] | Tags. | |
content_type | string | text, markdown, code, chat, transcript, file, structured. Auto-inferred from extension. | |
source | string | Origin: cli, import, hook, web, api, sdk, mcp, extraction. | |
source_agent | string | Agent slug (claude-code, cursor, etc.). | |
assisted_by_agent | string | Agent that helped (for human-authored pushes). | |
initiation_type | string | human_direct, human_requested_agent, agent_proactive, agent_automatic, import, unknown. | |
source_path | string | Relative path (used for dedup when re-ingesting). | |
hub_reason | string | Why this belongs in the shared hub. Accepted by the model but not enforced — the server does not reject team-hub pushes that omit it. | |
project_context | object | { repo, project, branch }. | |
file_ref | object | Attachment reference from POST /v1/uploads. |
Target hub is set via the X-Hub-ID request header, not a body
field. The PushRequest struct has no hub_id field — the server
resolves the target hub from the header (and falls back to the
caller's personal hub when absent).
There is no category field. Use kind/stability for
classification or tags for filterable grouping.
The boundary enum is defined (private | hub | team | org) but
the server always writes boundary = "private" on create — see
Access & visibility. Don't rely on
boundary for access control; it's hub-membership-based.
Response
{
"data": {
"id": "mem_a1b2c3d4",
"hub_id": "hub_abc",
"owner_id": "usr_xyz",
"title": "Auth architecture",
"content_type": "markdown",
"content_hash": "...",
"summary": "…",
"tags": ["auth", "jwt", "security"],
"kind": "semantic",
"stability": "evolving",
"boundary": "private",
"state": "active",
"source": "api",
"version": 1,
"created_at": "2026-04-21T10:30:00Z"
}
}state on the response depends on how the memory was ingested:
- Inline text / markdown (the normal
memax push/ RESTPOST /v1/memoriespath) returns withstate = "active"immediately — the row is queryable as soon as the response lands. Classification, chunking, and embedding still run asynchronously after the response in the background processor; the memory becomes fully recallable (and its AI-derived fields likesummary,kind,stabilitypopulate) within a few seconds. - File attachments (
file_ref— PDFs, images) and URL links (content_type=link) start atstate = "processing"and transition toactiveonly when the worker has finished extraction + embedding (typically under 5 seconds; can be longer for large PDFs).
List memories
GET /v1/memories?limit=20&cursor=<opaque>&sort=newestQuery parameters
| Parameter | Type | Description | Default |
|---|---|---|---|
limit | number | Page size. | 20 |
cursor | string | Pagination cursor (opaque). | — |
sort | string | newest (by created_at desc) or relevant (by access_count desc, tiebroken by created_at desc). | newest |
hub_id | string | Scope to a specific hub (ID, not slug). | all hubs |
topic_id | string | Scope to memories in a topic. | — |
There is no category, tags, or boundary query filter on this
endpoint today. Use POST /v1/recall for semantic filtering.
Response
{
"data": {
"memories": [
/* Memory[] */
],
"next_cursor": "eyJpZCI6...",
"has_more": true,
"total": 2457
}
}Get a memory
GET /v1/memories/{id}Returns a full Memory object (same shape as the Create response,
plus content and any attached attachments[]).
Related memories
GET /v1/memories/{id}/relatedReturns memories the server considers related to the one at id
(used by the web app's memory detail "Related" section).
Update a memory
PATCH /v1/memories/{id}Partial update. Include only fields you want to change.
{
"title": "Updated auth architecture",
"tags": ["auth", "jwt"]
}Delete a memory
DELETE /v1/memories/{id}Hard delete — the memory row, its chunks, and any attachments
are removed. Not recoverable. Returns the standard
{ "data": { ... } } envelope (not 204).
Soft-archive is a separate state (state = archived) used by
lifecycle / TTL flows; it is not what DELETE /v1/memories/{id}
does.
Batch delete:
POST /v1/memories/batch-deletewith { "ids": ["mem_a", "mem_b"] }.
Move memories between topics / hubs
POST /v1/memories/batch-move{
"ids": ["mem_a"],
"hub_id": "hub_target",
"topic_id": "top_xyz"
}Attachments
Use POST /v1/uploads to get a presigned upload URL, PUT the file
directly to object storage, then pass the resulting file_ref in
POST /v1/memories. Download via
GET /v1/memories/{id}/attachments/{attachmentID}/download.