Open app

API reference.

Every endpoint, request field, and error code. Plain HTTPS against api.leapmemory.com.

Authentication
One header on every request.
HTTP
Authorization: Bearer lm_sk_your_key_here

Keys are created in the dashboard under API keys. A missing, malformed, or revoked key returns 401 auth_required. Keep keys server-side; never ship one in a browser or mobile app.


Response envelope
Every response, success or error, uses one shape.
JSON
# success
{ "success": true,  "http_status": 200, "code": "ok",        "data": { ... } }

# error
{ "success": false, "http_status": 404, "code": "not_found", "message": "tenant not found" }

Branch on success, then read data or message. The code string is stable and safe to match on; message is human-readable and may change.


Key roles & scopes
What a key is allowed to do, and which tenants it can reach.
RoleCan call
adminEverything: tenant management and all memory operations.
ingestSave and delete turns (/turns, /turns/batch, DELETE /turns/{turn_id}).
recallRead only (/recall, /briefing, /turns/status).

Keys are also scoped: a project-scoped key reaches every tenant in the project; a tenant-scoped key reaches only the tenants it lists. Calling outside a key's scope returns 403 scope_denied. A common production setup is one project-scoped admin key on your server and narrow ingest and recall keys per service.


Error codes
Stable codes for every failure mode.
HTTPCodeMeaning
401auth_requiredMissing, malformed, or revoked key.
402payment_requiredDeveloper credit balance is empty. Ingest pauses until credits are added; stored memories and recall are unaffected.
403role_requiredKey's role does not permit this endpoint.
403scope_deniedKey is not scoped to this tenant.
404not_foundTenant or resource does not exist or is deleted.
409conflictDuplicate tenant_id, tenant not in a usable state, or a turn still being processed.
422validation_failedRequest body failed validation; the message names the field.
429rate_limitedPlan cap reached. Stored memories and recall are unaffected.
500internal_errorSomething failed on our side.

GET /v1/health

No authentication. Returns 200 when the API is up. Point your uptime checks here.


Tenants
A tenant is one isolated memory store, typically one per end user. Physically separate databases per tenant: no shared tables, no shared credentials, no row filters.
POST /v1/tenants

Provision a tenant. Requires admin.

FieldTypeNotes
tenant_idRequired stringYour identifier for this user. Unique within the project.
Shell
curl -X POST https://api.leapmemory.com/v1/tenants \
  -H "Authorization: Bearer $LM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tenant_id": "user_482"}'
Response · 201
{
  "success": true, "http_status": 201, "code": "created",
  "data": {
    "tenant_id": "user_482",
    "status": "ready",
    "created_at": "2026-07-10T09:39:11.548594+00:00"
  }
}

Provisioning is synchronous: when the call returns, the tenant is ready. A duplicate tenant_id returns 409 conflict.

If that tenant_id was soft-deleted, the 409 says so, and its data is still intact. Restore it instead of creating it again, or remove it permanently with ?hard=true before reusing the name.
GET /v1/tenants

Any role. Returns the tenants visible to the key's scope. Soft-deleted tenants are excluded by default.

QueryTypeNotes
include_deletedbooleanDefault false. Set to true to list soft-deleted tenants too. They come back with status: "deleted" and can be restored.
Response · 200
{
  "success": true, "http_status": 200, "code": "ok",
  "data": { "tenants": [
    { "tenant_id": "user_482", "status": "ready", "created_at": "..." }
  ] }
}
GET /v1/tenants/{tenant_id}

Any role within scope. Returns the same tenant_id / status / created_at object plus the two name lists below, or 404.

PATCH /v1/tenants/{tenant_id}

Requires admin. Sets the tenant's label and, optionally, who the two speakers are.

FieldTypeNotes
labelstringWritten on every call. Sending an empty string clears it.
owner_namesstring[]The names that mean the end user in this tenant: what they are called, a username, an address. Up to 10. Omit the field to leave the current list alone; send [] to clear it.
companion_namesstring[]The names that mean your assistant, if it has one it is spoken to by. Same rules.
Shell
curl -X PATCH https://api.leapmemory.com/v1/tenants/user_482 \
  -H "Authorization: Bearer $LM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "", "owner_names": ["john"], "companion_names": ["wren"]}'

Names are normalized on write: trimmed, lowercased, deduplicated. Non-ASCII letters are preserved, so nuhoglu and nuhoğlu are two entries and both can be registered.

If you ingest assistant turns, set these. When your application writes both sides of a conversation with role, LeapMemory needs to know which strings mean the user. Without them it still refuses to record facts about the assistant, but it also cannot attribute to the user what the assistant said about them: "your leg still hurts?" becomes a fact about nobody. You do not need the names at tenant creation. Send this call the moment you learn them, once.
DELETE /v1/tenants/{tenant_id}

Requires admin. Soft by default: the tenant stops serving but its storage is kept and it can be restored. Add ?hard=true to tear down every database permanently.

Shell
curl -X DELETE "https://api.leapmemory.com/v1/tenants/user_482?hard=true" \
  -H "Authorization: Bearer $LM_KEY"
