BackAPI Reference

API Reference

LongMemory is a memory layer for AI. Your app saves what its users say, and before the AI answers, one search returns everything relevant: memories, preferences, past events and document passages, as one block of text ready for the prompt.

You run it yourself: one program, with its storage built in, on your own machine or server. It makes no AI calls and needs no internet connection. Every example below assumes it is listening on localhost:8080.

Most apps need only three calls: save, search and upload a document. To capture memories from a chat, add the piggyback kit to the AI call your app already makes. All bodies are JSON; unknown fields are rejected with a 400; max body size is 1 MB (25 MB for document uploads).

Quickstart

Start LongMemory from its folder:

bash
./longmemory               # dashboard + API on http://127.0.0.1:8080
./longmemory --no-browser  # API only

1. Save what you learn about a user:

bash
curl -s localhost:8080/v1/memories -d '{
  "user_id": "u1",
  "memories": ["Allergic to shellfish, carries an EpiPen."],
  "preferences": [{"who": "wife", "topic": "diet", "value": "vegetarian", "type": "must", "until": ""}]
}'

2. Upload a document (optional):

bash
curl -s localhost:8080/v1/documents -F knowledge_base="Menus" -F file=@menu.pdf

3. Search before your AI answers:

bash
curl -s localhost:8080/v1/search -d '{"user_id": "u1", "query": "order dinner for me and my wife"}'

Put the contextfield of the response into your AI model's system prompt. That is the whole integration.

Authentication

Two modes, chosen by whether LM_ADMIN_KEY is set.

ModeWhenYour app calls withAdmin routes
Open single-tenantLM_ADMIN_KEY not setNo auth; one implicit tenantNot available (501)
Multi-tenantLM_ADMIN_KEY setAuthorization: Bearer <key>X-Admin-Key
Open mode serves every route with no authentication. The program refuses to start in open mode on a public address, and narrows a wildcard address to this computer only. Set LM_ADMIN_KEY for anything reachable from the internet, behind TLS.

Errors are {"error":{"code":"...","message":"..."}} with status 400/401/404/429/500/501. Rate limiting is per tenant (rate_rps, burst 2×) and returns 429 with Retry-After.

Core concepts

UserOne user_id per end user of your app (a customer or employee ID). Everything about a user is kept apart from every other user. The earlier name owner_id is still accepted.
MemoryA short note in plain language, such as what the user said. Found by keywords and by meaning. Saving the same text again updates it instead of creating a duplicate.
PreferenceA like, dislike, rule or fact about the user or someone in their life (who is a name, a relation such as wife, or self). A newer statement replaces an older one, temporary ones (until a date) expire on their own, and rules always come first.
EventSomething the user did: an order, a ride, a booking. When a pattern repeats, LongMemory turns it into a preference on its own, with no AI involved.
DocumentManuals, policies, guides. Shared documents belong to a knowledge base that every user's search covers; a document uploaded with a user_id is searched for that user only.
ContextThe block of text POST /v1/search returns: preferences in force now, the relevant memories, events and document passages, ready for your AI model's prompt.

Save

POST/v1/memories

One call takes any combination of memories, preferences and events, up to 100 of each. No AI is needed to save.

json
{
  "user_id": "u_42",
  "memories": ["Allergic to shellfish; carries an EpiPen.",
               {"text": "Works at Google as a software engineer.", "topic": "work"}],
  "preferences": [
    {"who": "self", "topic": "cuisine", "value": "biryani", "type": "like", "until": ""},
    {"who": "self", "topic": "sugar", "value": "none", "type": "must", "until": "2026-12-31"}
  ],
  "events": [{"kind": "order", "text": "Ordered a vegetable biryani", "payload": {"item": "veg biryani"}}]
}
PartFormatNotes
memoriesA string, or {"text", "topic"}Filed under the topic (default notes); same text, same memory
preferences{"who", "topic", "value", "type", "until"}type: like | dislike | must (a rule) | fact | forget (removes that topic for that person); until: a date for a temporary one, else ""
events{"kind", "text", "payload", "occurred_at"}Repeated patterns become inferred preferences automatically

Response

json
{"stored":[{"path":"/memories/notes/9f2a41c83d10.md"}, …], "preferences":[…], "events":[…]}

A preference that cannot be applied is reported as skipped with the reason; the rest of the save still goes through. Full preference operations (see Advanced) are accepted here too.

Documents

POST/v1/documents

Upload PDF, Word (.docx), text, Markdown, HTML, CSV or JSON files, up to 25 MB each, as multipart (file, repeatable) or JSON ({"name": "...", "text": "..."}). Searchable immediately; search returns the matching passages with the document name and page.

