Quickstart

Add your first memory and ask a question in 60 seconds. Python, TypeScript, and curl code samples.

Quickstart

You'll have memory working in under a minute. This guide adds one fact, searches it back, gets a synthesized answer — and explains exactly what's happening at each step.

Get an API key

Sign in to the MemHQ dashboard and create a project. From Settings → API Keys, generate a project key and copy it.

export MEMHQ_API_KEY="mhq_live_..."

Project keys are scoped to a single project and authorize the /v1/memhq/* endpoints. Keep them server-side — they grant write access to memory.

Install the SDK

pip install memhq
npm install @memhq/sdk
# or: pnpm add @memhq/sdk
# or: yarn add @memhq/sdk

No SDK needed — curl works against https://api.memhq.ai directly.

Add a memory

Memories belong to a user_id (per-user graph) or a group_id (shared graph). We'll use a per-user graph here.

from memhq import MemoryClient

client = MemoryClient()  # reads MEMHQ_API_KEY from the environment

result = client.add(
    user_id="user_42",
    messages=[
        {"role": "user", "content": "I prefer dark roast coffee, no sugar."},
    ],
)

print(result)

Response:

{
  "episode_id": "ep_a1b2c3d4",
  "status": "ingesting"
}
import { MemoryClient } from "@memhq/sdk";

const client = new MemoryClient(); // reads MEMHQ_API_KEY from process.env

const result = await client.add({
  userId: "user_42",
  messages: [
    { role: "user", content: "I prefer dark roast coffee, no sugar." },
  ],
});

console.log(result);

Response:

{
  "episodeId": "ep_a1b2c3d4",
  "status": "ingesting"
}
curl https://api.memhq.ai/v1/memhq/add \
  -H "Authorization: Bearer $MEMHQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "user_42",
    "messages": [
      {"role": "user", "content": "I prefer dark roast coffee, no sugar."}
    ]
  }'

Response:

{
  "episode_id": "ep_a1b2c3d4",
  "status": "ingesting"
}

What just happened?

The call returns immediately (under 100 ms) with an episode_id. In the background, MemHQ's extraction pipeline:

  1. Chunks the conversation into atomic units
  2. Extracts typed entities and facts using an LLM — in this case, {type: "preference", content: "Prefers dark roast coffee, no sugar"}
  3. Writes the fact to user_42's knowledge graph and creates a vector embedding for it

By the time the next step below runs, the fact is indexed and ready to retrieve.

Search it back

results = client.search(
    user_id="user_42",
    query="what coffee does the user like?",
    limit=5,
)

for hit in results.memories:
    print(f"{hit.score:.2f}  {hit.content}")

Response:

{
  "memories": [
    {
      "id": "m_xyz123",
      "content": "Prefers dark roast coffee, no sugar",
      "score": 0.94,
      "type": "preference",
      "valid_from": "2026-06-19T00:00:00Z",
      "valid_until": null
    }
  ],
  "total": 1
}
const results = await client.search({
  userId: "user_42",
  query: "what coffee does the user like?",
  limit: 5,
});

for (const hit of results.memories) {
  console.log(`${hit.score.toFixed(2)}  ${hit.content}`);
}

Response:

{
  "memories": [
    {
      "id": "m_xyz123",
      "content": "Prefers dark roast coffee, no sugar",
      "score": 0.94,
      "type": "preference",
      "validFrom": "2026-06-19T00:00:00Z",
      "validUntil": null
    }
  ],
  "total": 1
}
curl https://api.memhq.ai/v1/memhq/search \
  -H "Authorization: Bearer $MEMHQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "user_42",
    "query": "what coffee does the user like?",
    "limit": 5
  }'

Response:

{
  "memories": [
    {
      "id": "m_xyz123",
      "content": "Prefers dark roast coffee, no sugar",
      "score": 0.94,
      "type": "preference",
      "valid_from": "2026-06-19T00:00:00Z",
      "valid_until": null
    }
  ],
  "total": 1
}

What just happened?

search runs three retrieval lanes in parallel — BM25 keyword, vector similarity, and graph traversal — then merges the results with Reciprocal Rank Fusion. The score (0–1) reflects combined relevance across all three lanes. valid_until: null means the fact is currently true (no contradiction has overridden it yet).

Ask a synthesized question

For agent-style Q&A, skip search and go straight to ask. MemHQ retrieves, synthesizes, and returns an answer with citations in a single call — no LLM invocation needed on your side.

answer = client.ask(
    user_id="user_42",
    question="If I'm making them coffee, how should I prepare it?",
)

print(answer.answer)
# → "Brew a dark roast with no sugar."

for citation in answer.citations:
    print(f"  [{citation.score:.2f}] {citation.content}")
# →   [0.94] Prefers dark roast coffee, no sugar

Full response:

{
  "answer": "Brew a dark roast with no sugar.",
  "citations": [
    {
      "memory_id": "m_xyz123",
      "content": "Prefers dark roast coffee, no sugar",
      "score": 0.94,
      "valid_from": "2026-06-19T00:00:00Z"
    }
  ],
  "question_mode": "lookup"
}
const answer = await client.ask({
  userId: "user_42",
  question: "If I'm making them coffee, how should I prepare it?",
});

console.log(answer.answer);
// → "Brew a dark roast with no sugar."

for (const citation of answer.citations) {
  console.log(`  [${citation.score.toFixed(2)}] ${citation.content}`);
}
// →   [0.94] Prefers dark roast coffee, no sugar

Full response:

{
  "answer": "Brew a dark roast with no sugar.",
  "citations": [
    {
      "memoryId": "m_xyz123",
      "content": "Prefers dark roast coffee, no sugar",
      "score": 0.94,
      "validFrom": "2026-06-19T00:00:00Z"
    }
  ],
  "questionMode": "lookup"
}
curl https://api.memhq.ai/v1/memhq/ask \
  -H "Authorization: Bearer $MEMHQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "user_42",
    "question": "If I am making them coffee, how should I prepare it?"
  }'

Full response:

{
  "answer": "Brew a dark roast with no sugar.",
  "citations": [
    {
      "memory_id": "m_xyz123",
      "content": "Prefers dark roast coffee, no sugar",
      "score": 0.94,
      "valid_from": "2026-06-19T00:00:00Z"
    }
  ],
  "question_mode": "lookup"
}

What just happened?

ask is a convenience wrapper around the full retrieve → ground → synthesize pipeline:

  1. Retrieve — same hybrid search as search, returning the top-k memories
  2. Ground — memories are packed into a grounding prompt: "Answer ONLY using these facts. Cite each one."
  3. Synthesize — an LLM generates the answer, tagging each claim with the memory ID it came from

question_mode: "lookup" means MemHQ classified this as a factual retrieval question. Other modes are aggregate (counting questions) and temporal (questions about what was true at a specific time).

Complete working example

Here's the full script from this guide in one place:

import time
from memhq import MemoryClient

client = MemoryClient()  # MEMHQ_API_KEY from environment

# 1. Add a fact
client.add(
    user_id="user_42",
    messages=[
        {"role": "user", "content": "I prefer dark roast coffee, no sugar."},
    ],
)

# Give the async extraction pipeline a moment to finish
time.sleep(3)

# 2. Search it back
results = client.search(
    user_id="user_42",
    query="coffee preference",
    limit=5,
)
for hit in results.memories:
    print(f"search  {hit.score:.2f}  {hit.content}")

# 3. Ask a synthesized question
answer = client.ask(
    user_id="user_42",
    question="If I'm making them coffee, how should I prepare it?",
)
print(f"ask     {answer.answer}")
for c in answer.citations:
    print(f"        [{c.score:.2f}] {c.content}")
import { MemoryClient } from "@memhq/sdk";

const client = new MemoryClient(); // MEMHQ_API_KEY from process.env

// 1. Add a fact
await client.add({
  userId: "user_42",
  messages: [
    { role: "user", content: "I prefer dark roast coffee, no sugar." },
  ],
});

// Give the async extraction pipeline a moment to finish
await new Promise((r) => setTimeout(r, 3000));

// 2. Search it back
const results = await client.search({
  userId: "user_42",
  query: "coffee preference",
  limit: 5,
});
for (const hit of results.memories) {
  console.log(`search  ${hit.score.toFixed(2)}  ${hit.content}`);
}

// 3. Ask a synthesized question
const answer = await client.ask({
  userId: "user_42",
  question: "If I'm making them coffee, how should I prepare it?",
});
console.log(`ask     ${answer.answer}`);
for (const c of answer.citations) {
  console.log(`        [${c.score.toFixed(2)}] ${c.content}`);
}

The time.sleep(3) / setTimeout(3000) is only needed in scripts where you add and immediately search in the same run. In production, facts are added during one conversation turn and searched in the next — the async extraction window is invisible.

Where to go next