Response · 200
{
  "success": true, "http_status": 200, "code": "ok",
  "data": {
    "deleted": true, "tenant_id": "user_482",
    "mode": "hard", "restorable": false
  }
}

mode reports which delete actually ran, "soft" or "hard", and restorable is true only after a soft one. Read them rather than assuming: a call without ?hard=true keeps every byte of the tenant's data.

Hard deletion is structural: the tenant's databases are dropped entirely, not scanned for rows. Nothing is recoverable afterwards. This is the compliance answer for "erase this user."
POST /v1/tenants/{tenant_id}/restore

Requires admin. Brings a soft-deleted tenant back; storage was kept alive, so restore is instant. Restoring a tenant that is not deleted returns 409.

Response · 200
{
  "success": true, "http_status": 200, "code": "ok",
  "data": { "restored": true, "tenant_id": "user_482" }
}

Memory
Save what was said, recall it whenever it matters.
POST /v1/tenants/{tenant_id}/turns

Save a turn. Requires ingest.

FieldTypeNotes
roleRequired stringWho spoke, e.g. user or assistant. If you send assistant turns, register the speakers first with PATCH /v1/tenants/{id}; LeapMemory never records a fact about the assistant, and the names are what let it attribute to the user what the assistant said about them.
contentRequired stringThe words, verbatim. Up to 100,000 characters.
speaker_idOptional stringDistinguish speakers in multi-party conversations. Defaults from role.
tsOptional datetimeWhen it was said. Defaults to now. Set it when importing history.
Response · 201
{
  "success": true, "http_status": 201, "code": "accepted",
  "data": {
    "turn_id": "6a7892b0-46e9-454f-a225-fe321d006fb5",
    "role": "user",
    "speaker_id": "user",
    "status": "pending",
    "ts": "2026-07-10T09:39:14.949785Z"
  }
}
Ingest is asynchronous. accepted means the verbatim words are stored; extraction runs in the background and typically completes within seconds. One saved memory is billed per turn. See Billing.
POST /v1/tenants/{tenant_id}/turns/batch

Save 1–100 turns in parallel. Requires ingest. Body is {"turns": [ ... ]} with the same fields as a single turn. Use it for importing conversation history, with ts set on each turn.

Response · 201
{
  "success": true, "http_status": 201, "code": "accepted",
  "data": {
    "turns": [ { "turn_id": "...", "status": "pending", ... } ],
    "count": 3
  }
}
POST /v1/tenants/{tenant_id}/recall

Recall memories for a query. Requires recall. Free: recall never consumes credits or counts against a plan.

FieldTypeNotes
queryRequired stringNatural language, up to 10,000 characters. Any language.
speaker_idOptional stringOnly memories from this speaker.
anchor_entityOptional stringPin retrieval to one entity by name.
start, endOptional datetimeLimit to a time window.
Shell
curl -X POST https://api.leapmemory.com/v1/tenants/user_482/recall \
  -H "Authorization: Bearer $LM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "where does Sam work?"}'
Response · 200
{
  "success": true, "http_status": 200, "code": "ok",
  "data": {
    "facts": [
      {
        "subject": "sam",
        "verb": "works-at",
        "object": "beacon",
        "type_name": "works-at",
        "confidence": 0.99,
        "mention_count": 1,
        "last_mentioned": "2026-07-10T09:39:23.295529+00:00",
        "sentence": "Sam works at Beacon."
      }
    ],
    "chunks": [
      {
        "chunk_id": "6a7892b0-46e9-454f-a225-fe321d006fb5:0",
        "turn_id": "6a7892b0-46e9-454f-a225-fe321d006fb5",
        "content": "Sam works at Beacon. He owns the auth service and pairs with Aisha.",
        "role": "user",
        "speaker_id": "user",
        "score": 0.9945,
        "importance": 0.5,
        "created_at": "2026-07-10T09:39:23.319353Z"
      }
    ],
    "anchors_found": [
      { "name": "sam", "type_name": "person", "count": 1, "span_start": 11, "span_end": 14 }
    ]
  }
}
SectionWhat it is
factsDistilled subject-verb-object statements with confidence, mention count, and the exact sentence each came from.
chunksThe verbatim words as they were said, ranked by relevance score.
anchors_foundEntities detected in your query, with type and character span, that steered retrieval.

Feed facts and top chunks into your model's context, and cite sentence when your product needs to show where a memory came from.

GET /v1/tenants/{tenant_id}/briefing

The tenant's ambient picture: who this person is, as one ready-to-inject text block. Requires recall. Free, like recall.

Recall answers questions; the briefing needs none. It is built for the moment before a conversation starts: load it once when a session opens, place it in your model's system context as background knowledge, and let recall handle everything specific from there. The user's own facts come first, their most established relationships and preferences ahead of everything, followed by the freshest recent context. It is computed live from the memory on every call, so it can never go stale.

Shell
curl https://api.leapmemory.com/v1/tenants/user_482/briefing \
  -H "Authorization: Bearer $LM_KEY"