bash
# shared with every user, in a knowledge base ("General" if omitted)
curl -s localhost:8080/v1/documents -F knowledge_base="HR policies" -F file=@manual.pdf

# one user's own document: searched only for them, deleted with them
curl -s localhost:8080/v1/documents -F user_id=u_42 -F file=@contract.pdf

Uploading a file with the same name to the same place replaces it. Scanned PDFs contain only images of text and cannot be read without OCR.

Capture from chat: the piggyback kit

GET/v1/piggyback

LongMemory never calls an AI model. Your app already calls one to answer the user; the piggyback kit makes that same call also return what is worth remembering. No extra AI call, no extra bill.

The kit hasWhat it is for
instructionsText to add to your system prompt (about 120 tokens)
reply_schemaA reply format for structured output: {"reply", "memories", "preferences"} in one response
toolThe same lists as a save_to_memory tool, for apps that already use tools

How it works

  1. Fetch the kit once: GET /v1/piggyback.
  2. Add instructions to your system prompt, with today's date, and ask for reply_schema (or register tool).
  3. Show the user reply.
  4. Send memories and preferences from the model's output, as they are, to POST /v1/memories with the user_id.
json
// the model's reply, structured by reply_schema
{"reply": "Got it, I'll keep the biryani vegetarian for Priya.",
 "memories": ["Priya is the user's wife."],
 "preferences": [{"who": "wife", "topic": "diet", "value": "vegetarian", "type": "must", "until": ""}]}

Works with any model that supports structured output or tools, including models on your own servers. Check the results with your model before going live: smaller models may miss or mislabel preferences.

User data & GDPR

CallWhat it does
GET /v1/owners/{user_id}/memoryList a user’s memories
GET /v1/owners/{user_id}/exportExport everything about a user
DELETE /v1/owners/{user_id}Delete a user and everything about them, including their documents (right to erasure)
GET /v1/owners/{user_id}/personsThe people a user’s preferences are about
GET /v1/owners/{user_id}/profile?person=wife&history=1One person’s preferences; history adds replaced and expired ones
DELETE /v1/owners/{user_id}/persons/{id}Delete one person and everything about them
GET /v1/documents?knowledge_base=… or ?user_id=…List documents
DELETE /v1/documents/{id}Delete a document
GET /v1/knowledge-basesList shared knowledge bases (DELETE /v1/knowledge-bases/{name} deletes one)

Multi-tenant admin

Available when LM_ADMIN_KEY is set. A tenant is the trust boundary — give each customer their own tenant; never share one key across mutually untrusted users.

EndpointDescription
POST /v1/admin/tenantsCreate a tenant → returns api_key once (only its SHA-256 is stored)
GET /v1/admin/tenantsList tenants
DELETE /v1/admin/tenants/{id}Disable — revokes access, keeps data
Rate limits are per tenant, not per end user, and default to off. Set LM_RATE_RPS as instance self-protection; keep per-user quotas in your own gateway.

Configuration

Env / flagDefaultMeaning
LM_ADDR:8080Listen address (also --addr)
LM_DB./longmemory.dbData file (also --db). Back up by copying it
LM_ACCURACYstandardDefault for POST /v1/search: standard or high (also --accuracy)
LM_ADMIN_KEYunsetSet: multi-tenant mode with API keys
LM_RATE_RPS0 (off)Per-tenant rate limit, burst 2×
LM_EMBED_MODELthe model in the releaseLocal meaning-search model file; without one, search uses keywords only
LM_NO_BROWSERunset1: do not open the dashboard (also --no-browser)
LM_ALLOW_PUBLIC_OPENunsetAllow open mode on a public address (not recommended)
LM_LOGinfodebug logs every request with its time
LM_ANTHROPIC_KEYunsetAdvanced: enables POST /v1/extract

Also: GET /healthz (no auth) and GET /v1/stats for counts.

Several instances: failover and scaling

Instances do not sync writes with each other. Each LongMemory instance is one process and one SQLite data file, and it is the only writer of that file. There is no built-in replication, no consensus protocol (no Raft) and no shared state between instances. Durability across zones comes from streaming that file's changes to object storage; scale comes from giving each instance its own share of tenants or users.

Never point two instances at the same data file, and do not keep it on a network file system shared between machines (EFS, NFS, SMB): SQLite's WAL mode relies on shared memory on one machine, so locks are not reliable across hosts and the file can be corrupted. Use a local or block volume (EBS, a persistent disk, a local SSD).

