MemoryHQ: memory for your team, its agents and the products you build. Try it free

Docs

Memory API

Give your assistant a memory that stays current. Call it over REST or connect any MCP client.

Build your own assistant on top of MemoryHQ's memory: it remembers what users tell it, keeps facts current when they change, and hands you a ready-made context block for every turn.

Base URL: https://memoryhq.keronlabs.com/api/<workspaceId> Auth: x-api-key: lore_… or Authorization: Bearer lore_… (create keys in Settings → API Keys). A key acts as the member who created it: user scope means that member's private memories, and workspace scope is shared with the team. read scope is enough for search/context; write is needed to add, correct or forget.

End users

If you're building a product with its own users, use one workspace and one key, and send x-end-user-id: <your user's id> on every call. user scope then means that end user: what you add is private to them, and search, /context, /profile and the memory list only return their memories plus anything you stored with scope: "workspace" (shared knowledge, visible to all of them). One end user can't read, change or delete another's memories; those requests return 404. Shared knowledge is written without the header: a request that carries x-end-user-id can't add, correct or forget workspace memories (403), so one end user's session can't change what all of them see.

  • The id is whatever your system uses (user id, UUID, email): 1–128 characters of letters, digits and . _ : @ | + -. End users don't need a MemoryHQ account, and there's nothing to create first.
  • Rate limits apply per end user: 120 requests per minute for each key and end user pair.
  • DELETE /end-users/:id erases everything stored for that end user (memories, history and profile). Call it when your user deletes their account.
  • Documents and collections stay workspace-wide: every end user's /context and /search draw on the same documents.
  • Without the header, a key behaves as before and acts as the member who created it.

Model

  • Memory: one atomic fact ("User prefers aisle seats") with a type (fact, preference, person, org, decision, event, task), aliases for keyword recall, and optional validAt / expiresAt.
  • Never overwritten: when a new fact contradicts an old one, the old one gets invalidAt + supersededBy. Search returns current facts by default; pass includeHistory to see what used to be true.
  • Forgetting: expiresAt facts are soft-forgotten automatically, and you can forget manually. Forgotten memories are hidden from search but kept unless hard-deleted.
  • Profiles: a per-member and a per-workspace one-pager (staticFacts, dynamicFacts, summary), rebuilt in the background shortly after memories change.
  • Sources: everything you POST, chat statements in the MemoryHQ app, and every indexed document: uploads (PDF, DOCX, text, transcripts), connector pages, and the backlog from before memory existed. Document memories are workspace-scoped, inherit the collection's access rules, link back via sourceDocId, and are deleted with the document. Sensitive documents are excluded. New documents are mined about 10 minutes after upload.

Endpoints

MethodPathBody / queryNotes
POST/memories{ content? , messages?: [{role, content}], scope?: "user"|"workspace", infer?: true, expiresAt?: ISO }Extracts facts, dedupes, supersedes contradictions. Returns { added, updated, skipped: [{content, duplicateOf}] }. infer:false stores content verbatim. Over 12k chars → 202 {queued:true}, processed in background.
GET/memories?scope=user|workspace|all&include_history=true&include_forgotten=true&limit=50&offset=0Newest first.
POST/memories/search{ query, scope?: "all", limit?: 10, includeHistory?, includeForgotten? }Hybrid semantic + keyword/alias, ranked by relevance × recency × usage.
GET/memories/:id
PATCH/memories/:id{ content } or { forgotten: true|false }content = explicit correction (new memory supersedes this one).
DELETE/memories/:idHard delete.
GET/profile?refresh=true{ user, workspace } profiles; refresh rebuilds now.
POST/context{ query?, limit?: 10, documents?: 0 }{ profiles, memories, documents, prompt }. documents: n also returns the top n permission-filtered document passages (retrieval only, no LLM). prompt is a markdown block to put in your system prompt.
POST/search{ query, history?, collectionIds?, connectorIds? }Cited answer ([n] = n-th document in sources) over documents, personalised with memory.
DELETE/end-users/:idErases one end user's memories and profile. Returns { deletedMemories }.

Model-backed calls (add, profile refresh, document search) draw from the workspace's AI credits.

MCP

POST /mcp is an MCP server (Streamable HTTP, stateless JSON). Tools: get_profile, search_memories, add_memory, forget_memory, search_documents (passages only, no AI credits) and search_knowledge (written answer with sources, uses AI credits). Point Claude Desktop, Cursor and similar clients at it with your key in the x-api-key header.

A personal assistant in ~40 lines

import Anthropic from '@anthropic-ai/sdk'

const BASE = `https://memoryhq.keronlabs.com/api/${process.env.MEMORYHQ_WORKSPACE}`
const headers = { 'x-api-key': process.env.MEMORYHQ_KEY!, 'Content-Type': 'application/json' }
const claude = new Anthropic()

// userId: your own user's id. Each one gets private memory inside the same workspace.
async function mhq<T>(userId: string, path: string, body?: unknown): Promise<T> {
  const res = await fetch(BASE + path, {
    method: body ? 'POST' : 'GET',
    headers: { ...headers, 'x-end-user-id': userId },
    body: body && JSON.stringify(body),
  })
  if (!res.ok) throw new Error(`${path}: ${res.status} ${await res.text()}`)
  return res.json() as Promise<T>
}

export async function chat(userId: string, history: Anthropic.MessageParam[], userMessage: string) {
  // 1. Pull profile, relevant memories and document passages for this turn.
  const { prompt } = await mhq<{ prompt: string }>(userId, '/context', { query: userMessage, documents: 5 })

  // 2. Answer with memory in the system prompt.
  const reply = await claude.messages.create({
    model: 'claude-sonnet-5-5',
    max_tokens: 1024,
    system: `You are my personal assistant. Today is ${new Date().toDateString()}.\n\n${prompt}`,
    messages: [...history, { role: 'user', content: userMessage }],
  })
  const text = reply.content.flatMap((b) => (b.type === 'text' ? [b.text] : [])).join('')

  // 3. Remember the exchange (fire-and-forget). Extraction keeps only durable facts,
  //    dedupes against what's known, and supersedes anything that changed.
  void mhq(userId, '/memories', {
    scope: 'user',
    messages: [{ role: 'user', content: userMessage }, { role: 'assistant', content: text }],
  }).catch(console.error)

  return text
}

For agentic assistants, expose /memories/search, /memories (POST) and PATCH /memories/:id as tools so the model can look things up mid-task and correct or forget facts when the user asks.