Response · 200
{
  "success": true, "http_status": 200, "code": "ok",
  "data": {
    "text": "- Sam works at Beacon.\n- Sam owns the auth service.\n- Sam pairs with Aisha.",
    "facts_included": 3
  }
}
FieldWhat it is
textThe briefing, one fact per line, capped to stay small enough to sit in a system prompt without crowding the conversation.
facts_includedHow many facts made the cut.

A brand-new tenant returns an empty text with facts_included: 0: inject nothing and the conversation starts like meeting someone new. When you place the briefing in context, frame it as quiet background the model uses only when relevant, so it informs answers without being recited.

Pull the briefing once per session, not per message. Per-message context belongs to recall.
GET /v1/tenants/{tenant_id}/turns/status

Ingest progress for a tenant: how many turns are stored, how many are fully digested, and whether any failed. Requires recall. Free, like recall.

Ingest is asynchronous: accepted means the words are stored, and extraction finishes in the background. This endpoint tells you when that work is done. After importing history, poll it and flip your product to ready when indexed equals total. It also surfaces the ids of any turns that could not be processed, so a failure is never silent.

Shell
curl https://api.leapmemory.com/v1/tenants/user_482/turns/status \
  -H "Authorization: Bearer $LM_KEY"
Response · 200
{
  "success": true, "http_status": 200, "code": "ok",
  "data": {
    "total": 500,
    "pending": 12,
    "indexed": 487,
    "failed": 1,
    "failed_ids": ["6a7892b0-46e9-454f-a225-fe321d006fb5"]
  }
}
FieldWhat it is
totalTurns stored for this tenant.
pendingStored but still being digested. Not yet recallable.
indexedFully digested and recallable.
failedTurns that could not be processed after retries. The verbatim words are still stored safely.
failed_idsThe turn_id of each failed turn.

A failed count is often temporary: turns are retried automatically in the background, so a turn can move from failed back to indexed on its own. Treat a failure as final only when it persists.

Poll every few seconds during an import, not per message. In normal conversation a single turn is digested within seconds and polling adds nothing.
DELETE /v1/tenants/{tenant_id}/turns/{turn_id}

Delete one turn and everything derived from it. Requires ingest. Removes the verbatim turn, its recallable chunks, and any facts in the memory graph that came only from this turn. A fact also supported by other turns survives, with this turn's contribution removed. Free: deletion never consumes credits.

Shell
curl -X DELETE https://api.leapmemory.com/v1/tenants/user_482/turns/6a7892b0-46e9-454f-a225-fe321d006fb5 \
  -H "Authorization: Bearer $LM_KEY"
Response · 200
{
  "success": true, "http_status": 200, "code": "ok",
  "data": { "deleted": true, "turn_id": "6a7892b0-46e9-454f-a225-fe321d006fb5" }
}

A turn that is still pending returns 409 conflict; retry once extraction settles. An unknown or already-deleted turn_id returns 404. Deleting the same turn twice is safe: the second call is the 404.

Deletion is structural and immediate: the turn's words, chunks, and solely-derived facts are removed from the primary stores and search indexes in the same request.
GET /v1/usage

What this key's project has consumed, so you can put it in your own cost. Scoped to the key's project and nothing else. Free, and never billed.

Every call LeapMemory serves is metered. This is that meter, read back: writes, recalls, and briefings, over a window you choose. If you resell memory inside your own product, this is the number you bill against. A key scoped to specific tenants sees only those tenants, and its totals are summed from them alone.

Shell
curl "https://api.leapmemory.com/v1/usage?from=2026-09-01&to=2026-09-30&by_tenant=true" \
  -H "Authorization: Bearer $LM_KEY"
QueryWhat it is
fromYYYY-MM-DD. Defaults to the first of the current month.
toYYYY-MM-DD, inclusive of the day named. Defaults to today.
by_tenanttrue adds a per-tenant breakdown. Off by default.
Response · 200
{
  "success": true, "http_status": 200, "code": "ok",
  "data": {
    "period": { "from": "2026-09-01", "to": "2026-09-30" },
    "totals": { "ingest": 1420, "recall": 903, "briefing": 210 },
    "by_endpoint": {
      "turns.ingest": 1180,
      "turns.ingest_batch": 240,
      "memories.recall": 903,
      "memories.briefing": 188,
      "memories.briefing_init": 22
    },
    "tenants": [
      { "tenant_id": "user_482", "ingest": 96, "recall": 74, "briefing": 12,
        "by_endpoint": { "turns.ingest": 96, "memories.recall": 74 } }
    ]
  }
}
FieldWhat it is
totals.ingestTurns written, single and batch together. This is the metered unit.
totals.recallRecall calls. Free, counted so you can see the read load.
totals.briefingBriefings served, including the one a connector pulls when a session opens.
by_endpointThe raw counters behind the totals, so you can reconcile a number yourself instead of trusting the fold.
tenantsPresent only with by_tenant=true. Same shape, one entry per tenant that had activity.
Counters only. This endpoint says nothing about invoices, credit balance, or what you owe: it is the meter, not the bill.