GoalHowWhat to know
Failover across Availability ZonesOne active instance. Litestream streams every committed change of its data file to S3. On failure, a standby in another zone runs litestream restore, then starts LongMemory.Changes after Litestream’s last sync (about a second by default) can be lost. LongMemory starts in ~0.1 s on an existing file; restore time grows with file size. Your orchestration decides the failover and must stop the old writer first.
More capacityShard by tenant or by user: N instances, each the only owner of its share; your gateway routes every request for a tenant or user to its instance (e.g. a consistent hash of the ID). Each shard gets its own Litestream copy.Every request is for one tenant and one user, so no request needs two instances. Shared knowledge-base documents belong to a tenant: when sharding by user, upload them to every instance holding that tenant’s users.
Read replicasNot supported today.Live replicas (such as LiteFS) are untested, and LongMemory writes its own activity record to the data file even when it only searches, so it cannot run on a read-only copy.

Measured: 1 to 96 share-nothing instances on one AWS c5.24xlarge (96 vCPUs), each sent 1,000 searches at the same instant: throughput rose from 249 to 5,248 searches per second, with all 96,000 answered and 0 errors at 96 instances. Run log

Performance

WhatMeasuredWhere
Save one note0.51 ms typical, about 1,100 notes per secondApple M2 laptop
Search, standard (20 results)5.2 ms typical, 8.9 ms for the slowest 1 in 100Apple M2, 1,536 LoCoMo questions
Search, high accuracy (up to 50)about 8 ms typicalApple M2, 1,536 LoCoMo questions
1M tokens of history, one usersaved in 1.9 s; search 12.2 ms typical, 18.9 ms for the slowest 1 in 100Apple M2 laptop
Burst96,000 searches at the same instant, 0 errors, 5,248 per second96 instances on one AWS c5.24xlarge
Restartabout 0.1 s, every memory keptApple M2 laptop

Search cost follows the size of one user's data, not the whole database. Logs and scripts: AWS run · 1M tokens · search times

Advanced: customization

Optional. The three calls and the piggyback kit cover most apps. The calls below stay available for custom integrations: full control over structured preferences, preferences on their own, raw events, agents that manage their own memory files, and extraction with your own AI key. These calls name the user owner_id (the same value as user_id).

Preference operations

POST/v1/ops

Full control over structured preferences: a batch of operations applied atomically (all or nothing, up to 200). Use it when you need more than the short form of the save call: people with aliases, merging two people, context such as time of day or place, exact validity windows, or confidence.

json
{
  "owner_id": "u_42",
  "ops": [
    {"op":"person_upsert","name":"Priya","relation":"wife","aliases":["wife"]},
    {"op":"assert","person":"wife","domain":"food","key":"diet",
     "value":"vegetarian","polarity":"rule"},
    {"op":"assert","person":"self","domain":"food","key":"diet","value":"no sugar",
     "polarity":"rule","valid_until":"2027-01-31T23:59:59Z"},
    {"op":"retract","person":"self","domain":"food","key":"spice_tolerance"}
  ]
}

Operations

opRequiredNotes
assertperson, domain, key, valueCreates, reinforces on repeat, or supersedes conflicting facts
reinforceassertion_id or person+keyBumps observed_count; inferred confidence +0.1
retractassertion_id or person+keyMarks matching active assertions retracted (audit kept)
person_upsertnameMatches existing person by name/alias; sets relation, aliases
person_aliasperson, aliasesAdds aliases to an existing person
person_mergeinto, fromReassigns all data, tombstones the merged-from person
forget_personpersonHard-deletes a person and everything about them

assert fields

FieldTypeDescription
personstring"self", a p_… id, or a name/alias/relation. Auto-creates if unknown.
domainstringfood, dining, grocery, ride, place, schedule, general…
keystringsnake_case: diet, allergy, cuisine, budget_per_meal, home_address…
valueanyUsually a short string.
polarityenumrule | like | dislike | fact (default fact)
contextobjecttime_of_day[], days[], location, occasion — only when genuinely conditional
valid_fromRFC3339For preferences that start in the future
valid_untilRFC3339Makes it temporary: shadows, then auto-reverts
provenanceenumstated (default) | inferred | imported
confidencefloat0–1; overrides the provenance default
cardinalityenumsingle | multi — whether values replace or accumulate
relationstringSets the relation when auto-creating the person

Response

json
{"results":[
  {"index":0,"action":"person_created","person_id":"p_01hv…"},
  {"index":1,"action":"created","assertion_id":"a_01hv…","superseded":["a_01hu…"]}
]}

Actions: created, reinforced, retracted, person_created, person_updated, merged, forgotten, not_found (selector matched nothing — the batch still succeeds).

The emit_memory_ops tool

GET /v1/schema/ops returns the JSON Schema of an emit_memory_opstool that emits these full operations. Register it on your agent's AI call and send its output here. For most apps the piggyback kit is simpler: it uses the short form and also captures memories.

Recall

POST/v1/recall

Preferences only: everything known about the people named, valid now (or at at), matching the moment's context, ranked, plus a rendered context block. No AI, about 0.8 ms. POST /v1/search already includes these preferences; use recall when you want them alone, for named people or domains.

json
{
  "owner_id": "u_42",
  "persons": ["self", "wife"],        // default ["self"]; "*" = everyone
  "domains": ["food", "dining"],      // default all; "general" always included
  "at": "2026-07-08T20:00:00+05:30",  // default server now
  "location": "home",                 // optional
  "occasion": "date_night",           // optional
  "max_per_person": 12,               // default 12
  "token_budget": 800                 // context_block budget, default 800
}

Response

json
{
  "persons": [
    {"ref":"wife",
     "person":{"id":"p_…","name":"Priya","relation":"wife"},
     "assertions":[
       {"id":"a_…","key":"diet","value":"vegetarian","polarity":"rule",
        "score":1.1,"effective_confidence":1.0,"provenance":"stated"}
     ]}
  ],
  "unresolved": [
    {"ref":"rohit","candidates":[{"id":"p_…","name":"Rohit Sharma"},
                                 {"id":"p_…","name":"Rohit Verma"}]}
  ],
  "context_block": "### Memory (as of 2026-07-08 20:00, wed evening)\n- You: …",
  "at":"2026-07-08T14:30:00Z", "time_of_day":"evening", "day":"wed",
  "elapsed_ms":0.8
}
Ambiguous person references are never guessed. They come back in unresolvedwith candidates, so your agent can ask “which Rohit?” instead of remembering the wrong person's allergy.

Ranking

Rules sort first and survive token-budget trimming. Everything else ranks by provenance weight × effective confidence, plus a context-specificity bonus and a recency term. Inferred facts decay on a 180-day half-life and drop out below 0.35 confidence. Identical inputs always produce identical output.

Events

POST/v1/episodes

Log events on their own, up to 500 per batch (the save call takes the same events, up to 100). Recognized payload fields become behaviour signals; three observations of the same pattern within 90 days become an inferred preference, scoped to a time of day when at least 80% of them agree. No AI involved. Search events alone with POST /v1/events/search.

json
{
  "owner_id": "u_42",
  "episodes": [{
    "kind": "order_completed",
    "domain": "food",
    "persons": ["self"],
    "text": "Ordered Chicken Biryani from Meghana Foods",
    "payload": {"restaurant":"Meghana Foods",
                "items":[{"name":"Chicken Biryani","cuisine":"biryani"}],
                "total":540},
    "occurred_at": "2026-07-07T20:05:00+05:30"
  }]
}

Recognized payload fields

Payload fieldBecomes key
restaurant | place | venue | storefrequent_place
destinationfrequent_destination
ride_typeride_type_affinity
items[].cuisinecuisine_affinity
items[].namedish_affinity

Anything else in the payload is stored but ignored for inference. Only text is full-text indexed.

Claude memory tool

LongMemory also implements Anthropic's client-side memory_20250818 tool contract, so Claude can manage its own notes and have them be durable, multi-user and searchable. Forward each memory tool_use block's input verbatim, plus an owner_id.

bash
POST /v1/memory
{"owner_id":"u1","command":"create",
 "path":"/memories/user.md","file_text":"name: AJ\n"}

→ {"content":"File created successfully at: /memories/user.md",
   "is_error":false}

Commands: view, create, str_replace, insert, delete, rename. Paths are jailed under /memories. Command-level failures return HTTP 200 with is_error: true — relay that straight back as the tool result.

EndpointPurpose
GET /v1/memory/toolTool descriptor + wiring instructions (no auth)
POST /v1/memory/searchRank a user’s memory files by keywords and meaning
GET /v1/owners/{owner}/memoryList the whole /memories tree

Extraction (optional)

POST/v1/extract

The one call that uses an AI model, and only if you configure it: text in, preference operations out, using your own Anthropic key (LM_ANTHROPIC_KEY; model LM_ANTHROPIC_MODEL, default claude-haiku-4-5-20251001). Returns 501 when not configured — by design.

json
{"owner_id":"u_42",
 "text":"my wife Priya is vegetarian and I'm off sugar this month",
 "at":"2026-07-07","apply":true}

Response: {"ops":[...], "results":[...]} — results only with apply:true. The piggyback kit is the recommended way and needs no extra AI call.