Technical Documentation: supermemoryai/supermemory
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (README.md)
Supermemory is the memory and context layer for AI. #1 on LongMemEval, LoCoMo, and ConvoMem — the three major benchmarks for AI memory.
We are a research lab building the engine, plugins and tools around it.
Your AI forgets everything between conversations. Supermemory fixes that.
It automatically learns from conversations, extracts facts, builds user profiles, handles knowledge updates and contradictions, forgets expired information, and delivers the right context at the right time. Full RAG, connectors, file processing — the entire context stack, one system.
| 🧠 Memory | Extracts facts from conversations. Handles temporal changes, contradictions, and automatic forgetting. |
| 👤 User Profiles | Auto-maintained user context — stable facts + recent activity. One call, ~50ms. |
| 🔍 Hybrid Search | RAG + Memory in a single query. Knowledge base docs and personalized context together. |
| 🔌 Connectors | Google Drive · Gmail · Notion · OneDrive · GitHub — auto-sync with real-time webhooks. |
| 📄 Multi-modal Extractors | PDFs, images (OCR), videos (transcription), code (AST-aware chunking). Upload and it works. |
All of this is in our single memory structure and ontology.
Use Supermemory
| 🧑💻 I use AI tools
Give Claude Code, Muse Code, Cursor, Codex and OpenCode persistent memory across every conversation with a plugin or the MCP server.
Your AI remembers your preferences, projects, past discussions — and gets smarter over time.
→ Jump to User setup | 🔧 I'm building AI products
Add memory, RAG, user profiles, and connectors to your agents and apps with a single API.
No vector DB config. No embedding pipelines. No chunking strategies.
→ Jump to developer quickstart |
| --- | --- |
| 🖥️ I want to run it myself
State-of-the-art memory, on your machine. One binary. Zero config. Bring any model — or run fully offline with Ollama.
curl -fsSL https://supermemory.ai/install | bash→ Jump to Supermemory local | |
Give your AI memory
Plugins and the MCP server give any compatible AI assistant persistent memory. One install, and your AI remembers you.
Supermemory Plugins
Supermemory comes built with plugins for Claude Code, Muse Code, Cursor, Codex, OpenCode, OpenClaw, and Hermes.
These plugins are implementations of the supermemory API, and they are open source!
You can find them here:
- Claude Code plugin: https://github.com/supermemoryai/claude-supermemory
- Muse Code plugin: https://github.com/supermemoryai/muse-supermemory
- Cursor plugin: https://github.com/supermemoryai/cursor-supermemory
- Codex plugin: https://github.com/supermemoryai/codex-supermemory
- OpenClaw plugin: https://github.com/supermemoryai/openclaw-supermemory
- OpenCode plugin: https://github.com/supermemoryai/opencode-supermemory
- Hermes agent (Supermemory memory provider): https://github.com/NousResearch/hermes-agent
MCP
Server URL:
https://mcp.supermemory.ai/mcp{
"mcpServers": {
"supermemory": {
"url": "https://mcp.supermemory.ai/mcp"
}
}
}Read more about our MCP here - https://supermemory.ai/docs/supermemory-mcp/mcp
What your AI gets
| Tool | What it does |
|---|---|
memory |
Save or forget information. Your AI calls this automatically when you share something worth remembering. |
recall |
Search memories by query. Returns relevant memories + your user profile summary. |
context |
Injects your full profile (preferences, recent activity) into the conversation at start. In Cursor and Claude Code, just type /context. |
How it works
Once installed, Supermemory runs in the background:
- You talk to your AI normally. Share preferences, mention projects, discuss problems.
- Supermemory extracts and stores the important stuff. Facts, preferences, project context — not noise.
- Next conversation, your AI already knows you. It recalls what you're working on, how you like things, what you discussed before.
Memory is scoped with projects (container tags) so you can separate work and personal context, or organize by client, repo, or anything else.
Supported clients
Claude Desktop · Cursor · Windsurf · VS Code · Claude Code · OpenCode · OpenClaw · Hermes
The MCP server is open source — view the source.
Manual configuration
Add this to your MCP client config:
{
"mcpServers": {
"supermemory": {
"url": "https://mcp.supermemory.ai/mcp"
}
}
}Build with Supermemory (API)
If you're building AI agents or apps, Supermemory gives you the entire context stack through one API — memory, RAG, user profiles, connectors, and file processing.
Install
npm install supermemory # or: pip install supermemoryQuickstart
import Supermemory from "supermemory";
const client = new Supermemory();
// Store a conversation
await client.add({
content: "User loves TypeScript and prefers functional patterns",
containerTag: "user_123",
});
// Get user profile + relevant memories in one call
const { profile, searchResults } = await client.profile({
containerTag: "user_123",
q: "What programming style does the user prefer?",
});
// profile.static → ["Loves TypeScript", "Prefers functional patterns"]
// profile.dynamic → ["Working on API integration"]
// searchResults → Relevant memories ranked by similarityfrom supermemory import Supermemory
client = Supermemory()
client.add(
content="User loves TypeScript and prefers functional patterns",
container_tag="user_123"
)
result = client.profile(container_tag="user_123", q="programming style")
print(result.profile.static) # Long-term facts
print(result.profile.dynamic) # Recent contextSupermemory automatically extracts memories, builds user profiles, and returns relevant context. No embedding pipelines, no vector DB config, no chunking strategies.
Framework integrations
Drop-in wrappers for every major AI framework:
// Vercel AI SDK
import { withSupermemory } from "@supermemory/tools/ai-sdk";
const model = withSupermemory(openai("gpt-4o"), { containerTag: "user_123", customId: "conv-1" });
// Mastra
import { withSupermemory } from "@supermemory/tools/mastra";
const agent = new Agent(withSupermemory(config, "user-123", { mode: "full" }));Vercel AI SDK · LangChain · LangGraph · OpenAI Agents SDK · Mastra · Agno · Claude Memory Tool · n8n
Search modes
// Hybrid (default) — RAG + Memory in one query
const results = await client.search({
q: "how do I deploy?",
containerTag: "user_123",
searchMode: "hybrid",
});
// Returns deployment docs (RAG) + user's deploy preferences (Memory)
// Memories only
const results = await client.search({
q: "user preferences",
containerTag: "user_123",
searchMode: "memories",
});User profiles
Traditional memory relies on search — you need to know what to ask for. Supermemory automatically maintains a profile for every user:
const { profile } = await client.profile({ containerTag: "user_123" });
// profile.static → ["Senior engineer at Acme", "Prefers dark mode", "Uses Vim"]
// profile.dynamic → ["Working on auth migration", "Debugging rate limits"]One call. ~50ms. Inject into your system prompt and your agent instantly knows who it's talking to.
Connectors
Auto-sync external data into your knowledge base:
Google Drive · Gmail · Notion · OneDrive · GitHub · Web Crawler
Real-time webhooks. Documents automatically processed, chunked, and searchable.
API at a glance
| Method | Purpose |
|---|---|
client.add() |
Store content — text, conversations, URLs, HTML |
client.profile() |
User profile + optional search in one call |
client.search() |
Hybrid search across memories and documents (searchMode) |
client.search.documents() |
Document search with metadata filters (legacy v3 response shape) |
client.documents.uploadFile() |
Upload PDFs, images, videos, code |
client.documents.list() |
List and filter documents |
client.settings.update() |
Configure memory extraction and chunking |
Full API reference → supermemory.ai/docs
Supermemory local — run it yourself
State-of-the-art memory, on your machine. One binary. Zero config.
curl -fsSL https://supermemory.ai/install | bash
# or
npx supermemory localsupermemory-serverFirst boot sets up the embedded Supermemory graph engine, local embeddings, and your credentials, then prints an API key. The full Memory API — documents, memories, user profiles, hybrid search — runs against http://localhost:6767.
const client = new Supermemory({
apiKey: "sm_...",
baseURL: "http://localhost:6767", // that's the only change
});- Bring any model — OpenAI, Anthropic, Gemini, Groq, or any OpenAI-compatible endpoint. An interactive wizard walks you through it on first boot.
- Embeddings — local
Xenova/bge-base-en-v1.5by default (no API key); optionally OpenAI, Gemini, or Ollama. Same provider stack as cloud. - Offline text memories if you want — point it at Ollama (
gpt-oss:20bworks great), disable telemetry withSUPERMEMORY_DISABLE_TELEMETRY=1, and use local text or file inputs after the first embedding-model download. URL ingestion uses a hosted reader service. - Your data, one directory — everything lives in
~/.supermemory, easy to back up or move. - Same API as the platform — prototype locally, ship on the hosted platform by changing
baseURL.
Run with Docker
Prebuilt images support Linux amd64 and arm64. You don’t need to build or clone the server source.
For a new installation, run these commands in an empty directory and set your own LLM provider key:
release_url=https://github.com/supermemoryai/supermemory/releases
curl -fL "$release_url/download/server-v0.0.9/compose.yaml" -o compose.yaml
export OPENAI_API_KEY=your_openai_api_key_here
export SUPERMEMORY_VERSION=0.0.9
docker compose up -d --wait --wait-timeout 180The API listens on http://localhost:6767. Data and credentials persist in the supermemory_supermemory-data volume. Get your local API key with docker compose exec supermemory cat /data/api-key; requests require it even on localhost.
Read the Docker setup and upgrade instructions for persistent provider configuration, backups and upgrades. Keep the existing volume and keys when upgrading; don’t run docker compose down -v.
Read the self-hosting docs — quickstart, configuration, embeddings, and local vs. Enterprise.
Benchmarks
Supermemory is state of the art across all major AI memory benchmarks:
| Benchmark | What it measures | Result |
|---|---|---|
| LongMemEval | Long-term memory across sessions with knowledge updates | #1 |
| LoCoMo | Fact recall across extended conversations (single-hop, multi-hop, temporal, adversarial) | #1 |
| ConvoMem | Personalization and preference learning | #1 |
On LongMemEval, supermemory reaches 95% Recall@15 while adding only ~720 tokens of context — a 99.4% context reduction (99.6% at @10, 99.8% at @5). Recall by category: Knowledge Updates 99%, Assistant recall 100%, User recall 97%, Multi-session 93%, Temporal Reasoning 91%, Preference 90%.
We also built the Supermemory Filesystem (SMFS), which uses 3.0× fewer tokens on Claude (24M vs 72M) and 1.75× fewer on Codex across the 110-question xAFS benchmark. See the full write-ups on our research page.
We also built MemoryBench — an open-source framework for standardized, reproducible benchmarks of memory providers. Compare Supermemory, Mem0, Zep, and others head-to-head:
bun run src/index.ts run -p supermemory -b longmemeval -j gpt-4o -r my-runBenchmarking your own memory solution
We provide an Agent skill for companies to benchmark their own context and memory solutions against supermemory.
npx skills add supermemoryai/memorybenchSimply run this and do /benchmark-context - Supermemory will automatically do the work for you!
How memory works under the hood
Your app / AI tool
↓
Supermemory
│
├── Memory Engine Extracts facts, tracks updates, resolves contradictions,
│ auto-forgets expired info
├── User Profiles Static facts + dynamic context built from engine, always fresh
├── Hybrid Search RAG + Memory in one query
├── Connectors Real-time sync from Google Drive, Gmail, Notion, GitHub...
└── File Processing PDFs, images, videos, code → searchable chunksMemory is not RAG. RAG retrieves document chunks — stateless, same results for everyone. Memory extracts and tracks facts about users over time. It understands that "I just moved to SF" supersedes "I live in NYC." Supermemory runs both together by default, so you get knowledge base retrieval and personalized context in every query. Read more about this here - https://supermemory.ai/docs/concepts/memory-vs-rag
Automatic forgetting. Supermemory knows when memories become irrelevant. Temporary facts ("I have an exam tomorrow") expire after the date passes. Contradictions are resolved automatically. Noise never becomes permanent memory.
Links
- 📖 Documentation
- 🚀 Quickstart
- 🖥️ Self-hosting (Supermemory local)
- 🧪 MemoryBench
- 🔌 Integrations
- 💬 Discord
- 𝕏 Twitter
Apps Docs Index (`apps/docs/index.mdx`)
title: "Introduction" mode: "custom"
import { AGENT_PROMPT } from "/snippets/agent-prompt.jsx"
import { HERO_TS, HERO_PY, HERO_CURL } from "/snippets/hero-samples.jsx"
import { AgentMarks, CopyPromptButton, PromptPanel } from "/snippets/agent-prompt-panel.jsx"
export const HomeLink = ({ href, icon, children }) => (
{children}export const PathCard = ({ title, description, href, image, children }) => (
<a className="sm-home-card-media" href={href} aria-label={title}>
</a>
<a className="sm-home-card-title" href={href}>{title}</a>
{description}
{children})
export const ListCard = ({ title, children }) => (
{title}
{children})
<h1 className="sm-home-title sm-home-title-split">
Memory and continual learning for the world’s best agents.
</h1>
Supermemory gives your agent long-term memory of every user. Paste one prompt into your coding agent and it wires memory into your project.
<CopyPromptButton prompt={AGENT_PROMPT} label="Copy prompt" copiedLabel="Copied" className="sm-home-btn sm-home-btn-primary" />
[Read the quickstart](/quickstart)
<AgentMarks />Choose how you use supermemory
Start with the path that fits your app and your team.<HomeLink href="/quickstart" icon="/icons/hugeicons/play-circle.svg">Quickstart</HomeLink>
<HomeLink href="/concepts/how-it-works" icon="/icons/hugeicons/layers-01.svg">How it works</HomeLink>
<HomeLink href="/api-reference/overview" icon="/icons/hugeicons/source-code.svg">API reference</HomeLink>
<HomeLink href="/supermemory-mcp/mcp" icon="/icons/hugeicons/plug-01.svg">Supermemory MCP</HomeLink>
<HomeLink href="/integrations/claude-code" icon="/icons/hugeicons/command-line.svg">Claude Code</HomeLink>
<HomeLink href="/integrations/cursor" icon="/icons/hugeicons/cursor-01.svg">Cursor</HomeLink>
<HomeLink href="/self-hosting/quickstart" icon="/icons/hugeicons/play-circle.svg">Local quickstart</HomeLink>
<HomeLink href="/self-hosting/configuration" icon="/icons/hugeicons/sliders-horizontal.svg">Configuration</HomeLink>
<HomeLink href="/self-hosting/providers" icon="/icons/hugeicons/server-stack-01.svg">Model providers</HomeLink>Explore features
Browse memory, retrieval, profiles, and connectors.<ListCard title="Memory and retrieval">
<HomeLink href="/concepts/graph-memory" icon="/icons/hugeicons/brain-01.svg">Graph memory</HomeLink>
<HomeLink href="/concepts/super-rag" icon="/icons/hugeicons/search-01.svg">SuperRAG</HomeLink>
<HomeLink href="/recall/search" icon="/icons/hugeicons/file-search.svg">Search memories</HomeLink>
<HomeLink href="/concepts/user-profiles" icon="/icons/hugeicons/user.svg">User profiles</HomeLink>
<HomeLink href="/concepts/filtering" icon="/icons/hugeicons/filter.svg">Filtering</HomeLink>
<HomeLink href="/concepts/memory-vs-rag" icon="/icons/hugeicons/balance-scale.svg">Memory vs RAG</HomeLink>
</ListCard>
<ListCard title="Build and connect">
<HomeLink href="/ingestion/add-memories" icon="/icons/hugeicons/plus-sign.svg">Add memories</HomeLink>
<HomeLink href="/connectors/overview" icon="/icons/hugeicons/plug-01.svg">Connectors</HomeLink>
<HomeLink href="/smfs/overview" icon="/icons/hugeicons/folder-01.svg">SMFS</HomeLink>
<HomeLink href="/concepts/container-tags" icon="/icons/hugeicons/tag-01.svg">Namespaces</HomeLink>
<HomeLink href="/concepts/customization" icon="/icons/hugeicons/sliders-horizontal.svg">Customization</HomeLink>
<HomeLink href="/migration/from-mem0" icon="/icons/hugeicons/arrow-data-transfer-horizontal.svg">Migrate from mem0</HomeLink>
</ListCard>Apps Docs Quickstart (`apps/docs/quickstart.mdx`)
title: "Quickstart" icon: "/icons/hugeicons/play.svg"
By the end of this page you will:
- Ingest a conversation (how personal memory actually arrives)
- Ingest a document (how knowledge for RAG arrives)
- Retrieve three ways — document search (RAG), memory graph traversal, and user profile
- Drop it into a chat harness that remembers across restarts
Same namespace for everything. One engine, three ways out. A namespace is what v3/v4 called a container tag.
Get an API key
Grab a key from the developer console — API Keys → Create API Key.
console.supermemory.ai is where keys and usage live.
pip install supermemory
export SUPERMEMORY_API_KEY="sm_..."export SUPERMEMORY_API_KEY="sm_..."1. Ingest a conversation
Real apps do not push four isolated one-liners as separate “memories.” They send conversation turns — often the full session — under a stable id so the pipeline can extract facts and link entities.
We’ll use one user (user_4f8a) and one chat session. The turns never say “Sarah is my VP of Product” — that connection is what the graph should resolve later.
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY });
const user = "user_4f8a"; // the namespace
const conversation = user: Just got back from Tokyo — the team offsite went great. assistant: Glad it went well! Anything stand out? user: Sarah presented the Q3 roadmap at the offsite. assistant: Sounds like a big moment for her. user: She's being promoted to VP of Product. assistant: Congrats to Sarah — that's huge. user: I need a gift idea for my VP of Product. assistant: Happy to help brainstorm something personal..trim();
const conv = await supermemory.add(user, {
content: conversation,
id: "chat_offsite_2026", // one session → one document
metadata: { type: "conversation" },
dreaming: "instant", // process this document now — see note below
});
console.log(conv.id, conv.status); // e.g. "queued"
```python Python
from supermemory import Supermemory
client = Supermemory()
user = "user_4f8a"
conversation = """
user: Just got back from Tokyo — the team offsite went great.
assistant: Glad it went well! Anything stand out?
user: Sarah presented the Q3 roadmap at the offsite.
assistant: Sounds like a big moment for her.
user: She's being promoted to VP of Product.
assistant: Congrats to Sarah — that's huge.
user: I need a gift idea for my VP of Product.
assistant: Happy to help brainstorm something personal.
""".strip()
conv = client.add(
user,
content=conversation,
id="chat_offsite_2026", # one session → one document
metadata={"type": "conversation"},
dreaming="instant", # process this document now, see note below
)
print(conv.id, conv.status) # e.g. "queued"curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "user: Just got back from Tokyo — the team offsite went great.\nassistant: Glad it went well! Anything stand out?\nuser: Sarah presented the Q3 roadmap at the offsite.\nassistant: Sounds like a big moment for her.\nuser: She is being promoted to VP of Product.\nassistant: Congrats to Sarah — that is huge.\nuser: I need a gift idea for my VP of Product.\nassistant: Happy to help brainstorm something personal.",
"id": "chat_offsite_2026",
"metadata": { "type": "conversation" },
"dreaming": "instant"
}'add returns immediately with status: "queued". Processing is still async — wait until done before searching.
2. Ingest a document
Now add knowledge the agent should ground on — a short internal note the conversation never fully spelled out. This is the SuperRAG / document path.
When someone is promoted to VP or above, the company recommends a thoughtful gift
in the $75–$150 range. Experiences tied to recent team milestones land better
than generic swag.
For product leadership, books on platform strategy or a dinner near the last
offsite city are common picks. Tokyo offsites often inspire travel-themed gifts.
`.trim();
const doc = await supermemory.add(user, {
content: handbook,
id: "doc_gift_policy",
metadata: { type: "document", source: "handbook" },
taskType: "superrag",
});
console.log(doc.id, doc.status);
```python Python
handbook = """
# Team notes — gifts & recognition
When someone is promoted to VP or above, the company recommends a thoughtful gift
in the $75–$150 range. Experiences tied to recent team milestones land better
than generic swag.
For product leadership, books on platform strategy or a dinner near the last
offsite city are common picks. Tokyo offsites often inspire travel-themed gifts.
""".strip()
doc = client.add(
user,
content=handbook,
id="doc_gift_policy",
metadata={"type": "document", "source": "handbook"},
task_type="superrag",
)
print(doc.id, doc.status)curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "# Team notes — gifts & recognition\n\nWhen someone is promoted to VP or above, the company recommends a thoughtful gift in the $75–$150 range. Experiences tied to recent team milestones land better than generic swag.\n\nFor product leadership, books on platform strategy or a dinner near the last offsite city are common picks. Tokyo offsites often inspire travel-themed gifts.",
"id": "doc_gift_policy",
"metadata": { "type": "document", "source": "handbook" },
"taskType": "superrag"
}'3. Wait until both are `done`
Poll document status. With dreaming: "instant", once system.status is done the document is indexed and memories for that document should be available for search and profiles (queued → extracting → … → done).
Note that the preferred way is to have
dreaming: dynamic. supermemory charges one extra operation for instant dreaming. Instant is good for one off tests, setup, debugging and benchmarking.
await waitUntilDone(conv.id);
await waitUntilDone(doc.id);
console.log("ready to search");
```python Python
import time
def wait_until_done(doc_id: str):
while True:
d = client.documents.get(user, doc_id)
if d.system.status in ("done", "failed"):
return d
time.sleep(1.5)
wait_until_done(conv.id)
wait_until_done(doc.id)
print("ready to search")# replace DOC_ID with each document id from the add responses
curl "https://api.supermemory.ai/ns/user_4f8a/document/DOC_ID" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
# repeat until "system": { "status": "done" }Short text with instant dreaming usually finishes in a few seconds. Larger PDFs take longer. If you omit dreaming (default "dynamic"), document RAG can work after done while memory extraction may still be batching — use "instant" when the next step is memory search or profiles. system.dreamingStatus turns done once a document's memories are ready.
4. Three ways to get context back
A. Document search (RAG)
Chunk-level retrieval over raw knowledge — use when you need what the docs say.
for (const hit of rag.results) {
console.log(hit.similarity, hit.chunk);
}
```python Python
rag = client.search(
user,
query="gift ideas for a VP promotion after a Tokyo offsite",
search_mode="chunks",
limit=3,
)
for hit in rag.results:
print(hit.similarity, hit.chunk)curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "gift ideas for a VP promotion after a Tokyo offsite",
"searchMode": "chunks",
"limit": 3
}'You should see chunks from the handbook (budget range, Tokyo offsite angle) — document grounding, not personal facts.
Omit searchMode (or set it to "hybrid", the default) to get extracted memories and document chunks together:
await supermemory.search(user, {
query: "gift ideas for a VP promotion after a Tokyo offsite",
searchMode: "hybrid",
limit: 5,
});B. Memory graph traversal
Search extracted memories with related edges. This is the entity-chain moment: gift → VP of Product → Sarah → Tokyo offsite.
console.log(JSON.stringify(memories, null, 2));
```python Python
memories = client.search(
user,
query="What gift should I get, and why?",
search_mode="memories",
include={"related": True},
limit=5,
)
print(memories.to_json())curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "What gift should I get, and why?",
"searchMode": "memories",
"include": { "related": true },
"limit": 5
}'Abbreviated shape:
{
"results": [
{
"id": "mem_8c1f",
"memory": "Sarah is being promoted to VP of Product",
"similarity": 0.81,
"included": {
"related": {
"parents": [
{ "id": "mem_2a90", "memory": "Sarah presented the Q3 roadmap at the Tokyo offsite" }
],
"children": [
{ "id": "mem_b713", "memory": "User needs a gift idea for their VP of Product, Sarah" }
]
}
}
}
],
"searchTime": 287
}You never wrote “Sarah is my VP of Product” as one sentence. The graph connected sessions of speech. Deep dive: graph memory.
C. User profile
Profiles are the always-on summary (static + recent dynamic) of an entity (or a namespace) - what you inject every turn without re-searching the world.
console.log("static:", profile.static.map((m) => m.memory));
console.log("dynamic:", profile.dynamic.map((m) => m.memory));
```python Python
profile = client.profile(user).profile
print("static:", [m.memory for m in profile.static])
print("dynamic:", [m.memory for m in profile.dynamic])curl -X POST "https://api.supermemory.ai/ns/user_4f8a/profile" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"The v5 profile call takes no query. When you also want query-ranked memories, run a search next to it (the harness below does exactly that).
There is a lot more to profiles - with Buckets, for example, you can make supermemory learn and categorize incoming information for learning specific things.
| Path | Use when |
|---|---|
| Document search | Ground in policies, docs, handbooks |
| Memory + related | Personal facts, entity links, “what’s true about this user” |
| Profile | Cheap always-on context every LLM turn |
Same namespace → same context pool. See Memory vs RAG.
5. Put it in a harness
There is no single required harness. The pattern is the same wherever you run the model: read context (profile / search / docs), generate, write the turn back under a stable id so the session stays one document.
Here are two example shapes — pick whatever matches your stack.
Example: explicit profile + search + add
# pip install supermemory openai
from supermemory import Supermemory
from openai import OpenAI
memory = Supermemory()
llm = OpenAI()
user = "user_4f8a"
session_id = "chat_live_session"
while True:
question = input("you: ")
profile = memory.profile(user).profile
related = memory.search(user, query=question, search_mode="memories", limit=5)
# optional: also pull document chunks for grounding
rag = memory.search(user, query=question, search_mode="chunks", limit=3)
context = "\n".join(
[
"## Profile (static)",
*[m.memory for m in profile.static],
"## Profile (dynamic)",
*[m.memory for m in profile.dynamic],
"## Related memories",
*[r.memory for r in related.results if r.memory],
"## Docs",
*[r.chunk for r in rag.results if r.chunk],
]
)
res = llm.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": f"You help this user. Context:\n{context}"},
{"role": "user", "content": question},
],
)
answer = res.choices[0].message.content or ""
print(f"assistant: {answer}")
# append this turn into the same conversation document
memory.add(
user,
content=f"user: {question}\nassistant: {answer}",
id=session_id,
)Example: Vercel AI SDK
Same pattern, wrapped: withSupermemory injects context and can save the conversation for you. Details: AI SDK integration.
// npm install ai @ai-sdk/openai @supermemory/tools
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { withSupermemory } from "@supermemory/tools/ai-sdk";
import * as readline from "node:readline/promises";
const model = withSupermemory(openai("gpt-4o"), {
namespace: "user_4f8a",
id: "chat_live_session", // keep stable for the whole session
mode: "full", // profile + query search
});
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
while (true) {
const prompt = await rl.question("you: ");
const { text } = await generateText({ model, prompt });
console.log(`assistant: ${text}`);
}Try:
you: What gift should I get for the person being promoted?
assistant: You're looking for something for Sarah — she's being promoted to VP of
Product after presenting the Q3 roadmap in Tokyo. Your handbook suggests $75–$150
and something tied to the offsite; a Tokyo-inspired experience or platform-strategy
book would fit…Kill it, restart it
Ctrl+C the process, start again with the same namespace (and optional same id for the live session). Ask:
you: who's getting promoted?
assistant: Sarah — she's being promoted to VP of Product.Nothing was reloaded from your process. Memory and docs live in supermemory.
Mental model
INGEST WAIT RETRIEVE
────── ──── ────────
Conversation (id) → system.status === done → Memory graph (+ related)
Document (id) → system.status === done → Document search (RAG)
→ Profile (static + dynamic)
│
▼
Chat harnessWhere next
Apps Docs Api Reference Overview (`apps/docs/api-reference/overview.mdx`)
title: "API reference" icon: "/icons/hugeicons/plug-socket.svg"
This is the contract-level reference for Supermemory: methods, paths, parameters, and the playground.
For narrative guides (when to use what, patterns, SDKs), start with the Quickstart and Using supermemory.
Base URL
https://api.supermemory.aiSelf-hosted: use your instance URL (for example http://localhost:6767). See Self-hosting.
Authentication
All endpoints use a Bearer API key. Create one in the developer console.
Authorization: Bearer sm_...Details: API keys & auth.
Mental model
| Group | What it does |
|---|---|
| Ingest | Add documents, files, batches, and conversations into the pipeline |
| Documents | Get status, list, update, delete, chunks, and file URLs |
| Search | Semantic recall — memories, documents, or hybrid |
| Profiles | Static + dynamic facts for a container (user / entity) |
| Memories | Create, list, update, and forget extracted memory entries |
| Container tags | Multi-tenant settings, merge, and delete for a container |
| Connections | OAuth connectors (Drive, Notion, Gmail, …) and sync |
| Settings | Org-level customization, buckets, and reset |
Same containerTag scopes ingest, search, and profiles — one engine, multiple ways out.
Suggested order
- Ingest —
POST /v3/documents(SDK:client.add) - Documents —
GET /v3/documents/{id}untilstatus: "done" - Search —
POST /v4/search - Profiles —
POST /v4/profile
Full walkthrough with conversation + document examples: Quickstart.
SDKs
Official clients wrap this API:
- TypeScript:
npm install supermemory - Python:
pip install supermemory
See Supermemory SDK.
The API's OpenAPI generator supplies official TypeScript / Python SDK samples via x-codeSamples, plus cURL. Playground snippets use the deployed spec, so their availability depends on the API version serving it.
SDK generation is migrating off Stainless SaaS to stlc soon; documented OpenAPI samples will then be produced by the SDK build instead of a hand-maintained map.
OpenAPI
Spec (live): https://api.supermemory.ai/v3/openapi
Apps Docs Connectors Overview (`apps/docs/connectors/overview.mdx`)
title: "Connectors overview" sidebarTitle: "Overview" icon: "/icons/hugeicons/layers-01.svg"
Connect external platforms to automatically sync documents into supermemory. Supported connectors include Google Drive, Gmail, Notion, OneDrive, GitHub, Granola and Web Crawler with real-time synchronization and intelligent content processing.
New v5 connectors belong to one namespace. The namespace goes in the URL (/ns/{namespace}/connectors) and is the first argument of namespace-scoped SDK calls. Organization-wide listing uses connectors.listAll() in TypeScript or connectors.list_all() in Python without a namespace argument. Legacy connectors can span multiple namespaces or have no namespace.
Supported connectors
Real-time sync via webhooks. Supports shared drives, nested folders, and collaborative documents.Real-time sync via Pub/Sub webhooks. Syncs threads with full conversation history and metadata.Instant sync of workspace content. Handles rich formatting, embeds, and database properties.Scheduled sync every 4 hours. Supports personal and business accounts with file versioning.Real-time incremental sync via webhooks. Supports documentation files in repositories.Syncs AI meeting notes, summaries, attendees, and transcripts from your Granola workspace.Crawl websites automatically with robots.txt compliance. Scheduled recrawling keeps content up to date.Quick start
1. Create a Connector
import { Supermemory } from "supermemory"
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("user-123", {
provider: "notion",
redirectUrl: "https://yourapp.com/callback",
documentLimit: 5000,
})
// Send the user to authorization.url to finish OAuth
console.log("Connector:", connector.id)
console.log("Auth URL:", connector.authorization?.url)
console.log("Expires at:", connector.authorization?.expiresAt)
// Output: Auth URL: https://api.supermemory.ai/v3/connections/auth/redirect?state=...from supermemory import Supermemory
import os
client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY"))
connector = client.connectors.create(
"user-123",
request={
"provider": "notion",
"redirect_url": "https://yourapp.com/callback",
"document_limit": 5000,
},
)
# Send the user to authorization.url to finish OAuth
print(f"Connector: {connector.id}")
print(f"Auth URL: {connector.authorization.url}")
print(f"Expires at: {connector.authorization.expires_at}")
# Output: Auth URL: https://api.supermemory.ai/v3/connections/auth/redirect?state=...curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "notion",
"redirectUrl": "https://yourapp.com/callback",
"documentLimit": 5000
}'
# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z"
# }
# }2. Handle OAuth callback
Send the user to authorization.url before authorization.expiresAt. When the user authorizes, the provider returns them to your redirectUrl and the first sync starts. GitHub, Gmail and Google Drive (with the default syncScope: "selected") first send the user through Supermemory's hosted picker to choose what to sync. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with system.status: "pending_authorization".
Config providers (Web Crawler, S3, Granola) return authorization: null and start syncing immediately.
3. Monitor sync status
import { Supermemory } from "supermemory"
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
// Read one connector with its recent sync runs
const connector = await supermemory.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", {
include: "syncs",
})
console.log("Provider:", connector.provider)
console.log("Account:", connector.account)
console.log("Last sync:", connector.latestRun?.system.status, connector.latestRun?.error)
console.log("Last synced at:", connector.system.lastSuccessfulSyncAt)
console.log("Documents:", connector.documentCount)
console.log("Recent runs:", connector.syncs)
// List the documents the connector synced into the namespace
const docs = await supermemory.list("user-123", "documents")
console.log(`Synced ${docs.pagination.totalItems} documents`)
// Output: Synced 45 documentsfrom supermemory import Supermemory
import os
client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY"))
# Read one connector with its recent sync runs
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
print(f"Provider: {connector.provider}")
print(f"Account: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
if connector.latest_run and connector.latest_run.error:
print(connector.latest_run.error)
print(f"Last synced at: {connector.system.last_successful_sync_at}")
print(f"Documents: {connector.document_count}")
print(f"Recent runs: {connector.syncs}")
# List the documents the connector synced into the namespace
docs = client.list("user-123", "documents")
print(f"Synced {docs.pagination.total_items} documents")
# Output: Synced 45 documents# Read one connector with its recent sync runs
curl "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA?include=syncs" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "provider": "notion",
# "namespace": "user-123",
# "account": "[email protected]",
# "documentLimit": 5000,
# "documentCount": 45,
# "latestRun": { "errorCode": null, "error": null, "system": { "status": "completed", "startedAt": "...", "completedAt": "..." } },
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "2024-01-15T10:45:00.000Z" },
# "syncs": [...]
# }
# List synced documents in the namespace
curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
# Response: {"documents": [...], "pagination": {"totalItems": 45, ...}}How connectors work
Authentication flow
- Create Connector: Call
POST /ns/{namespace}/connectorswith aprovider. OAuth providers return anauthorizationlink; config providers (Web Crawler, S3, Granola) take aconfigobject and returnauthorization: null - User Authorization: Send the user to
authorization.urlwhen the provider requires it - Automatic Setup: The connector is established. Save any required label, repository, or file selection before content sync can proceed; providers without a selection step start their initial sync after setup
- Continuous Sync: Real-time updates via webhooks + scheduled sync every 4 hours (or scheduled recrawling for Web Crawler)
Document processing pipeline
graph TD
A[External Document] --> B[Webhook/Schedule Trigger]
B --> C[Content Extraction]
C --> D[Chunking & Embedding]
D --> E[Index in Supermemory]
E --> F[Searchable Memory]
E --> G[Document Search]Sync mechanisms
| Provider | Real-time Sync | Scheduled Sync | Manual Sync |
|---|---|---|---|
| Google Drive | ✅ Webhooks (7-day expiry) | ✅ Every 4 hours | ✅ On-demand |
| Gmail | ✅ Pub/Sub (7-day expiry) | ✅ Every 4 hours | ✅ On-demand |
| Notion | ✅ Webhooks | ✅ Every 4 hours | ✅ On-demand |
| OneDrive | ✅ Webhooks (30-day expiry) | ✅ Every 4 hours | ✅ On-demand |
| GitHub | ✅ Webhooks | ✅ Every 4 hours | ✅ On-demand |
| Granola | ❌ Not supported | ❌ Not supported | ✅ On-demand |
| Web Crawler | ❌ Not supported | ✅ Scheduled recrawling (7+ days) | ✅ On-demand |
Connector Management
List Connectors
import { Supermemory } from "supermemory"
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
// Connectors in one namespace
const { connectors } = await supermemory.connectors.list("org-123")
for (const connector of connectors) {
console.log(`${connector.provider}: ${connector.account} (${connector.id})`)
console.log(`Documents: ${connector.documentCount} of ${connector.documentLimit}`)
console.log(`Last sync: ${connector.latestRun?.system.status ?? "never"}`)
}
// Connectors across every namespace in the organization
const all = await supermemory.connectors.listAll()from supermemory import Supermemory
import os
client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY"))
# Connectors in one namespace
connectors = client.connectors.list("org-123").connectors
for connector in connectors:
print(f"{connector.provider}: {connector.account} ({connector.id})")
print(f"Documents: {connector.document_count} of {connector.document_limit}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
# Connectors across every namespace in the organization
all_connectors = client.connectors.list_all()# Connectors in one namespace
curl "https://api.supermemory.ai/ns/org-123/connectors" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
# Response: {
# "connectors": [
# {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "provider": "notion",
# "namespace": "org-123",
# "account": "[email protected]",
# "documentLimit": 5000,
# "documentCount": 45,
# "latestRun": { "system": { "status": "completed", ... }, "error": null },
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "2024-01-15T10:45:00.000Z" }
# }
# ],
# "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
# }
# Connectors across every namespace in the organization
curl "https://api.supermemory.ai/connectors" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"Trigger a Sync
A connector syncs on its own schedule. Start one now with connectors.sync. The call returns 409 while a sync for that connector is already running.
const run = await supermemory.connectors.sync("org-123", "PTzGiUYei7pgzg5buzZHgA")
console.log(run.status)
// Output: queuedcurl -X POST "https://api.supermemory.ai/ns/org-123/connectors/PTzGiUYei7pgzg5buzZHgA/sync" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
# Response: {"id": "PTzGiUYei7pgzg5buzZHgA", "status": "queued"}Update a Connector
Change the document limit, or the selection for providers that support one, with connectors.update. A new selection replaces the old one. See Managing Connector Selection.
const connector = await supermemory.connectors.update("org-123", "PTzGiUYei7pgzg5buzZHgA", {
documentLimit: 8000,
})
console.log(connector.documentLimit)
// Output: 8000curl -X PATCH "https://api.supermemory.ai/ns/org-123/connectors/PTzGiUYei7pgzg5buzZHgA" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"documentLimit": 8000}'
# Response: the updated connector objectDelete Connectors
DELETE /ns/{namespace}/connectors/{id} accepts an optional deleteDocuments query parameter:
| Parameter | Type | Default | Description |
|---|---|---|---|
deleteDocuments |
boolean | true |
When true, all documents imported by the connector are permanently deleted. When false, the connector is removed but documents are kept. |
import { Supermemory } from "supermemory"
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
// Delete the connector and all imported documents (default)
await supermemory.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA")
// Delete the connector but keep imported documents
await supermemory.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", {
deleteDocuments: false,
})from supermemory import Supermemory
import os
client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY"))
# Delete the connector and all imported documents (default)
client.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA")
# Delete the connector but keep imported documents
client.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", delete_documents=False)# Delete the connector and all imported documents (default)
curl -X DELETE "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
# Delete the connector but keep imported documents
curl -X DELETE "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA?deleteDocuments=false" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"Custom OAuth applications
By default, Supermemory uses its own OAuth applications to connect to third-party providers. You can use your own OAuth app credentials for tighter control over data access, which is useful for enterprise customers. Custom OAuth credentials are an organization setting and are not part of the v5 connector routes.
- Create the OAuth application on the provider's developer console:
- For Google Drive specifically: choose application type Web application, and enable the Google Drive API under "APIs and Services" in the Cloud Console. Google also requires verification/approval before custom keys work in production.
- Set the redirect URL to
https://api.supermemory.ai/v3/connections/auth/callback/{provider}(for example,.../auth/callback/google-drive).
Apps Docs Memorybench Overview (`apps/docs/memorybench/overview.mdx`)
title: "MemoryBench" sidebarTitle: "Overview" icon: "/icons/hugeicons/test-tube-01.svg"
Benchmarking memory systems is hard, and most comparisons you'll find aren't apples-to-apples — different datasets, different judges, different prompts, cherry-picked runs. MemoryBench (supermemoryai/memorybench, MIT licensed) is the open-source framework we built to fix that: the same benchmark questions, the same pipeline, and the same judges run against every provider, so a comparison actually means something.
We open-sourced it so you don't have to take our word for anything — you can run it yourself, against your own memory implementation, on the datasets that match your use case.
Benchmark your own memory system
MemoryBench ships a Claude Code skill that automates the entire process of benchmarking a custom memory implementation — yours — against Supermemory, Mem0, and Zep. Point it at your code and it handles discovery, integration, and the run:
- Asks a few questions about your setup (provider name, where your memory code lives, which benchmark, which competitors, how many questions)
- Analyzes your memory code to find its init, ingest, and search methods
- Generates a provider adapter and registers it with the framework
- Runs the full benchmark against your chosen competitors
- Reports accuracy, latency, and context-token results side by side
# From your project root
/memorybenchNo manual TypeScript required to get a first result — see Adding a Provider for what the skill generates and how to do it by hand.
How it works
Every run goes through the same checkpointed pipeline, regardless of provider or benchmark:
INGEST → SEARCH → ANSWER → EVALUATE → REPORTIngestion can take hours for large datasets, and API calls fail — so every phase checkpoints independently and a run resumes from the last completed step instead of starting over.
| Benchmark | Tests | Source |
|---|---|---|
| LoCoMo | Fact recall across extended, multi-session conversations — single-hop, multi-hop, temporal, adversarial | snap-research/locomo |
| LongMemEval | Long-term memory across sessions, including knowledge that gets updated mid-conversation | xiaowu0162/longmemeval |
| ConvoMem | Personalization, preference learning, and reference resolution within a conversation | Salesforce/ConvoMem |
None of these fit your use case? Build your own benchmark — providers, benchmarks, and judges are all pluggable.
Judging is judge-agnostic too — score a run with GPT-4o, Claude, Gemini, or any model you configure, so results aren't an artifact of one evaluator's bias.
Read next
Apps Docs Self Hosting Overview (`apps/docs/self-hosting/overview.mdx`)
title: "Supermemory local" sidebarTitle: "Overview" icon: "/icons/hugeicons/server-stack-01.svg"
Supermemory runs on your own hardware. It's the same memory engine behind the hosted platform — ingestion, memory extraction, hybrid semantic search, and the full API — as a single self-contained binary.
npx supermemory localNo Docker. No database to provision. No config files. It boots in seconds with everything built in. The SDKs and other components in the public repository are open source; the downloadable self-hosted server binary is built from a separate, non-public codebase.
Zero config, actually
Run the binary with nothing set and you get a complete memory system:
- The Supermemory graph engine, embedded — created automatically on first boot. No database to stand up, no connection strings.
- Built-in local embeddings — default
Xenova/bge-base-en-v1.5(768d) on your machine, no API key. Opt into OpenAI, Gemini, or Ollama instead — see Embeddings. - An API key, generated for you — printed on first boot, ready to paste into any SDK.
- The full Memory API —
/ns/{namespace}/document,/ns/{namespace}/search,/ns/{namespace}/profile, namespaces, the works.
The only thing you bring is a model. In production, Supermemory runs its own proprietary models, purpose-tuned for long-horizon data understanding and memory extraction. Self-hosted, the same pipeline runs on whatever model you point it at — OpenAI, Anthropic, Gemini, Groq, or any OpenAI-compatible endpoint. Bring a key and go. Or don't bring one at all:
Runs fully offline
Supermemory works with any OpenAI-compatible endpoint, which means it runs end-to-end on your machine with a local model — Ollama, LM Studio, vLLM, llama.cpp. gpt-oss-20b is a great fit:
OPENAI_BASE_URL=http://localhost:11434/v1 \
OPENAI_API_KEY=ollama \
OPENAI_MODEL=gpt-oss:20b \
supermemory-serverLocal graph engine, local embeddings, local LLM. Text-memory processing can stay on your machine after the embedding model's first download. Supermemory collects telemetry; you can disable it with SUPERMEMORY_DISABLE_TELEMETRY=1. Webpage ingestion sends the URL to Firecrawl and needs FIRECRAWL_API_KEY; use local text inputs for offline operation. Also set SUPERMEMORY_NO_UPDATE_CHECK=1 if the server must not check for releases.
Drop-in with your existing code
The self-hosted server speaks the same API as the hosted platform. Point any Supermemory SDK at it with a one-line change:
const client = new Supermemory({
apiKey: "sm_...", // printed on first boot
baseUrl: "http://localhost:6767",
})Everything in the Memory API docs works the same way. The coding plugins do too — Claude Code, Muse Code, Codex, and OpenCode all target your local server with SUPERMEMORY_API_URL=http://localhost:6767 (Muse: set baseUrl in .muse/supermemory.json, because hook env is cleared).
Self-hosted vs. the platform
Self-hosted is free within its lite license limit and useful for local development and privacy-sensitive workloads. The server binary is not open source; the hosted platform is where the full product lives:
| Self-hosted | Platform | |
|---|---|---|
| Full Memory API | ✅ | ✅ |
| Hybrid semantic search | ✅ | ✅ |
| Embeddings | Local default (or OpenAI / Gemini / Ollama) | Managed |
| File ingestion (PDFs, images) | ✅ (PDFs need GEMINI_API_KEY; images use Gemini or Vertex AI) |
✅ |
| Connectors (Google Drive, Notion, Gmail, OneDrive) | — | ✅ |
| Supermemory MCP | — | ✅ |
| Memory extraction | Your model, your key | Proprietary long-horizon models — higher quality, cheaper at scale |
| Infrastructure | Your machine | Globally distributed, scales with you |
If you outgrow a single machine — or want connectors, MCP, and the best-tuned extraction pipeline — switch to the platform with its baseUrl and a hosted API key. Changing the connection does not transfer your local data. Running this for a team or organization? See Local vs. Enterprise.
Next steps
Apps Docs Self Hosting Quickstart (`apps/docs/self-hosting/quickstart.mdx`)
title: "Self-hosting quickstart" sidebarTitle: "Quickstart" icon: "/icons/hugeicons/play.svg"
Install
The installer detects your OS and architecture, downloads the right binary, verifies it, and (when run interactively) prompts you for an LLM API key. Supported platforms: macOS (Apple Silicon & Intel), Linux (x64 & arm64).
Pin or change versions
Pass an explicit version to install (or roll back to) a specific release instead of latest:
curl -fsSL https://supermemory.ai/install | bash -s -- 0.0.3Release tags are server-v<version> on GitHub Releases (for example server-v0.0.3).
To move to the newest release later:
supermemory-server upgradeThe binary may also print an “update available” notification on startup. If you intentionally pinned an older version (for example while debugging a regression), you can ignore that message until you are ready to upgrade.
Run
supermemory-serverFirst boot sets everything up — the embedded Supermemory graph engine, local embeddings, and your credentials:
╭───────────────────────────────────────────────────────────────╮
│ → supermemory ready │
│ │
│ url http://localhost:6767 │
│ database encrypted local storage (~/.supermemory) │
│ embeddings local · Xenova/bge-base-en-v1.5 · 768d │
│ workflow rivet │
│ api key sm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx │
│ org id xxxxxxxxxxxxxxxxxxxxxx │
│ boot 4.2s │
╰───────────────────────────────────────────────────────────────╯
every request needs this key: Authorization: Bearer <api key>Save that API key — it's your bearer token for every request.
For the prebuilt Docker image, follow Run Supermemory with Docker for installation, authentication, backups, and upgrades.
Add your first memory
const client = new Supermemory({
apiKey: "sm_...",
baseUrl: "http://localhost:6767",
})
await client.add("user_dhravya", {
content: "I'm Dhravya. I love building dev tools and I'm allergic to peanuts.",
dreaming: "instant",
})
</Tab>
<Tab title="Python">
```python
from supermemory import Supermemory
client = Supermemory(
api_key="sm_...",
base_url="http://localhost:6767",
)
client.add(
"user_dhravya",
content="I'm Dhravya. I love building dev tools and I'm allergic to peanuts.",
dreaming="instant",
)Search it
That's it. Everything in the Memory API — documents, memories, user profiles, spaces, filtering — works identically against your local server.
Where things live
By default, all state lives in a single directory you can back up or move:
| Path | Contents |
|---|---|
~/.supermemory/ (or $SUPERMEMORY_DATA_DIR; ./.supermemory/ before v0.0.9) |
The Supermemory graph engine's data, API key, auth secret, embedding model cache |
~/.supermemory/env.enc |
Provider keys and embedding choices saved by the first-boot wizard, encrypted and loaded on every launch |
~/.supermemory/env |
Plaintext API keys written by the installer. On launch, the server copies parsed values into env.enc before attempting to remove this file; it can remain if migration or removal fails |
Next steps
Apps Docs Smfs Overview (`apps/docs/smfs/overview.mdx`)
title: "SMFS" sidebarTitle: "Overview" icon: "/icons/hugeicons/database-01.svg"
SMFS mounts your Supermemory container as a real directory. Agents read it with ls, cat, and grep. No SDK to learn, no client to wire up, no embeddings to think about.
SMFS is open source and free for everyone.
Why a filesystem
Every model already knows how a filesystem works. It can ls, cat, grep, find, redirect with >, pipe with |. You don't have to teach it a new API surface, and the grammar carries across runtimes.
The catch: a filesystem on its own isn't great for memory. Search means walking the tree. Long files burn through context. The model has to hold the directory structure in its head. None of that scales as memory grows.
SMFS fixes the catch. The shell is real, but underneath:
- Semantic
grepby default. One call surfaces what matters across the whole container, ranked by meaning. Pass any flag and you fall through to the realgrepfor exact matches. - Memory paths get distilled. Files marked as memory paths are extracted and indexed by Supermemory. They don't bloat the model's context.
- Virtual
profile.md. A live digest of the container at the mount root. The model cancat profile.mdfor a one-shot summary instead of walking every file. - Bidirectional sync runs in the background. Local reads hit cache; writes push to Supermemory.
You get filesystem ergonomics without paying the filesystem tax in tokens.
Two ways to use SMFS
Pick by where your agent runs.
Use SMFS with your sandbox provider
Already using a sandbox or agent platform? Jump straight to the guide for your provider.
Next steps
Apps Docs Snippets Api V5 Overview (`apps/docs/snippets/api-v5-overview.mdx`)
Migrate by hand
The API base URL and bearer keys do not change.
Where parameters live: GET options go in the query string. POST, PATCH, and PUT options go in the JSON body, or as form fields on file uploads; the one exception is list pagination (page, limit, sort, order), which stays in the query string. DELETE options such as moveTo go in the query string, while bulk deletes send their ids in the JSON body. Most routes reject options sent in the wrong place with 400. DELETE /ns/{namespace} does not: it ignores a JSON body, so a moveTo sent in the body permanently deletes the namespace instead of moving it. v5 application routes are unversioned; /v5/reference is the interactive v5 reference, not an API path prefix.
Recommended migration process
Upgrade the SDK
The v5 TypeScript SDK ships as the same supermemory package. Install it, then replace the legacy client:
npm i supermemoryconst client = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
await client.add({ content: "new turn", containerTag: "user_1" })
```ts v5
import { Supermemory } from "supermemory"
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
await supermemory.add("user_1", { content: "new turn" })URL values are positional (namespace first, then id where there is one); everything else goes in one object. Legacy containerTag never appears in a body again. The Python SDK speaks v5 from 5.0.0 with the same shape and keyword arguments: client.add("user_1", content="new turn").
Endpoint map
| Legacy | v5 | SDK method |
|---|---|---|
POST /v3/documents |
POST /ns/{namespace}/document |
supermemory.add |
POST /v3/documents/batch |
POST /ns/{namespace}/document/batch |
supermemory.documents.batchAdd |
POST /v3/documents/file |
POST /ns/{namespace}/document/file |
supermemory.documents.uploadFile |
GET/PATCH /v3/documents/{id} |
GET/PATCH /ns/{namespace}/document/{id} |
supermemory.documents.get / supermemory.documents.update |
| File replace or partial file update | POST/PATCH /ns/{namespace}/document/file/{id} |
supermemory.documents.replaceWithFile / supermemory.documents.updateFile |
| Single or bulk document delete | DELETE /ns/{namespace}/document |
supermemory.documents.delete |
| Legacy document or memory lists | POST /ns/{namespace}/list/{type} |
supermemory.list |
POST /v3/search or /v4/search |
POST /ns/{namespace}/search |
supermemory.search |
POST /v4/profile |
POST /ns/{namespace}/profile |
supermemory.profile |
POST /v4/profile/buckets |
GET/PUT/DELETE /ns/{namespace}/profile/buckets |
supermemory.profiles.getBuckets / setBuckets / deleteBuckets |
| Legacy memory forget routes | DELETE /ns/{namespace}/memories... |
supermemory.memories.forget / supermemory.memories.forgetMatching |
| Container-tag settings and lifecycle | /namespaces and /ns/{namespace} |
supermemory.namespaces.list / get / update / delete |
GET/PATCH /v3/settings |
GET/PATCH /organization |
supermemory.organization.get / update |
Connectors
Connector routes also move under the namespace. Legacy containerTags arrays become one namespace per connector.
| Legacy | v5 | SDK method |
|---|---|---|
POST /v3/connections/list |
GET /ns/{namespace}/connectors or GET /connectors |
supermemory.connectors.list / supermemory.connectors.listAll |
POST /v3/connections/{provider} |
POST /ns/{namespace}/connectors |
supermemory.connectors.create |
GET /v3/connections/{connectionId} |
GET /ns/{namespace}/connectors/{id} |
supermemory.connectors.get |
POST /v3/connections/{connectionId}/configure |
PATCH /ns/{namespace}/connectors/{id} |
supermemory.connectors.update |
DELETE /v3/connections/{connectionId} |
DELETE /ns/{namespace}/connectors/{id} |
supermemory.connectors.delete |
POST /v3/connections/{provider}/import |
POST /ns/{namespace}/connectors/{id}/sync |
supermemory.connectors.sync |
const { id, authorization } = await supermemory.connectors.create("user_1", {
provider: "notion",
redirectUrl: "https://app.example.com/connected"
})authorization is null for config providers (web-crawler, s3, granola), which start syncing immediately. connectors.sync returns { id, status: "queued" } and 409 when a sync is already running.
Apps Docs V5 Api Reference Overview (`apps/docs/v5/api-reference/overview.mdx`)
title: "API reference" sidebarTitle: "Overview" icon: "/icons/hugeicons/plug-socket.svg"
import MigrateCli from "/snippets/migrate-cli.mdx";
v5 makes namespace scope explicit in the URL and consolidates overlapping legacy operations.
https://api.supermemory.ai
/ns/{namespace}/document
/ns/{namespace}/search
/ns/{namespace}/profile
/ns/{namespace}/list/{type}
/ns/{namespace}/memories
/namespaces
/organizationUse the v5 reference for the stable v5 API. /reference always points to the latest public version.
Install the TypeScript SDK
npm i supermemoryimport { Supermemory } from "supermemory"
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })URL values are positional arguments (namespace first, then id where there is one), and everything else goes in one object: supermemory.add("user_123", { content }). Each reference page below shows the SDK call next to the equivalent curl. The Python SDK (pip install supermemory, 5.0.0 and later) follows the same shape with keyword arguments: client.add("user_123", content="...").
Apps Docs Agents And Mcp (`apps/docs/agents-and-mcp.mdx`)
title: "Agents, skills and MCP" sidebarTitle: "Agents, skills and MCP" icon: "/icons/hugeicons/robotic.svg"
This page is for building with Supermemory using coding agents: scaffolding a project, following the real API, and searching product docs.
This is not the consumer memory MCP that gives Claude or Cursor long-term memory about you. That is a separate product: Supermemory MCP.
| Path | How | For |
|---|---|---|
| CLI | npx supermemory |
Setup, smoke tests, agent-driven integration |
| Skill | npx skills add … --skill supermemory |
Teach the agent the real API surface |
| Docs MCP | https://supermemory.ai/docs/mcp |
Search these docs while the agent codes |
CLI
Agents (and humans) can set things up from the terminal easily using our CLI
npx supermemoryUseful for coding agents:
npx supermemory setup # detect project, launch/print integration flow
npx supermemory setup --prompt # print integration prompt only
npx supermemory help --json # agent-readable command catalog
npx supermemory help --all
npx supermemory migrate # move a v3/v4 project to v5 with your agentAlso available for smoke tests against your key: add, search, profile, docs, namespaces, config, whoami. Auth via first-run credentials or SUPERMEMORY_API_KEY.
npx supermemory add "User prefers TypeScript" --namespace user_123
npx supermemory search "language preference" --namespace user_123
npx supermemory profile --namespace user_123Skill
Install the official skill so the agent uses the real endpoints, auth, and namespace rules instead of hallucinating APIs:
npx skills add https://github.com/supermemoryai/skills --skill supermemorySource: github.com/supermemoryai/skills.
Docs MCP
Remote MCP that lets the agent search Supermemory documentation while it implements an integration.
Server URL:
https://supermemory.ai/docs/mcpSetup by client
```json
{
"mcpServers": {
"supermemory-docs": {
"url": "https://supermemory.ai/docs/mcp"
}
}
}
```Or project `.mcp.json`:
```json
{
"mcpServers": {
"supermemory-docs": {
"type": "http",
"url": "https://supermemory.ai/docs/mcp"
}
}
}
```Or `~/.codex/config.toml`:
```toml
[mcp_servers.supermemory-docs]
url = "https://supermemory.ai/docs/mcp"
``````json
{
"servers": {
"supermemory-docs": {
"type": "http",
"url": "https://supermemory.ai/docs/mcp"
}
}
}
```Stdio-only clients can proxy:
```json
{
"mcpServers": {
"supermemory-docs": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://supermemory.ai/docs/mcp"]
}
}
}
```Starter prompt (docs + setup)
You are integrating Supermemory into my app.
- Use the supermemory-docs MCP (or https://supermemory.ai/docs/llms.txt) before inventing endpoints.
- Prefer `npx supermemory setup` / the supermemory skill for correct auth, namespace, and SDK usage.
- Canonical writes: POST /ns/{namespace}/document · search: POST /ns/{namespace}/search · profile: POST /ns/{namespace}/profile
- Auth: Authorization: Bearer $SUPERMEMORY_API_KEY only
- Always scope with one namespace in the URL path on write and search; there is no namespace field in the body
- SDK: import { Supermemory } from "supermemory"; version 5 or later; the namespace is the first argument: client.add(namespace, { content }), client.search(namespace, { query })
- For demos use dreaming: "instant" when memories must be ready right after status done
- Already on Supermemory v3/v4? Run `npx supermemory@latest migrate` instead of rewriting calls by handIntegrate prompt (optional)
If the skill is not installed, paste a fuller prompt so the agent asks the right product questions:
Memory MCP (different product)
Want your assistant to remember you across chats (save/recall/profile in Claude, Cursor, etc.)? That is the Memory MCP, not the docs MCP:
Next steps
Apps Docs Authentication (`apps/docs/authentication.mdx`)
title: "API keys & auth" sidebarTitle: "API keys" icon: "/icons/hugeicons/key-01.svg"
API keys
All API requests require authentication using a Bearer token. Get your API key from the Developer Platform.
Include your key in all requests:
curl -X POST https://api.supermemory.ai/ns/user_123/search \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
-d '{"query": "hello"}'import { Supermemory } from "supermemory";
const supermemory = new Supermemory({ apiKey: "YOUR_API_KEY" });from supermemory import Supermemory
client = Supermemory(api_key="YOUR_API_KEY")Connector branding
When users connect external services (Google Drive, Notion, OneDrive), they see a "Log in to Supermemory" prompt by default. You can replace this with your own app name by providing your own OAuth credentials.
This works for Google Drive, Notion, and OneDrive. See the full setup in Customization.
Scoped API keys
Scoped keys are restricted to one namespace (what v3/v4 called a container tag), or to a fixed list of namespaces. They can read and write content only inside those namespaces, subject to the key's read/write permission and member restrictions — use them to give a client, session, or tenant limited access without shipping your org master key.
Pairs with namespaces for multi-tenant isolation.
Allowed endpoints: /ns/{namespace}/document, /ns/{namespace}/list, /ns/{namespace}/search, /ns/{namespace}/profile, and /ns/{namespace}/memories; /namespaces and /ns/{namespace}; and the legacy /v3/documents, /v3/memories, /v4/memories, /v4/conversations, /v3/search, /v4/search, /v4/profile, /v3/container-tags/list, and /v3/session routes. Any other route returns 403.
Scoped keys cannot read billing, manage org settings, or mint further keys.
Create a scoped key
The scoped-key endpoint still takes the namespace in a field named containerTag.
curl https://api.supermemory.ai/v3/auth/scoped-key \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
-d '{
"containerTag": "my-project",
"name": "my-key-name",
"expiresInDays": 30
}'Parameters
| Parameter | Required | Default | Description |
|---|---|---|---|
containerTag |
Yes, unless containerTags is set |
— | The namespace to scope the key to. Up to 100 characters: letters, numbers, hyphens, underscores, colons |
containerTags |
No | — | Scope the key to several namespaces instead of one. Takes precedence over containerTag |
name |
No | scoped_{containerTag} |
Display name for the key |
expiresInDays |
No | — | 1–365 days |
rateLimitMax |
No | 500 |
Max requests per window (1–10,000) |
rateLimitTimeWindow |
No | 60000 |
Window in milliseconds (1–3,600,000) |
Response
{
"key": "sm_orgId_...",
"id": "key-id",
"name": "scoped_my-project",
"containerTag": "my-project",
"containerTags": ["my-project"],
"expiresAt": "2026-03-08T00:00:00.000Z",
"allowedEndpoints": ["/v3/session", "/v3/documents", "/v3/memories", "/v4/memories", "/v4/conversations", "/ns/*/memories", "/v3/search", "/v4/search", "/v4/profile", "/v3/container-tags/list", "/ns/*/document", "/ns/*/list", "/ns/*/search", "/ns/*/profile", "/namespaces$", "/ns$", "/ns/*$"]
}Use the returned key like a normal API key — it just will not work outside its namespace.
Disable a scoped key
Revoke with the id from creation. Subsequent requests get 401. Memories and namespaces are not deleted.
curl https://api.supermemory.ai/v3/auth/scoped-key/KEY_ID \
--request DELETE \
--header 'Authorization: Bearer YOUR_API_KEY'{ "success": true }Apps Docs Install (`apps/docs/install.md`)
You are integrating Supermemory into my application. Supermemory provides user memory, semantic search, and automatic knowledge extraction for AI applications.
You can always reference the documentation by using the SearchSupermemoryDocs MCP or running a web search tool for content on supermemory.ai/docs.
STEP 1: ASK ME THESE QUESTIONS
What are you building?
- Personal chatbot/assistant
- Team knowledge base
- Customer support bot
- Document Q&A
- Other
How do you want to integrate?
- Vercel AI SDK (@supermemory/tools)
- OpenAI plugins
- Direct SDK (supermemory npm/pip)
- Direct API calls
Data model?
- Individual users only → containerTag: userId
- Organizations only → containerTag: orgId
- Both users AND orgs → ask for strategy
Do you want USER PROFILES?
User profiles are automatically-maintained facts about users (what they like, what they're working on, preferences).- Yes (RECOMMENDED) → Use client.profile() to get context
- No → Just use search
How should I retrieve context?
- OPTION A: One call with search included →
profile({ containerTag, q: userMessage }) - OPTION B: Separate calls →
profile()for facts,search()for memories
- OPTION A: One call with search included →
STEP 2: INSTALL
# Get API key: https://console.supermemory.ai
npm install supermemory # or: pip install supermemory
# For Vercel AI SDK: npm install @supermemory/tools
export SUPERMEMORY_API_KEY="sm_..."STEP 3: CONFIGURE SETTINGS (DO THIS FIRST)
// PATCH https://api.supermemory.ai/v3/settings
fetch('https://api.supermemory.ai/v3/settings', {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${process.env.SUPERMEMORY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
shouldLLMFilter: true,
filterPrompt: `This is a [your app description]. containerTag is [userId/orgId]. We store [what data].`
})
})STEP 4: CONTAINER TAG STRATEGY
Based on their data model answer:
USER-ONLY APP:
containerTag: userIdORG-ONLY APP:
containerTag: orgId // Org members share memoriesBOTH (ask which):
// Option A: Unique per user-org combination
containerTag: `${userId}-${orgId}`
// Option B: Org-scoped with user metadata
containerTag: orgId, metadata: { userId }
// Option C: User-scoped with org metadata
containerTag: userId, metadata: { orgId }STEP 5: INTEGRATION CODE
Based on their integration choice:
VERCEL AI SDK
import { streamText } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'
import { supermemoryTools } from '@supermemory/tools/ai-sdk'
// Option 1: Agent tools (recommended for agentic flows)
const result = await streamText({
model: anthropic('claude-3-5-sonnet-20241022'),
prompt: userMessage,
tools: supermemoryTools(process.env.SUPERMEMORY_API_KEY, {
containerTags: [userId]
})
})
// Agent gets searchMemories, addMemory, fetchMemory tools
// Option 2: Profile middleware (automatic context injection)
import { withSupermemory } from '@supermemory/tools/ai-sdk'
const modelWithMemory = withSupermemory(anthropic('claude-3-5-sonnet-20241022'), {
containerTag: userId,
customId: 'conversation-1',
})
const result = await generateText({
model: modelWithMemory,
messages: [{ role: 'user', content: userMessage }]
})
// Profile is automatically injected into contextDIRECT SDK (WITH PROFILES)
import Supermemory from 'supermemory'
const client = new Supermemory()
// Before each LLM call:
const { profile, searchResults } = await client.profile({
containerTag: userId,
q: userMessage // Include this if they chose OPTION A (one call)
// Omit if they chose OPTION B (separate calls)
})
// Build context
const context = `
Static facts: ${profile.static.join('\n')}
Recent context: ${profile.dynamic.join('\n')}
${searchResults ? `Memories: ${searchResults.results.map(r => r.content).join('\n')}` : ''}
`
// Send to LLM
const messages = [
{ role: 'system', content: `User context:\n${context}` },
{ role: 'user', content: userMessage }
]
// After LLM responds:
await client.memories.add({
content: `user: ${userMessage}\nassistant: ${response}`,
containerTag: userId
})DIRECT SDK (NO PROFILES)
import Supermemory from 'supermemory'
const client = new Supermemory()
// Search for relevant memories
const results = await client.search({
q: userMessage,
containerTag: userId,
searchMode: 'hybrid', // Searches memories + document chunks
limit: 5
})
// Build context
const context = results.results.map(r => r.content).join('\n')
// Send to LLM with context
const messages = [
{ role: 'system', content: `Relevant context:\n${context}` },
{ role: 'user', content: userMessage }
]
// Store the conversation
await client.memories.add({
content: `user: ${userMessage}\nassistant: ${response}`,
containerTag: userId
})PYTHON VERSION
from supermemory import Supermemory
client = Supermemory()
# With profiles (if they want it)
profile_data = client.profile(
container_tag=user_id,
q=user_message # Include if OPTION A, omit if OPTION B
)
context = f"""
Static: {chr(10).join(profile_data.profile.static)}
Dynamic: {chr(10).join(profile_data.profile.dynamic)}
"""
# Store conversation
client.add(content=f"user: {user_message}\\nassistant: {response}", container_tag=user_id)DIRECT API
# Add memory
curl -X POST https://api.supermemory.ai/v3/documents \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content": "conversation", "containerTag": "userId"}'
# Get profile
curl -X POST https://api.supermemory.ai/v4/profile \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"containerTag": "userId", "q": "search query"}'
# Search
curl -X POST https://api.supermemory.ai/v4/search \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"q": "query", "containerTag": "userId", "searchMode": "hybrid"}'STEP 6: FILE UPLOADS (if they need it)
// Files are automatically extracted (PDFs, images with OCR, videos with transcription)
const formData = new FormData()
formData.append('file', fileBlob)
formData.append('containerTag', userId)
await fetch('https://api.supermemory.ai/v3/documents/file', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SUPERMEMORY_API_KEY}`,
'Content-Type': 'application/json',
},
body: formData
})
// Processing is async - check status before assuming searchable
// GET /v3/documents/{documentId}STEP 7: SEARCH MODES
// HYBRID (recommended) - searches memories + document chunks
searchMode: 'hybrid'
// MEMORIES ONLY - just extracted memories, no original text
searchMode: 'memories'STEP 8: METADATA FILTERS (if they need secondary filtering)
await client.search({
q: query,
containerTag: userId,
filters: {
AND: [
{ key: 'type', value: 'conversation', type: 'string_equal' },
{ key: 'timestamp', value: '2024', type: 'string_contains' }
]
}
})KEY POINTS:
- Configure settings FIRST with filterPrompt
- User profiles = automatic facts about users (profile.static + profile.dynamic)
- profile({ containerTag, q }) combines profile + search in ONE call
- Search modes: 'hybrid' (recommended) or 'memories'
- File extraction is automatic - no config needed
- Store conversations after each interaction
- containerTag should match what you put in filterPrompt
TESTING:
# 1. Configure settings
curl -X PATCH https://api.supermemory.ai/v3/settings \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"shouldLLMFilter": true, "filterPrompt": "..."}'
# 2. Add test memory
curl -X POST https://api.supermemory.ai/v3/documents \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content": "Test", "containerTag": "test_user"}'
# 3. Get profile
curl -X POST https://api.supermemory.ai/v4/profile \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"containerTag": "test_user"}'NOW:
- Ask me the 5 questions above
- Generate complete working code based on my answers
- Include installation, settings config, and full integration
DOCS: https://supermemory.ai/docs
Apps Docs Invite Team Members (`apps/docs/invite-team-members.mdx`)
title: "Invite team members" sidebarTitle: "Invite Team Members" contentType: "How-to" icon: "/icons/hugeicons/user-multiple.svg"
Invite colleagues from the Supermemory console so they can access your organization’s workspace. Choose their role and access, then share an invitation link they can accept with their own account.
Before you begin
Check these requirements before you invite someone:
- You’re an Owner or Admin in the organization.
- Your organization has a plan that supports team invitations. Pro and higher include this feature.
- You have an available member seat. Members and unexpired pending invitations count toward your limit.
- You know the email address your colleague uses to sign in to Supermemory.
If Invite member is disabled, hover over it to check the plan or member limit. Open Billing to upgrade if needed.
Create and share an invitation
Create the invitation in the organization your colleague needs to join:
<Frame caption="Team settings lists members and pending invitations. This screenshot uses a local Acme demo organization.">
</Frame>- **Member**: Use this role for workspace access without team or billing administration.
- **Admin**: Use this role for full access, including members, billing, and settings.
For a **Member**, keep **Full access** to include all containers. On Scale or Enterprise, choose **Restricted** to limit access to selected [container tags](/concepts/container-tags). Add at least one tag and select **Read** or **Write** for each tag.
<Frame caption="The Invite member dialog defaults to Member and Full access.">
</Frame>The invitation expires after seven days. To cancel it, click the **×** at the end of its row.Accept the invitation
Ask your colleague to complete these steps:
- Open the invitation link you shared.
- Log in or create an account with the email address you invited.
- Check the organization name and role, then click ACCEPT.
If they see This invitation is not for you, ask them to click LOG IN WITH A DIFFERENT ACCOUNT. They need to sign in with the invited email address. If the invitation has expired or you canceled it, create a new invitation and share its new link.
Return to Settings → Team and confirm your colleague appears under Members. Their invitation no longer appears under Pending invitations after they accept.
Apps Docs Using Supermemory (`apps/docs/using-supermemory.mdx`)
title: "Using Supermemory" sidebarTitle: "Overview" icon: "/icons/hugeicons/compass-01.svg"
import { Journey, JourneyStep, JourneyItem } from "/snippets/journey.mdx";
Everything in this section is one of four steps. Same loop whether you're building personal memory, RAG over docs, or both on the same namespace.
Raw input automatically becomes memories (the knowledge graph) and indexed document chunks (for RAG). You don't choose one or the other; both come from the same write. Retrieval then gives you three ways to read that pool back. If you're unsure which fits, read Memory vs RAG.
Where next
Apps Docs Api Reference Connections (`apps/docs/api-reference/connections.mdx`)
title: "Connections" sidebarTitle: "Overview" icon: "/icons/hugeicons/book-open-01.svg"
Connections pull content from Notion, Google Drive, Gmail, OneDrive, S3, GitHub, and more.
| Area | Endpoints |
|---|---|
| Create / delete | POST/DELETE /v3/connections/{provider} |
| List / get | POST /v3/connections/list, GET …/{connectionId} |
| Configure / resources | POST …/configure, GET …/resources |
| Sync / documents | POST …/import, POST …/documents |
Guides: Connectors overview · provider pages under Connectors
Apps Docs Api Reference Container Tags (`apps/docs/api-reference/container-tags.mdx`)
title: "Container tags" sidebarTitle: "Overview" icon: "/icons/hugeicons/book-open-01.svg"
containerTag is the primary multi-tenant key (user id, workspace id, etc.). These endpoints manage settings and lifecycle for a tag.
| Endpoint | Use when |
|---|---|
GET /v3/container-tags/{containerTag} |
Read tag settings |
PATCH /v3/container-tags/{containerTag} |
Update tag settings |
DELETE /v3/container-tags/{containerTag} |
Delete a container and its data |
POST /v3/container-tags/merge |
Merge exactly two listed tags into either listed tag or a new target tag |
GET /v3/container-tags/merge/{mergeId} |
Poll merge status |
Guide: Container tags · Filtering
Apps Docs Api Reference Documents (`apps/docs/api-reference/documents.mdx`)
title: "Documents" sidebarTitle: "Overview" icon: "/icons/hugeicons/book-open-01.svg"
Documents are the unit of ingestion. Adds return immediately with status: "queued"; poll until done before relying on search or profiles.
| Endpoint | Use when |
|---|---|
GET /v3/documents/{id} |
Status + metadata for one document |
POST /v3/documents/list |
Filter and paginate documents |
GET /v3/documents/processing |
Currently processing items |
PATCH /v3/documents/{id} |
Update content or metadata |
DELETE /v3/documents/{id} |
Delete by id or customId |
DELETE /v3/documents/bulk |
Bulk delete |
GET /v3/documents/{id}/chunks |
Inspect RAG chunks |
GET /v3/documents/{id}/file-url |
Presigned URL for uploaded files |
Guide: Document operations
Apps Docs Api Reference Ingest (`apps/docs/api-reference/ingest.mdx`)
title: "Ingest" sidebarTitle: "Overview" icon: "/icons/hugeicons/book-open-01.svg"
Send raw content into the processing pipeline. Supermemory extracts memories, chunks for RAG, and updates profiles asynchronously.
| Endpoint | Use when |
|---|---|
POST /v3/documents |
Text, URLs, or structured content |
POST /v3/documents/file |
Binary file upload |
POST /v3/documents/batch |
Many documents in one request |
POST /v4/conversations |
Chat sessions with turn-aware ingest |
Guides: Add memories · Quickstart
Apps Docs Api Reference Memories (`apps/docs/api-reference/memories.mdx`)
title: "Memories" sidebarTitle: "Overview" icon: "/icons/hugeicons/book-open-01.svg"
These endpoints operate on extracted memories, not raw documents.
| Endpoint | Use when |
|---|---|
POST /v4/memories |
Write memories directly (skip document pipeline) |
POST /v4/memories/list |
List with history / versions |
PATCH /v4/memories |
Update (creates a new version) |
DELETE /v4/memories |
Forget a specific memory |
POST /v4/memories/forget-matching |
Forget by natural-language match |
For document-level CRUD, use Documents. For pipeline ingest, use Ingest.
Guide: Memory operations
Apps Docs Api Reference Profiles (`apps/docs/api-reference/profiles.mdx`)
title: "Profiles" sidebarTitle: "Profiles overview" icon: "/icons/hugeicons/id.svg"
Profiles summarize what Supermemory knows about a user or entity in a containerTag.
| Endpoint | Use when |
|---|---|
POST /v4/profile |
Fetch static + dynamic profile for a container |
POST /v4/profile/buckets |
Profile organized by custom buckets |
Guides: User profiles API · Concepts · Buckets
Apps Docs Api Reference Search (`apps/docs/api-reference/search.mdx`)
title: "Recall" sidebarTitle: "Overview" icon: "/icons/hugeicons/book-open-01.svg"
Get context back out of Supermemory: search extracted memories / documents, or fetch a user profile.
| Endpoint | Role |
|---|---|
POST /v4/search |
Primary recall — searchMode: memories, documents, or hybrid |
POST /v3/search |
Document / SuperRAG-oriented search |
POST /v4/profile |
Static + dynamic profile for a container |
POST /v4/profile/buckets |
Profile organized by custom buckets |
Prefer v4 with searchMode: "hybrid" unless you only need document chunks or only extracted memories.
Guides: Search · User profiles · SuperRAG · Memory vs RAG
Apps Docs Api Reference Settings (`apps/docs/api-reference/settings.mdx`)
title: "Settings" sidebarTitle: "Overview" icon: "/icons/hugeicons/book-open-01.svg"
Org-level configuration for extraction, customization, and profile buckets.
| Endpoint | Use when |
|---|---|
GET /v3/settings |
Read org settings |
PATCH /v3/settings |
Update org settings |
POST /v3/settings/suggest-buckets |
Suggest profile buckets |
POST /v3/settings/reset |
Reset organization data (destructive) |
Guide: Customization
Apps Docs Concepts Container Tags (`apps/docs/concepts/container-tags.mdx`)
title: "Namespaces" sidebarTitle: "Namespaces" icon: "/icons/hugeicons/folder-01.svg" keywords: ["container tag", "container tags", "containerTag", "containerTags", "namespace"]
A namespace is the primary way you organize and isolate memories in Supermemory. It is a string identifier you choose, and it scopes every call: add, search, list, update, and delete all happen inside one namespace.
Every memory in user_alex lives in its own isolated space, completely separate from memories in user_jordan. This is what makes Supermemory safe to use in multi-tenant applications: one user can never see another user's memories unless you explicitly query both namespaces.
How it works
When you add a document to a namespace, Supermemory creates that namespace (scoped to your organization) if it does not exist yet. You don't need to provision anything ahead of time. The first write to a new namespace creates it, and later writes reuse it.
import { Supermemory } from "supermemory";
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY });
// First call auto-creates the "user_alex" namespace
await supermemory.add("user_alex", {
content: "Alex prefers dark mode and concise answers",
});
// Later, retrieve only Alex's memories
const results = await supermemory.search("user_alex", {
query: "what are the user's UI preferences?",
});curl -X POST "https://api.supermemory.ai/ns/user_alex/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "Alex prefers dark mode and concise answers" }'
curl -X POST "https://api.supermemory.ai/ns/user_alex/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "query": "what are the user'\''s UI preferences?" }'Under the hood, each namespace is backed by a dedicated vector index. Embeddings, chunks, and memory entries for one namespace are stored and searched independently of every other namespace. There is no shared index to filter through, which is why isolation is strict rather than best-effort.
Naming rules
Namespaces are validated on every request. A namespace must:
- Be 100 characters or less
- Contain only alphanumeric characters, hyphens (
-), underscores (_), and colons (:)
Matching pattern: ^[a-zA-Z0-9_:-]+$
// ✅ Valid
"user_123"
"project-mobile-app"
"org:acme:user:john"
"tenant_42_workspace_7"
// ❌ Invalid — spaces, slashes, and other symbols are rejected
"user 123"
"project/mobile"
"team@acme"The colon is intentionally allowed so you can build hierarchical namespaces (for example org:acme:user:john) that encode several levels of structure in a single identifier.
Scope lives in the URL
In v5 the namespace is a path segment, not a body field. Every scoped endpoint starts with /ns/{namespace}/, and every scoped SDK call takes namespace as its first argument.
| Legacy (v3/v4) | v5 |
|---|---|
containerTag string in the request body |
/ns/{namespace} in the URL |
containerTags array |
Not supported. One namespace per request. |
Where namespaces are used
The same namespace flows through the entire lifecycle of a memory. Pass it consistently and your data stays neatly partitioned.
| Operation | Behavior |
|---|---|
| Add | Writes the document into the namespace (auto-creating it). |
| Search | Restricts retrieval to the namespace. |
| List | Returns only documents, chunks, or memories in the namespace. |
| Update / Delete | Targets the document inside the namespace. |
// Add
await supermemory.add("project_q1", { content: "Q1 planning notes" });
// Search within the same namespace
await supermemory.search("project_q1", { query: "planning" });
// List every document in the namespace
await supermemory.list("project_q1", "documents");Access control
Namespaces are also an authorization boundary, not just an organizational one. Two mechanisms can restrict which namespaces a given caller may touch:
- API key scopes — an API key can be limited to a specific set of namespaces, and a key can be read-only.
- Member restrictions — an organization member can be granted access to only certain namespaces, with read or write permission per namespace.
When a request is restricted, Supermemory validates the namespace in the URL against the caller's allowed set:
- Requesting a namespace outside the allowed set returns
403 Forbidden. - A write (add/update/delete) to a read-only namespace returns
403 Forbidden.
This means you can hand out an API key that is physically incapable of reading or writing another tenant's data, enforced at the data layer rather than in your application code.
Managing namespaces
The namespaces.* SDK methods read and change a namespace itself, as opposed to the documents inside it.
| Method | What it does |
|---|---|
supermemory.namespaces.list() |
Page through namespaces with documentCount and memoryCount. |
supermemory.namespaces.get(namespace) |
Read one namespace and its supportingContext. |
supermemory.namespaces.update(namespace, { supportingContext }) |
Set a context prompt applied when processing documents in this namespace. Useful for steering extraction per project or tenant. |
supermemory.namespaces.delete(namespace) |
Delete the namespace, its namespace-owned memory records, and documents exclusive to it. Shared documents are retained. |
supermemory.namespaces.delete(namespace, { moveTo }) |
Move the content into another namespace, then remove this one. Queued; returns an operationId. |
supportingContext is what v3/v4 called entityContext. The legacy "merge" operation is now a delete with moveTo, one source namespace per request.
Namespace deletion and moves require an org admin or owner with write access to the source namespace; moves also require write access to the destination. The default sm_project_default namespace cannot be permanently deleted. See deletion scope and erasure limits.
await supermemory.namespaces.update("project_research", {
supportingContext: "This project contains research papers about machine learning.",
});
const { namespaces, pagination } = await supermemory.namespaces.list();
// namespaces: [{ id, namespace, documentCount, memoryCount, description, system }]
// Consolidate: move everything from the old namespace, then remove it
await supermemory.namespaces.delete("project_research_old", {
moveTo: "project_research",
});
// { status: "queued", operationId, namespace, moveTo }curl -X PATCH "https://api.supermemory.ai/ns/project_research" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "supportingContext": "This project contains research papers about machine learning." }'
curl "https://api.supermemory.ai/namespaces" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
curl -X DELETE "https://api.supermemory.ai/ns/project_research_old?moveTo=project_research" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"Choosing a convention
Pick a naming scheme that maps onto the isolation boundaries your application actually needs.
| Pattern | Example | Use case |
|---|---|---|
| User isolation | user_{userId} |
Per-user memory in a consumer app |
| Project grouping | project_{projectId} |
Project- or workspace-scoped content |
| Agent scoping | agent_{agentId} |
Separate long-term memory per AI agent |
| Hierarchical | org:{orgId}:user:{userId} |
Multi-level, multi-tenant SaaS |
Next steps
Apps Docs Concepts Content Types (`apps/docs/concepts/content-types.mdx`)
title: "Supported content types" sidebarTitle: "Multi-modal ingestion" icon: "/icons/hugeicons/files-01.svg"
Supermemory automatically extracts and indexes content from various formats. There are two entry points: supermemory.add() for text and URLs, supermemory.documents.uploadFile() for actual files. Both take the target namespace as the first argument. See Add Memories to learn how to ingest content via the API.
Text content
Raw text, conversations, notes, or any string content.
await supermemory.add("user_123", {
content: "User prefers dark mode and uses vim keybindings",
});Best for: Chat messages, user preferences, notes, logs, transcripts.
URLs & Web pages
Send a URL and Supermemory fetches, extracts, and indexes the content.
await supermemory.add("documentation", {
content: "https://docs.example.com/api-reference"
});Extracts: Article text, headings, metadata. Strips navigation, ads, boilerplate.
Documents
Files are binary, so they go through uploadFile, not add — pass a stream, not base64. The upload is multipart, so metadata is a JSON string here:
import fs from 'fs';
await supermemory.documents.uploadFile("user_123", {
file: fs.createReadStream('report.pdf'),
metadata: JSON.stringify({ title: "Q4 Financial Report" }),
});Extracts: Text, tables, headers. OCR for scanned documents.
Advanced document extraction
Scale and Enterprise include advanced extraction for PDFs: page-by-page OCR with descriptions of figures and diagrams merged into the extracted text. This helps make the visual content in reports, research papers, and technical documents searchable alongside their text.
Free, Pro, and Max continue to support standard PDF extraction, including OCR for scanned documents. The advanced extraction tier applies to PDFs; it does not change support for other file types.
Use the same uploadFile call shown above. Supermemory selects the extraction tier automatically from your organization's plan; no additional request parameter is needed. If advanced extraction cannot process a PDF, Supermemory falls back to standard extraction, so figure and diagram descriptions are not guaranteed for every document.
See Billing & usage for plan availability.
Microsoft Office
Word, Excel, and PowerPoint files upload the same way — Supermemory detects the type from the file itself:
await supermemory.documents.uploadFile("user_123", {
file: fs.createReadStream('roadmap.docx'),
metadata: JSON.stringify({ title: "Product Roadmap" }),
});Google Workspace
Automatically handled via Google Drive connector:
- Google Docs
- Google Sheets
- Google Slides
Code & markdown
Both are plain text, so they go through add like any other string content — no file upload needed:
// Markdown
await supermemory.add("user_123", {
content: markdownContent,
metadata: { title: "README.md" },
});
// Code (language auto-detected)
await supermemory.add("user_123", {
content: codeContent,
metadata: { language: "typescript" },
});Extracts: Structure, headings, code blocks with syntax awareness.
See Super RAG for how Supermemory optimizes chunking for each content type.
Images
Set fileType: "image" and mimeType so Supermemory knows exactly how to process it. Both are multipart form fields, sent in the same object as file and metadata:
await supermemory.documents.uploadFile("user_123", {
file: fs.createReadStream('diagram.png'),
metadata: JSON.stringify({ title: "Architecture Diagram" }),
fileType: "image",
mimeType: "image/png",
});Extracts: OCR text, visual descriptions, diagram interpretations.
Supported: PNG, JPG, JPEG, WebP, GIF
Audio & video
Video and audio each have a dedicated fileType ("video", "audio"); set it with mimeType when inference is not enough:
// Video
await supermemory.documents.uploadFile("user_123", {
file: fs.createReadStream('demo.mp4'),
metadata: JSON.stringify({ title: "Product Demo" }),
fileType: "video",
mimeType: "video/mp4",
});
// Audio
await supermemory.documents.uploadFile("user_123", {
file: fs.createReadStream('call-recording.mp3'),
metadata: JSON.stringify({ title: "Customer Call Recording" }),
fileType: "audio",
mimeType: "audio/mpeg",
});Extracts: Transcription, speaker detection, topic segmentation.
Supported: MP3, WAV, M4A, MP4, WebM
Structured data
JSON and CSV are text — stringify and send them through add(), no file upload needed.
JSON
await supermemory.add("user_123", {
content: JSON.stringify(userData),
metadata: { title: "User Profile Data", format: "json" },
});CSV
await supermemory.add("user_123", {
content: csvContent,
metadata: { title: "Sales Data Q4", format: "csv" },
});File upload
For any binary file, use uploadFile — it accepts a stream, not base64:
import fs from 'fs';
await supermemory.documents.uploadFile("user_123", {
file: fs.createReadStream('./document.pdf'),
metadata: JSON.stringify({ title: "document.pdf" }),
});No Node fs access? uploadFile also accepts a web File or Blob:
await supermemory.documents.uploadFile("user_123", {
file: new File(['my bytes'], 'file.txt'),
});
await supermemory.documents.uploadFile("user_123", {
file: await fetch('https://somesite/file').then((r) => r.blob())
});Auto-Detection
add() tells URLs and plain text apart on its own — no extra flag needed:
// URL detected automatically
await supermemory.add("user_123", { content: "https://example.com/page" });
// Plain text detected automatically
await supermemory.add("user_123", { content: "User said they prefer email contact" });For files, uploadFile detects type from the file itself in most cases. fileType and mimeType only exist to force specific processing when inference is not enough — set them for images, video, and audio.
Content limits
| Type | Max Size |
|---|---|
| Text | 1MB |
| Files | 50MB |
| URLs | Fetched content up to 10MB |
Typical processing time: text is near-instant; PDFs take 1-5s; images 2-10s; video 10s+; webpages 1-3s. Text content is split on sentence boundaries, with about 200 characters of overlap carried between chunks.
Next steps
Apps Docs Concepts Customization (`apps/docs/concepts/customization.mdx`)
title: "Customizing for your use case" sidebarTitle: "Customizing" icon: "/icons/hugeicons/settings-02.svg"
Configure how Supermemory processes and retrieves content for your specific use case.
Organizational Context
Tell Supermemory what your organization does and what content matters during ingestion. This steers what gets extracted into memories across every namespace. In v3/v4 this field was called filterPrompt.
// Example: Brand guidelines assistant
await supermemory.organization.update({
organizationalContext: `You are ingesting content for Brand.ai's brand guidelines system.
Index:
- Official brand values and mission statements
- Approved tone of voice guidelines
- Logo usage and visual identity docs
- Approved messaging and taglines
Skip:
- Draft documents and work-in-progress
- Outdated brand materials (pre-2024)
- Internal discussions about brand changes
- Competitor analysis docs`,
});The value replaces the existing context in full. Send null to clear it. The legacy shouldLLMFilter, categories, includeItems, and excludeItems settings are not part of the v5 organization resource.
Supporting Context
Guide memory extraction for a specific namespace. Organizational context is org-wide; supporting context is per namespace. In v3/v4 this field was called entityContext.
await supermemory.add("session_abc123", {
content: "User asked about logo variations for dark backgrounds...",
supportingContext: `Design exploration conversation between [email protected] and Brand.ai assistant.
Focus on John's design preferences and brand requirements.`,
});await supermemory.namespaces.update("session_abc123", {
supportingContext: `Design exploration conversation between [email protected] and Brand.ai assistant.
Focus on John's design preferences and brand requirements.`,
});Chunk size
Control how documents are split into searchable pieces. Smaller chunks = more precise retrieval but less context per result.
await client.settings.update({
chunkSize: 512 // -1 for default
});| Use Case | Chunk Size | Why |
|---|---|---|
| Citations & references | 256-512 |
Precise source attribution |
| Q&A / Support | 512-1024 |
Balanced context |
| Long-form analysis | 1024-2048 |
More context per chunk |
| Default | -1 |
Supermemory's optimized default |
Connector branding
Show "Log in to YourApp" instead of "Log in to Supermemory" when users connect external services. See Connectors Overview for the full list of supported integrations.
```typescript
await client.settings.update({
googleDriveCustomKeyEnabled: true,
googleDriveClientId: "your-client-id.apps.googleusercontent.com",
googleDriveClientSecret: "your-client-secret"
});
``````typescript
await client.settings.update({
notionCustomKeyEnabled: true,
notionClientId: "your-notion-client-id",
notionClientSecret: "your-notion-client-secret"
});
``````typescript
await client.settings.update({
onedriveCustomKeyEnabled: true,
onedriveClientId: "your-azure-app-id",
onedriveClientSecret: "your-azure-client-secret"
});
```API reference
// Read organization settings
const { organizationalContext, namespaceCount } = await supermemory.organization.get();
// Update organization context
await supermemory.organization.update({ organizationalContext: "..." });
// Read and update one namespace
const ns = await supermemory.namespaces.get("session_abc123");
await supermemory.namespaces.update("session_abc123", {
supportingContext: "...",
});Next steps
Apps Docs Concepts Filtering (`apps/docs/concepts/filtering.mdx`)
title: "Organizing & filtering memories" sidebarTitle: "Metadata filtering" icon: "/icons/hugeicons/filter.svg"
Supermemory provides two ways to organize your memories:
Both can be used independently or together for precise filtering.
Namespaces
Namespaces create isolated memory spaces. Use them to separate memories by user, project, or any logical boundary. The namespace is part of the URL (/ns/{namespace}/...), so namespace-scoped SDK calls take it as the first argument.
Adding Memories to a Namespace
await supermemory.add("user_123", {
content: "Meeting notes from Q1 planning",
});Searching a Namespace
const results = await supermemory.search("user_123", {
query: "planning notes",
searchMode: "chunks",
});Recommended patterns
| Pattern | Example | Use Case |
|---|---|---|
| User isolation | user_{userId} |
Per-user memories |
| Project grouping | project_{projectId} |
Project-specific content |
| Hierarchical | org_{orgId}_team_{teamId} |
Multi-level organization |
// Search only within that user's org context
const results = await supermemory.search("org_acme_user_john", {
query: "vacation policy",
searchMode: "chunks",
});
// Project-based isolation
await supermemory.add("project_mobile_app", {
content: "Sprint 5 retrospective notes",
});
// Time-based segmentation
await supermemory.add("user_cfo_2024_q1", {
content: "Q1 2024 financial report",
});
```
The namespace is a path parameter on every v5 call (`add`, `search`, `list`, `profile`, `documents.*`). There is no array form; one request touches one namespace.Metadata
Metadata lets you attach custom properties to memories and filter by them later.
Adding memories with metadata
await supermemory.add("user_123", {
content: "Technical design document for auth system",
metadata: {
category: "engineering",
priority: "high",
year: 2024,
},
});Searching with a Filter
A filter is a typed expression. One condition is an object with field, operator, and value. Several conditions are combined with an and or or node that lists them in operands:
const results = await supermemory.search("user_123", {
query: "design document",
filter: {
operator: "and",
operands: [
{ field: "category", operator: "eq", value: "engineering" },
{ field: "priority", operator: "eq", value: "high" },
],
},
searchMode: "chunks",
});A single condition needs no wrapper:
filter: { field: "status", operator: "eq", value: "published" }Operators
| Operator | Value type | Example |
|---|---|---|
eq, neq |
string, number, boolean | { field: "status", operator: "eq", value: "published" } |
gt, gte, lt, lte |
number | { field: "priority", operator: "gte", value: 5 } |
contains, notContains |
string | { field: "title", operator: "contains", value: "react" } |
arrayContains, arrayNotContains |
string | { field: "tags", operator: "arrayContains", value: "important" } |
String conditions accept an optional caseSensitive boolean. Numeric values are JSON numbers, not numeric strings.
Combining filters
Nest and and or nodes for complex queries:
const results = await supermemory.search("user_123", {
query: "meeting notes",
filter: {
operator: "and",
operands: [
{ field: "type", operator: "eq", value: "meeting" },
{
operator: "or",
operands: [
{ field: "team", operator: "eq", value: "engineering" },
{ field: "team", operator: "eq", value: "product" },
],
},
],
},
searchMode: "chunks",
});Excluding results
Use the negative operators (neq, notContains, arrayNotContains) to exclude matches:
const results = await supermemory.search("user_123", {
query: "documentation",
filter: { field: "status", operator: "neq", value: "draft" },
searchMode: "chunks",
});**Numeric comparisons:**
```typescript
// Find high-priority items created after a specific date
const results = await supermemory.search("user_123", {
query: "tasks",
filter: {
operator: "and",
operands: [
{ field: "priority", operator: "gte", value: 7 },
{ field: "created_timestamp", operator: "gte", value: 1704067200 }, // Unix timestamp
],
},
searchMode: "chunks",
});
```
**Array membership:**
```typescript
// Find documents where a specific user is a participant
const results = await supermemory.search("user_123", {
query: "meeting notes",
filter: { field: "participants", operator: "arrayContains", value: "[email protected]" },
searchMode: "chunks",
});
```
**Complex nested filters:**
```typescript
// (category = "tech" OR category = "science") AND status != "archived"
const results = await supermemory.search("user_123", {
query: "research papers",
filter: {
operator: "and",
operands: [
{
operator: "or",
operands: [
{ field: "category", operator: "eq", value: "tech" },
{ field: "category", operator: "eq", value: "science" },
],
},
{ field: "status", operator: "neq", value: "archived" },
],
},
searchMode: "chunks",
});
```**Team meeting notes with specific participants:**
```typescript
const results = await supermemory.search("project_alpha", {
query: "sprint planning",
filter: {
operator: "and",
operands: [
{ field: "type", operator: "eq", value: "meeting" },
{
operator: "or",
operands: [
{ field: "participants", operator: "arrayContains", value: "alice" },
{ field: "participants", operator: "arrayContains", value: "bob" },
],
},
],
},
searchMode: "chunks",
});
```
**Exclude drafts and deprecated content:**
```typescript
const results = await supermemory.search("user_123", {
query: "documentation",
filter: {
operator: "and",
operands: [
{ field: "status", operator: "neq", value: "draft" },
{ field: "content", operator: "notContains", value: "deprecated" },
{ field: "tags", operator: "arrayNotContains", value: "archived" },
],
},
searchMode: "chunks",
});
```Filters Beyond Search
The same filter expression works on list and profile calls:
// Page through matching documents
const { documents, pagination } = await supermemory.list("user_123", "documents", {
filter: { field: "category", operator: "eq", value: "engineering" },
});
// Build the profile only from matching memories
const { profile } = await supermemory.profile("user_123", {
filter: { field: "source", operator: "eq", value: "onboarding" },
});Quick reference
When adding memories
await supermemory.add("user_123", { // Isolation
content: "Your content here",
metadata: { key: "value" }, // Custom properties
});When searching
const results = await supermemory.search("user_123", { // Scopes results to this namespace
query: "search query",
filter: { field: "status", operator: "eq", value: "published" }, // Optional
searchMode: "chunks",
});Metadata key rules
- Allowed characters:
a-z,A-Z,0-9,_,-,. - No spaces or special characters
- Keys are literal:
customer.planis one key namedcustomer.plan, not a nested path
Query complexity limits
- Maximum 5 levels of nested
and/orexpressions - Maximum 200 operands per logical group
Reading One Document's Chunks
Scoping a search to a single document (docId) is not part of v5. To work with the chunks of one long document, fetch the document with its chunks attached:
const doc = await supermemory.documents.get("user_123", "doc_123", {
include: ["chunks"],
});Next steps
Apps Docs Concepts Graph Memory (`apps/docs/concepts/graph-memory.mdx`)
title: "Graph memory" sidebarTitle: "Graph memory" icon: "/icons/hugeicons/share-08.svg"
How understanding is stored and stays true over time.
Supermemory builds a living knowledge graph of facts on top of other facts — not a static folder of embeddings, and not classic entity–relation–entity triples you maintain by hand.
The pipeline that turns a chat or file into memories is How it works.
This page is the model: what a memory is, how edges form, and why agents utilize supermemory's graph
Try it
Get a key from the developer console — API Keys → Create API Key — then add a memory and pull it back with related edges:
import { Supermemory } from "supermemory";
const supermemory = new Supermemory({ apiKey: "sm_..." }); // from console.supermemory.ai → API Keys
await supermemory.add("user_123", {
content: "Alex mentioned he just started at Stripe",
dreaming: "instant", // memories land in the graph right away
});
const { results } = await supermemory.search("user_123", {
query: "where does Alex work?",
include: { related: true },
searchMode: "memories",
});Full walkthrough with a live example: Quickstart.
Documents vs memories
| Documents | Memories | |
|---|---|---|
| What | Raw input you send | Facts Supermemory extracts |
| Examples | PDF, chat log, Drive file, URL | “Alex is a PM at Stripe” |
| Role | Source of truth for RAG / SuperRAG | Personal and entity state over time |
| Lifecycle | You add / update / delete | Graph updates, extends, derives, forgets |
Think of documents as books you hand the system. Memories are the insights it keeps — connected to each other as new content arrives.
Properties and rules of memories
- Memories are atomic - Each memory has enough information and context about one particular topic
- They always build on top of each other - with
updates, the model knows the history, a memoryextendsfrom other memories, and new facts are derived (derivesrelation) from existing knowledge.
Memory relationships
When content is processed, new facts connect to existing ones through three relationship types.
Updates — information changes
New fact replaces what was true before for search purposes; history can remain for audit.
Memory 1: "Alex works at Google as a software engineer"
Memory 2: "Alex just started at Stripe as a PM"
→ Memory 2 UPDATES Memory 1isLatest (and related graph fields) keep retrieval on the current fact without erasing the past.
Extends — information enriches
New fact adds detail without invalidating the old one.
Memory 1: "Alex works at Stripe as a PM"
Memory 2: "Alex focuses on payments and leads a team of 5"
→ Memory 2 EXTENDS Memory 1Both stay valid; context gets richer.
Derives — information infers
Supermemory infers a fact you never stated in one place, from patterns across memories.
Memory 1: "Alex is a PM at Stripe"
Memory 2: "Alex frequently discusses payment APIs and fraud detection"
→ Derived: "Alex likely works on Stripe's core payments product"That is the same class of “entity chain” you see in the quickstart (gift → VP of Product → Sarah → Tokyo offsite). Search can expose edges via include: { related: true }; they come back under included.related — see Search API.
Automatic extraction (one input → many facts)
Input:
Had a great call with Alex. He's enjoying the new PM role at Stripe, though the payments work is intense. He moved to Seattle for the job—Capitol Hill. Wants dinner next time I'm in town.
Example extracted memories:
- Alex works at Stripe as a PM
- Alex works on payments infrastructure (extends role)
- Alex lives in Seattle, Capitol Hill
- Alex wants to meet for dinner (episodic)
You do not define schema or draw edges. You add content; the graph updates.
Dreaming keeps the graph alive
Ingest is not a one-shot snapshot. After (and alongside) indexing, dreaming continues building the graph: extracting facts, linking related memories, resolving updates, and producing derives you never stated in one place.
By default Supermemory uses dreaming: "dynamic" — related documents are grouped so memories form from coherent units (e.g. a real multi-turn session), not each isolated write in isolation. That is why production quality is higher when you keep a stable id on conversations and let dynamic dreaming do its job.
Use dreaming: "instant" when this document must hit the graph immediately (demos, “search right after add”). That path processes the document alone and costs an extra operation.
How to set the flag, statuses, and when done means what: How it works → Dreaming and Processing modes.
Memory types
| Type | Example | Behavior |
|---|---|---|
| Facts | “Alex is a PM at Stripe” | Persists until updated |
| Preferences | “Alex prefers morning meetings” | Strengthens with repetition |
| Episodes | “Met Alex for coffee Tuesday” | Decays unless significant |
Automatic forgetting
- Time-based — temporary facts drop after they expire (“exam tomorrow”, “meeting at 3pm today”).
- Contradiction — updates win for “what’s true now.”
- Noise filtering — casual, non-meaningful chatter is less likely to become durable memory.
For explicit product controls (forget, review low-confidence derives), see Forget & update and Memory review.
What you don’t do
You do not hand-maintain the graph. You:
await supermemory.add("user_123", {
content: "Alex mentioned he just started at Stripe",
});
const { results } = await supermemory.search("user_123", {
query: "where does Alex work?",
include: { related: true },
searchMode: "memories",
});
// Prefer latest work fact (Stripe); history remains in the graph.
// Related edges come back under results[i].included.relatedRelated in the docs
| If you need… | Go to |
|---|---|
| Pipeline statuses, dreaming, documents in | How it works |
| Memory vs document retrieval | Memory vs RAG · SuperRAG |
| Always-on summary of a user | Profiles |
| Isolation / tenants | Multi-tenancy |
| API: add / search / forget | Ingestion · Search · Forget & update |
Apps Docs Concepts How It Works (`apps/docs/concepts/how-it-works.mdx`)
title: "How Supermemory works" sidebarTitle: "How it works" icon: "/icons/hugeicons/cpu.svg"
At its core, supermemory is powered by a custom learning model and a graph database that we built internally.
But, you don't have to think about the above. The interface for users is as simple as it gets.
Get started in under a minute
const supermemory = new Supermemory({ apiKey: "sm_..." }); // from console.supermemory.ai → API Keys
await supermemory.add("user_123", {
content: "The user loves Paris.",
dreaming: "instant", // memories available right after processing
});
const { results } = await supermemory.search("user_123", {
query: "where does the user want to travel?",
});
```python Python
# pip install supermemory
from supermemory import Supermemory
client = Supermemory(api_key="sm_...") # from console.supermemory.ai → API Keys
client.add(
"user_123",
content="The user loves Paris.",
dreaming="instant", # memories available right after processing
)
results = client.search("user_123", query="where does the user want to travel?")curl -X POST "https://api.supermemory.ai/ns/user_123/document" \
-H "Authorization: Bearer sm_..." \
-H "Content-Type: application/json" \
-d '{ "content": "The user loves Paris.", "dreaming": "instant" }'
curl -X POST "https://api.supermemory.ai/ns/user_123/search" \
-H "Authorization: Bearer sm_..." \
-H "Content-Type: application/json" \
-d '{ "query": "where does the user want to travel?" }'What you send: documents
A document is raw input — whatever you hand Supermemory:
- Conversation transcripts and messages
- Text, markdown, HTML
- PDFs, images, audio/video, code
- URLs and connector items (Drive, Notion, Gmail, …)
You do not pre-chunk or pick an embedding model. See Multi-modal ingestion for formats, and Add context for the API.
Supermemory handles the ingestion and extraction for you. This also gives us a big advantage for quality - The engine extracts it in an optimized way with Contextual Chunking and other features for better quality search and memory generation.
Use a stable
idwhen the same conversation or file will be updated later (sessions, connector syncs). That identity also drives diff billing on re-ingest.
What the pipeline does
| Stage | What happens |
|---|---|
| Queued | Accepted; waiting to run |
| Extracting | Text / OCR / transcription / page fetch |
| Chunking | Splits content for retrieval (type-aware where needed) |
| Embedding | Vectors for similarity search |
| Indexing | Makes chunks and derived structure searchable |
| Done | Document path is ready for search |
const doc = await supermemory.add("user_123", {
content: conversationText,
id: "chat_session_1",
});
// Poll until ready
const { system } = await supermemory.documents.get("user_123", doc.id);
// system.status → "queued" | "extracting" | ... | "done" | "failed"Larger PDFs and long video take longer. Short chat turns usually finish in seconds.
Dreaming (how memories enter the graph)
A document with status done has its chunks indexed for search. Memories (the graph's facts, updates and derived facts) come from a second phase called dreaming.
This is when the content is passed through the memory model and merged, arranged and organized for the future.
Pass dreaming on add (a field in the JSON body, next to content in the SDK):
| Mode | Default? | Behavior | When to use |
|---|---|---|---|
dynamic |
Yes | Related documents are grouped so memories form from coherent units, not one isolated write at a time. Graph quality is higher for real multi-turn / multi-doc flows. Memory extraction may continue after status: "done". |
Production agents, connectors, ongoing sessions |
instant |
No | This document is dreamed on its own, right away. Memories are available as soon as processing finishes for that document. Bills one extra operation per document. | Demos, quickstarts, “I need the graph now” |
// Production default — omit or set explicitly
await supermemory.add("user_123", {
content: conversationText,
id: "chat_session_1",
dreaming: "dynamic",
});
// Need memories immediately (e.g. tutorial)
await supermemory.add("user_123", {
content: conversationText,
id: "chat_session_1",
dreaming: "instant",
});Rule of thumb: use dynamic in real apps for better quality and cost, because batching lets new memories connect to related ones. Use instant when the very next step is a memory search or profile that must already reflect this document, as in the quickstart.
How those memories connect and stay true over time is Graph memory. API detail: Processing modes.
What you get out
After the pipeline runs, the same document leads to three things -> Chunks, Memories and Profile. (in the same namespace):
| Output | Role | Go deeper |
|---|---|---|
| Document chunks | Grounding in the raw source (RAG / SuperRAG) | SuperRAG, Search API |
| Memories | Extracted facts in a living graph — updates, links, time | Graph memory |
| Profile | A sample of memories, static + dynamic summary for always-on context | Profiles, Profile API |
Supermemory does more than store the file. It derives memories (what it understood) and keeps chunks (the source), so you can both personalize and ground answers. That distinction is the core of Memory vs RAG.
Isolation and identity
namespace— hard isolation boundary (user, tenant, project), carried in the URL. See Namespaces.- Metadata — soft dimensions inside a namespace for filtering. See Metadata filtering.
- Scoped API keys — credentials that cannot cross a namespace. See API keys.
Next steps
Apps Docs Concepts Memory Vs Rag (`apps/docs/concepts/memory-vs-rag.mdx`)
title: "Memory vs RAG: Understanding the difference" sidebarTitle: "Memory vs RAG" icon: "/icons/hugeicons/balance-scale.svg"
Most developers confuse RAG (Retrieval-Augmented Generation) with agent memory. They're not the same thing, and using RAG for memory is why your agents keep forgetting important context. Let's understand the fundamental difference.
The core problem
When building AI agents, developers often treat memory as just another retrieval problem. They store conversations in a vector database, embed queries, and hope semantic search will surface the right context.
This approach fails because memory isn't about finding similar text—it's about understanding relationships, temporal context, and user state over time.
Documents vs memories in Supermemory
Supermemory makes a clear distinction between these two concepts:
Documents: Raw knowledge
Documents are the raw content you send to Supermemory—PDFs, web pages, text files. They represent static knowledge that doesn't change based on who's accessing it.
Characteristics:
- Stateless: A document about Python programming is the same for everyone
- Unversioned: Content doesn't track changes over time
- Universal: Not linked to specific users or entities
- Searchable: Perfect for semantic similarity search
Use Cases:
- Company knowledge bases
- Technical documentation
- Research papers
- General reference material
Memories: Contextual understanding
Memories are the insights, preferences, and relationships extracted from documents and conversations. They're tied to specific users or entities and evolve over time.
Characteristics:
- Stateful: "User prefers dark mode" is specific to that user
- Temporal: Tracks when facts became true or invalid
- Personal: Linked to users, sessions, or entities
- Relational: Understands connections between facts
Use Cases:
- User preferences and history
- Conversation context
- Personal facts and relationships
- Behavioral patterns
Why RAG fails as memory
Let's look at a real scenario that illustrates the problem:
# Semantic search finds closest match
result = vector_search(query)
# Returns: "I love Adidas sneakers" (highest similarity)
# Agent recommends Adidas 🤦
```
**Problem**: RAG finds the most semantically similar text but misses the temporal progression and causal relationships.# Memory retrieval considers:
# 1. Temporal validity (Adidas preference is outdated)
# 2. Causal relationships (broke → disappointment → switch)
# 3. Current state (now prefers Puma)
# Agent correctly recommends Puma ✅
```
**Solution**: Memory systems track when facts become invalid and understand causal chains.The technical difference
RAG: Semantic similarity
Query → Embedding → Vector Search → Top-K Results → LLMRAG excels at finding information that's semantically similar to your query. It's stateless—each query is independent.
Memory: Contextual graph
Query → Entity Recognition → Graph Traversal → Temporal Filtering → Context Assembly → LLMMemory systems build a knowledge graph that understands:
- Entities: Users, products, concepts
- Relationships: Preferences, ownership, causality
- Temporal Context: When facts were true
- Invalidation: When facts became outdated
When to use each
Real-world examples
E-commerce assistant
```python
# Good for RAG
"What are the specs of iPhone 15?"
"Compare Nike and Adidas running shoes"
"Show me waterproof jackets"
``````python
# Needs Memory
"What size do I usually wear?"
"Did I like my last purchase?"
"What's my budget preference?"
```Customer support bot
```python
# Good for RAG
"How do I reset my password?"
"What's your return policy?"
"Troubleshooting WiFi issues"
``````python
# Needs Memory
"Is my issue from last week resolved?"
"What plan am I on?"
"You were helping me with..."
```How Supermemory handles both
Supermemory provides a unified platform that correctly handles both patterns:
1. Document storage (RAG)
# Add a document for RAG-style retrieval
client.add(
"product_knowledge", # Shared namespace, not tied to a user
content="iPhone 15 has a 48MP camera and A17 Pro chip",
dreaming="instant"
)2. Memory creation
# Add a user-specific memory
client.add(
"user_123", # User-specific namespace
content="User prefers Android over iOS",
dreaming="instant",
metadata={
"type": "preference",
"confidence": "high"
}
)3. Hybrid retrieval
# Search is scoped to one namespace, so query each side on its own
user = client.profile("user_123") # User's Android preference (memory)
specs = client.search(
"product_knowledge", # Latest Android phone specs (document chunks)
query="What phone should I recommend?",
search_mode="chunks",
)
# Hand both to the model: user.profile + specs.resultsThe bottom line
Stop treating memory like a retrieval problem. Your agents need both:
- RAG for accessing knowledge
- Memory for understanding users
Supermemory provides both capabilities in a unified platform, ensuring your agents have the right context at the right time.
Next steps
Apps Docs Concepts Multi Tenancy Examples (`apps/docs/concepts/multi-tenancy-examples.mdx`)
title: "Multi-tenancy examples" sidebarTitle: "Examples" icon: "/icons/hugeicons/check-list.svg"
A few common shapes multi-tenancy takes in practice, combining namespaces for isolation with metadata filters for organization within a boundary.
Personal agent
A single namespace per user is enough — there's no shared data to leak, so metadata is optional.
await supermemory.add(userId, { // one namespace per user
content: "User prefers morning workouts and vegetarian meals",
});
const results = await supermemory.search(userId, {
query: "workout preferences",
});Company agent (shared + personal memory)
A company-wide assistant usually needs two kinds of namespaces: one shared namespace the whole org reads from, and one personal namespace per employee that nobody else can see.
// Shared org knowledge — visible to everyone at the company
await supermemory.add("org_acme_shared", {
content: "Q3 roadmap: ship the mobile app redesign by end of August",
metadata: { team: "product", type: "roadmap" },
});
// Personal memory — only this employee's agent should see this
await supermemory.add("org_acme_user_alex", {
content: "Prefers async updates over meetings",
});Inside the shared namespace, use metadata to scope queries to a team rather than creating a namespace per team:
const results = await supermemory.search("org_acme_shared", {
query: "roadmap updates",
filter: { field: "team", operator: "eq", value: "product" },
searchMode: "chunks",
});An employee's agent typically queries both namespaces — their personal one plus the shared one — and merges the results, since a request is scoped to exactly one namespace.
Email assistant
One namespace per user, with metadata carrying email-specific properties like label, sender, or folder — so the assistant can answer things like "find the Spotify email tagged Promotional".
await supermemory.add(userId, {
content: "Your Spotify Premium receipt for July — $11.99 charged",
metadata: {
source: "gmail",
sender: "[email protected]",
label: "Promotional",
},
});
const results = await supermemory.search(userId, {
query: "spotify",
filter: {
operator: "and",
operands: [
{ field: "source", operator: "eq", value: "gmail" },
{ field: "label", operator: "eq", value: "Promotional" },
],
},
searchMode: "chunks",
});Multi-tenant support platform
Each customer gets their own namespace, and metadata tracks ticket-level fields like status and priority — so "open, high-priority tickets" is a filter, not a new namespace, and it can never accidentally include another customer's tickets.
await supermemory.add("org_customer_442", {
content: "Customer reports checkout button unresponsive on Safari",
metadata: { status: "open", priority: "high", channel: "chat" },
});
const results = await supermemory.search("org_customer_442", {
query: "checkout issue",
filter: {
operator: "and",
operands: [
{ field: "status", operator: "eq", value: "open" },
{ field: "priority", operator: "eq", value: "high" },
],
},
searchMode: "chunks",
});Next steps
Apps Docs Concepts Multi Tenancy (`apps/docs/concepts/multi-tenancy.mdx`)
title: "Multi-tenancy overview" sidebarTitle: "Overview" icon: "/icons/hugeicons/user-multiple.svg"
Most apps built on Supermemory serve more than one user, customer, or tenant out of a single Supermemory organization. Multi-tenancy is how you keep those memories apart — so User A's data is never visible to User B, and so you can still slice and query within a user's own data by things like category, status, or date.
Supermemory gives you two complementary tools for this:
They solve different problems, and most production apps use both together.
Why two mechanisms
It's tempting to reach for one tool and make it do everything, but namespaces and metadata aren't interchangeable — they answer different questions.
| Question | Answer |
|---|---|
| "Which tenant does this memory belong to?" | Namespace |
"Within this tenant's memories, which ones match status: open?" |
Metadata filter |
| "Can this API key even see tenant X's data?" | Namespace (enforced as an access boundary) |
"Find memories tagged engineering created after March" |
Metadata filter |
A namespace decides whether a memory is reachable at all for a given request. Metadata decides which of the reachable memories match. Filtering never crosses a namespace boundary — you can't use metadata to peek into another tenant's namespace.
How they work together
A typical multi-tenant write scopes the memory to a tenant with a namespace, then attaches metadata for finer-grained querying later:
await supermemory.add("org_acme", { // isolates to the "acme" tenant
content: "Customer requested a refund for order #4821",
metadata: {
category: "support",
status: "open",
priority: "high",
},
});And a search combines both: the namespace restricts which tenant's data is in scope, and the filter narrows down which memories within that tenant come back:
const results = await supermemory.search("org_acme", {
query: "refund request",
filter: {
operator: "and",
operands: [
{ field: "category", operator: "eq", value: "support" },
{ field: "status", operator: "eq", value: "open" },
],
},
searchMode: "chunks",
});Choosing your boundary
The namespace is the layer that should map to your actual tenancy model — pick the level that matches what "one isolated space" means in your app:
| Pattern | Example | Use case |
|---|---|---|
| Per-user | user_{userId} |
Consumer app, personal memory per user |
| Per-tenant/org | org_{orgId} |
B2B SaaS, one namespace per customer org |
| Hierarchical | org:{orgId}:user:{userId} |
Multi-level — isolate by org, and optionally drill into a user within it |
| Per-project | project_{projectId} |
Workspace- or project-scoped content |
The simplest form is one namespace per user, derived from an ID you already have:
await supermemory.add(userId, { // one namespace per user
content: "User prefers morning workouts",
});
const results = await supermemory.search(userId, {
query: "workout preferences",
});Everything within that boundary — categories, statuses, dates, custom fields — is metadata, not a new namespace. Don't create a new namespace for every property you want to filter on; that's what metadata is for.
Access control
Namespaces aren't just organizational — they're enforced as an authorization boundary. API keys and org members can be restricted to specific namespaces, so a request for a namespace outside the caller's allowed set is rejected with 403 Forbidden rather than silently filtered. See Namespaces → Access control for the details.
Next steps
Apps Docs Concepts Rules (`apps/docs/concepts/rules.mdx`)
title: "Rules of supermemory" sidebarTitle: "Rules of supermemory" icon: "/icons/hugeicons/justice-scale-01.svg"
Supermemory provides powerful primitives and the full context stack for building AI agents. This page collects rules of thumb from building and running supermemory in production. They aren't hard constraints, just shortcuts that save you time, cost, and confusing search results.
Thinking about ingestion
What to ingest, and what not to
Send what you would send to a human for memory
Treat supermemory as a database for human-like understanding of knowledge and search. You should be feeding it unstructured data like documents, chat conversations, or even images, videos, and websites. You should not be ingesting database records or CSVs, since those are more structured.
Although supermemory does support learning from long-horizon structured data, typically the right approach is to give an agent tools to traverse the structure directly.
Agents benefit most from having a general idea of the topic alongside tools to look through the data. For example, knowing "this company uses PostHog and has three products (API, Console, and Landing Page)" helps the agent navigate the PostHog data more effectively.
A quick test for where information belongs
| Context | Test result | Where it goes |
|---|---|---|
| "Sarah prefers async updates and is being promoted to VP of Product" | A colleague would remember this | supermemory: memory search + profile |
| The Q3 planning doc, support tickets, the API changelog | A colleague would look it up by meaning | supermemory: ingested as documents, recalled with document search |
Invoice #4821, total $1,340.50, status paid |
Queried by ID, summed in reports | your database |
| "Answer in the user's language. Never quote internal pricing." | Every request needs it, verbatim | system prompt |
Two things about this table that trip people up.
"Remember" and "look up" are both supermemory, but different reads. You ingest documents; the pipeline derives memories from them and maintains a profile per namespace. supermemory.search(namespace, { query, searchMode: "memories" }) recalls the derived facts. supermemory.search(namespace, { query, searchMode: "chunks" }) recalls the source material itself. A support agent usually needs both: memories for "this customer runs self-hosted and already tried reinstalling", documents for the actual troubleshooting guide.
Supermemory is not your system of record. There's no SQL over memories, no joins, no aggregates, no querying by primary key. Keep transactional data in your database, and ingest the narrative around it ("the customer disputed invoice #4821 and churned over it") so your AI understands what the rows mean.
Ingest with SuperRag when you just need search
When you know you only want search, you can cut costs by 5x. Just set taskType when ingesting:
await supermemory.add("test", {
content: "testing",
taskType: "superrag",
});client.add(
"test",
content="testing",
task_type="superrag",
)curl -X POST "https://api.supermemory.ai/ns/test/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "testing", "taskType": "superrag" }'Use hybrid mode when searching over SuperRag content
hybrid mode (the v5 default) makes it much easier to get complete results from supermemory when you have both memories and documents.
const { results } = await supermemory.search("test", {
query: "test",
searchMode: "hybrid",
});results = client.search(
"test",
query="test",
search_mode="hybrid",
)curl -X POST "https://api.supermemory.ai/ns/test/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "query": "test", "searchMode": "hybrid" }'Each entry in results carries either memory or chunk:
{ results: ({ memory: string } | { chunk: string })[], searchTime: number }Use item.memory || item.chunk when reading results.
Keep documents medium-sized
While supermemory can handle documents with 400k+ tokens, sending smaller, self-contained documents produces better-quality learnings. The internal learning agent and "dreaming" jobs reflect on memories to build relations between them. If documents are too long, fewer memories get generated and fewer relations get made.
We also recommend ingesting documents sequentially within a single namespace where possible, since that's how supermemory determines what came first (used for updates relations and temporal reasoning).
Handling single-threaded chatbots
Many agent harnesses, like openclaw, hermes, and other single-threaded custom agents, run one long conversation with compaction. Some tips for managing single-threaded (and other long-running) conversations:
- Send an
idwhen you can: a sessionId, conversationId, document ID, or any representation of a "session" in your application. - Generate one if you don't have one, e.g. the current 4-hour window:
${new Date().toISOString().slice(0,10)}-${new Date().getHours()>>2}. Adjust the window size based on traffic per namespace. - Send the same prefix: keep the start of the document identical across ingests under the same
idso supermemory can diff cleanly. You can either resend the full growing transcript each time, or send only the new turns since your last ingest. Just don't mix the two for the sameid.
Ingestion 1:
Assistant: Hey, how are you?
User: I'm fine.
Ingestion 2 (full transcript):
Assistant: Hey, how are you?
User: I'm fine.
Assistant: Anything I can help with today?
Ingestion 2 (delta only):
Assistant: Anything I can help with today?You're only billed for the new (diff) content you send, so doing this well improves performance, cuts cost, and keeps usage simple.
Architecture and design
Let supermemory handle the learning
Don't pass content through an additional LLM before sending it to supermemory. Supermemory does that learning automatically. Because the engine already knows what it knows, it can contextually summarize, update, and forget information as needed.
Configure what you want it to learn
Ground it with supportingContext (called entityContext in v3/v4) to prevent drift over time. Picture a third person watching a conversation between two people: what do they remember, and about whom? Giving supermemory context about the entity itself helps ground its learnings and prevents drift and decay over time.
const user = auth.user.name;
await supermemory.add(user, {
content: "Hey, I'm doing great!",
supportingContext: `User is ${user}, talking to assistant Kira`,
}); // -> supermemory learns "Dhravya is doing great"user = auth.user.name
client.add(
user,
content="Hey, I'm doing great!",
supporting_context=f"User is {user}, talking to assistant Kira",
) # -> supermemory learns "Dhravya is doing great"curl -X POST "https://api.supermemory.ai/ns/dhravya/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Hey, I'\''m doing great!",
"supportingContext": "User is dhravya, talking to assistant Kira"
}'Use namespaces, don't over-stuff a single one
Use a namespace wherever there's a hard permission boundary.
- Don't: ingest everything into one namespace and filter through it with metadata.
- Do: give each user their own namespace (
namespace: userId), and still filter by metadata inside it if needed.
There's little correlation between the number of items in a namespace and its quality or latency. Supermemory is built for multi-tenant workloads and supports up to 1M documents and 10M memories per namespace.
Use metadata filtering for detailed scoping inside namespaces
You'll often want to ingest and search with filtering inside a single namespace. Say the engineering team ingests this:
await supermemory.add("org-supermemory", {
content: "The team prefers TypeScript",
metadata: { team: "Engineering" },
group: { team: "Engineering" },
});client.add(
"org-supermemory",
content="The team prefers TypeScript",
metadata={"team": "Engineering"},
group={"team": "Engineering"},
)curl -X POST "https://api.supermemory.ai/ns/org-supermemory/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "The team prefers TypeScript",
"metadata": { "team": "Engineering" },
"group": { "team": "Engineering" }
}'Tip:
group(calledfilterByMetadatain v3/v4) ensures a fact like "the team prefers TypeScript" is only built on top of the engineering team's knowledge.
Later, the research team ingests this into the same namespace (POST /ns/org-supermemory/document) but with different metadata:
{
"content": "The team prefers Python",
"metadata": { "team": "Research" },
"group": { "team": "Research" }
}This keeps research's and engineering's memories from mixing, even though they share a namespace. When searching:
const results = await supermemory.search("org-supermemory", {
query: "preferred language",
filter: { field: "team", operator: "eq", value: "Research" },
searchMode: "chunks",
}); // -> "python"results = client.search(
"org-supermemory",
query="preferred language",
search_mode="chunks",
filter={"field": "team", "operator": "eq", "value": "Research"},
) # -> "python"curl -X POST "https://api.supermemory.ai/ns/org-supermemory/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "preferred language",
"searchMode": "chunks",
"filter": { "field": "team", "operator": "eq", "value": "Research" }
}'Thinking about harness
Think about how to bring memory back into the harness itself.
Embrace a little noise
You might want to hyper-optimize everything that goes into the model's prompt, but counterintuitively, you sometimes want to embrace noise, since true personalization comes from distinctive information.
Example: a user says "hi" and the LLM responds "Hey Dhravya! How's it going? How's the new office coming along?" instead of something generic.
Supermemory is designed for this: it returns an average of 10 tokens per fact, so even 50 facts is just 500 tokens of context, cheap enough to stay generous.
Tools, hooks, and making the choice
Think about how supermemory fits into your harness. Example, a personal agent:
- Session start hook → load profile
- On-message hook → enrich the prompt with search
- On-stop hook → save the conversation
Play around with these options in our playground, and read more in this post on memory at the harness level.
Apps Docs Concepts Super Rag (`apps/docs/concepts/super-rag.mdx`)
title: "SuperRAG (managed RAG as a service)" sidebarTitle: "SuperRAG" icon: "/icons/hugeicons/flash.svg"
Supermemory doesn't just store your content—it transforms it into optimized, searchable knowledge. Every upload goes through an intelligent pipeline that extracts, chunks, and indexes content in the ideal way for its type.
Automatic content intelligence
When you add content, Supermemory:
- Detects the content type — PDF, code, markdown, images, video, etc.
- Extracts content optimally — Uses type-specific extraction (OCR for images, transcription for audio)
- Chunks intelligently — Applies the right chunking strategy for the content type
- Generates embeddings — Creates vector representations for semantic search
- Builds relationships — Connects new knowledge to existing memories
// Just upload — Supermemory handles the rest
await supermemory.documents.uploadFile("docs_kb", {
file: fs.createReadStream('technical-documentation.pdf'),
metadata: JSON.stringify({ title: "Technical Documentation" }),
});No chunking strategies to configure. No embedding models to choose. It just works.
Ingesting as pure SuperRAG (`taskType: "superrag"`)
By default, every supermemory.add() call runs on the memory path (taskType: "memory"): Supermemory chunks and embeds the content for retrieval, and runs it through the memory pipeline — extracting facts, updating the profile, and linking it into the knowledge graph.
If you're ingesting content that's purely reference material — documentation, a large PDF, a knowledge base article — and you don't need Supermemory to derive personal facts or update a profile from it, set taskType: "superrag" in the request body. It skips the memory pipeline entirely and only does the chunk → embed → index work needed to make the content searchable.
await supermemory.add("docs_kb", { // e.g. a long internal wiki page
content: "...",
taskType: "superrag",
});client.add(
"docs_kb",
content="...", # e.g. a long internal wiki page
task_type="superrag",
)curl -X POST "https://api.supermemory.ai/ns/docs_kb/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "...", "taskType": "superrag" }'taskType: "memory" (default) |
taskType: "superrag" |
|
|---|---|---|
| Chunking, embedding, indexing | ✅ | ✅ — searchable immediately via searchMode: "chunks" |
| Fact extraction into memories | ✅ | ❌ skipped |
Profile (static/dynamic/buckets) updates |
✅ | ❌ skipped |
| Graph linking (updates/extends/derives) | ✅ | ❌ skipped |
| Price per ingested token | Full rate | 5x cheaper |
When you're searching over a mix of both, searchMode: "hybrid" (below) is what pulls memory-path facts and superrag-path document chunks into one result set. More ingestion guidance: Rules of supermemory → Ingest with SuperRag when you just need search.
Smart chunking by content type
Different content types need different chunking strategies. Supermemory applies the optimal approach automatically:
Documents (PDF, DOCX)
PDFs and documents are chunked by semantic sections — headers, paragraphs, and logical boundaries. This preserves context better than arbitrary character splits.
├── Executive Summary (chunk 1)
├── Introduction (chunk 2)
├── Section 1: Architecture
│ ├── Overview (chunk 3)
│ └── Components (chunk 4)
└── Conclusion (chunk 5)Code
When content is recognized as Markdown, fenced code blocks are kept whole when they fit in a chunk. Larger blocks are split and re-wrapped in the original fence, including its language tag. Overlap can contain part of a previous line, so chunks are not guaranteed to be independently valid code.
Raw source files and content not recognized as Markdown use plain-text chunking. A lone fenced block may not be enough for automatic Markdown detection.
Web pages
URLs are fetched, cleaned of navigation/ads, and chunked by article structure — headings, paragraphs, lists.
Markdown
Chunked by heading hierarchy, preserving the document structure.
See Content Types for the full list of supported formats.
Hybrid memory + RAG
Supermemory combines the best of both approaches in every search:
With searchMode: "hybrid" (the default), you get both:
const results = await supermemory.search("user_123", {
query: "how do I deploy the app?",
searchMode: "hybrid",
});
// Returns:
// - Deployment docs from your knowledge base (RAG)
// - User's previous deployment preferences (Memory)
// - Their specific environment configs (Memory)Search optimization
Two flags give you fine-grained control over result quality:
Reranking
Re-scores results using a cross-encoder model for better relevance. rerank takes "none" (default), "order", or "aggregate":
const results = await supermemory.search("user_123", {
query: "complex technical question",
rerank: "order", // +~100ms, significantly better ranking
});When to use: Complex queries, technical documentation, when precision matters more than speed.
Query rewriting
Expands your query to capture more relevant results:
const results = await supermemory.search("user_123", {
query: "how to auth",
rewriteQuery: true, // Expands to "authentication login oauth jwt..."
});When to use: Short queries, user-facing search, when recall matters.
Why it's "Super"
| Traditional RAG | SUPER RAG |
|---|---|
| Manual chunking config | Automatic per content type |
| One-size-fits-all splits | AST-aware code chunking |
| Just document retrieval | Hybrid memory + documents |
| Static embeddings | Relationship-aware graph |
| Generic search | Rerank + query rewriting |
You focus on building your product. Supermemory handles the RAG complexity.
Next steps
Apps Docs Concepts User Profiles (`apps/docs/concepts/user-profiles.mdx`)
title: "User profiles" sidebarTitle: "Profiles" icon: "/icons/hugeicons/user-circle.svg"
User profiles are automatically maintained collections of facts about your users that Supermemory builds from all their interactions. Think of it as a persistent "about me" document that's always up-to-date.
Each namespace gets its own profile.
Note: It's called "user" profile, but in reality it can be anything - an agent, organization, etc.
Why Profiles?
Traditional memory systems rely entirely on search:
| Problem | Search Only | With Profiles |
|---|---|---|
| Context retrieval | 3-5 queries | 1 call |
| Response time | 200-500ms | 50-100ms |
| Basic user info | Requires specific queries | Always available |
Search is too narrow: When you search for "project updates", you miss that the user prefers bullet points, works in PST, and uses specific terminology.
Profiles provide the foundation: Instead of searching for basic context, profiles give your LLM a complete picture of who the user is.
A pure search architecture means every turn pays a search(prompt) round trip before the agent can respond. A profile is attached once and sits alongside every user prompt and agent output — no extra call, no latency, and no risk of the query missing something important.
Non-literal-matching use cases
Semantic search retrieves content that's similar to the query — it's built for questions like "what did we discuss about the migration?" It's a poor fit for facts that should be known regardless of what's being asked, because there's rarely a query that's semantically close to them.
The clearest example is the user's own name. If someone tells your agent "call me Dhravya, not my full name" once during onboarding, that fact has almost nothing in common — vector-wise — with "help me plan a trip to Japan" or "review this PR." A search for either of those queries will not surface the name preference, because search only returns what's relevant to the query, and a name preference isn't relevant to trip planning or code review — it should just always be there.
// Weeks earlier, during onboarding
await supermemory.add("user_123", {
content: "Call me Dhravya, not my full first name",
});
// Later — an unrelated query
const { results } = await supermemory.search("user_123", {
query: "help me plan a trip to Japan",
});
// The name preference won't be in `results` — it's not semantically
// related to trip planning, so search correctly leaves it out.
// But it's always in the profile, independent of the query:
const { profile } = await supermemory.profile("user_123");
console.log(profile.static.map((m) => m.memory)); // ["User goes by Dhravya, not their full name", ...]This is the general pattern: names, pronouns, timezone, tone/format preferences, role, and other facts that should color every response — not just responses to a matching query — belong in the profile, not left to be caught by search. If your agent needs to "just know" something at all times, that's a strong signal it belongs in the profile rather than relying on a lucky semantic match.
Static vs dynamic
Profiles separate two types of information:
Static profile
Long-term, stable facts:
- "Sarah is a senior software engineer at TechCorp"
- "Sarah specializes in distributed systems"
- "Sarah prefers technical docs over video tutorials"
Dynamic profile
Recent context and temporary states:
- "Sarah is migrating the payment service to microservices"
- "Sarah is preparing for a conference talk next month"
- "Sarah is debugging a memory leak in auth service"
Buckets
Static and dynamic split facts by how long-lived they are. Buckets split them by topic — a third, independent axis you define, like preferences, goals, or work. As content is ingested, a classifier sorts each fact into the buckets it matches.
Every org starts with a default preferences bucket, which is always present — custom buckets are added alongside it, never in place of it. Add your own in console settings at the organization level, or per namespace with supermemory.profiles.setBuckets() — namespace buckets are add-only, so a namespace always keeps every org-level bucket.
// Define a namespace-level bucket (name → description)
await supermemory.profiles.setBuckets("user_123", {
buckets: { goals: "Goals and targets the user is actively working toward" },
});
const { profile } = await supermemory.profile("user_123", { // optional — omit for all configured buckets
buckets: ["preferences", "goals"],
});
console.log(profile.buckets.preferences); // [{ id, memory }, ...]
console.log(profile.buckets.goals);Bucket descriptions steer the classifier, so a precise description ("explicit first-person preferences only, exclude inferred traits") produces cleaner buckets than a vague one. Buckets are separate from organizationalContext (called filterPrompt in v3/v4), which steers what gets extracted at all — buckets only organize facts that already made it into the profile.
How it works
Profiles are built automatically through ingestion:
- Ingest content — Users add documents, chat, or any content
- Extract facts — AI analyzes content for facts about the user
- Update profile — System adds, updates, or removes facts
- Always current — Profiles reflect the latest information
Profiles + search
Profiles don't replace search — they complement it:
- Profile = broad foundation (who the user is, preferences, background)
- Search = specific details (exact memories matching a query)
Example
User asks: "Can you help me debug this?"
Without profiles: LLM has no context about expertise, projects, or preferences.
With profiles: LLM knows:
- Senior engineer (adjust technical level)
- Working on payment service (likely context)
- Prefers CLI tools (tool suggestions)
- Recent memory leak issues (possible connection)
Filtering profiles
Not many people realize this, but profiles support the same metadata filtering as memory and document search. A profile is synthesized from the underlying memories in a namespace, so any filter expression you'd pass to search also narrows which memories are eligible to contribute to static, dynamic, and buckets.
// Only build the profile from memories tagged as onboarding data
const { profile } = await supermemory.profile("user_123", {
filter: { field: "source", operator: "eq", value: "onboarding" },
});This is useful when a namespace mixes memories from several sources or contexts and you only want one of them reflected in the profile — for example, a support agent that should only see profile facts derived from support tickets, not from an internal wiki synced into the same namespace:
const { profile } = await supermemory.profile("org_customer_442", {
filter: { field: "channel", operator: "eq", value: "support_ticket" },
});A v5 profile call takes no search query. If you also need query-ranked memories, make a separate supermemory.search() call with the same filter. See Filtering Profiles for the full parameter reference.
Use cases
Personalized AI assistants
Profiles provide: expertise level, communication preferences, tools used, current projects.
const systemPrompt = `You are assisting ${userName}.
Background: ${profile.static.map((m) => m.memory).join('\n')}
Current focus: ${profile.dynamic.map((m) => m.memory).join('\n')}
Adjust responses to their expertise and preferences.`;Customer support
Profiles provide: product usage, previous issues, tech proficiency.
- No more "let me look up your account"
- Agents immediately understand context
- AI support references past interactions naturally
Educational platforms
Profiles provide: learning style, completed courses, strengths/weaknesses.
Development tools
Profiles provide: preferred languages, coding style, current project context.
Next steps
Apps Docs Connectors Github (`apps/docs/connectors/github.mdx`)
title: "GitHub connector" sidebarTitle: "GitHub" icon: "/images/github-icon.svg"
Connect GitHub repositories to sync documentation files into your Supermemory knowledge base with OAuth authentication, webhook support, and automatic incremental syncing.
Quick setup
1. Create GitHub Connector
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("user-123", {
provider: "github",
redirectUrl: "https://yourapp.com/auth/github/callback",
documentLimit: 5000,
})
// Send the user to GitHub to authorize
if (connector.authorization) window.location.href = connector.authorization.url
console.log("Auth URL expires at:", connector.authorization?.expiresAt)
```client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY"))
connector = client.connectors.create(
"user-123",
request={
"provider": "github",
"redirect_url": "https://yourapp.com/auth/github/callback",
"document_limit": 5000,
},
)
# Send the user to GitHub to authorize
print(f"Redirect to: {connector.authorization.url}")
print(f"Auth URL expires at: {connector.authorization.expires_at}")
```# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z"
# }
# }
```2. Handle OAuth callback
Send the user to authorization.url before authorization.expiresAt. After the user grants permissions, Supermemory sends them to a hosted repository picker; once they choose repositories, they return to your redirectUrl. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with system.status: "pending_authorization".
3. Select Repositories
Unlike most connectors, GitHub requires repository selection before syncing begins. There are two ways to set it:
- Hosted picker: read the connector with
include: "picker"and send the user topicker.url. The link works once and expires atpicker.expiresAt. - Direct selection: if you already know the repository ids, pass them in
selection.reposwithconnectors.update. A new selection replaces the old one and starts a sync.
if (withPicker.picker) window.location.href = withPicker.picker.url
// Option B: set the repositories directly by id
await supermemory.connectors.update("user-123", connectorId, {
selection: {
repos: ["123456789", "987654321"],
},
})
console.log("Repository sync initiated")
```if with_picker.picker:
print(f"Redirect to: {with_picker.picker.url}")
# Option B: set the repositories directly by id
client.connectors.update(
"user-123",
connector_id,
selection={"repos": ["123456789", "987654321"]},
)
print("Repository sync initiated")
```# Response includes: "picker": { "url": "https://...", "expiresAt": "2024-01-15T11:30:00.000Z" }
# Option B: set the repositories directly by id
curl -X PATCH "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"selection": {
"repos": ["123456789", "987654321"]
}
}'
```Supported document types
The GitHub connector syncs documentation and text files with the following extensions:
- Markdown files:
.md,.mdx,.markdown - Text files:
.txt - reStructuredText:
.rst - AsciiDoc:
.adoc - Org-mode:
.org
Files are indexed as github_markdown document type in Supermemory.
Incremental sync with webhooks
The GitHub connector automatically sets up webhooks for real-time incremental syncing. When files are pushed or deleted in configured repositories, Supermemory is notified immediately.
How it works
- Webhook Setup: When you set the selection, a webhook is automatically installed in each repository
- Push Events: When commits are pushed to the default branch, changed documentation files are synced
- Delete Events: When documentation files are deleted, they're removed from your Supermemory knowledge base
- Incremental Updates: Only changed files are processed, keeping sync fast and efficient
Webhook security
Webhooks are secured using HMAC-SHA256 signature validation with constant-time comparison. Supermemory automatically validates that webhook events come from GitHub before processing them. Each repository gets a unique webhook secret for maximum security.
console.log("Webhooks:", connector.capabilities.webhooks)
console.log("Last synced:", connector.system.lastSuccessfulSyncAt)
console.log("Last sync:", connector.latestRun?.system.status, connector.latestRun?.error)
console.log("Repositories:", connector.selection?.repos)
console.log("Recent runs:", connector.syncs)
```print(f"Webhooks: {connector.capabilities.webhooks}")
print(f"Last synced: {connector.system.last_successful_sync_at}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
if connector.latest_run and connector.latest_run.error:
print(connector.latest_run.error)
repos = (connector.selection or {}).get("repos") or []
print(f"Repositories: {[r.name for r in repos]}")
print(f"Recent runs: {connector.syncs}")
```Connector Management
List All Connectors
connectors
.filter(connector => connector.provider === "github")
.forEach(connector => {
console.log(`Provider: ${connector.provider}`)
console.log(`ID: ${connector.id}`)
console.log(`Account: ${connector.account}`)
console.log(`Created: ${connector.system.createdAt}`)
console.log(`Document limit: ${connector.documentLimit}`)
console.log(`Repositories: ${connector.selection?.repos?.length ?? 0}`)
console.log("---")
})
```for connector in connectors:
print(f"Provider: {connector.provider}")
print(f"ID: {connector.id}")
print(f"Account: {connector.account}")
print(f"Created: {connector.system.created_at}")
print(f"Document limit: {connector.document_limit}")
repos = (connector.selection or {}).get("repos") or []
print(f"Repositories: {len(repos)}")
print("---")
```Update Repository Selection
You can update which repositories are synced at any time. The new selection replaces the old one and starts a sync.
console.log("Repository selection updated")
```print("Repository selection updated")
```Delete Connector
// Delete the connector but keep the imported documents
await supermemory.connectors.delete("user-123", connectorId, {
deleteDocuments: false,
})
```# Delete the connector but keep the imported documents
client.connectors.delete("user-123", connector_id, delete_documents=False)
```# Delete the connector but keep the imported documents
curl -X DELETE "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}?deleteDocuments=false" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
```Manual sync
Trigger a manual synchronization for all selected repositories. The call returns 409 while a sync for that connector is already running.
console.log(run.status)
// Output: queued
```print(run.status)
# Output: queued
```# Response: {"id": "PTzGiUYei7pgzg5buzZHgA", "status": "queued"}
```Advanced configuration
Custom OAuth application
For white-label deployments or custom branding you can connect with your own GitHub OAuth app. Custom OAuth credentials are an organization setting and are not part of the v5 connector routes. See Custom OAuth Applications.
- Go to GitHub Settings → Developer settings → OAuth Apps
- Click "New OAuth App"
- Set Authorization callback URL to:
https://api.supermemory.ai/v3/connections/auth/callback/github - Copy the Client ID and generate a Client Secret
After configuration, all new GitHub connectors will use your custom OAuth app instead of Supermemory's default app.
Apps Docs Connectors Gmail (`apps/docs/connectors/gmail.mdx`)
title: "Gmail connector" sidebarTitle: "Gmail" icon: "/icons/hugeicons/mail-01.svg"
Connect Gmail to automatically sync email threads into your supermemory knowledge base. Supports real-time updates via Google Cloud Pub/Sub webhooks and incremental synchronization.
Quick setup
1. Create Gmail Connector
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("user-123", {
provider: "gmail",
redirectUrl: "https://yourapp.com/auth/gmail/callback",
documentLimit: 5000,
})
// Send the user to Google to authorize
if (connector.authorization) window.location.href = connector.authorization.url
console.log("Auth URL expires at:", connector.authorization?.expiresAt)
```client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY"))
connector = client.connectors.create(
"user-123",
request={
"provider": "gmail",
"redirect_url": "https://yourapp.com/auth/gmail/callback",
"document_limit": 5000,
},
)
# Send the user to Google to authorize
print(f"Redirect to: {connector.authorization.url}")
print(f"Auth URL expires at: {connector.authorization.expires_at}")
```# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z"
# }
# }
```2. Handle OAuth callback
Send the user to authorization.url before authorization.expiresAt. After the user grants permissions, Supermemory sends them to a hosted label picker; once they choose labels, they return to your redirectUrl and the initial sync begins. Nothing syncs until at least one label is selected. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with system.status: "pending_authorization".
To change the labels later, read the connector with include: "picker" and send the user to picker.url, or set label ids directly with connectors.update(namespace, id, { selection: { labels: ["INBOX"] } }). See Managing Connector Selection.
3. Check Connector Status
console.log("Connected email:", connector.account)
console.log("Last sync:", connector.latestRun?.system.status)
console.log("Threads synced:", connector.documentCount)
// List synced email threads in the namespace
const docs = await supermemory.list("user-123", "documents")
console.log(`Synced ${docs.pagination.totalItems} email threads`)
```print(f"Connected email: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Threads synced: {connector.document_count}")
# List synced email threads in the namespace
docs = client.list("user-123", "documents")
print(f"Synced {docs.pagination.total_items} email threads")
```# List synced email threads in the namespace
curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
```What gets synced
Email threads
Gmail threads (conversations) are synced as individual documents with all messages included:
- Thread content converted to structured markdown
- All messages within each thread preserved in order
- Message metadata: subject, from, to, cc, bcc, date
- HTML content converted to clean markdown
- Attachment metadata: filename, mime type, size (attachments are referenced, not stored)
Document metadata
Each synced thread includes searchable metadata:
| Field | Description |
|---|---|
type |
Always gmail_thread |
subject |
Email subject line |
threadId |
Gmail thread ID |
from |
Sender, as the raw From header (for example Team <[email protected]>) |
to |
Recipient addresses, comma-separated |
date |
Date of first message |
messageCount |
Number of messages in thread |
attachmentCount |
Number of attachments (if any) |
attachmentNames |
List of attachment filenames |
You can filter searches using these metadata fields:
const results = await supermemory.search("user-123", {
query: "project update",
filter: {
operator: "and",
operands: [
{ field: "type", operator: "eq", value: "gmail_thread" },
{ field: "from", operator: "contains", value: "[email protected]" },
],
},
searchMode: "chunks",
})Connector Management
List All Connectors
connectors.forEach(connector => {
console.log(`Provider: ${connector.provider}`)
console.log(`ID: ${connector.id}`)
console.log(`Account: ${connector.account}`)
console.log(`Created: ${connector.system.createdAt}`)
console.log(`Document limit: ${connector.documentLimit}`)
console.log("---")
})
```for connector in connectors:
print(f"Provider: {connector.provider}")
print(f"ID: {connector.id}")
print(f"Account: {connector.account}")
print(f"Created: {connector.system.created_at}")
print(f"Document limit: {connector.document_limit}")
print("---")
```Delete Connector
// Delete the connector but keep the imported documents
await supermemory.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", {
deleteDocuments: false,
})
```# Delete the connector but keep the imported documents
client.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", delete_documents=False)
```# Delete the connector but keep the imported documents
curl -X DELETE "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA?deleteDocuments=false" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
```Manual sync
Trigger a manual synchronization. The call returns 409 while a sync for that connector is already running.
console.log(run.status)
// Output: queued
```print(run.status)
# Output: queued
```# Response: {"id": "PTzGiUYei7pgzg5buzZHgA", "status": "queued"}
```Sync mechanism
Gmail connector supports multiple sync methods:
| Feature | Behavior |
|---|---|
| Real-time sync | Via Google Cloud Pub/Sub webhooks (7-day expiry, auto-renewed) |
| Scheduled sync | Every 4 hours |
| Manual sync | On-demand via API |
| Incremental sync | Uses Gmail historyId to fetch only changed threads |
How real-time sync works
- After labels are selected, supermemory registers a Gmail API "watch" subscription when Pub/Sub is configured
- Gmail sends notifications to a Google Cloud Pub/Sub topic when emails change
- supermemory receives these notifications and fetches updated threads
- Watch subscriptions expire after 7 days and are automatically renewed
Permissions & scopes
The Gmail connector requests the following OAuth scopes:
| Scope | Purpose |
|---|---|
gmail.readonly |
Read-only access to Gmail messages and threads |
userinfo.email |
Access to user's email address for connector identification |
Limitations
Troubleshooting
OAuth fails or missing refresh token
If OAuth fails or the connector stops syncing:
- Delete the existing connector
- Create a new connector
- Ensure the user completes the full OAuth flow with consent
// Re-create the connector to get fresh tokens
await supermemory.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", {
deleteDocuments: false,
})
const next = await supermemory.connectors.create("user-123", {
provider: "gmail",
redirectUrl: "https://yourapp.com/auth/gmail/callback"
})
// User must re-authenticate
if (next.authorization) window.location.href = next.authorization.urlEmails not syncing in real-time
If real-time sync isn't working:
- Scheduled sync (every 4 hours) and manual sync still work
- Real-time sync requires supermemory's Pub/Sub infrastructure
- Check if the connector was created recently (watch registration happens on creation)
- Trigger a manual sync to verify the connector is working
Permission denied errors
If you see permission errors:
- Ensure the user granted the required Gmail scopes during OAuth
- Verify your organization has Max Plan or above access
- Check if the user revoked app access in their Google Account settings
Apps Docs Connectors Google Drive (`apps/docs/connectors/google-drive.mdx`)
title: "Google Drive connector" sidebarTitle: "Google Drive" icon: "/images/google-drive-icon.svg"
Connect Google Drive to sync documents into your Supermemory knowledge base with OAuth authentication and custom app support.
Sync scope
Default for new connectors: after OAuth, the user completes a folder and file picker (Google Docs, Sheets, Slides, and PDFs). Only items they select are synced and updated until they change the selection (for example from the Supermemory console).
Whole Drive: set config.syncScope to "full" when creating the connector so the entire Drive syncs without the picker.
Explicit scoped mode: set config.syncScope to "selected" for the picker flow, or rely on the default for new connects.
Quick setup
1. Create Google Drive Connector
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("user-123", {
provider: "google-drive",
redirectUrl: "https://yourapp.com/auth/google-drive/callback",
documentLimit: 3000,
config: { syncScope: "selected" },
})
// Send the user to Google to authorize
if (connector.authorization) window.location.href = connector.authorization.url
console.log("Auth URL expires at:", connector.authorization?.expiresAt)
```client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY"))
connector = client.connectors.create(
"user-123",
request={
"provider": "google-drive",
"redirect_url": "https://yourapp.com/auth/google-drive/callback",
"document_limit": 3000,
"config": {"sync_scope": "selected"},
},
)
# Send the user to Google to authorize
print(f"Redirect to: {connector.authorization.url}")
print(f"Auth URL expires at: {connector.authorization.expires_at}")
```# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z"
# }
# }
```2. Handle OAuth callback
Send the user to authorization.url before authorization.expiresAt. After the user grants permissions, Google redirects through Supermemory to finish the connector. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with system.status: "pending_authorization".
With scoped sync (syncScope omitted or "selected"), the user is sent to Supermemory’s hosted file and folder picker; they must complete that step before syncs run. With syncScope: "full", Supermemory redirects to your redirectUrl without the picker.
You can open the picker again later for an existing connector. Read the connector with include: "picker" and send the user to picker.url; the link works once and expires at picker.expiresAt. Pass returnUrl to choose where the picker sends the user afterwards.
if (connector.picker) window.location.href = connector.picker.url
```# Response includes: "picker": { "url": "https://...", "expiresAt": "2024-01-15T11:30:00.000Z" }
```If you already know the Drive file and folder ids, set them directly. A new selection replaces the old one and starts a sync.
3. Check Connector Status
console.log("Account:", connector.account)
console.log("Selection:", connector.selection)
console.log("Last sync:", connector.latestRun?.system.status)
// List synced documents in the namespace
const { documents } = await supermemory.list("user-123", "documents")
```print(f"Account: {connector.account}")
print(f"Selection: {connector.selection}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
# List synced documents in the namespace
documents = client.list("user-123", "documents").documents
```# List synced documents in the namespace
curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
```Supported document types
Based on the API type definitions, Google Drive documents are identified with these types:
google_doc- Google Docsgoogle_slide- Google Slidesgoogle_sheet- Google Sheets
PDF and Markdown files stored in Drive are synced too.
Connector Management
List All Connectors
connectors.forEach(connector => {
console.log(`Provider: ${connector.provider}`)
console.log(`ID: ${connector.id}`)
console.log(`Account: ${connector.account}`)
console.log(`Created: ${connector.system.createdAt}`)
console.log(`Document limit: ${connector.documentLimit}`)
console.log("---")
})
```for connector in connectors:
print(f"Provider: {connector.provider}")
print(f"ID: {connector.id}")
print(f"Account: {connector.account}")
print(f"Created: {connector.system.created_at}")
print(f"Document limit: {connector.document_limit}")
print("---")
```# Response example:
# {
# "connectors": [
# {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "provider": "google-drive",
# "namespace": "user-123",
# "account": "[email protected]",
# "documentLimit": 3000,
# "documentCount": 120,
# "latestRun": { "system": { "status": "completed", ... }, "error": null },
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "..." }
# }
# ],
# "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
# }
```Delete Connector
// Delete the connector but keep the imported documents
await supermemory.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", {
deleteDocuments: false,
})
```# Delete the connector but keep the imported documents
client.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", delete_documents=False)
```# Delete the connector but keep the imported documents
curl -X DELETE "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA?deleteDocuments=false" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
```Manual sync
Trigger a manual synchronization. The call returns 409 while a sync for that connector is already running.
console.log(run.status)
// Output: queued
```print(run.status)
# Output: queued
```# Response: {"id": "PTzGiUYei7pgzg5buzZHgA", "status": "queued"}
```Advanced configuration
Custom OAuth application
You can connect with your own Google OAuth app. Custom OAuth credentials are an organization setting and are not part of the v5 connector routes. See Custom OAuth Applications for the setup steps and callback URL.
Apps Docs Connectors Granola (`apps/docs/connectors/granola.mdx`)
title: "Granola connector" sidebarTitle: "Granola" icon: "/images/granola.svg"
Connect Granola to sync AI meeting notes and transcripts into your Supermemory knowledge base. The connector uses a Granola API key, so there is no OAuth redirect flow.
Quick setup
From the console
- Open the Supermemory Console.
- Go to Connectors.
- Find the Granola row and click Connect.
- Paste your Granola API key.
- Optionally set a document limit and namespace.
- Click Connect.
The console creates the connector and starts the initial sync automatically.
With the API
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("org-123", {
provider: "granola",
config: {
apiKey: process.env.GRANOLA_API_KEY!,
},
documentLimit: 1000,
})
// Granola doesn't require OAuth; authorization is null and the first sync starts now
console.log("Granola connector:", connector.id)
```client = Supermemory(api_key=os.environ["SUPERMEMORY_API_KEY"])
connector = client.connectors.create(
"org-123",
request={
"provider": "granola",
"config": {
"api_key": os.environ["GRANOLA_API_KEY"],
},
"document_limit": 1000,
},
)
# Granola doesn't require OAuth; authorization is None and the first sync starts now
print(f"Granola connector: {connector.id}")
```# Response: {"id": "PTzGiUYei7pgzg5buzZHgA", "authorization": null}
```Supermemory validates the Granola API key before creating the connector. The initial sync starts automatically after the connector is created.
Configuration options
For Granola, provider-specific fields go inside the config object. The namespace is in the URL, and documentLimit stays top-level in the body.
| Parameter | Location | Required | Description |
|---|---|---|---|
apiKey |
config.apiKey |
Yes | Granola API key from Settings > Connectors > API keys |
documentLimit |
top-level | No | Maximum notes to sync per connector (default: 10,000) |
What gets synced
Granola notes are synced as markdown documents. Each document can include:
- Note title
- Meeting time, attendees, and meeting URL when Granola returns them
- AI-generated summary when present
- Full transcript when present
Document metadata
Each synced note includes searchable metadata:
| Field | Description |
|---|---|
type |
Always granola |
title |
Granola note title |
createdAt |
Granola note creation timestamp |
updatedAt |
Granola note update timestamp |
url |
Granola note URL, when available |
attendees |
Attendee names or emails, when available |
meetingStart |
Calendar event start time, when available |
You can filter searches using these metadata fields:
const results = await supermemory.search("org-123", {
query: "customer onboarding discussion",
filter: {
operator: "and",
operands: [
{ field: "type", operator: "eq", value: "granola" },
{ field: "attendees", operator: "arrayContains", value: "[email protected]" },
],
},
searchMode: "chunks",
})Connector Management
Check Sync Status
console.log("Last sync:", connector.latestRun?.system.status, connector.latestRun?.error)
console.log("Notes synced:", connector.documentCount)
```Delete Connector
Manual sync
The call returns 409 while a sync for that connector is already running.
# Response: {"id": "PTzGiUYei7pgzg5buzZHgA", "status": "queued"}
```Sync behavior
| Feature | Behavior |
|---|---|
| Initial sync | Fetches Granola notes up to documentLimit |
| Incremental sync | Uses Granola updated_at timestamps to fetch notes changed since the previous sync |
| Transcript handling | Fetches each note with transcript content included |
| Sync schedule | Initial sync after connector creation + manual triggers |
| Document limit | 10,000 notes per connector (default) |
Troubleshooting
| Error | Solution |
|---|---|
Granola API key is required |
Include a non-empty config.apiKey value when creating the connector |
Granola API key is invalid |
Create a new key in Granola and reconnect |
Could not reach Granola API |
Retry after checking Granola API availability and network access |
| Missing notes | Check documentLimit; if the workspace has more notes than the limit, only notes up to the limit are imported |
Apps Docs Connectors Managing Resources (`apps/docs/connectors/managing-resources.mdx`)
title: 'Managing Connector Selection' sidebarTitle: 'Managing Resources' icon: 'folder-sync'
Some connectors let you select which resources to sync. v5 exposes this through two calls on the connector itself: read it with include: "picker" to get a hosted picker URL, or send the ids directly with connectors.update.
Open the Hosted Picker
GET /ns/{namespace}/connectors/{id}?include=picker
Returns a one-time picker URL. Send the user there; Supermemory lists their resources, saves the selection and starts a sync. Pass returnUrl to choose where the picker sends the user when they finish.
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.get("user-123", connectorId, {
include: "picker",
returnUrl: "https://yourapp.com/settings/integrations"
})
console.log("Picker URL:", connector.picker?.url)
console.log("Expires at:", connector.picker?.expiresAt)
console.log("Selectable:", connector.capabilities.selectable)
```python Python
from supermemory import Supermemory
import os
client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY"))
connector = client.connectors.get(
"user-123",
connector_id,
include=["picker"],
return_url="https://yourapp.com/settings/integrations",
)
print(f"Picker URL: {connector.picker.url if connector.picker else None}")
print(f"Expires at: {connector.picker.expires_at if connector.picker else None}")
print(f"Selectable: {connector.capabilities.selectable}")curl "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}?include=picker&returnUrl=https%3A%2F%2Fyourapp.com%2Fsettings%2Fintegrations" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"Query parameters
include:pickerfor the hosted picker URL,syncsfor recent sync runs, orsyncs,pickerfor bothreturnUrl: Optional. Where the hosted picker sends the user when they finish
Response
{
"id": "PTzGiUYei7pgzg5buzZHgA",
"provider": "github",
"namespace": "user-123",
"capabilities": { "selectable": true, "webhooks": true },
"selection": {
"repos": [
{ "id": "123456789", "name": "your-org/documentation" }
]
},
"picker": {
"url": "https://...",
"expiresAt": "2024-01-15T11:30:00.000Z"
}
}Set the Selection Directly
PATCH /ns/{namespace}/connectors/{id}
Send the ids to sync, grouped by kind. The new selection replaces the current one and starts a sync. Webhooks are registered for the selected resources.
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.update("user-123", connectorId, {
selection: {
repos: ["123456789", "987654321"],
},
})
console.log("Selection:", connector.selection)
console.log("Last sync:", connector.latestRun?.system.status)
```python Python
from supermemory import Supermemory
import os
client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY"))
connector = client.connectors.update(
"user-123",
connector_id,
selection={"repos": ["123456789", "987654321"]},
)
print(f"Selection: {connector.selection}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")curl -X PATCH "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"selection": {
"repos": ["123456789", "987654321"]
}
}'Request body
{
"selection": {
"repos": ["123456789"]
}
}The selection key depends on the provider:
- GitHub:
repos(repository ids) - Gmail:
labels(label ids, for exampleINBOX) - Google Drive:
filesandfolders(Drive ids)
You can send documentLimit in the same body. Each call must include selection, documentLimit, or both.
Response
The updated connector object, including the saved selection as { id, name } pairs.
{
"id": "PTzGiUYei7pgzg5buzZHgA",
"provider": "github",
"selection": {
"repos": [
{ "id": "123456789", "name": "your-org/documentation" },
{ "id": "987654321", "name": "your-org/api-docs" }
]
},
"latestRun": { "errorCode": null, "error": null, "system": { "status": "running", "startedAt": "...", "completedAt": null } }
}Error Responses
400: Connector does not support selection, the selection is empty or has unknown ids, or the connector needs to be reconnected401: Unauthorized403: The key lacks admin role or write access to the connector's namespaces404: Connector not found409: Connector is still waiting for the user to finish authorizing
Example: GitHub repository selection
Here's a complete example for GitHub:
// 2. Send the user to GitHub. After they authorize, Supermemory shows the hosted
// repository picker, then returns them to redirectUrl
if (created.authorization) window.location.href = created.authorization.url
// 3. To change repositories later, open the hosted picker again
const connector = await supermemory.connectors.get("user-123", created.id, {
include: "picker",
returnUrl: "https://yourapp.com/settings/integrations"
})
if (connector.picker) window.location.href = connector.picker.url
// 4. Or, if you already know the repository ids, set them directly
await supermemory.connectors.update("user-123", created.id, {
selection: { repos: selectedRepositoryIds },
})
```python Python
# 1. Create the connector
created = client.connectors.create(
"user-123",
request={
"provider": "github",
"redirect_url": "https://yourapp.com/callback",
},
)
# 2. Send the user to GitHub. After they authorize, Supermemory shows the hosted
# repository picker, then returns them to redirect_url
print(f"Redirect to: {created.authorization.url}")
# 3. To change repositories later, open the hosted picker again
connector = client.connectors.get(
"user-123",
created.id,
include=["picker"],
return_url="https://yourapp.com/settings/integrations",
)
if connector.picker:
print(f"Redirect to: {connector.picker.url}")
# 4. Or, if you already know the repository ids, set them directly
client.connectors.update(
"user-123",
created.id,
selection={"repos": selected_repository_ids},
)# 1. Create the connector
curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"provider": "github", "redirectUrl": "https://yourapp.com/callback"}'
# 2. ... (user authorizes at authorization.url and picks repositories in the hosted picker) ...
# 3. To change repositories later, get a one-time hosted picker URL
curl "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}?include=picker" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
# 4. Or set the repositories directly
curl -X PATCH "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"selection": {
"repos": ["123456789"]
}
}'Apps Docs Connectors Notion (`apps/docs/connectors/notion.mdx`)
title: "Notion connector" sidebarTitle: "Notion" icon: "/images/notion-icon.svg"
Connect Notion workspaces to automatically sync pages, databases, and content blocks into your Supermemory knowledge base. Supports real-time updates, rich formatting, and database properties.
Quick setup
1. Create Notion Connector
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("user-123", {
provider: "notion",
redirectUrl: "https://yourapp.com/auth/notion/callback",
documentLimit: 2000,
})
// Send the user to Notion to authorize
if (connector.authorization) window.location.href = connector.authorization.url
```client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY"))
connector = client.connectors.create(
"user-123",
request={
"provider": "notion",
"redirect_url": "https://yourapp.com/auth/notion/callback",
"document_limit": 2000,
},
)
# Send the user to Notion to authorize
print(f"Redirect to: {connector.authorization.url}")
```# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z"
# }
# }
```2. Handle OAuth flow
Send the user to authorization.url before authorization.expiresAt. After the user grants workspace access, Notion redirects to your redirectUrl and the first sync starts. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with system.status: "pending_authorization".
3. Monitor sync progress
console.log("Connected workspace:", connector.account)
console.log("Last sync:", connector.latestRun?.system.status)
console.log("Documents synced:", connector.documentCount)
// List synced pages and databases
const { documents } = await supermemory.list("user-123", "documents")
```print(f"Connected workspace: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Documents synced: {connector.document_count}")
# List synced pages and databases
documents = client.list("user-123", "documents").documents
```# Response includes connector details:
# {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "provider": "notion",
# "namespace": "user-123",
# "account": "[email protected]",
# "documentLimit": 2000,
# "documentCount": 120,
# "latestRun": { "system": { "status": "completed", ... }, "error": null },
# "system": { "status": "active", "createdAt": "2024-01-15T10:00:00Z", "lastSuccessfulSyncAt": "2024-01-15T10:30:00Z" },
# "syncs": [...]
# }
# List synced documents in the namespace
curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
# Response: {"documents": [{"title": "Product Roadmap", "system": {"status": "done", ...}, ...}], "pagination": {...}}
```Document limit
Each connector has a documentLimit (optional when creating the connector; allowed range 1–10,000). For Notion, each sync run asks the Notion Search API for pages shared with the integration, ordered by last edited time, newest first, and stops after that many pages (or when Search has no more results).
- Full sync: If your workspace has more shareable pages than
documentLimit, the rest are not included in that run. Pages that look “missing” are often older or less recently edited relative to that ordering. IncreasedocumentLimitor trigger another sync after pages change if you need broader coverage. - Incremental sync: Only pages edited after the previous sync are candidates; each one still counts toward the same
documentLimit. If more pages changed than the limit since last sync, only the first batch in that newest-first order is returned for that run.
Nested and child pages still count as normal pages in Search if the integration can access them—they are not skipped because they are nested. The limit applies to how many pages are fetched per sync, not to depth.
Change the limit later with supermemory.connectors.update(namespace, id, { documentLimit }).
Manual Sync
console.log(run.status)
// Output: queued
```# Response: {"id": "PTzGiUYei7pgzg5buzZHgA", "status": "queued"}
# 409 if a sync is already running
```Supported content types
Notion pages
- Rich text blocks with formatting preserved
- Nested pages and hierarchical structure
- Embedded content (images, videos, files)
- Code blocks with syntax highlighting
- Callouts and quotes converted to markdown
Notion databases
- Database entries synced as individual documents
- Properties included in metadata
- Relations between database entries
- Formulas and rollups calculated values
- Multi-select and select properties
Block types
| Block Type | Processing | Markdown Output |
|---|---|---|
| Text | Formatting preserved | **bold**, *italic*, ~~strikethrough~~ |
| Heading | Hierarchy maintained | # H1, ## H2, ### H3 |
| Code | Language detected | python\ncode here\n |
| Quote | Blockquote format | > quoted text |
| Callout | Custom formatting | > 💡 **Note:** callout text |
| List | Structure preserved | - item 1\n - nested item |
| Table | Markdown tables | ` |
| Image | Referenced with metadata | `` |
| Embed | Link with context | [Embedded Content](url) |
Delete Connector
Remove a Notion connector when no longer needed:
// Delete the connector but keep the imported documents
await supermemory.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", {
deleteDocuments: false,
})
```# Delete the connector but keep the imported documents
client.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", delete_documents=False)
```# Delete the connector but keep the imported documents
curl -X DELETE "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA?deleteDocuments=false" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
```Advanced configuration
Custom Notion integration
For production deployments you can connect with your own Notion integration. Custom OAuth credentials are an organization setting and are not part of the v5 connector routes. See Custom OAuth Applications for the setup steps and callback URL.
Workspace permissions
Notion connector respects workspace permissions:
| Permission Level | Sync Behavior |
|---|---|
| Admin | Full workspace access |
| Member | Pages with read access |
| Guest | Only shared pages |
| No Access | Removed from index |
Database integration
Database properties
Notion database properties are mapped to metadata keys named notion_prop_, lowercased with spaces replaced by underscores (a Status property becomes notion_prop_status). The page title is stored as title, and every page has type: "notion_doc".
// List documents in the namespace (there is no per-connector document list in v5)
const { documents } = await supermemory.list("user-123", "documents")
// Find database entries by a property value
const projectEntries = documents.filter(doc =>
doc.metadata?.notion_prop_status === "In Progress"
)
// Database properties become searchable metadata
const projectWithStatus = await supermemory.search("user-123", {
query: "machine learning project",
filter: {
operator: "and",
operands: [
{ field: "notion_prop_status", operator: "eq", value: "In Progress" },
{ field: "notion_prop_priority", operator: "eq", value: "High" },
],
},
searchMode: "chunks",
})Optimization strategies
- Set
documentLimithigh enough for your workspace size (see Document limit) - Use one namespace per user or tenant for efficient organization
- Monitor database sync performance for large datasets
- Share only the pages you need with the integration so each sync stays small
- Handle webhook delays gracefully in your application
Apps Docs Connectors Onedrive (`apps/docs/connectors/onedrive.mdx`)
title: "OneDrive connector" sidebarTitle: "OneDrive" icon: "/images/microsoft-icon.svg"
Connect OneDrive to automatically sync Word documents, Excel spreadsheets, and PowerPoint presentations into your Supermemory knowledge base. Supports both personal and business accounts with scheduled synchronization.
Quick setup
1. Create OneDrive Connector
import { Supermemory } from "supermemory"
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("user-123", {
provider: "onedrive",
redirectUrl: "https://yourapp.com/auth/onedrive/callback",
documentLimit: 1500,
})
// Send the user to Microsoft to authorize
if (connector.authorization) window.location.href = connector.authorization.url
// Output: Redirects to https://api.supermemory.ai/v3/connections/auth/redirect?state=...from supermemory import Supermemory
import os
client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY"))
connector = client.connectors.create(
"user-123",
request={
"provider": "onedrive",
"redirect_url": "https://yourapp.com/auth/onedrive/callback",
"document_limit": 1500,
},
)
# Send the user to Microsoft to authorize
print(f"Redirect to: {connector.authorization.url}")
# Output: Redirect to: https://api.supermemory.ai/v3/connections/auth/redirect?state=...curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "onedrive",
"redirectUrl": "https://yourapp.com/auth/onedrive/callback",
"documentLimit": 1500
}'
# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z"
# }
# }2. Handle Microsoft OAuth
Send the user to authorization.url before authorization.expiresAt. After the user grants permissions, Microsoft redirects to your redirectUrl and the initial sync begins. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with system.status: "pending_authorization".
3. Monitor sync status
console.log("Account:", connector.account)
console.log("Last sync:", connector.latestRun?.system.status)
console.log("Documents synced:", connector.documentCount)
// List synced Office documents in the namespace
const { documents } = await supermemory.list("user-123", "documents")
```# Check connector details with recent sync runs
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
print(f"Account: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Documents synced: {connector.document_count}")
# List synced Office documents in the namespace
documents = client.list("user-123", "documents").documents
```
```bash cURL
# Get connector details with recent sync runs
curl "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA?include=syncs" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
# List synced Office documents in the namespace
curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
```
</CodeGroup>
<Note>
There is no per-connector document list in v5. `supermemory.list(namespace, "documents")` returns every document in the namespace.
</Note>
## Supported document types
### Microsoft Word documents
- **Rich text formatting** converted to markdown
- **Headers and styles** preserved as markdown hierarchy
- **Images and charts** extracted and referenced
- **Tables** converted to markdown tables
### Excel spreadsheets
- **Worksheet data** converted to structured markdown
- **Multiple sheets** processed separately
- **Charts and graphs** extracted as images
- **Formulas** converted to calculated values
- **Cell formatting** simplified in markdown
### PowerPoint presentations
- **Slide content** converted to structured markdown
- **Speaker notes** included when present
- **Images and media** extracted and referenced
- **Embedded objects** processed when possible
## Sync mechanism
Webhooks lead to real-time syncing of changes in documents. You may also manually trigger a sync.
### Manual sync trigger
The call returns `409` while a sync for that connector is already running.
<CodeGroup>
```typescript Typescript
const run = await supermemory.connectors.sync("user-123", "PTzGiUYei7pgzg5buzZHgA")
console.log(run.status)
// Output: queued
```
```python Python
run = client.connectors.sync("user-123", "PTzGiUYei7pgzg5buzZHgA")
print(run.status)
# Output: queued
```
```bash cURL
curl -X POST "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA/sync" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
# Response: {"id": "PTzGiUYei7pgzg5buzZHgA", "status": "queued"}
```
</CodeGroup>
## Delete Connector
Remove a OneDrive connector when no longer needed:
<CodeGroup>
```typescript Typescript
// Delete the connector and its imported documents (default)
await supermemory.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA")
// Delete the connector but keep the imported documents
await supermemory.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", {
deleteDocuments: false,
})# Delete the connector and its imported documents (default)
client.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA")
# Delete the connector but keep the imported documents
client.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", delete_documents=False)# Delete the connector and its imported documents (default)
curl -X DELETE "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
# Delete the connector but keep the imported documents
curl -X DELETE "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA?deleteDocuments=false" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"Advanced configuration
Custom Microsoft app
For production deployments you can connect with your own Microsoft application. Custom OAuth credentials are an organization setting and are not part of the v5 connector routes. See Custom OAuth Applications for the setup steps and callback URL.
Optimization tips
- Set realistic document limits based on storage and usage
- Monitor sync health regularly with
latestRunandinclude: "syncs" - Trigger manual syncs when immediate updates are needed
- Consider account type when setting expectations
Apps Docs Connectors S3 (`apps/docs/connectors/s3.mdx`)
title: "S3 Connector" sidebarTitle: "S3" icon: "/icons/hugeicons/database-01.svg"
Connect Amazon S3 buckets or S3-compatible storage services (MinIO, DigitalOcean Spaces, Cloudflare R2, Tigris) to sync files into your Supermemory knowledge base.
Quick setup
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("org-123", {
provider: "s3",
config: {
bucket: "my-documents-bucket",
region: "us-east-1",
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
})
// S3 doesn't require OAuth; authorization is null and the first sync starts now
console.log("Connector ID:", connector.id)
```client = Supermemory(api_key=os.environ["SUPERMEMORY_API_KEY"])
connector = client.connectors.create(
"org-123",
request={
"provider": "s3",
"config": {
"bucket": "my-documents-bucket",
"region": "us-east-1",
"access_key_id": os.environ["AWS_ACCESS_KEY_ID"],
"secret_access_key": os.environ["AWS_SECRET_ACCESS_KEY"],
},
},
)
# S3 doesn't require OAuth; authorization is None and the first sync starts now
print(f"Connector ID: {connector.id}")
```# Response: {"id": "PTzGiUYei7pgzg5buzZHgA", "authorization": null}
```Configuration options
For S3, provider-specific fields go inside the config object. The namespace is in the URL, and documentLimit stays top-level in the body.
| Parameter | Location | Required | Description |
|---|---|---|---|
bucket |
config.bucket |
Yes | S3 bucket name |
region |
config.region |
Yes | AWS region (e.g., us-east-1). Use auto for S3-compatible providers that don't expose AWS-style regions (MinIO, R2, Tigris). |
accessKeyId |
config.accessKeyId |
Yes | AWS access key ID or S3-compatible service key |
secretAccessKey |
config.secretAccessKey |
Yes | AWS secret access key |
endpoint |
config.endpoint |
No | Custom endpoint for S3-compatible services |
prefix |
config.prefix |
No | Key prefix filter (e.g., documents/) |
documentLimit |
top-level | No | Maximum documents to sync (default: 10,000) |
S3-compatible services
Use config.endpoint to connect to S3-compatible storage. These services don't use AWS-style regions, so set config.region to auto. The value is still required for request signing but the service ignores it.
// MinIO
const connector = await supermemory.connectors.create("minio-sync", {
provider: "s3",
config: {
bucket: "my-bucket",
region: "auto",
accessKeyId: "minio-key",
secretAccessKey: "minio-secret",
endpoint: "https://minio.example.com",
},
})Common S3-compatible endpoint values:
| Service | config.endpoint |
config.region |
|---|---|---|
| DigitalOcean Spaces | https://nyc3.digitaloceanspaces.com |
nyc3 |
| Cloudflare R2 | https://<account-id>.r2.cloudflarestorage.com |
auto |
| Tigris | https://t3.storage.dev |
auto |
Cloudflare R2 example:
const connector = await supermemory.connectors.create("r2-sync", {
provider: "s3",
config: {
bucket: "my-bucket",
region: "auto",
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
endpoint: "https://<account-id>.r2.cloudflarestorage.com",
},
})Prefix filtering
Sync only files within a specific path:
const connector = await supermemory.connectors.create("engineering-docs", {
provider: "s3",
config: {
bucket: "company-data",
region: "us-east-1",
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
prefix: "documents/engineering/", // Only syncs files under this path
},
})Multi-Tenant Buckets
A connector syncs into exactly one namespace. For a bucket shared by many tenants, create one connector per tenant namespace and point each at that tenant's path with config.prefix:
await supermemory.connectors.create("user-123", {
provider: "s3",
config: {
bucket: "user-files",
region: "us-east-1",
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
prefix: "users/user-123/",
},
})Connector Management
Check Sync Status
console.log("Bucket:", connector.config?.bucket)
console.log("Last sync:", connector.latestRun?.system.status, connector.latestRun?.error)
console.log("Files synced:", connector.documentCount)
```Delete Connector
Manual sync
The call returns 409 while a sync for that connector is already running.
# Response: {"id": "PTzGiUYei7pgzg5buzZHgA", "status": "queued"}
```Sync behavior
| Feature | Behavior |
|---|---|
| Initial sync | Fetches all files matching prefix filter |
| Incremental sync | Only files modified since last sync |
| Sync schedule | Every 4 hours + manual triggers |
| Document limit | 10,000 files per connector (default) |
IAM permissions
Minimum required permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::your-bucket-name",
"arn:aws:s3:::your-bucket-name/*"
]
}
]
}Error codes
Supermemory checks the credentials against the bucket before it creates the connector. If that check fails, POST /ns/{namespace}/connectors returns 400 with one of these messages. The code column is the status S3 returned.
| S3 status | Message | Solution |
|---|---|---|
| 401 | Authentication failed | Verify access key and secret |
| 403 | Access denied | Check IAM permissions and bucket policy |
| 404 | Bucket not found | Verify bucket name and region |
Apps Docs Connectors Troubleshooting (`apps/docs/connectors/troubleshooting.mdx`)
title: "Connector troubleshooting" sidebarTitle: "Troubleshooting" icon: "/icons/hugeicons/wrench-01.svg"
Quick guide to resolve common connector issues with authentication, syncing, and permissions.
Quick health check
Check if your connectors are working properly:
const { connectors } = await supermemory.connectors.list("user-123")
connectors.forEach(connector => {
console.log(`${connector.provider}: ${connector.account} - last sync ${connector.latestRun?.system.status ?? "never"}`)
})
// Inspect a failing connector's recent runs and the items that failed
const failing = connectors.filter(connector => connector.latestRun?.system.status === "failed")
for (const connector of failing) {
const detail = await supermemory.connectors.get("user-123", connector.id, {
include: "syncs",
})
console.log(`⚠️ ${connector.provider}: ${detail.latestRun?.error}`)
console.log(detail.syncs)
}
// Check for stuck documents in the namespace
const { documents } = await supermemory.list("user-123", "documents")
const stuck = documents.filter(doc => doc.system.status === "failed")
if (stuck.length > 0) {
console.log(`⚠️ ${stuck.length} documents failed to process`)
}connectors = client.connectors.list("user-123").connectors
for connector in connectors:
status = connector.latest_run.system.status if connector.latest_run else "never"
print(f"{connector.provider}: {connector.account} - last sync {status}")
# Inspect a failing connector's recent runs and the items that failed
failing = [c for c in connectors if c.latest_run and c.latest_run.system.status == "failed"]
for connector in failing:
detail = client.connectors.get("user-123", connector.id, include=["syncs"])
print(f"⚠️ {connector.provider}: {detail.latest_run.error}")
print(detail.syncs)
# Check for stuck documents in the namespace
documents = client.list("user-123", "documents").documents
stuck = [doc for doc in documents if doc.system.status == "failed"]
if stuck:
print(f"⚠️ {len(stuck)} documents failed to process")# List connectors in the namespace
curl "https://api.supermemory.ai/ns/user-123/connectors" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
# Read one connector with its recent sync runs
curl "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA?include=syncs" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
# Check document status in the namespace
curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filter": {"field": "type", "operator": "eq", "value": "notion_doc"}}'Common issues
Connector Missing From List
Problem: connectors.create returned an id, but connectors.list does not show it
Solution: A pending OAuth connector is not returned by list until the user finishes authorization. connectors.get still returns it with system.status: "pending_authorization". Send the user to authorization.url before authorization.expiresAt. If the link expired, create the connector again.
OAuth callback fails
Problem: "Invalid redirect URI" error after user grants permissions
Solution: The provider always redirects to Supermemory's callback, not to your redirectUrl. If you use a custom OAuth application, its redirect URI must be exactly https://api.supermemory.ai/v3/connections/auth/callback/{provider}. Your redirectUrl is where Supermemory sends the user after it handles that callback, and must be an http or https URL:
const connector = await supermemory.connectors.create("user-123", {
provider: "notion",
redirectUrl: "https://yourapp.com/auth/notion/callback"
})Prevention:
- Use HTTPS for production URLs
- Register
https://api.supermemory.ai/v3/connections/auth/callback/{provider}in your custom OAuth app settings - Test the flow in development first
Documents not syncing
Problem: Documents stuck in "queued" or "extracting" status for over 30 minutes
Solution: Trigger a manual sync:
// Force a sync for the connector
await supermemory.connectors.sync("user-123", "PTzGiUYei7pgzg5buzZHgA")
// 409 means a sync is already running; wait for latestRun.system.status to changeIf documents consistently fail:
- Read
latestRun.errorandinclude: "syncs"for the failed items - Check if files are over 50MB (may timeout)
- Verify you have permission to access the documents
- Ensure the document type is supported
Permission denied errors
Problem: Some documents show "permission denied" or aren't syncing
Solution: Re-authenticate with proper permissions:
// Delete and recreate the connector
await supermemory.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", {
deleteDocuments: false,
})
const next = await supermemory.connectors.create("user-123", {
provider: "google-drive",
redirectUrl: "https://yourapp.com/callback"
})
// User must re-authenticate
if (next.authorization) window.location.href = next.authorization.urlSync takes too long
Problem: Hundreds of documents taking hours to sync
Solution: Set reasonable document limits:
const connector = await supermemory.connectors.create("user-123", {
provider: "onedrive",
redirectUrl: "https://yourapp.com/callback",
documentLimit: 500, // Start with fewer documents
})
// Raise it later without reconnecting
await supermemory.connectors.update("user-123", connector.id, {
documentLimit: 2000,
})Provider-specific issues
Google Drive
Shared Drive Issues
Shared drives require special permissions. Make sure:
- User has access to the shared drive
- OAuth app has drive.readonly scope
- User is a member of the shared drive
Picker Not Completed
With the default scoped sync, syncs may skip the connector until the user finishes the file picker. Read the connector with include: "picker" and send the user to picker.url.
Notion
Database Not Syncing
Notion databases need explicit permission. If databases aren't syncing:
- Go to Notion workspace settings
- Find your integration under "Connections"
- Click on the integration
- Select specific pages/databases to share
- Re-sync after granting access
Workspace Access
For full workspace access, a workspace admin must:
- Approve the integration
- Grant access to all pages
- Enable "Read content" permission
OneDrive
Business vs Personal Accounts
Business accounts may have additional restrictions:
- Admin consent might be required
- Some SharePoint sites may be restricted
- Compliance policies may block certain files
Gmail
Real-time Sync Not Working
If emails aren't syncing in real-time but scheduled/manual sync works:
- Real-time sync uses Google Cloud Pub/Sub webhooks
- Watch subscriptions expire after 7 days (supermemory auto-renews)
- Only INBOX label triggers real-time updates
- Trigger a manual sync to verify the connector is healthy:
await supermemory.connectors.sync("user-123", "PTzGiUYei7pgzg5buzZHgA")Missing Refresh Token
If Gmail stops syncing after initial setup:
- The user may have revoked app access in Google Account settings
- Delete and recreate the connector
- Ensure user completes full OAuth consent flow
// Re-authenticate to get fresh tokens
await supermemory.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", {
deleteDocuments: false,
})
const next = await supermemory.connectors.create("user-123", {
provider: "gmail",
redirectUrl: "https://yourapp.com/callback"
})Max Plan Required Error
Gmail connector requires Max Plan or above. If you see access errors:
- Verify your organization has the required plan
- Contact support to upgrade your plan
Best practices
- Set reasonable document limits - Start with 500-1000 documents
- Use one namespace per user or tenant - Makes debugging easier
- Monitor
latestRun- Check weekly for failed syncs - Handle rate limits gracefully - Implement exponential backoff
- Test OAuth in development - Ensure redirect URLs work before production
Apps Docs Connectors Web Crawler (`apps/docs/connectors/web-crawler.mdx`)
title: "Web Crawler connector" sidebarTitle: "Web Crawler" icon: "/icons/hugeicons/globe-02.svg"
Connect websites to automatically crawl and sync web pages into your Supermemory knowledge base. The web crawler respects robots.txt rules, includes SSRF protection, and automatically recrawls sites on a schedule.
Quick setup
1. Create Web Crawler Connector
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("user-123", {
provider: "web-crawler",
config: {
startUrl: "https://docs.example.com",
crawlDepth: 3,
},
documentLimit: 5000,
})
// Web crawler doesn't require OAuth; crawling starts immediately
console.log("Connector ID:", connector.id)
// Note: connector.authorization is null for web-crawler
```client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY"))
connector = client.connectors.create(
"user-123",
request={
"provider": "web-crawler",
"config": {
"start_url": "https://docs.example.com",
"crawl_depth": 3,
},
"document_limit": 5000,
},
)
# Web crawler doesn't require OAuth; crawling starts immediately
print(f"Connector ID: {connector.id}")
# Note: connector.authorization is None for web-crawler
```# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": null
# }
```Configuration options
| Parameter | Location | Required | Description |
|---|---|---|---|
startUrl |
config.startUrl |
Yes | Public URL where the crawl starts |
crawlDepth |
config.crawlDepth |
No | How many links deep to follow (1 to 5, default 3) |
documentLimit |
top-level | No | Maximum pages to sync (1 to 10,000) |
2. Connector Established
Unlike OAuth connectors, the web crawler doesn't require user authorization. authorization is null, the connector is visible immediately, and crawling begins automatically.
3. Monitor sync progress
console.log("Start URL:", connector.config?.startUrl)
console.log("Last sync:", connector.latestRun?.system.status)
console.log("Pages synced:", connector.documentCount)
// List synced web pages in the namespace
const docs = await supermemory.list("user-123", "documents")
console.log(`Synced ${docs.pagination.totalItems} documents`)
```print(f"Start URL: {(connector.config or {}).get('startUrl')}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Pages synced: {connector.document_count}")
# List synced web pages in the namespace
docs = client.list("user-123", "documents")
print(f"Synced {docs.pagination.total_items} documents")
```# Response includes connector details:
# {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "provider": "web-crawler",
# "namespace": "user-123",
# "config": {"startUrl": "https://docs.example.com", "crawlDepth": 3},
# "documentLimit": 5000,
# "documentCount": 240,
# "latestRun": { "system": { "status": "completed", ... }, "error": null },
# "system": { "status": "active", "createdAt": "2024-01-15T10:00:00Z", "lastSuccessfulSyncAt": "..." }
# }
# List synced documents in the namespace
curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
# Response: {"documents": [{"title": "Getting Started", "system": {"status": "done", ...}, ...}], "pagination": {...}}
```Supported content types
Web pages
- HTML content extracted and converted to markdown
- Same-domain crawling only (respects hostname boundaries)
- Robots.txt compliance - respects disallow rules
- Content filtering - only HTML pages (skips non-HTML content)
URL requirements
The web crawler only processes valid public URLs:
- Must be a public URL (not localhost, private IPs, or internal domains)
- Must be accessible from the internet
- Must return HTML content (non-HTML files are skipped)
Sync mechanism
The web crawler uses scheduled recrawling rather than real-time webhooks:
- Initial Crawl: Begins immediately after connector creation
- Scheduled Recrawling: Automatically recrawls sites that haven't been synced in 7+ days
- No Real-time Updates: Unlike other connectors, web crawler doesn't support webhook-based real-time sync
Manual Recrawl
Start a crawl now. The call returns 409 while a sync for that connector is already running.
console.log(run.status)
// Output: queued
```# Response: {"id": "PTzGiUYei7pgzg5buzZHgA", "status": "queued"}
```Connector Management
List All Connectors
const webCrawlerConnectors = connectors.filter(
connector => connector.provider === "web-crawler"
)
webCrawlerConnectors.forEach(connector => {
console.log(`Start URL: ${connector.config?.startUrl}`)
console.log(`Connector ID: ${connector.id}`)
console.log(`Created: ${connector.system.createdAt}`)
})
```for connector in connectors:
print(f"Start URL: {(connector.config or {}).get('startUrl')}")
print(f"Connector ID: {connector.id}")
print(f"Created: {connector.system.created_at}")
```# Response: {
# "connectors": [
# {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "provider": "web-crawler",
# "namespace": "user-123",
# "documentLimit": 5000,
# "config": {"startUrl": "https://docs.example.com", "crawlDepth": 3},
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "..." }
# }
# ],
# "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
# }
```Delete Connector
Remove a web crawler connector when no longer needed:
// Delete the connector but keep the crawled pages
await supermemory.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", {
deleteDocuments: false,
})
```# Delete the connector but keep the crawled pages
client.connectors.delete("user-123", "PTzGiUYei7pgzg5buzZHgA", delete_documents=False)
```# Delete the connector but keep the crawled pages
curl -X DELETE "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA?deleteDocuments=false" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
```Security & compliance
SSRF protection
Built-in protection against Server-Side Request Forgery (SSRF) attacks:
- Blocks private IP addresses (10.x.x.x, 192.168.x.x, 172.16-31.x.x)
- Blocks localhost and internal domains
- Blocks cloud metadata endpoints
- Only allows public, internet-accessible URLs
URL validation
All URLs are validated before crawling:
- Must be valid HTTP/HTTPS URLs
- Must be publicly accessible
- Must return HTML content
- Response size limited to 10MB
Apps Docs Ingestion Add Memories (`apps/docs/ingestion/add-memories.mdx`)
title: "Ingesting context to supermemory" sidebarTitle: "API" icon: "/icons/hugeicons/plus-sign.svg"
Send any raw content to Supermemory — conversations, documents, files, URLs. We extract the memories automatically. Pass id to identify content and avoid duplicates, and taskType: "superrag" if you just need it searchable, not remembered — that's 5x cheaper per token.
Every write goes to one namespace (what v3/v4 called a container tag). The namespace sits in the URL path, never in the body.
Quick start
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY });
// Add text content
await supermemory.add("user_123", {
content: "Machine learning enables computers to learn from data",
metadata: { category: "ai" },
});
// Add a URL (auto-extracted)
await supermemory.add("user_123", {
content: "https://youtube.com/watch?v=dQw4w9WgXcQ"
});
```client = Supermemory()
# Add text content
client.add(
"user_123",
content="Machine learning enables computers to learn from data",
metadata={"category": "ai"},
)
# Add a URL (auto-extracted)
client.add("user_123", content="https://youtube.com/watch?v=dQw4w9WgXcQ")
```Response:
{ "id": "abc123", "status": "queued" }Updating content
Use id to update existing documents or conversations. When you send content with the same id, Supermemory intelligently processes only what's new.
Two ways to update:
Option 1: Send only the new content
// First request
await supermemory.add("user_sarah", {
content: "user: Hi, I'm Sarah.\nassistant: Nice to meet you!",
id: "conv_123",
});
// Later: send only new messages
await supermemory.add("user_sarah", {
content: "user: What's the weather?\nassistant: It's sunny today.",
id: "conv_123", // Same ID — Supermemory links them
});Option 2: Send the full updated content
// Supermemory detects the diff and only processes new parts
await supermemory.add("user_sarah", {
content: "user: Hi, I'm Sarah.\nassistant: Nice to meet you!\nuser: What's the weather?\nassistant: It's sunny today.",
id: "conv_123",
});Both work — choose what fits your architecture.
Replace entire document
To completely replace a document's content (not append), use documents.update():
// Replace the entire document content
await supermemory.documents.update("user_sarah", "doc_id_123", {
content: "Completely new content replacing everything",
metadata: { version: 2 },
});This triggers full reprocessing of the document. If you only update metadata (no content change), the document is updated in place with no reindexing.
Formatting conversations
Format your conversations however you want. Supermemory handles any string format:
// Simple string
content: "user: Hello\nassistant: Hi there!"
// JSON stringify
content: JSON.stringify(messages)
// Template literal
content: messages.map(m => `${m.role}: ${m.content}`).join('\n')
// Any format — just make it a string
content: formatConversation(messages)Upload files
Upload PDFs, images, and documents directly. This is a multipart request, so metadata is a JSON string here, not an object.
await supermemory.documents.uploadFile("user_123", {
file: await openAsBlob("document.pdf"),
metadata: JSON.stringify({ source: "upload" }),
});
```with open("document.pdf", "rb") as file:
client.documents.upload_file(
"user_123",
file=file,
metadata=json.dumps({"source": "upload"}),
)
```Supported file types
| Type | Formats | Processing |
|---|---|---|
| Documents | PDF, DOC, DOCX, TXT, MD | Text extraction, OCR for scans |
| Images | JPG, PNG, GIF, WebP | OCR text extraction |
| Spreadsheets | CSV, Google Sheets | Structured data extraction |
| Videos | YouTube URLs, MP4 | Auto-transcription |
Limits: 50MB max file size
Parameters
namespace is the first argument on the SDK call and the URL path on the HTTP route. Every other parameter, including taskType and dreaming, goes in the request object (the JSON body over HTTP). The route accepts no query parameters; sending one returns 400.
| Parameter | Type | Description |
|---|---|---|
namespace |
string | Required. Group by user/project. Goes in the URL path. Required for user profiles |
content |
string | Required. Any raw content — text, conversations, URLs, HTML |
id |
string | Recommended. Your ID for the content (conversation ID, doc ID). Enables updates and deduplication. The response id echoes it back |
metadata |
object | Key-value pairs for filtering (strings, numbers, booleans, or arrays of strings) |
group |
object | Filter which existing memories are used as context during ingestion. See Filtered Writes |
supportingContext |
string | Context for memory extraction on this namespace. Max 1500 chars. See Customization |
date |
string | ISO 8601 date the content was originally created. Use when backfilling. See Backfill historical data |
dreaming |
"dynamic" | "instant" |
Processing mode. Default "dynamic". "instant" processes each document on its own and bills one extra operation. See Processing Modes |
taskType |
"memory" | "superrag" |
Pipeline to run. Default "memory". "superrag" skips fact extraction and profile updates, doing only chunk/embed/index — at 5x cheaper per token. See SuperRAG ingestion |
// URLs (auto-detected and extracted)
{ content: "https://example.com/article" }
{ content: "https://youtube.com/watch?v=abc123" }
// Markdown, HTML, or any format
{ content: "# Project Docs\n\n## Features\n- Real-time sync" }
```
**Namespaces** (first argument; letters, numbers, `-`, `_`, and `:`, up to 100 characters):
```typescript
// By user
await supermemory.add("user_123", { content: "..." });
// By project
await supermemory.add("project_alpha", { content: "..." });
// Hierarchical
await supermemory.add("org_456_team_backend", { content: "..." });
```
**IDs (Recommended):**
```typescript
// Use IDs from your system
{ id: "conv_abc123" } // Conversation ID
{ id: "doc_456" } // Document ID
{ id: "thread_789" } // Thread ID
{ id: "meeting_2024_01_15" } // Meeting ID
// Updates: same id = same document
// Supermemory only processes new/changed content
await supermemory.add("user_123", {
content: "Updated content...",
id: "doc_456", // Links to existing document
});
```
**Metadata:**
```typescript
{
metadata: {
source: "slack",
author: "john",
priority: 1,
reviewed: true
}
}
```
- No nested objects
- Values: string, number, boolean, or an array of strings
**Supporting Context:**
```typescript
// Guide memory extraction for this namespace
await supermemory.add("session_abc123", {
content: "...",
supportingContext: `Design exploration conversation between [email protected] and Brand.ai assistant.
Focus on John's design preferences and brand requirements.`,
});
```
- Max 1500 characters
- Persists on the namespace
- Combines with org-level organizational contextProcessing modes
Dreaming: dynamic vs instant
The dreaming parameter controls how Supermemory turns a document into memories.
"dynamic"(default) — groups related documents together so memories form from coherent, logical units rather than one isolated entry at a time. A fresh namespace can show zero memories and an empty profile for several minutes."instant"— processes each document on its own right away, and bills one extra operation per document. Use it for quickstarts, tests, and any flow that reads memories or a profile right afteradd.
await supermemory.add("user_123", {
content: "...",
dreaming: "instant",
});Memory vs SuperRAG ingestion
The taskType parameter controls whether that content also feeds the memory pipeline.
"memory"(default) — chunks/embeds for search and extracts facts, updates the user's profile, and links into the graph."superrag"— chunks/embeds for search only. No fact extraction, no profile updates. Priced at 5x cheaper per token than"memory".
await supermemory.add("user_123", {
content: "...",
taskType: "superrag",
});Use "superrag" for reference material you want searchable but that shouldn't shape what Supermemory knows about a user. Full explanation: SuperRAG → Ingesting as pure SuperRAG.
Filtered writes
By default, when you add content, Supermemory uses all existing memories in the namespace as context for generating new memories. With filtered writes, you can scope this context to only memories from documents matching specific metadata.
This is useful when you have many documents in a namespace but want new memories to build on top of a specific subset — for example, only memories from a particular source, category, or user.
How it works
When group is provided:
- Profile memories (static context) are filtered by the existing memories' metadata
- Similar memories used as context during ingestion are filtered the same way
- The new document's own metadata is written normally — the filter only affects which existing memories are used as context
`group` parameter
| Key | Type | Description |
|---|---|---|
group |
Record<string, string | number | boolean | string[]> |
Key-value pairs to filter existing memories by their own metadata, rather than a live join to source-document metadata |
- Scalar values (string, number, boolean) match exactly
- Array values match if any value in the array matches (OR logic)
- Multiple keys are combined with AND logic
// Match documents where category is "ml" AND source is either "arxiv" or "pubmed"
await supermemory.add("user_123", {
content: "...",
group: {
category: "ml",
source: ["arxiv", "pubmed"],
},
});Processing pipeline
When you add content, Supermemory:
- Validates your request
- Stores the document and queues for processing
- Extracts content (OCR, transcription, web scraping)
- Chunks into searchable memories
- Embeds for vector search
- Indexes for retrieval
Track progress with GET /ns/{namespace}/document/{id}:
const doc = await supermemory.documents.get("user_123", "abc123");
console.log(doc.system.status); // "queued" | "extracting" | "chunking" | "embedding" | "done" | "failed"```typescript
async function batchUpload(documents: Array<{id: string, content: string}>) {
const results = [];
for (const doc of documents) {
try {
const result = await supermemory.add("batch_import", {
content: doc.content,
id: doc.id,
});
results.push({ id: doc.id, success: true, docId: result.id });
} catch (error) {
results.push({ id: doc.id, success: false, error });
}
// Rate limit: 1 second between requests
await new Promise(r => setTimeout(r, 1000));
}
return results;
}
```
**Tips:**
- Batch size: 3-5 documents at once
- Delay: 1-2 seconds between requests
- Use `id` to track and deduplicate
- For large backfills use `documents.batchAdd` instead. See [Backfill historical data](/ingestion/batch-ingest-historical-data)```typescript
try {
await supermemory.add("user_123", { content: "..." });
} catch (error) {
// Back off on 429, top up on 402, fix the request on 400, retry later on 5xx
console.error("add failed:", error);
}
```**Delete everything in a namespace:**
```typescript
// Delete all content for a user
await supermemory.namespaces.delete("user_123");
```
Deletes are permanent — no recovery.Next steps
- How to backfill historical data — Import dated content with the batch API
- Search Memories — Query your content
- User Profiles — Get user context
- Organizing & Filtering — Namespaces and metadata
Apps Docs Ingestion Batch Ingest Historical Data (`apps/docs/ingestion/batch-ingest-historical-data.mdx`)
title: "How to backfill historical data into Supermemory" sidebarTitle: "Backfill historical data" icon: "/icons/hugeicons/clock-01.svg"
Use POST /ns/{namespace}/document/batch to backfill exports, emails, messages, or other dated records.
Backfill in batches
Backfill dated content by setting date on each document, sorting the source records oldest to newest, and sending them in batches. Each request can contain up to 600 documents.
Endpoint: POST /ns/{namespace}/document/batch
import { Supermemory } from "supermemory";
type SourceDocument = {
id: string;
content: string;
createdAt: string;
};
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY });
const batchSize = 100;
async function backfillHistoricalData(sourceDocuments: SourceDocument[]) {
const documents = sourceDocuments
.map((document) => ({
content: document.content,
id: document.id,
date: new Date(document.createdAt).toISOString()
}))
.sort((a, b) => a.date.localeCompare(b.date));
for (let offset = 0; offset < documents.length; offset += batchSize) {
const result = await supermemory.documents.batchAdd("historical_import", {
documents: documents.slice(offset, offset + batchSize),
});
if (result.failed > 0) {
throw new Error(`${result.failed} documents failed to ingest`);
}
}
}from datetime import datetime, timezone
from supermemory import Supermemory
client = Supermemory()
batch_size = 100
def to_utc(value: str) -> str:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
if parsed.tzinfo is None:
raise ValueError("created_at must include a timezone")
return parsed.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
def backfill_historical_data(source_documents: list[dict[str, str]]) -> None:
documents = sorted(
[
{
"content": document["content"],
"id": document["id"],
"date": to_utc(document["created_at"]),
}
for document in source_documents
],
key=lambda document: document["date"],
)
for offset in range(0, len(documents), batch_size):
result = client.documents.batch_add(
"historical_import",
documents=documents[offset : offset + batch_size],
)
if result.failed > 0:
raise RuntimeError(f"{result.failed} documents failed to ingest")curl -X POST "https://api.supermemory.ai/ns/historical_import/document/batch" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"documents": [
{ "content": "first message", "id": "msg_001", "date": "2023-01-05T09:00:00Z" },
{ "content": "second message", "id": "msg_002", "date": "2023-01-06T14:30:00Z" }
]
}'results lists accepted documents first, in request order, then failed ones, so match each result by id. Inspect count (accepted), failed, and every item in results; a batch can contain successful and failed items together.
Optional: wait for processing to finish
Endpoint: GET /ns/{namespace}/document/{id}
The batch endpoint returns after accepting the documents. If a later step depends on the documents being indexed, poll the returned document IDs until system.status is done. If that later step also needs the extracted memories, send the batch with dreaming: "instant"; under the default dynamic mode memory extraction is batched and can lag done by minutes. Each document reports system.dreamingStatus: "done" once its memories are ready.
async function waitUntilDone(ids: string[]) {
while (true) {
const documents = await Promise.all(
ids.map((id) => supermemory.documents.get("historical_import", id))
);
if (documents.some((document) => document.system.status === "failed")) {
throw new Error("A document failed to process");
}
if (documents.every((document) => document.system.status === "done")) {
return;
}
await new Promise((resolve) => setTimeout(resolve, 10_000));
}
}import time
def wait_until_done(ids: list[str]) -> None:
while True:
documents = [
client.documents.get("historical_import", document_id) for document_id in ids
]
if any(document.system.status == "failed" for document in documents):
raise RuntimeError("A document failed to process")
if all(document.system.status == "done" for document in documents):
return
time.sleep(10)curl "https://api.supermemory.ai/ns/historical_import/document/msg_001" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
# repeat until "system": { "status": "done" }Apps Docs Ingestion Document Operations (`apps/docs/ingestion/document-operations.mdx`)
title: "Document operations" sidebarTitle: "Documents" icon: "/icons/hugeicons/files-02.svg"
Manage documents after ingestion using the SDK. Every call is scoped to one namespace (what v3/v4 called a container tag).
List documents
Retrieve paginated documents with filtering. One list call serves documents, chunks, and memories; pick with type.
documents.forEach(d => {
console.log(d.id, d.title, d.system.status);
});
```for doc in page.documents:
print(doc.id, doc.title, doc.system.status)
```Response:
{
"documents": [
{
"id": "doc_abc123",
"title": "Meeting notes",
"type": "text",
"summary": "Team sync covering the Q3 roadmap",
"metadata": { "source": "slack" },
"url": null,
"system": {
"status": "done",
"dreamingStatus": "done",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
}
],
"chunks": [],
"memories": [],
"pagination": {
"currentPage": 1,
"limit": 10,
"totalItems": 25,
"totalPages": 3
}
}Every response contains documents, chunks, memories, and pagination. Only the array for the requested type is filled.
Parameters
namespace and type are required. Over HTTP, pagination and sorting are query parameters and filter and include go in the JSON body; the SDK takes them all in one options object after namespace and type. Unknown query or body keys return 400.
| Parameter | Type | Default | Description |
|---|---|---|---|
type |
string | required | documents, chunks, or memories |
limit |
number | 10 | Items per page (maximum 100) |
page |
number | 1 | Page number |
sort |
string | createdAt |
createdAt or updatedAt for documents and memories; createdAt or position for chunks |
order |
string | desc |
desc (newest) or asc (oldest) |
body.filter |
object | — | Metadata filter. See Organizing & Filtering |
body.include.content |
boolean | false |
Documents only: include last published extracted text as content |
body.include.forgotten |
boolean | false |
Memories only: include forgotten or expired rows that remain latest, returned with isForgotten: true. Normal forgetting makes a memory non-latest, so it still will not appear in this list |
Include extracted content
Document lists omit content by default. To include the last published extracted text, including text from uploaded files, set include.content in the JSON body:
curl -X POST "https://api.supermemory.ai/ns/user_123/list/documents?limit=10" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"include":{"content":true}}'content is null until the first extraction publishes. When you replace a file, the previous text remains until the new extraction publishes. It also remains if the replacement fails. Including content can make pages large.
while (true) {
const { documents, pagination } = await supermemory.list(namespace, "documents", {
limit: 100,
page,
});
all.push(...documents);
if (page >= pagination.totalPages) break;
page++;
}
return all;}
</Accordion>
<Accordion title="Filter by metadata">
```typescript
const { documents } = await supermemory.list("user_123", "documents", {
filter: {
operator: "and",
operands: [
{ field: "status", operator: "eq", value: "reviewed" },
{ field: "priority", operator: "eq", value: "high" },
],
},
});Get document
Get a specific document with its processing status. Pass include to include its chunks and memories.
console.log(doc.system.status); // "queued" | "extracting" | ... | "done" | "failed"
console.log(doc.content);
```print(doc.system.status) # "queued" | "extracting" | ... | "done" | "failed"
print(doc.content)
```The id may be the Supermemory document ID or the id you supplied at ingestion. It resolves only inside the namespace in the path.
Processing status
| Status | Description |
|---|---|
queued |
Waiting to process |
extracting |
Extracting content (OCR, transcription) |
chunking |
Breaking into searchable pieces |
embedding |
Creating vector representations |
done |
Ready for search |
failed |
Processing failed |
Treat any status other than done or failed as still in progress.
Memory generation status
system.dreamingStatus tracks memory generation separately from system.status.
| Status | Description |
|---|---|
dreaming |
Memories for the latest content are queued, being generated, or waiting for a retry |
done |
Memories are generated, or the document needed none |
With the default dreaming: "dynamic", Supermemory batches related documents before forming memories, so status can be done minutes before dreamingStatus is. If your next step reads memories or profiles, wait for dreamingStatus to be done too.
if (doc.system.status === "done") return doc;
if (doc.system.status === "failed") throw new Error("Processing failed");
await new Promise(r => setTimeout(r, 2000));
}}
</Accordion>
---
## Update document
Update a document's content or metadata. **Content changes** trigger full reprocessing; **metadata-only changes** (e.g. updating `accepted`, `version`) do not reindex. The body accepts any non-empty subset of `content`, `metadata`, `supportingContext`, or `date`. Metadata merges key by key. `group` is an ingestion-side setting: it picks which existing memories serve as context while content is processed, so changing it only takes effect when the document is re-ingested. Send it together with `content`.
<Tabs>
<Tab title="TypeScript">
```typescript
await supermemory.documents.update("user_123", "doc_abc123", {
content: "Updated content here",
metadata: { version: 2, reviewed: true },
});
```
</Tab>
<Tab title="Python">
```python
client.documents.update(
"user_123",
"doc_abc123",
content="Updated content here",
metadata={"version": 2, "reviewed": True},
)
```
</Tab>
<Tab title="cURL">
```bash
curl -X PATCH "https://api.supermemory.ai/ns/user_123/document/doc_abc123" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content": "Updated content here", "metadata": {"version": 2}}'
```
</Tab>
</Tabs>
For file-backed documents use `documents.updateFile` (PATCH: partial, `file` optional, metadata merges key by key) or `documents.replaceWithFile` (POST: full replace, `file` required, omitted metadata keys are cleared).
```typescript
await supermemory.documents.updateFile("user_123", "doc_abc123", {
metadata: JSON.stringify({ reviewed: true }),
});
await supermemory.documents.replaceWithFile("user_123", "doc_abc123", {
file,
metadata: JSON.stringify({ revision: 2 }),
});Delete documents
Permanently remove documents. One call deletes 1 to 100 documents by Supermemory or caller-defined ID.
// Delete a whole namespace (all content for a user)
await supermemory.namespaces.delete("user_123");
```# Delete a whole namespace (all content for a user)
client.namespaces.delete("user_123")
```# Delete a whole namespace
curl -X DELETE "https://api.supermemory.ai/ns/user_123" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
```Inspect both count and per-ID errors; HTTP success can include partial failures. Only documents whose system.status is done or failed can be deleted; a document that is still processing is reported in errors as Document is still processing. Memories derived from a deleted document are forgotten.
Processing queue
There is no separate processing endpoint. List documents and read system.status and system.dreamingStatus on each item in the HTTP response. The SDK examples below count documents still being indexed; use raw HTTP for memory-generation status with the SDK versions noted above.
Next steps
- Memory Operations — Forget memories
- Search — Query your memories
- Ingesting Content — Add new content
Apps Docs Integrations Agent Framework (`apps/docs/integrations/agent-framework.mdx`)
title: "Microsoft Agent Framework" sidebarTitle: "MS Agent Framework" icon: "/images/microsoft-icon.svg"
Microsoft's Agent Framework is a Python framework for building AI agents with tools, handoffs, and context providers. Supermemory integrates natively as a context provider, tool set, or middleware — so your agents remember users across sessions.
What you can do
- Automatically inject user memories before every agent run (context provider)
- Give agents tools to search and store memories on their own
- Intercept chat requests to add memory context via middleware
- Combine all three for maximum flexibility
Setup
Install adapter version >=2.0.0,<3 with the OpenAI client package:
pip install "supermemory-agent-framework>=2.0.0,<3" agent-framework-openaiOr with uv:
uv add "supermemory-agent-framework>=2.0.0,<3" agent-framework-openaiThe adapter requires supermemory>=5.0.0,<6. The OpenAI examples also require the separate agent-framework-openai package and use its current OpenAIChatClient Responses API client. Older supported framework cores can have different OpenAI client names and model arguments.
Set up your environment:
# .env
SUPERMEMORY_API_KEY=your-supermemory-api-key
OPENAI_API_KEY=your-openai-api-keyConnection
All integration points share a single AgentSupermemory connection. This ensures the same API client, container tag, and conversation ID are used across middleware, tools, and context providers.
from supermemory_agent_framework import AgentSupermemory
conn = AgentSupermemory(
api_key="your-supermemory-api-key", # or set SUPERMEMORY_API_KEY env var
container_tag="user-123", # memory scope (e.g., user ID)
conversation_id="session-abc", # optional, auto-generated if omitted
entity_context="The user is a Python developer.", # optional
)Connection options
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key |
str |
env var | Supermemory API key. Falls back to SUPERMEMORY_API_KEY |
container_tag |
str |
"msft_agent_chat" |
Memory scope (e.g., user ID) |
conversation_id |
str |
auto-generated | Groups messages into a conversation |
entity_context |
str |
None |
Custom context about the user, prepended to memories |
Pass this connection to any integration:
middleware = SupermemoryChatMiddleware(conn, options=...)
tools = SupermemoryTools(conn)
provider = SupermemoryContextProvider(conn, mode="full")Existing callers and data
Keep the existing container_tag and conversation_id arguments. The adapter sends the container tag as the v5 namespace and the unchanged conversation_<conversation_id> as the document id. Choose a distinct container tag per user or tenant: the default msft_agent_chat scope is shared. Repeated writes append/diff into the same conversation document; they do not replace previous turns. entity_context remains context prepended to retrieved memories, not an ingestion setting.
Existing Agent Framework session exports remain loadable because the provider does not change its scoped state. Reconstruct the connection with the same identifiers after loading a session; connections and API credentials are not part of provider state. This adapter migration does not move existing server-side data. Confirm old data is available in the corresponding v5 namespace using the v5 migration guide; the adapter does not delete or rewrite legacy data.
Context provider (recommended)
The most idiomatic integration. Follows the same pattern as Agent Framework's built-in Mem0 provider — memories are automatically fetched before the LLM runs and conversations can be stored afterward.
import asyncio
from agent_framework import AgentSession
from agent_framework.openai import OpenAIChatClient
from supermemory_agent_framework import AgentSupermemory, SupermemoryContextProvider
async def main():
conn = AgentSupermemory(container_tag="user-123")
provider = SupermemoryContextProvider(conn, mode="full")
agent = OpenAIChatClient(model="gpt-5").as_agent(
name="MemoryAgent",
instructions="You are a helpful assistant with memory.",
context_providers=[provider],
)
session = AgentSession()
response = await agent.run(
"What's my favorite programming language?",
session=session,
)
print(response.text)
asyncio.run(main())How it works
before_run()— Searches Supermemory for the user's profile and relevant memories, then injects them into the session context as additional instructionsafter_run()— Ifstore_conversations=True, saves the conversation to Supermemory so future sessions have more context
Configuration options
| Parameter | Type | Default | Description |
|---|---|---|---|
connection |
AgentSupermemory |
required | Shared connection |
mode |
str |
"full" |
"profile", "query", or "full" |
store_conversations |
bool |
False |
Save conversations after each run |
context_prompt |
str |
built-in | Custom prompt describing the memories |
verbose |
bool |
False |
Enable detailed logging |
Memory tools
Give agents explicit control over memory operations. The agent decides when to search or store information.
import asyncio
from agent_framework.openai import OpenAIChatClient
from supermemory_agent_framework import AgentSupermemory, SupermemoryTools
async def main():
conn = AgentSupermemory(container_tag="user-123")
tools = SupermemoryTools(conn)
agent = OpenAIChatClient(model="gpt-5").as_agent(
name="MemoryAgent",
instructions="""You are a helpful assistant with memory.
When users share preferences, save them. When they ask questions, search memories first.""",
)
response = await agent.run(
"Remember that I prefer Python over JavaScript",
tools=tools.get_tools(),
)
print(response.text)
asyncio.run(main())Available tools
The agent gets three tools:
search_memories— Search for relevant memories by queryadd_memory— Store new information for later recallget_profile— Fetch the user's full profile (static + dynamic facts)
Tool JSON envelopes are preserved. Profiles still expose string arrays for static, dynamic, and bucket facts. A query passed to get_profile triggers a separate v5 search, returned under search_results with results, timing, and total (the number returned). Search results keep their top-level updated_at timestamp and include v5 metadata; legacy optional fields not returned by v5 remain present as null because their values cannot be reconstructed. The deprecated Python-only include_full_docs argument remains ignored and is not exposed to the model.
Chat middleware
Intercept chat requests to automatically inject memory context. Useful when you want memory injection without the session-based context provider pattern.
import asyncio
from agent_framework.openai import OpenAIChatClient
from supermemory_agent_framework import (
AgentSupermemory,
SupermemoryChatMiddleware,
SupermemoryMiddlewareOptions,
)
async def main():
conn = AgentSupermemory(container_tag="user-123")
middleware = SupermemoryChatMiddleware(
conn,
options=SupermemoryMiddlewareOptions(
mode="full",
add_memory="always",
),
)
agent = OpenAIChatClient(model="gpt-5").as_agent(
name="MemoryAgent",
instructions="You are a helpful assistant.",
middleware=[middleware],
)
response = await agent.run("What's my favorite programming language?")
await middleware.wait_for_background_tasks()
print(response.text)
asyncio.run(main())Memory modes
SupermemoryContextProvider(conn, mode="full") # or "profile" / "query"| Mode | What it fetches | Best for |
|---|---|---|
"profile" |
User profile (static + dynamic facts) only | Personalization without query overhead |
"query" |
Memories relevant to the current message only | Targeted recall, no profile data |
"full" (default) |
Profile + query search combined | Maximum context |
V5 profile retrieval does not accept a search query. Full mode makes separate profile and search requests and deduplicates overlapping facts before injection. Profile-associated searches keep the legacy memory-only mode and 0.6 threshold; the explicit search tool keeps hybrid mode and 0.6. Writes are asynchronous; an accepted document may not be immediately searchable, and this migration does not change the SDK's default processing mode.
Example: support agent with memory
A support agent that remembers customers across sessions:
/* Detailed source-code truncated for AI context efficiency. */This example stores conversations through the provider only. Enabling middleware storage as well can submit overlapping content; use both only when intentional.
Error handling
The package provides specific exception types:
Tools return operation failures as JSON (success: false, error). Provider retrieval/storage and middleware retrieval failures are logged and let the agent continue without memories. Middleware background write failures are logged rather than raised by wait_for_background_tasks(); a timeout on that wait still raises asyncio.TimeoutError.
from supermemory_agent_framework import (
AgentSupermemory,
SupermemoryConfigurationError,
SupermemoryAPIError,
SupermemoryNetworkError,
)
try:
conn = AgentSupermemory() # no API key set
except SupermemoryConfigurationError as e:
print(f"Missing API key: {e}")| Exception | When |
|---|---|
SupermemoryConfigurationError |
Missing API key or invalid config |
SupermemoryAPIError |
API returned an error response |
SupermemoryNetworkError |
Connection failure |
SupermemoryTimeoutError |
Request timed out |
SupermemoryMemoryOperationError |
Memory add/search failed |
Related docs
Apps Docs Integrations Agno (`apps/docs/integrations/agno.mdx`)
title: "Agno" sidebarTitle: "Agno" icon: "/icons/hugeicons/brain-01.svg"
Agno agents are stateless by default. Each conversation starts fresh. Supermemory changes that - your agents can remember users, recall past conversations, and build on previous interactions.
What you can do
- Give agents access to user profiles and conversation history
- Store agent interactions for future sessions
- Let agents search memories to answer questions with context
Setup
Install the packages:
pip install agno supermemory python-dotenvSet up your environment:
# .env
SUPERMEMORY_API_KEY=your-supermemory-api-key
OPENAI_API_KEY=your-openai-api-keyBasic integration
Fetch user context before running an agent, then store the interaction after.
/* Detailed source-code truncated for AI context efficiency. */Core concepts
User profiles
Supermemory keeps two buckets of user info:
- Static facts: Things that stay consistent (name, preferences, expertise)
- Dynamic context: What they're focused on lately
result = memory.profile("user_123")
print([m.memory for m in result.profile.static]) # ["Vegetarian", "Allergic to nuts"]
print([m.memory for m in result.profile.dynamic]) # ["Learning Italian cuisine", "Meal prepping"]Storing memories
Save interactions so future sessions have context:
def store_chat(user_id: str, user_msg: str, agent_response: str):
memory.add(
user_id,
content=f"User asked: {user_msg}\nAgent said: {agent_response}",
metadata={"type": "conversation"},
dreaming="instant"
)Searching memories
Look up past interactions:
results = memory.search(
"user_123",
query="pasta recipes we discussed",
search_mode="hybrid", # Searches memories + document chunks
limit=5
)
for r in results.results:
print(r.memory or r.chunk)Example: personal assistant with memory
An assistant that actually knows who it's talking to. Preferences stick around. Past conversations inform new ones.
/* Detailed source-code truncated for AI context efficiency. */Using Agno tools with memory
Give your agent tools that can search and store memories directly.
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools import tool
from supermemory import Supermemory
memory = Supermemory()
@tool
def search_memory(query: str, user_id: str) -> str:
"""Search for information in the user's memory.
Args:
query: What to look for
user_id: The user's ID
"""
results = memory.search(user_id, query=query, limit=5)
if not results.results:
return "Nothing relevant found in memory."
return "\n".join([r.memory or r.chunk for r in results.results])
@tool
def remember(content: str, user_id: str) -> str:
"""Store something important about the user.
Args:
content: What to remember
user_id: The user's ID
"""
memory.add(user_id, content=content, dreaming="instant")
return f"Remembered: {content}"
agent = Agent(
name="memory_agent",
model=OpenAIChat(id="gpt-4o"),
tools=[search_memory, remember],
description="""You are an assistant with memory.
When users share preferences or important info, use the remember tool.
When they ask about past conversations, search your memory first.""",
markdown=True
)Image context with memory
Agno handles images too. When users share photos, you can store what the agent saw for later.
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.media import Image
from pathlib import Path
from supermemory import Supermemory
memory = Supermemory()
def analyze_and_remember(user_id: str, image_path: str, question: str) -> str:
"""Analyze an image, answer a question, and store the context."""
agent = Agent(
name="vision_agent",
model=OpenAIChat(id="gpt-4o"),
description="You analyze images and answer questions about them.",
markdown=True
)
# Get the agent's analysis
response = agent.run(question, images=[Image(filepath=Path(image_path))])
# Store the interaction with image context
memory.add(
user_id,
content=f"User shared an image and asked: {question}\nAnalysis: {response.content}",
metadata={"type": "image_analysis", "image": image_path},
dreaming="instant"
)
return response.contentMetadata for filtering
Tags let you narrow down searches:
# Store with metadata
memory.add(
"user_123",
content="User prefers dark mode interfaces",
dreaming="instant",
metadata={
"type": "preference",
"category": "ui",
"source": "onboarding"
}
)
# Search with filters
results = memory.search(
"user_123",
query="interface preferences",
filter={
"operator": "and",
"operands": [
{"field": "type", "operator": "eq", "value": "preference"},
{"field": "category", "operator": "eq", "value": "ui"}
]
}
)Related docs
Apps Docs Integrations Ai Sdk (`apps/docs/integrations/ai-sdk.mdx`)
title: "Vercel AI SDK" sidebarTitle: "Vercel AI SDK" icon: "/icons/hugeicons/triangle.svg"
import MigrateCli from "/snippets/migrate-cli.mdx";
The Supermemory AI SDK provides native integration with Vercel's AI SDK through two approaches: User Profiles for automatic personalization and Memory Tools for agent-based interactions.
Installation
npm install @supermemory/toolsQuick comparison
| Approach | Use Case | Setup |
|---|---|---|
| User Profiles | Personalized LLM responses with automatic user context | Simple middleware |
| Memory Tools | AI agents that need explicit memory control | Tool definitions |
User profiles with middleware
Automatically inject user profiles into every LLM call for instant personalization.
import { generateText } from "ai"
import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { openai } from "@ai-sdk/openai"
const modelWithMemory = withSupermemory(openai("gpt-5"), {
namespace: "user-123",
id: "conversation-456",
})
const result = await generateText({
model: modelWithMemory,
messages: [{ role: "user", content: "What do you know about me?" }]
})Required fields
Both namespace and id are required.
namespace— who the memories belong to. Use a stable identifier per user, workspace, or tenant (e.g."user-123","acme-workspace"). Memory search and writes are scoped to this tag.id— which conversation this turn belongs to. Use it to group messages from the same chat session into a single document (e.g."chat-2026-04-25", a thread ID, or a UUID per session).
const modelWithMemory = withSupermemory(openai("gpt-5"), {
namespace: "user-123",
id: "conversation-456",
addMemory: "never",
})Memory search modes
Profile Mode (Default) - Retrieves the user's complete profile:
const model = withSupermemory(openai("gpt-4"), { namespace: "user-123", id: "conv-1", mode: "profile" })Query Mode - Searches memories based on the user's message:
const model = withSupermemory(openai("gpt-4"), { namespace: "user-123", id: "conv-1", mode: "query" })Full Mode - Combines profile AND query-based search:
const model = withSupermemory(openai("gpt-4"), { namespace: "user-123", id: "conv-1", mode: "full" })Custom prompt templates
Customize how memories are formatted. The template receives userMemories, generalSearchMemories, and searchResults (raw array for filtering by metadata):
import { withSupermemory, type MemoryPromptData } from "@supermemory/tools/ai-sdk"
const claudePrompt = (data: MemoryPromptData) => `
<context>
<user_profile>
${data.userMemories}
</user_profile>
<relevant_memories>
${data.generalSearchMemories}
</relevant_memories>
</context>
`.trim()
const model = withSupermemory(anthropic("claude-3-sonnet"), {
namespace: "user-123",
id: "conv-1",
mode: "full",
promptTemplate: claudePrompt,
})Verbose logging
const model = withSupermemory(openai("gpt-4"), {
namespace: "user-123",
id: "conv-1",
verbose: true,
})
// Console output shows memory retrieval detailsWhen Supermemory errors (default: continue without memories)
If the Supermemory API returns an error, is unreachable, or retrieval hits the internal time limit, memory injection is skipped. skipMemoryOnError defaults to true, so the LLM call still runs with the original prompt (no injected memories). Use verbose: true if you want console output when that happens.
To fail the call when memory retrieval fails instead, set skipMemoryOnError: false:
const model = withSupermemory(openai("gpt-5"), {
namespace: "user-123",
id: "conv-1",
skipMemoryOnError: false,
})Persisting tool calls (default: off)
By default, saved conversations include only user and assistant text — tool calls and tool results are dropped, since tool payloads are often large and low-signal and would pollute memory extraction. To persist the full tool round trip (tool calls with their arguments, plus tool results, in their original order), set includeToolCalls: true:
const model = withSupermemory(openai("gpt-5"), {
namespace: "user-123",
id: "conv-1",
includeToolCalls: true,
})Memory tools
Add memory capabilities to AI agents with search, add, and fetch operations.
import { streamText } from "ai"
import { createAnthropic } from "@ai-sdk/anthropic"
import { supermemoryTools } from "@supermemory/tools/ai-sdk"
const anthropic = createAnthropic({ apiKey: "YOUR_ANTHROPIC_KEY" })
const result = await streamText({
model: anthropic("claude-3-sonnet"),
prompt: "Remember that my name is Alice",
tools: supermemoryTools("YOUR_SUPERMEMORY_KEY")
})Available tools
Search Memories - Semantic search through user memories:
const result = await streamText({
model: openai("gpt-5"),
prompt: "What are my dietary preferences?",
tools: supermemoryTools("API_KEY")
})
// AI will call: searchMemories({ informationToGet: "dietary preferences" })Add Memory - Store new information:
const result = await streamText({
model: anthropic("claude-3-sonnet"),
prompt: "Remember that I'm allergic to peanuts",
tools: supermemoryTools("API_KEY")
})
// AI will call: addMemory({ memory: "User is allergic to peanuts" })Using individual tools
For more control, import tools separately:
import {
searchMemoriesTool,
addMemoryTool
} from "@supermemory/tools/ai-sdk"
const result = await streamText({
model: openai("gpt-5"),
prompt: "What do you know about me?",
tools: {
searchMemories: searchMemoriesTool("API_KEY", { namespace: "personal" }),
createEvent: yourCustomTool,
}
})Tool results
// searchMemories result
{ success: true, results: [...], count: 5 }
// addMemory result
{ success: true, memory: { id: "mem_123", ... } }Apps Docs Integrations Cartesia (`apps/docs/integrations/cartesia.mdx`)
title: "Cartesia" sidebarTitle: "Cartesia (voice)" icon: "/images/cartesia.svg"
Supermemory integrates with Cartesia, providing long-term memory capabilities for voice AI agents. Your Cartesia applications will remember past conversations and provide personalized responses based on user history.
Installation
To use Supermemory with Cartesia, install the required dependencies:
pip install supermemory-cartesiaSet up your API key as an environment variable:
export SUPERMEMORY_API_KEY=your_supermemory_api_keyYou can obtain an API key from console.supermemory.ai.
Configuration
Supermemory integration is provided through the SupermemoryCartesiaAgent wrapper class:
from supermemory_cartesia import SupermemoryCartesiaAgent
from line.llm_agent import LlmAgent, LlmConfig
# Create base LLM agent
base_agent = LlmAgent(
model="anthropic/claude-haiku-4-5-20251001",
api_key=os.getenv("ANTHROPIC_API_KEY"),
config=LlmConfig(
system_prompt="""You are a helpful voice assistant with memory.""",
introduction="Hello! Great to talk with you again!",
),
)
# Wrap with Supermemory
memory_agent = SupermemoryCartesiaAgent(
agent=base_agent,
api_key=os.getenv("SUPERMEMORY_API_KEY"),
container_tag="user-123",
custom_id="session-456", # Required: groups all messages in same document
config=SupermemoryCartesiaAgent.MemoryConfig(
mode="full", # "profile" | "query" | "full"
search_limit=10, # Max memories to retrieve
search_threshold=0.3, # Relevance threshold (0.0-1.0)
),
)Agent wrapper pattern
The SupermemoryCartesiaAgent wraps your existing LlmAgent to add memory capabilities:
from line.voice_agent_app import VoiceAgentApp
async def get_agent(env, call_request):
# Extract container_tag from call metadata (typically user ID)
container_tag = call_request.metadata.get("user_id", "default-user")
# Create base agent
base_agent = LlmAgent(...)
# Wrap with memory
memory_agent = SupermemoryCartesiaAgent(
agent=base_agent,
container_tag=container_tag,
custom_id=call_request.call_id, # Required: groups all messages in same document
)
return memory_agent
# Create voice agent app
app = VoiceAgentApp(get_agent=get_agent)How it works
When integrated with Cartesia Line, Supermemory provides two key functionalities:
1. Memory retrieval
When a UserTurnEnded event is detected, Supermemory retrieves relevant memories:
- Static Profile: Persistent facts about the user
- Dynamic Profile: Recent context and preferences
- Search Results: Semantically relevant past memories
2. Context enhancement
Retrieved memories are formatted and injected into the agent's system prompt before processing, giving the model awareness of past conversations.
3. Background storage
Conversations are automatically stored in Supermemory (non-blocking) for future retrieval.
Memory modes
| Mode | Static Profile | Dynamic Profile | Search Results | Use Case |
|---|---|---|---|---|
"profile" |
Yes | Yes | No | Personalization without search |
"query" |
No | No | Yes | Finding relevant past context |
"full" |
Yes | Yes | Yes | Complete memory (default) |
Configuration options
You can customize how memories are retrieved and used:
MemoryConfig
SupermemoryCartesiaAgent.MemoryConfig(
mode="full", # Memory mode (default: "full")
search_limit=10, # Max memories to retrieve (default: 10)
search_threshold=0.1, # Similarity threshold 0.0-1.0 (default: 0.1)
system_prompt="Based on previous conversations:\n\n",
)| Parameter | Type | Default | Description |
|---|---|---|---|
search_limit |
int | 10 | Maximum number of memories to retrieve per query |
search_threshold |
float | 0.1 | Minimum similarity threshold for memory retrieval |
mode |
str | "full" | Memory retrieval mode: "profile", "query", or "full" |
system_prompt |
str | "Based on previous conversations:\n\n" | Prefix text for memory context |
Agent parameters
SupermemoryCartesiaAgent(
agent=base_agent, # Required: Cartesia Line LlmAgent
container_tag="user-123", # Required: Primary container tag (e.g., user ID)
custom_id="session-456", # Required: Groups all messages in same document
add_memory="always", # Optional: "always" (default) or "never"
container_tags=["org-acme", "prod"], # Optional: Additional tags
api_key=os.getenv("SUPERMEMORY_API_KEY"), # Optional: defaults to env var
config=MemoryConfig(...), # Optional: memory configuration
base_url=None, # Optional: custom API endpoint
)| Parameter | Type | Required | Description |
|---|---|---|---|
agent |
LlmAgent | Yes | The Cartesia Line agent to wrap |
container_tag |
str | Yes | Primary container tag for memory scoping (e.g., user ID) |
custom_id |
str | Yes | Groups all messages in the same document (e.g., call ID, conversation ID) |
add_memory |
str | No | Memory persistence mode: "always" (default) or "never" |
container_tags |
List[str] | No | Additional container tags for organization (e.g., ["org", "prod"]) |
api_key |
str | No | Supermemory API key (or set SUPERMEMORY_API_KEY env var) |
config |
MemoryConfig | No | Advanced configuration |
base_url |
str | No | Custom API endpoint |
Container tags
Container tags allow you to organize memories across multiple dimensions:
memory_agent = SupermemoryCartesiaAgent(
agent=base_agent,
container_tag="user-alice", # Primary: user ID
container_tags=["org-acme", "prod"], # Additional: organization, environment
)Memories are stored with all tags:
{
"content": "User: What's the weather?\nAssistant: It's sunny today!",
"container_tags": ["user-alice", "org-acme", "prod"],
"metadata": { "platform": "cartesia" }
}Automatic document grouping
The SDK automatically groups all messages from the same conversation into a single Supermemory document using custom_id:
memory_agent = SupermemoryCartesiaAgent(
agent=base_agent,
container_tag="user-alice",
custom_id=call_request.call_id, # Required: Groups all messages together
)How it works:
- The
custom_idparameter groups all messages into the same Supermemory document - Typically you use the call ID or conversation ID from Cartesia
- All messages from that conversation are appended to the same document
- This ensures conversation continuity and proper memory generation
Example: Basic voice agent with memory
Here's a complete example of a Cartesia Line voice agent with Supermemory integration:
import os
from line.llm_agent import LlmAgent, LlmConfig
from line.voice_agent_app import VoiceAgentApp
from supermemory_cartesia import SupermemoryCartesiaAgent
async def get_agent(env, call_request):
# Extract container_tag from call metadata (typically user ID)
container_tag = call_request.metadata.get("user_id", "default-user")
# Create base LLM agent
base_agent = LlmAgent(
model="anthropic/claude-haiku-4-5-20251001",
api_key=os.getenv("ANTHROPIC_API_KEY"),
config=LlmConfig(
system_prompt="""You are a helpful voice assistant with memory.""",
introduction="Hello! Great to talk with you again!",
),
)
# Wrap with Supermemory
memory_agent = SupermemoryCartesiaAgent(
agent=base_agent,
api_key=os.getenv("SUPERMEMORY_API_KEY"),
container_tag=container_tag,
custom_id=call_request.call_id, # Required: Groups all messages
)
return memory_agent
# Create voice agent app
app = VoiceAgentApp(get_agent=get_agent)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)Example: Advanced agent with tools
Here's an example with custom tools and multi-tag support:
import os
from line.llm_agent import LlmAgent, LlmConfig
from line.tools import LoopbackTool
from line.voice_agent_app import VoiceAgentApp
from supermemory_cartesia import SupermemoryCartesiaAgent
# Define custom tool
async def get_weather(location: str) -> str:
return f"The weather in {location} is sunny, 72°F"
weather_tool = LoopbackTool(
name="get_weather",
description="Get current weather for a location",
function=get_weather
)
async def get_agent(env, call_request):
container_tag = call_request.metadata.get("user_id", "default-user")
org_id = call_request.metadata.get("org_id")
# Create LLM agent with tools
base_agent = LlmAgent(
model="gemini/gemini-2.5-flash-preview-09-2025",
tools=[weather_tool],
config=LlmConfig(
system_prompt="You are a personal assistant with memory and tools.",
introduction="Hi! How can I help you today?"
)
)
# Wrap with Supermemory
memory_agent = SupermemoryCartesiaAgent(
agent=base_agent,
api_key=os.getenv("SUPERMEMORY_API_KEY"),
container_tag=container_tag,
custom_id=call_request.call_id, # Required: Groups all messages
container_tags=[org_id] if org_id else None,
config=SupermemoryCartesiaAgent.MemoryConfig(
mode="full",
search_limit=15,
search_threshold=0.15,
)
)
return memory_agent
app = VoiceAgentApp(get_agent=get_agent)Deployment
To deploy to Cartesia Line, create a main.py file in your project root:
import os
import sys
# Add src to path for local imports
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from line.llm_agent import LlmAgent, LlmConfig
from line.voice_agent_app import VoiceAgentApp
from supermemory_cartesia import SupermemoryCartesiaAgent
async def get_agent(env, call_request):
"""Create a memory-enabled voice agent."""
container_tag = call_request.metadata.get("user_id", "default-user")
base_agent = LlmAgent(
model="anthropic/claude-haiku-4-5-20251001",
api_key=os.getenv("ANTHROPIC_API_KEY"),
config=LlmConfig(
system_prompt="""You are a helpful voice assistant with memory.
You remember past conversations and can reference them naturally.
Keep responses brief and conversational.""",
introduction="Hello! Great to talk with you again!",
),
)
memory_agent = SupermemoryCartesiaAgent(
agent=base_agent,
api_key=os.getenv("SUPERMEMORY_API_KEY"),
container_tag=container_tag,
custom_id=call_request.call_id, # Required: Groups all messages
)
return memory_agent
app = VoiceAgentApp(get_agent=get_agent)Then deploy with:
cartesia deployMake sure to set these environment variables in your Cartesia deployment:
SUPERMEMORY_API_KEY- Your Supermemory API keyANTHROPIC_API_KEY- Your Anthropic API key (or the key for your chosen LLM provider)
Apps Docs Integrations Claude Code (`apps/docs/integrations/claude-code.mdx`)
title: "Claude Code" sidebarTitle: "Claude Code" icon: "/images/claude-code-icon.svg"
supermemory is a Claude Code plugin that gives your AI persistent memory across sessions. Your agent remembers what you worked on — across sessions, across projects.
Install the plugin
Requires Node.js 18+ on your PATH — the memory hooks run as Node scripts.
# Add the plugin marketplace
/plugin marketplace add supermemoryai/claude-supermemory
# Install the plugin
/plugin install supermemory/plugin marketplace update supermemory-plugins
/plugin install supermemory@supermemory-plugins
# Only if the old plugin is still installed:
/plugin uninstall claude-supermemory@supermemory-pluginsAuthenticate
Create a Supermemory API key from the API Keys page in the console, then add it to your shell profile:
How it works
Once installed, the plugin runs automatically:
- Reasoned recall — Before each turn, Claude decides whether recalling memory would help the current message, and only searches when it is worth it.
- Auto-capture — Conversations and important tool usage are saved for later sessions.
- Team memory — Project knowledge is shared separately from personal memories.
- Explicit skills — Ask Claude to search or save memories when you need control.
Commands
| Command | Description |
|---|---|
/supermemory:index |
Index codebase architecture and patterns |
/supermemory:project-config |
Configure project-level settings |
/supermemory:logout |
Clear saved credentials |
/supermemory:session |
Show a clickable URL for the current session document |
/supermemory:status |
Show authentication status |
Configuration
Environment variables
SUPERMEMORY_CC_API_KEY=sm_... # Required
SUPERMEMORY_DEBUG=true # Optional: enable debug loggingGlobal settings
Create ~/.supermemory-claude/settings.json:
{
"maxProfileItems": 5,
"signalExtraction": true,
"signalKeywords": ["remember", "architecture", "decision", "bug", "fix"],
"signalTurnsBefore": 3,
"includeTools": ["Edit", "Write"]
}| Option | Description |
|---|---|
maxProfileItems |
Max memories in context (default: 5) |
recallDirective |
Override the built-in reasoned-recall instruction |
signalExtraction |
Only capture important turns (default: false) |
signalKeywords |
Keywords that trigger capture |
signalTurnsBefore |
Context turns before a signal (default: 3) |
includeTools |
Tools to explicitly capture |
Project config
Per-repo overrides in .claude/.supermemory-claude/config.json. Run /supermemory:project-config or create manually:
{
"apiKey": "sm_...",
"baseUrl": "https://api.supermemory.ai",
"repoContainerTag": "my-team-project",
"signalExtraction": true
}| Option | Description |
|---|---|
apiKey |
Project-specific API key |
baseUrl |
Supermemory API URL (use for self-hosted) |
personalContainerTag |
Override personal container |
repoContainerTag |
Override team container tag |
Next steps
Apps Docs Integrations Claude Memory (`apps/docs/integrations/claude-memory.mdx`)
title: "Claude Memory Tool" sidebarTitle: "Claude Memory Tool" icon: "/images/anthropic-1.svg"
Claude has a native memory tool that allows it to store and retrieve information across conversations. Supermemory provides a backend implementation that maps Claude's memory commands to persistent storage.
This integration works with Claude's built-in memory tool type, introduced in the Anthropic API. It requires the context-management beta flag.
Installation
npm install @supermemory/tools @anthropic-ai/sdkQuick start
/* Detailed source-code truncated for AI context efficiency. */Configuration
import { createClaudeMemoryTool } from "@supermemory/tools/claude-memory"
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
// Scope memories to a project or user
namespace: "my-app",
// Or use container tags for more flexibility
namespace: "user-123",
// Custom memory container prefix (default: "claude_memory")
// Custom API endpoint
baseUrl: "https://custom.api.com",
})How it works
Claude's memory tool uses a file-system metaphor. Supermemory maps these operations to document storage:
| Claude Command | Supermemory Action |
|---|---|
view |
Search/retrieve documents |
create |
Add new document |
str_replace |
Update document content |
insert |
Insert content at line |
delete |
Delete document |
rename |
Move document to new path |
Memory path structure
All memory paths must start with /memories/:
/memories/preferences.txt # User preferences
/memories/projects/react.txt # Project-specific notes
/memories/context/current.txt # Current contextCommands reference
View (read/list)
// List directory contents
{ command: "view", path: "/memories/" }
// Read file contents
{ command: "view", path: "/memories/preferences.txt" }
// Read specific lines
{ command: "view", path: "/memories/notes.txt", view_range: [1, 10] }Create
{
command: "create",
path: "/memories/preferences.txt",
file_text: "User prefers dark mode\nFavorite language: TypeScript"
}String replace
{
command: "str_replace",
path: "/memories/preferences.txt",
old_str: "dark mode",
new_str: "light mode"
}Insert
{
command: "insert",
path: "/memories/notes.txt",
insert_line: 5,
insert_text: "New note added here"
}Delete
{ command: "delete", path: "/memories/old-notes.txt" }Rename
{
command: "rename",
old_path: "/memories/old-name.txt",
new_path: "/memories/new-name.txt"
}Complete example
/* Detailed source-code truncated for AI context efficiency. */Environment variables
SUPERMEMORY_API_KEY=your_supermemory_key
ANTHROPIC_API_KEY=your_anthropic_keyComparison with other approaches
| Feature | Claude Memory Tool | OpenAI SDK Tools | AI SDK Tools |
|---|---|---|---|
| Automatic memory | ✅ Claude decides | ❌ Manual control | ❌ Manual control |
| Filesystem metaphor | ✅ Files/directories | ❌ Flat storage | ❌ Flat storage |
| Path organization | ✅ Hierarchical | ❌ Tags only | ❌ Tags only |
| Integration | Anthropic SDK only | OpenAI SDK only | Vercel AI SDK |
Next steps
Apps Docs Integrations Codex (`apps/docs/integrations/codex.mdx`)
title: "Codex" sidebarTitle: "Codex" icon: "/images/codex.svg"
codex-supermemory wires Supermemory into the OpenAI Codex CLI via hooks and skills. Your agent gets two layers of memory:
- Implicit (hooks) — automatically recalls context before each prompt and captures conversations incrementally during the session.
- Explicit (skills) — lets you or the agent save, search, and manage memories on demand.
Install the plugin
npx codex-supermemory@latest installThis command:
- Copies hook and skill scripts to
~/.codex/supermemory/ - Enables
codex_hooksin~/.codex/config.toml - Registers
UserPromptSubmit(recall) andStop(flush) hooks in~/.codex/hooks.json - Installs explicit memory skills under
~/.codex/skills/
Restart Codex CLI after installing.
Authenticate
Browser auth is preferred. Start Codex CLI — on your first prompt a browser window opens to authenticate with Supermemory.
Alternatively:
- Use
$supermemory-login//supermemory-logininside Codex - Or set an API key from API Keys:
How it works
Once installed, the plugin runs on every Codex session:
| Hook | Event | What it does |
|---|---|---|
recall |
UserPromptSubmit |
Captures new turns (every N prompts), searches Supermemory, injects memories + profile as additionalContext |
flush |
Stop |
Captures any remaining turns at session end so nothing is lost |
- Incremental capture — Memories are saved every N turns (default: 3) so mid-session context is available for later prompts in the same session.
- Privacy — Content wrapped in
...is redacted before storage.
Memory scopes
| Tag | Derived from | Description |
|---|---|---|
| User | git config user.email (hashed) |
Preferences and workflows across projects |
| Project | Git common directory (hashed) | Repo-scoped knowledge (worktrees share by default) |
Override tags in ~/.codex/supermemory.json if needed:
{
"userContainerTag": "my-custom-user-tag",
"projectContainerTag": "my-custom-project-tag"
}Set SUPERMEMORY_ISOLATE_WORKTREES=true to keep each worktree isolated.
Explicit memory skills
| Skill | Description |
|---|---|
supermemory-search |
Search memories by natural-language query |
supermemory-save |
Save important project knowledge |
supermemory-forget |
Remove outdated or incorrect memories |
supermemory-profile |
Show remembered profile facts |
supermemory-status |
Check connection, hooks, config, and skills |
supermemory-login |
Re-authenticate with Supermemory |
supermemory-logout |
Remove saved local credentials |
Example prompts:
> Remember that this project uses Vitest for unit tests and Playwright for E2E.
> What do you remember about our database schema?
> Forget the memory about the old API endpoint.
> Is Supermemory connected?Verify installation
npx codex-supermemory statusUninstall
npx codex-supermemory uninstallThis removes hook registrations and skill scripts. Your existing memories in Supermemory are preserved.
Configuration
Create ~/.codex/supermemory.json to override defaults:
{
"apiKey": "sm_...",
"baseUrl": "https://api.supermemory.ai",
"similarityThreshold": 0.6,
"maxMemories": 5,
"maxProfileItems": 5,
"injectProfile": true,
"containerTagPrefix": "codex",
"autoSaveEveryTurns": 3,
"signalExtraction": false,
"debug": false
}| Option | Default | Description |
|---|---|---|
apiKey |
— | API key (env / browser auth preferred) |
baseUrl |
https://api.supermemory.ai |
API base URL (SUPERMEMORY_API_URL overrides) |
similarityThreshold |
0.6 |
Minimum match score for recall (0–1) |
maxMemories |
5 |
Max memories injected per prompt |
maxProfileItems |
5 |
Max profile facts injected per prompt |
injectProfile |
true |
Include user profile in context |
containerTagPrefix |
"codex" |
Prefix for auto-generated container tags |
autoSaveEveryTurns |
3 |
Save memories every N turns |
signalExtraction |
false |
Only capture turns with signal keywords |
debug |
false |
Write debug logs to ~/.codex-supermemory.log |
Logging
export SUPERMEMORY_DEBUG=true
tail -f ~/.codex-supermemory.logNext steps
Apps Docs Integrations Convex (`apps/docs/integrations/convex.mdx`)
title: "Convex" sidebarTitle: "Convex" icon: "/icons/hugeicons/database-01.svg"
Convex apps don't have built-in memory for AI. Supermemory fixes that. You get a memory layer that stores conversations, builds user profiles, and gives your AI context about who it's talking to.
What you can do
- Store user interactions and retrieve them in future sessions
- Build automatic user profiles from conversations
- Search memories to give your AI relevant context
- Keep everything in your Convex database for full visibility
Setup
Install the packages:
npm install supermemory convexFor the AI chat example, also install the AI SDK packages:
npm install @supermemory/tools @ai-sdk/openai aiSet up your environment variable in Convex:
npx convex env set SUPERMEMORY_API_KEY your-supermemory-api-keyBasic integration
Create simple helper functions for each Supermemory operation:
// convex/memory.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { Supermemory } from "supermemory";
const memory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY });
// Get user profile
export const getProfile = action({
args: { userId: v.string() },
handler: async (ctx, { userId }) => {
return await memory.profile(userId);
},
});
// Add a memory
export const addMemory = action({
args: { userId: v.string(), content: v.string() },
handler: async (ctx, { userId, content }) => {
return await memory.add(userId, {
content,
dreaming: "instant", // memories are ready as soon as status is done
});
},
});
// Search memories
export const searchMemories = action({
args: { userId: v.string(), query: v.string(), limit: v.optional(v.number()) },
handler: async (ctx, { userId, query, limit }) => {
return await memory.search(userId, {
query,
searchMode: "hybrid",
limit: limit ?? 10,
});
},
});Example: AI chat with memory
A chat endpoint using the Supermemory AI SDK middleware. It automatically injects context and saves memories.
// convex/chat.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { withSupermemory } from "@supermemory/tools/ai-sdk";
export const chat = action({
args: { userId: v.string(), message: v.string() },
handler: async (ctx, { userId, message }) => {
// Wrap the model - automatically injects context and saves memories
const model = withSupermemory(openai("gpt-4o-mini"), {
namespace: userId,
id: `convex-chat-${userId}`,
mode: "full",
addMemory: "always",
});
const { text } = await generateText({
model,
system: "You are a helpful assistant.",
prompt: message,
});
return text;
},
});Storing memories in Convex tables
Keep a local copy of memories in your Convex database for full visibility:
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
memories: defineTable({
userId: v.string(),
content: v.string(),
createdAt: v.number(),
}).index("by_user", ["userId"]),
});// convex/memory.ts
import { action, mutation, query } from "./_generated/server";
import { api } from "./_generated/api";
import { v } from "convex/values";
import { Supermemory } from "supermemory";
const memory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY });
// Store in Convex
export const storeMemory = mutation({
args: { userId: v.string(), content: v.string() },
handler: async (ctx, { userId, content }) => {
return await ctx.db.insert("memories", {
userId,
content,
createdAt: Date.now(),
});
},
});
// Add memory to both Supermemory and Convex
export const addMemory = action({
args: { userId: v.string(), content: v.string() },
handler: async (ctx, { userId, content }) => {
// Add to Supermemory
await memory.add(userId, { content });
// Store in Convex
// Note: in production, handle partial failures — if the Convex mutation
// fails after the Supermemory write succeeds, the two stores will be out of sync.
await ctx.runMutation(api.memory.storeMemory, { userId, content });
},
});
// List memories from Convex
export const listMemories = query({
args: { userId: v.string() },
handler: async (ctx, { userId }) => {
return await ctx.db
.query("memories")
.withIndex("by_user", q => q.eq("userId", userId))
.order("desc")
.take(50);
},
});Related docs
Apps Docs Integrations Crewai (`apps/docs/integrations/crewai.mdx`)
title: "CrewAI" sidebarTitle: "CrewAI" icon: "/icons/hugeicons/user-multiple.svg"
CrewAI agents don't remember anything between runs by default. Supermemory fixes that. You get a memory layer that stores what happened, who the user is, and what they care about. Your crews can pick up where they left off.
What you can do
- Give agents access to user preferences and past interactions
- Store crew outputs so future runs can reference them
- Search memories to give agents relevant context before they start
Setup
Install the required packages:
pip install crewai supermemory python-dotenvConfigure your environment:
# .env
SUPERMEMORY_API_KEY=your-supermemory-api-key
OPENAI_API_KEY=your-openai-api-keyBasic integration
Initialize Supermemory and inject user context into your agent's backstory:
import os
from crewai import Agent, Task, Crew, Process
from supermemory import Supermemory
from dotenv import load_dotenv
load_dotenv()
memory = Supermemory()
def build_context(user_id: str, query: str) -> str:
"""Fetch user profile and relevant memories."""
profile = memory.profile(user_id).profile
memories = memory.search(user_id, query=query, limit=5).results
static = [m.memory for m in profile.static]
dynamic = [m.memory for m in profile.dynamic]
return f"""
User Profile:
{chr(10).join(static) if static else 'No profile data.'}
Current Context:
{chr(10).join(dynamic) if dynamic else 'No recent activity.'}
Relevant History:
{chr(10).join([m.memory or m.chunk for m in memories[:5]]) if memories else 'None.'}
"""
def create_agent_with_memory(user_id: str, role: str, goal: str, query: str) -> Agent:
"""Create an agent with user context baked into its backstory."""
context = build_context(user_id, query)
return Agent(
role=role,
goal=goal,
backstory=f"""You have access to the following information about the user:
{context}
Use this context to personalize your work.""",
verbose=True
)Core concepts
User profiles
Supermemory tracks two kinds of user data:
- Static facts: Things that don't change often (preferences, job title, tech stack)
- Dynamic context: What the user is working on right now
result = memory.profile("user_abc")
print([m.memory for m in result.profile.static]) # ["Prefers Agile methodology", "Senior engineer"]
print([m.memory for m in result.profile.dynamic]) # ["Working on Q2 roadmap", "Focused on API design"]Storing memories
Save crew outputs so future runs can reference them:
def store_crew_result(user_id: str, task_description: str, result: str):
"""Save crew output as a memory."""
memory.add(
user_id,
content=f"Task: {task_description}\nResult: {result}",
metadata={"type": "crew_execution"},
dreaming="instant"
)Searching memories
Pull up past interactions before running a crew:
results = memory.search(
"user_abc",
query="previous project recommendations",
search_mode="hybrid", # Searches memories + document chunks
limit=10
)
for r in results.results:
print(r.memory or r.chunk)Example: research crew with memory
This crew has two agents: a researcher and a writer. The researcher adjusts its technical depth based on the user's background. The writer remembers formatting preferences. Both can see what the user has asked about before.
/* Detailed source-code truncated for AI context efficiency. */More patterns
Crews with multiple users
Sometimes you need context from several users at once:
def create_collaborative_context(user_ids: list[str], topic: str) -> str:
"""Aggregate context from multiple users."""
combined = []
for user_id in user_ids:
static = [m.memory for m in memory.profile(user_id).profile.static]
if static:
combined.append(f"{user_id}: {', '.join(static[:3])}")
return "\n".join(combined) if combined else "No shared context available."Only storing successful runs
You might not want to save every crew output:
def store_if_successful(user_id: str, task: str, result: str, success: bool):
"""Only store successful task completions."""
if not success:
return
memory.add(
user_id,
content=f"Completed: {task}\nOutcome: {result}",
metadata={"type": "success", "task": task},
dreaming="instant"
)Using metadata to organize memories
Metadata lets you filter memories by project, agent, or whatever else makes sense:
# Store with metadata
memory.add(
"user_123",
content="Research findings on distributed systems",
dreaming="instant",
metadata={
"project": "infrastructure-review",
"agents": ["researcher", "writer"],
"confidence": "high"
}
)
# Search with filters
results = memory.search(
"user_123",
query="distributed systems",
filter={
"operator": "and",
"operands": [
{"field": "project", "operator": "eq", "value": "infrastructure-review"},
{"field": "confidence", "operator": "eq", "value": "high"}
]
}
)Related docs
Apps Docs Integrations Cursor (`apps/docs/integrations/cursor.mdx`)
title: "Cursor" sidebarTitle: "Cursor" icon: "/images/cursor-logo.svg"
Your agent remembers the decisions, bugs, and conventions from earlier chats instead of starting cold every time.
Install
Run this in Cursor:
/add-plugin cursor-supermemoryOr install it from the Cursor Marketplace: open Customize, find Supermemory, select Install, and choose project or user scope.
Restart Cursor or run Developer: Reload Window afterwards.
Authenticate
Open a new chat in Cursor and run:
/supermemory-setupA browser window opens. Sign in to Supermemory and you are done.
As a fallback, set an API key from API Keys:
Restart Cursor after installing the plugin or changing credentials.
Check the connection any time with /supermemory-status.
node "${CURSOR_PLUGIN_ROOT}/dist/cli.js" login
node "${CURSOR_PLUGIN_ROOT}/dist/cli.js" status
node "${CURSOR_PLUGIN_ROOT}/dist/cli.js" logoutCURSOR_PLUGIN_ROOT is set for plugin hooks. If it is empty in your shell, run node dist/cli.js <command> from the installed plugin directory.
Credentials are stored in ~/.supermemory-cursor/credentials.json.
How it works
| Layer | What it does |
|---|---|
| Session profile | Loads your persistent profile when a Cursor conversation starts |
| Automatic recall | Searches on substantive prompts, deduplicates results, and injects them after the first supported tool result |
| Incremental capture | Saves each completed turn, and retries unsaved transcript deltas at session end |
| MCP tools | Explicit memory control from any Cursor AI session |
| Context gatherer | Fans out targeted searches before substantial work |
| Always-on rule | Makes the agent recall relevant history proactively |
Skills and commands
| Name | Type | Description |
|---|---|---|
memory-init |
Skill | Explore the codebase and initialize project memory |
memory-save |
Skill | Save an insight, decision, or solution worth keeping |
memory-search |
Skill | Search memory for past work, bugs, and decisions |
supermemory-context-gatherer |
Agent | Gather deep background before substantial work |
supermemory-setup |
Command | Connect Supermemory to Cursor |
supermemory-status |
Command | Check authentication and live connectivity |
supermemory-config |
Command | Create or edit the project config file |
supermemory-logout |
Command | Disconnect Supermemory from Cursor |
MCP tools
| Tool | Description |
|---|---|
supermemory_get_config |
Show current config, resolved container tags, and config file paths |
supermemory_set_config |
Update config at project or global scope |
supermemory_containers |
Show what user and project container tags resolve to |
supermemory_search |
Search memories by query |
supermemory_add |
Save new information to memory |
supermemory_list |
List stored memories |
supermemory_forget |
Delete a memory by id or content |
supermemory_profile |
Get your user profile summary |
Every tool that takes a container argument accepts:
"user"(default): personal memories for the current repository"project": project knowledge for the current repository"both": both scopes plus compatible legacy memories- any custom string: used as a raw container tag
user and project write to the same repository container. The sm_scope metadata field is what keeps personal and session memories separate from explicit project knowledge when an agent asks for one scope.
Container tags
Cursor shares one repository tag with the Claude Code, Muse Code, OpenAI Codex, and OpenCode plugins, so agents working on the same repo read and write the same memory:
repo_<project_name>__<project_id>The project ID is a stable hash of the normalized Git remote. Repositories without a remote fall back to their resolved local path. Two repos with the same directory name never collide, and different agents on the same repository share memory.
The plugin still reads the former cursor_user_* and cursor_project_* tags, along with legacy tags from the other agents. New writes only use the unified repository tag. Set repoContainerTag only when you need an explicit shared override.
Configuration
Environment variables
| Variable | Description |
|---|---|
SUPERMEMORY_API_KEY |
API key (overrides all other sources) |
SUPERMEMORY_API_URL |
Override the Supermemory API base URL |
SUPERMEMORY_REPO_TAG |
Override the unified repository container tag |
SUPERMEMORY_USER_TAG |
Legacy Cursor personal container to continue reading |
SUPERMEMORY_PROJECT_TAG |
Legacy Cursor project container to continue reading |
CURSOR_USER_EMAIL |
Used only to find legacy Cursor personal memories |
Global config
~/.config/cursor/supermemory.json holds user-wide defaults and applies to all projects.
{
"repoContainerTag": "repo_my_project__0123456789abcdef",
"similarityThreshold": 0.55,
"maxMemories": 10,
"injectProfile": true,
"signalExtraction": false,
"signalKeywords": ["remember", "architecture", "decision", "bug", "fix"],
"signalTurnsBefore": 3
}Project config
.cursor/.supermemory/config.json holds per-workspace overrides and wins over global config. Add it to .gitignore if it contains an API key.
{
"apiKey": "sm_...",
"repoContainerTag": "repo_my_project__0123456789abcdef",
"similarityThreshold": 0.55,
"maxMemories": 10,
"injectProfile": true
}| Option | Default | Description |
|---|---|---|
apiKey |
— | Project-specific API key |
baseUrl |
Supermemory API | Override the Supermemory API base URL |
repoContainerTag |
derived from normalized Git remote or project path | Override the unified repository container |
userContainerTag |
— | Legacy Cursor personal container to continue reading |
projectContainerTag |
— | Legacy Cursor project container to continue reading |
similarityThreshold |
0.55 |
Minimum similarity for prompt recall. Values below 0.55 are floored. |
maxMemories |
10 |
Max profile facts injected at session start |
injectProfile |
true |
Whether to inject the user profile at session start |
signalExtraction |
false |
Capture only turns containing durable-signal keywords |
signalKeywords |
remember, architecture, decision, bug, fix |
Keywords that trigger signal-based capture |
signalTurnsBefore |
3 |
Number of nearby turns retained around a signal |
You can also set these from the agent with supermemory_set_config, or edit the file by hand.
Log out
Run /supermemory-logout in Cursor.
This removes the stored credentials. Your memories in Supermemory are preserved.
Next steps
Apps Docs Integrations Eve (`apps/docs/integrations/eve.mdx`)
title: "Eve" sidebarTitle: "Eve" icon: "/images/eve.svg"
Your next conversation can start where the last one left off. Supermemory gives your Eve agent the context to continue a project: what was decided, what changed, and the source material behind it.
You don't need to build your own parsing, embedding, indexing or memory-retrieval pipeline. Supermemory handles that infrastructure. Eve's memory scope and namespace let you keep context separated by user or workspace.
Add Supermemory
Start with an existing Eve project, Node.js 24 or newer, and an Eve version compatible with eve@^0.47.3.
```bash
eve add memory/supermemory
```
Eve installs `@supermemory/eve` and creates `agent/memory/supermemory.ts`, the file that connects your agent to Supermemory.```bash .env.local
SUPERMEMORY_API_KEY="your-supermemory-api-key"
``````bash
eve dev
```
Automatic recall and conversation capture are enabled by default. The memory tools are available to your agent alongside its existing tools.You can also install the provider manually.
How memory works
Two conversations about the same launch show the three parts working together: session history, indexed sources and memories.
<video
className="eve-memory-video"
controls
playsInline
preload="metadata"
poster="/images/eve-memory-poster.png"
aria-label="How Eve saves context and retrieves it in a new session"
After a successful turn, the provider saves the user and assistant messages as session history. Supermemory processes that conversation into durable memories, such as the launch date or the decision to keep the first release web-only. Capture is automatic; processing happens asynchronously.
Files take a separate path. When Eve works with an attachment, URL, or text, SuperRAG parses or transcribes it into an indexed source the agent can read. Eve can then save a useful conclusion as a memory, retaining the source document's ID. In the example, the budget stays connected to the launch brief.
Before a later turn, the provider loads the user profile and searches for relevant saved context. Eve can read an earlier session or follow a source-backed memory to the original document when it needs more detail. The new conversation can then use both the earlier decision and the evidence behind it. Recall also runs after context compaction.
Memory tools
The provider gives Eve seven tools alongside automatic recall and capture. The generated file is named supermemory.ts, so Eve prefixes their names with supermemory__.
| Tool | Purpose |
|---|---|
search |
Find relevant memories and session history in the current memory scope. |
read_session |
Read a stored conversation found through search. |
read_document |
Read sections of a processed source, fetching more as needed. |
remember |
Save a standalone fact, preference, decision, or correction; optionally retain its sourceDocumentId. |
extract |
Parse, transcribe, and index an Eve attachment, URL, or text with SuperRAG. |
forget |
Remove one memory by ID. |
forget_matching |
Preview memories within a requested boundary, then remove selected IDs. |
Forgetting removes derived memories. The original session history and source documents remain stored.
Choose whose memory Eve uses
The generated memory file uses Eve's byPrincipal scope. In an authenticated application, memory follows the caller's identity. During eve dev, it uses the shared local-dev scope. Outside local development, anonymous and runtime principals have memory disabled.
For a team or multi-tenant application, derive the scope from verified user and tenant identity. Eve resolves that scope before the provider runs, so the agent's tools cannot choose another user's memory. See Eve's multi-tenant memory guide.
Eve also supplies a default namespace for the memory slot. You only need to set namespace yourself when you want a custom memory domain. Keep it stable: changing it selects a different memory space. Eve's namespace documentation explains the defaults and how to share a domain intentionally.
Configure the provider
Edit the options passed to supermemory(...) in agent/memory/supermemory.ts. The generated file only needs your API key; the defaults enable automatic search and capture.
You can adjust those behaviors independently. Set autoSearch.enabled to false to disable automatic search while keeping profile recall. Set capture.enabled to false to stop saving completed turns automatically. Both settings leave the explicit memory tools available.
Namespace, description, and scope are Eve memory-slot settings. Configure them on defineMemory(...), outside the provider options.
Manual installation
If you prefer to create the memory file yourself, install the package in your Eve project:
npm install @supermemory/eveThen create the same memory slot that eve add generates:
import supermemory from "@supermemory/eve";
import { defineMemory } from "eve/memory";
import { byPrincipal } from "eve/memory/scope";
export default defineMemory({
description: "Recall and manage durable context for the current user.",
provider: supermemory({
apiKey: process.env.SUPERMEMORY_API_KEY!,
}),
scope: byPrincipal,
});Set SUPERMEMORY_API_KEY in .env.local and start your agent as described in Add Supermemory.
For implementation details, see the provider repository and Eve's Supermemory integration.
Apps Docs Integrations Grok Bot (`apps/docs/integrations/grok-bot.mdx`)
title: "Grok Bot" sidebarTitle: "Grok Bot" icon: "/images/grok-logo.png"
Grok Bots are cloud agents. A new task can mean a new machine and a blank context. Supermemory is the memory they keep between those tasks — findings, decisions, and preferences — so the next Bot does not start cold.
How it helps
Without memory, a Bot redoes investigation you already paid for. With Supermemory:
- Later Bots know what earlier ones already figured out
- You can save a decision or finding once and have it stick
- You can ask what it already remembers instead of starting over
Install once, sign in, and it is available to your Grok Bots.
Install
Open the Supermemory for Grok Bot plugin page and select Add to Grok Bot.
Restart Grok Bot afterwards so the plugin loads.
Authenticate
Just ask Grok Bot:
Sign me in to SupermemoryIt shows a connect card. Follow it to link your Supermemory account.
To check later, ask Is Supermemory connected?
Skills
Grok Bot picks these up from what you ask. There are no slash commands.
| Skill | Ask for it like this |
|---|---|
memory-init |
"Learn this codebase and remember it" |
memory-save |
"Remember that we use Vitest for unit tests" |
memory-search |
"What do you remember about our database schema?" |
Log out
Ask Grok Bot to Disconnect Supermemory.
This removes the stored credentials. Your memories in Supermemory are preserved.
Next steps
Apps Docs Integrations Hermes (`apps/docs/integrations/hermes.mdx`)
title: "Hermes" sidebarTitle: "Hermes" icon: "/images/hermes.svg"
Hermes agent ships a native supermemory memory provider: semantic long-term memory, profile recall, search, explicit memory tools, and session-aware ingest — not a bolt-on script the model might skip. Hermes runs across Telegram, Discord, Slack, WhatsApp, Signal, and the CLI from one gateway.
Hermes also ships with built-in MEMORY.md and USER.md files. Supermemory adds structure and isolation (profile-scoped and optional multi-container tags), plus retrieval that goes beyond a single flat file.
Get your API key
Create a supermemory API key from the API Keys page in the console. During hermes memory setup you can paste it when prompted, or persist it in your environment:
Install the memory provider
pip install supermemoryhermes memory setupSelect supermemory when prompted and paste your API key.
Or set the provider and key manually:
hermes config set memory.provider supermemory
echo 'SUPERMEMORY_API_KEY=sm_...' >> ~/.hermes/.env(Adjust the path if your Hermes home directory differs.)
How it works
Once configured, the provider runs through Hermes’s normal memory lifecycle:
- Prefetch — Relevant memory context can be loaded before each turn.
- Turn capture — Cleaned user/assistant turns can be stored after each completed response.
- Session ingest — The full session can be ingested at session end for richer graph updates.
- Explicit tools — Search, store, forget, and profile tools are available to the model when appropriate.
- Built-in file memory — This does not replace
MEMORY.md/USER.md; mirroring behavior depends on Hermes version and config (see upstream README).
Tools
Kebab-case names are registered for the agent; snake_case aliases remain supported.
| Tool | Alias | Description |
|---|---|---|
supermemory-save |
supermemory_store |
Store an explicit memory. |
supermemory-search |
supermemory_search |
Search by semantic similarity. |
supermemory-forget |
supermemory_forget |
Forget a memory by ID or best-match query. |
supermemory-profile |
supermemory_profile |
Retrieve persistent profile and recent context. |
Commands
Interactive setup: pick supermemory and enter your API key.
hermes memory setupEnvironment variables
These variables configure the supermemory provider (for example in your shell or Hermes env file):
SUPERMEMORY_API_KEY=sm_... # Required
SUPERMEMORY_CONTAINER_TAG=hermes-work # Optional: overrides container tag from configMulti-container tags
By default, recall and capture use a single primary container_tag (optionally profile-scoped with {identity}). Multi-container mode adds extra named tags so the model can read and write specific namespaces — for example work vs personal, or one bucket per project.
How to enable — In $HERMES_HOME/supermemory.json, set:
enable_custom_container_tagstotruecustom_containersto an array of allowed tag strings (e.g.work,personal,project-alpha)custom_container_instructions(recommended) — short guidance the provider injects into the system prompt so Hermes knows when to use which tag
Your primary container_tag stays the default namespace; listed custom tags are additional allowlisted namespaces.
How it works
supermemory_search,supermemory_store,supermemory_forget, andsupermemory_profileaccept an optionalcontainer_tagargument. The tag must be either the primarycontainer_tag(after template resolution) or one ofcustom_containers.- Automatic behavior (turn sync, prefetch, mirroring built-in memory writes, session-end ingest) always uses the primary container only — it does not pick a custom tag for you.
- Instructions in
custom_container_instructionssteer the model toward passing the rightcontainer_tagon tool calls when the user’s intent matches a namespace (e.g. “check my personal notes” →personal).
Example:
{
"container_tag": "hermes",
"enable_custom_container_tags": true,
"custom_containers": ["work", "personal", "shared-knowledge"],
"custom_container_instructions": "Use work for job and coding context, personal for life and hobbies, shared-knowledge for facts that apply across both."
}See the upstream plugin README for the exact schema and any newer options.
Config file
Create or edit $HERMES_HOME/supermemory.json. Common keys:
| Key | Default | Description |
|---|---|---|
container_tag |
hermes |
Tag for search/writes; use {identity} for profile-scoped tags (e.g. hermes-{identity} → hermes-coder). |
auto_recall |
true |
Inject memory context before turns. |
auto_capture |
true |
Store turns after each response. |
max_recall_results |
10 |
Max items merged into context. |
profile_frequency |
50 |
Profile on first turn and every N turns. |
capture_mode |
all |
How aggressively turns are captured. |
search_mode |
hybrid |
hybrid, memories, or documents. |
api_timeout |
5.0 |
SDK / ingest timeout (seconds). |
enable_custom_container_tags |
false |
Set true to allow extra namespaces; see Multi-container tags above. |
custom_containers |
— | Allowlisted tags beyond the primary container_tag. |
custom_container_instructions |
— | Prompt text that explains when to pass container_tag on tools. |
Example profile-scoped container:
{
"container_tag": "hermes-{identity}",
"search_mode": "hybrid",
"auto_recall": true,
"auto_capture": true
}Self-hosted API
If you run your own supermemory API, set base_url (and any other host-specific options) in supermemory.json or via env as documented in the upstream plugin README — alongside your key and container settings.
Next steps
Questions about the API or product? Discord · [email protected] · Developer docs
Apps Docs Integrations Langchain (`apps/docs/integrations/langchain.mdx`)
title: "LangChain" sidebarTitle: "LangChain" icon: "/icons/hugeicons/link-01.svg"
Build AI applications with LangChain that remember context across conversations. Supermemory handles memory storage, retrieval, and user profiling while LangChain manages your conversation flow.
Overview
This guide shows how to integrate Supermemory with LangChain to create AI agents that:
- Maintain user context through automatic profiling
- Store and retrieve relevant memories semantically
- Personalize responses based on conversation history
Setup
Install the required packages:
pip install langchain langchain-openai supermemory python-dotenvConfigure your environment:
# .env
SUPERMEMORY_API_KEY=your-supermemory-api-key
OPENAI_API_KEY=your-openai-api-keyBasic integration
Initialize both clients and set up a simple chat function with memory:
/* Detailed source-code truncated for AI context efficiency. */Core concepts
User profiles
Supermemory automatically maintains user profiles with two types of information:
- Static facts: Long-term information about the user (preferences, expertise, background)
- Dynamic context: Recent activity and current focus areas
# Fetch the profile for a namespace
result = memory.profile("user_123")
print([m.memory for m in result.profile.static]) # ["User is a Python developer", "Prefers dark mode"]
print([m.memory for m in result.profile.dynamic]) # ["Currently working on API integration", "Debugging auth issues"]Memory storage
Content you add is automatically processed into searchable memories:
# Store a conversation
memory.add(
"user_123",
content="User asked about async Python patterns. Explained asyncio basics.",
metadata={"topic": "python", "type": "conversation"},
dreaming="instant"
)
# Store a document
memory.add(
"user_123",
content="https://docs.python.org/3/library/asyncio.html",
dreaming="instant"
)Memory search
Search returns both extracted memories and document chunks:
results = memory.search(
"user_123",
query="async programming",
search_mode="hybrid", # Searches memories + document chunks
limit=5
)
for r in results.results:
print(r.memory or r.chunk, r.similarity)Complete example: Code review assistant
Here's a full example of a code review assistant that learns from past reviews and adapts to the user's coding style:
/* Detailed source-code truncated for AI context efficiency. */
```\n{code}\n```")
])
chain = prompt | self.llm
response = chain.invoke({})
# Store the review for future context
self.memory.add(
user_id,
content=f"Code review feedback: {response.content[:500]}",
metadata={"type": "code_review", "language": language},
dreaming="instant"
)
return response.content
def learn_preference(self, user_id: str, preference: str):
"""Store a coding preference or style guideline."""
self.memory.add(
user_id,
content=f"Developer preference: {preference}",
metadata={"type": "preference"},
dreaming="instant"
)
# Usage
if __name__ == "__main__":
assistant = CodeReviewAssistant()
user_id = "dev_alice"
# Teach the assistant about preferences
assistant.learn_preference(user_id, "Prefers functional programming patterns")
assistant.learn_preference(user_id, "Values descriptive variable names over comments")
# Review some code
code = """
def calc(x, y):
r = []
for i in x:
if i in y:
r.append(i)
return r
"""
review = assistant.review(user_id, code, language="python")
print(review)Advanced patterns
Conversation history with memory
Maintain multi-turn conversations while building long-term memory:
/* Detailed source-code truncated for AI context efficiency. */Metadata filtering
Use metadata to organize and filter memories:
# Store with metadata
memory.add(
"user_123",
content="Discussed React hooks and state management",
dreaming="instant",
metadata={
"topic": "react",
"type": "discussion",
"project": "frontend-redesign"
}
)
# Search with filters
results = memory.search(
"user_123",
query="state management",
filter={
"operator": "and",
"operands": [
{"field": "topic", "operator": "eq", "value": "react"},
{"field": "project", "operator": "eq", "value": "frontend-redesign"}
]
}
)Batch memory operations
Efficiently store multiple memories:
# Store meeting notes as separate memories
notes = [
"Decided to use PostgreSQL for the new service",
"Timeline: MVP ready by end of Q2",
"Alice will lead the database migration"
]
for note in notes:
memory.add(
"team_standup",
content=note,
metadata={"date": "2024-01-15", "type": "decision"},
dreaming="instant"
)Next steps
Apps Docs Integrations Langgraph (`apps/docs/integrations/langgraph.mdx`)
title: "LangGraph" sidebarTitle: "LangGraph" icon: "/images/langgraph.svg"
Build stateful agents with LangGraph that remember context across sessions. Supermemory handles memory storage and retrieval while LangGraph manages your graph-based conversation flow.
Overview
This guide shows how to integrate Supermemory with LangGraph to create agents that:
- Maintain user context through automatic profiling
- Store and retrieve relevant memories at each node
- Use conditional logic to decide what's worth remembering
- Combine short-term (session) and long-term (cross-session) memory
Setup
Install the required packages:
pip install langgraph langchain-openai supermemory python-dotenvConfigure your environment:
# .env
SUPERMEMORY_API_KEY=your-supermemory-api-key
OPENAI_API_KEY=your-openai-api-keyBasic integration
A minimal agent that fetches user context before responding and stores the conversation after:
/* Detailed source-code truncated for AI context efficiency. */Core concepts
User profiles
Supermemory automatically builds user profiles from stored memories:
- Static facts: Long-term information (preferences, expertise, background)
- Dynamic context: Recent activity and current focus
result = memory.profile("user_123")
print([m.memory for m in result.profile.static]) # ["User is a Python developer", "Prefers functional style"]
print([m.memory for m in result.profile.dynamic]) # ["Working on async patterns", "Debugging rate limiting"]Memory storage
Content you add gets processed into searchable memories:
# Store a conversation
memory.add(
"user_123",
content="User asked about graph traversal. Explained BFS vs DFS.",
metadata={"topic": "algorithms", "type": "conversation"},
dreaming="instant"
)
# Store a document
memory.add(
"user_123",
content="https://langchain-ai.github.io/langgraph/",
dreaming="instant"
)Memory search
Search returns both extracted memories and document chunks:
results = memory.search(
"user_123",
query="graph algorithms",
search_mode="hybrid", # Searches memories + document chunks
limit=5
)
for r in results.results:
print(r.memory or r.chunk, r.similarity)Complete example: support agent
A support agent that learns from past tickets and adapts to each user's technical level:
/* Detailed source-code truncated for AI context efficiency. */Advanced patterns
Conditional memory storage
Not everything is worth remembering. Use conditional edges to filter:
def should_store(state: State) -> str:
"""Skip storing trivial messages."""
last_msg = state["messages"][-1].content.lower()
skip_phrases = ["thanks", "ok", "got it", "bye"]
if len(last_msg) < 20 or any(p in last_msg for p in skip_phrases):
return "skip"
return "store"
graph.add_conditional_edges("respond", should_store, {
"store": "store",
"skip": END
})Parallel memory operations
Fetch memories and categorize at the same time:
from langgraph.graph import StateGraph, START, END
graph = StateGraph(State)
graph.add_node("retrieve", retrieve_context)
graph.add_node("categorize", categorize)
graph.add_node("respond", respond)
# Both run in parallel after START
graph.add_edge(START, "retrieve")
graph.add_edge(START, "categorize")
# Both must complete before respond
graph.add_edge("retrieve", "respond")
graph.add_edge("categorize", "respond")
graph.add_edge("respond", END)Metadata filtering
Organize memories by project, topic, or any custom field:
# Store with metadata
memory.add(
"user_123",
content="User prefers detailed error messages with stack traces",
dreaming="instant",
metadata={
"type": "preference",
"project": "api-v2",
"priority": "high"
}
)
# Search with filters
results = memory.search(
"user_123",
query="error handling preferences",
filter={
"operator": "and",
"operands": [
{"field": "type", "operator": "eq", "value": "preference"},
{"field": "project", "operator": "eq", "value": "api-v2"}
]
}
)Combining session and long-term memory
LangGraph's checkpointer handles within-session state. Supermemory handles cross-session memory. Use both:
from langgraph.checkpoint.memory import MemorySaver
# Session memory (cleared when thread ends)
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)
# Long-term memory (persists across sessions)
# Handled by Supermemory in your nodesNext steps
Apps Docs Integrations Livekit (`apps/docs/integrations/livekit.mdx`)
title: "LiveKit" sidebarTitle: "LiveKit (Voice)" icon: "/images/livekit.svg"
Supermemory integrates with LiveKit Agents, so a voice agent can remember a caller across sessions and use that context on the next turn. Recall runs before the model replies. Completed turns are stored automatically. The model can also search, remember, and forget explicitly.
A Supermemory timeout or outage is skipped. It does not end the call.
Installation
pip install supermemory-livekitexport SUPERMEMORY_API_KEY=your_supermemory_api_keyCreate a key at console.supermemory.ai. For a self-hosted API, pass base_url to SupermemoryLiveKit.
Scope
Memory is isolated by container tag. Use a stable caller id, the same one you use in the rest of your app, so a LiveKit call and a chat session share one profile.
Container tags may only contain letters, numbers, _, -, and :, and must be 100 characters or fewer. Two ways to set it:
- Pass
container_tagyourself, usually from job metadata. - Let the plugin read the participant attribute
supermemory_container_tag, or fall back to the participant identity.
Identities that are not valid container tags are sanitized to a stable tag. Set the attribute when the identity is an email, phone number, or SIP address and you already have memories under a different id.
Set the attribute in the access token your backend issues. Do not grant the caller canUpdateOwnMetadata, or they could change the attribute and read another caller's memory.
One call is stored as a single document. Pass the LiveKit room name as session_id. The session id is the document's custom id, so a later update with the same id appends to that call instead of creating another.
Use one SupermemoryLiveKit per call. If you do rebind it, turns already captured stay with the caller who said them, and memory from the earlier caller is never shown to the new one.
Quick start
Create one SupermemoryLiveKit inside the job. A worker handles many calls, and each call needs its own scope.
import json
import os
from livekit import agents
from livekit.agents import AgentServer, AgentSession, ChatContext, JobContext
from supermemory_livekit import SupermemoryAgent, SupermemoryLiveKit
server = AgentServer()
@server.rtc_session(agent_name="memory-agent")
async def entrypoint(ctx: JobContext):
memory = SupermemoryLiveKit(api_key=os.environ["SUPERMEMORY_API_KEY"])
metadata = json.loads(ctx.job.metadata or "{}")
container_tag = metadata.get("container_tag")
if container_tag:
memory.bind(container_tag=container_tag, session_id=ctx.room.name)
chat_ctx = ChatContext()
if container_tag:
await memory.preload(chat_ctx)
await ctx.connect()
if not container_tag:
participant = await ctx.wait_for_participant()
memory.bind(participant=participant, session_id=ctx.room.name)
session = AgentSession(
stt="deepgram/nova-3:en",
llm="openai/gpt-4.1-mini",
tts="cartesia/sonic-3",
)
memory.attach(session)
await session.start(
room=ctx.room,
agent=SupermemoryAgent(
memory,
chat_ctx=chat_ctx,
instructions=(
"You are a helpful voice assistant. You remember this caller across calls. "
"Use that naturally, and do not mention the memory system."
),
),
)
if __name__ == "__main__":
agents.cli.run_app(server)preload puts the caller's profile into the first turn, so the greeting can use it. attach stores the conversation after each agent reply, and stores anything left when the session closes, including a caller who hangs up mid-turn. SupermemoryAgent recalls inside llm_node before each reply and adds the memory tools. That covers voice turns, text turns from generate_reply or session.run, and LiveKit's preemptive generation. Each user message is recalled once, and tool follow-ups reuse it.
A runnable version, with a greeting that uses the caller's memory, is in the example agent.
agent_name turns on explicit dispatch, which is how job metadata reaches the agent. Remove it to join every new room automatically, for example when testing in the Agents Playground.
Your own agent
If you already subclass Agent, keep that class. Pass the tools in, and recall from llm_node.
from livekit.agents import Agent
from supermemory_livekit import SupermemoryLiveKit
class Assistant(Agent):
def __init__(self, memory: SupermemoryLiveKit) -> None:
super().__init__(
instructions="You are a helpful voice assistant.",
tools=memory.tools(),
)
self.memory = memory
async def llm_node(self, chat_ctx, tools, model_settings):
await self.memory.enrich(chat_ctx)
return Agent.default.llm_node(self, chat_ctx, tools, model_settings)Call memory.attach(session) before session.start.
Recall in llm_node rather than on_user_turn_completed. Changing the turn context in that hook makes LiveKit discard its preemptive generation, which adds latency to every turn.
Realtime models skip llm_node. With a realtime model, call await self.memory.on_user_turn_completed(turn_ctx, new_message) from on_user_turn_completed instead. That hook only runs when turn detection runs in your agent, not inside the model. See LiveKit's external data guide. SupermemoryAgent picks the right hook for you. Turn capture still listens for conversation_item_added.
What gets recalled
| Mode | Static profile | Dynamic profile | Search | Use when |
|---|---|---|---|---|
profile |
Yes | Yes | No | Durable facts are enough |
query |
No | No | Yes | Only this turn's related memories |
full |
Yes | Yes | Yes | Default |
from supermemory_livekit import InputParams, SupermemoryLiveKit
memory = SupermemoryLiveKit(
container_tag="user_123",
params=InputParams(
mode="full",
search_limit=10,
search_threshold=0.1,
recall_timeout=2.0,
capture="always",
capture_dreaming="instant",
),
)Recall waits at most recall_timeout seconds (default 2). If the profile call is slower than that, or fails, the turn falls back to the profile loaded by preload at the start of the call, or proceeds without memory if there was none. In our tests most recalls took 0.4 to 1.4 seconds, with occasional calls near 3 seconds. Raise recall_timeout if you would rather wait than miss memory on a slow turn. Retrieved text is inserted immediately before the user message and is not stored back as something the agent said.
| Parameter | Default | Description |
|---|---|---|
search_limit |
10 |
Maximum search results merged into the turn |
search_threshold |
0.1 |
Minimum similarity, from 0 to 1 |
mode |
"full" |
profile, query, or full |
recall_timeout |
2.0 |
Seconds to wait before skipping recall |
capture |
"always" |
never disables storing the call |
capture_dreaming |
"instant" |
How the call transcript becomes memories. See below. |
Tools
SupermemoryAgent adds these tools. They are scoped to the bound container tag, so the model cannot read or write another caller.
| Tool | Used for |
|---|---|
search_memories |
Look up facts and past calls when the automatic recall is not enough |
remember |
Save one explicit fact, preference, or correction |
forget |
Forget one fact by id from search_memories, or by exact text |
remember stores a standalone fact and processes it immediately (dreaming="instant"), so it is usually recallable within a minute. If your organization has no balance for instant processing, it is saved on the default schedule instead. It is not appended to the call transcript. Automatic capture is what records the conversation.
When a call becomes recallable
Captured turns are written after each agent reply, but they become memories on Supermemory's processing schedule:
- With the default
capture_dreaming="instant", each write is processed on its own and is usually recallable within a minute, so a caller who rings back is recalled from the last call. Each write bills one extra operation, and the call is written after every agent reply. If your organization has no balance for instant processing, the call is saved on thedynamicschedule instead. - With
capture_dreaming="dynamic", related documents are processed together at no extra cost. In our tests this took 10 to 20 minutes, so a caller who rings back right away is not recalled from the last call yet. - In both modes,
search_memoriesfinds the raw call text as soon as it is stored, so the model can still look up a recent call.
Self-hosting
memory = SupermemoryLiveKit(
api_key=os.environ["SUPERMEMORY_API_KEY"],
base_url="http://localhost:6767",
container_tag="user_123",
)Apps Docs Integrations Mastra (`apps/docs/integrations/mastra.mdx`)
title: "Mastra" sidebarTitle: "Mastra" icon: "/images/mastra-icon.svg"
import MigrateCli from "/snippets/migrate-cli.mdx";
Integrate Supermemory with Mastra to give your AI agents persistent memory. Use the withSupermemory wrapper for zero-config setup or processors for fine-grained control.
Installation
npm install @supermemory/tools @mastra/coreQuick start
Wrap your agent config with withSupermemory to add memory capabilities:
import { Agent } from "@mastra/core/agent"
import { withSupermemory } from "@supermemory/tools/mastra"
import { openai } from "@ai-sdk/openai"
// Create agent with memory-enhanced config
const agent = new Agent(withSupermemory(
{
id: "my-assistant",
name: "My Assistant",
model: openai("gpt-4o"),
instructions: "You are a helpful assistant.",
},
{
namespace: "user-123", // Required: scopes memories to this user
id: "conv-456", // Required: groups messages for contextual memory
mode: "full",
}
))
const response = await agent.generate("What do you know about me?")const agent = new Agent(withSupermemory(
{ id: "my-assistant", model: openai("gpt-4o"), ... },
{
namespace: "user-123",
id: "conv-456",
addMemory: "never", // Disable automatic conversation saving
}
))How it works
The Mastra integration uses Mastra's native Processor interface:
- Input Processor - Fetches relevant memories from Supermemory and injects them into the system prompt before the LLM call
- Output Processor - Optionally saves the conversation to Supermemory after generation completes
sequenceDiagram
participant User
participant Agent
participant InputProcessor
participant LLM
participant OutputProcessor
participant Supermemory
User->>Agent: Send message
Agent->>InputProcessor: Process input
InputProcessor->>Supermemory: Fetch memories
Supermemory-->>InputProcessor: Return memories
InputProcessor->>Agent: Inject into system prompt
Agent->>LLM: Generate response
LLM-->>Agent: Return response
Agent->>OutputProcessor: Process output
OutputProcessor->>Supermemory: Save conversation (if enabled)
Agent-->>User: Return responseConfiguration options
| Option | Type | Default | Description |
|---|---|---|---|
namespace |
string |
Required | User/container tag for scoping memories |
id |
string |
Required | Groups messages into a single document for contextual memory |
apiKey |
string |
SUPERMEMORY_API_KEY env |
Your Supermemory API key |
baseUrl |
string |
https://api.supermemory.ai |
Custom API endpoint |
mode |
"profile" | "query" | "full" |
"profile" |
Memory search mode |
addMemory |
"always" | "never" |
"always" |
Auto-save conversations |
verbose |
boolean |
false |
Enable debug logging |
promptTemplate |
function |
- | Custom memory formatting |
Memory search modes
Profile Mode (Default) - Retrieves the user's complete profile without query-based filtering:
const agent = new Agent(withSupermemory(config, {
namespace: "user-123",
id: "conv-456",
mode: "profile",
}))Query Mode - Searches memories based on the user's message:
const agent = new Agent(withSupermemory(config, {
namespace: "user-123",
id: "conv-456",
mode: "query",
}))Full Mode - Combines profile AND query-based search for maximum context:
const agent = new Agent(withSupermemory(config, {
namespace: "user-123",
id: "conv-456",
mode: "full",
}))
### Mode Comparison
| Mode | Description | Use Case |
|------|-------------|----------|
| `profile` | Static + dynamic user facts | General personalization |
| `query` | Semantic search on user message | Specific Q&A |
| `full` | Both profile and search | Chatbots, assistants |
---
## Saving Conversations
Conversation saving is enabled by default (`addMemory: "always"`). Messages are grouped using the required `id`:
```typescript
const agent = new Agent(withSupermemory(
{ id: "my-assistant", model: openai("gpt-4o"), instructions: "..." },
{
namespace: "user-123",
id: "conv-456", // Required: groups messages for contextual memory
}
))
// All messages in this conversation are saved automatically
await agent.generate("I prefer TypeScript over JavaScript")
await agent.generate("My favorite framework is Next.js")To disable automatic saving:
const agent = new Agent(withSupermemory(
{ id: "my-assistant", model: openai("gpt-4o"), instructions: "..." },
{
namespace: "user-123",
id: "conv-456",
addMemory: "never", // Only retrieve memories, don't save
}
))Custom Prompt Templates
Customize how memories are formatted and injected. The template receives userMemories, generalSearchMemories, and searchResults (raw array for filtering by metadata):
import { Agent } from "@mastra/core/agent"
import { withSupermemory } from "@supermemory/tools/mastra"
import type { MemoryPromptData } from "@supermemory/tools/mastra"
const claudePrompt = (data: MemoryPromptData) => `
<context>
${data.userMemories}
<relevant_memories>
${data.generalSearchMemories}
</relevant_memories>
</context>
`.trim()
const agent = new Agent(withSupermemory(
{ id: "my-assistant", model: openai("gpt-4o"), instructions: "..." },
{
namespace: "user-123",
id: "conv-456",
mode: "full",
promptTemplate: claudePrompt,
}
))Direct Processor Usage
For advanced use cases, use processors directly instead of the wrapper:
Input Processor Only
Inject memories without saving conversations:
import { Agent } from "@mastra/core/agent"
import { createSupermemoryProcessor } from "@supermemory/tools/mastra"
import { openai } from "@ai-sdk/openai"
const agent = new Agent({
id: "my-assistant",
name: "My Assistant",
model: openai("gpt-4o"),
inputProcessors: [
createSupermemoryProcessor({
namespace: "user-123",
id: "conv-456",
mode: "full",
addMemory: "never",
verbose: true,
}),
],
})Output Processor Only
Save conversations without memory injection:
import { Agent } from "@mastra/core/agent"
import { createSupermemoryOutputProcessor } from "@supermemory/tools/mastra"
import { openai } from "@ai-sdk/openai"
const agent = new Agent({
id: "my-assistant",
name: "My Assistant",
model: openai("gpt-4o"),
outputProcessors: [
createSupermemoryOutputProcessor({
namespace: "user-123",
id: "conv-456",
}),
],
})Both Processors
Use the factory function for shared configuration:
import { Agent } from "@mastra/core/agent"
import { createSupermemoryProcessors } from "@supermemory/tools/mastra"
import { openai } from "@ai-sdk/openai"
const { input, output } = createSupermemoryProcessors({
namespace: "user-123",
id: "conv-456",
mode: "full",
verbose: true,
})
const agent = new Agent({
id: "my-assistant",
name: "My Assistant",
model: openai("gpt-4o"),
inputProcessors: [input],
outputProcessors: [output],
})Using RequestContext for Dynamic Thread IDs
For server setups where one agent instance handles multiple concurrent conversations, use Mastra's RequestContext to provide per-request thread IDs. RequestContext takes precedence over the construction-time id:
import { Agent } from "@mastra/core/agent"
import { RequestContext, MASTRA_THREAD_ID_KEY } from "@mastra/core/request-context"
import { withSupermemory } from "@supermemory/tools/mastra"
import { openai } from "@ai-sdk/openai"
const agent = new Agent(withSupermemory(
{ id: "my-assistant", model: openai("gpt-4o"), instructions: "..." },
{
namespace: "user-123",
id: "fallback-conv", // Used only when RequestContext doesn't provide a threadId
mode: "full",
}
))
// Per-request threadId takes precedence over id
const ctx = new RequestContext()
ctx.set(MASTRA_THREAD_ID_KEY, "user-456-session-789")
await agent.generate("Hello!", { requestContext: ctx })
// This conversation is stored under "user-456-session-789", not "fallback-conv"Verbose Logging
Enable detailed logging for debugging:
const agent = new Agent(withSupermemory(
{ id: "my-assistant", model: openai("gpt-4o"), instructions: "..." },
{
namespace: "user-123",
id: "conv-456",
verbose: true,
}
))
// Console output:
// [supermemory] Starting memory search { namespace: "user-123", mode: "profile" }
// [supermemory] Found 5 memories
// [supermemory] Injected memories into system prompt { length: 1523 }Working with Existing Processors
The wrapper correctly merges with existing processors in the config:
// Supermemory processors are merged correctly:
// - Input: [supermemory, myLogging] (supermemory runs first)
// - Output: [myAnalytics, supermemory] (supermemory runs last)
const agent = new Agent(withSupermemory(
{
id: "my-assistant",
model: openai("gpt-4o"),
inputProcessors: [myLoggingProcessor],
outputProcessors: [myAnalyticsProcessor],
},
{
namespace: "user-123",
id: "conv-456",
}
))API Reference
`withSupermemory`
Enhances a Mastra agent config with memory capabilities.
function withSupermemory<T extends AgentConfig>(
config: T,
options: SupermemoryMastraOptions
): TParameters:
config- The Mastra agent configuration objectoptions- Configuration options (includes requirednamespaceandid)
Returns: Enhanced config with Supermemory processors injected
`createSupermemoryProcessor`
Creates an input processor for memory injection.
function createSupermemoryProcessor(
options: SupermemoryMastraOptions
): SupermemoryInputProcessor`createSupermemoryOutputProcessor`
Creates an output processor for conversation saving.
function createSupermemoryOutputProcessor(
options: SupermemoryMastraOptions
): SupermemoryOutputProcessor`createSupermemoryProcessors`
Creates both processors with shared configuration.
function createSupermemoryProcessors(
options: SupermemoryMastraOptions
): {
input: SupermemoryInputProcessor
output: SupermemoryOutputProcessor
}`SupermemoryMastraOptions`
interface SupermemoryMastraOptions {
namespace: string // Required: User/container tag for scoping memories
id: string // Required: Groups messages for contextual memory generation
apiKey?: string
baseUrl?: string
mode?: "profile" | "query" | "full"
addMemory?: "always" | "never" // Default: "always"
verbose?: boolean
promptTemplate?: (data: MemoryPromptData) => string
}Environment Variables
SUPERMEMORY_API_KEY=your_supermemory_keyError Handling
Processors gracefully handle errors without breaking the agent:
- API errors - Logged and skipped; agent continues without memories
- Missing API key - Throws immediately with helpful error message
// Missing API key throws immediately
const agent = new Agent(withSupermemory(
{ id: "my-assistant", model: openai("gpt-4o"), instructions: "..." },
{
namespace: "user-123",
id: "conv-456",
apiKey: undefined, // Will check SUPERMEMORY_API_KEY env
}
))
// Error: SUPERMEMORY_API_KEY is not setNext Steps
Apps Docs Integrations Memory Graph (`apps/docs/integrations/memory-graph.mdx`)
title: 'Memory Graph' sidebarTitle: "Memory Graph" description: 'Interactive visualization for documents, memories and connections' icon: "/icons/hugeicons/hierarchy-square-01.svg"
Memory Graph is a React component that visualizes your Supermemory documents and memories as an interactive network. Documents appear as rectangular nodes, memories as hexagonal nodes, and connections between them show relationships and similarity.
Installation
npm install @supermemory/memory-graphRequirements: React 18.0.0 or higher
Quick start
'use client'; // For Next.js App Router
import { MemoryGraph } from '@supermemory/memory-graph';
import type { DocumentWithMemories } from '@supermemory/memory-graph';
import { useEffect, useState } from 'react';
export default function GraphPage() {
const [documents, setDocuments] = useState<DocumentWithMemories[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch('/api/graph')
.then(res => res.json())
.then(data => {
setDocuments(data.documents);
setIsLoading(false);
})
.catch(err => {
setError(err);
setIsLoading(false);
});
}, []);
return (
<MemoryGraph
documents={documents}
isLoading={isLoading}
error={error}
variant="console"
/>
);
}Optional rendering and document navigation
Existing icon-based document nodes and connection styling remain the defaults. Opt in to stacked squares and stronger semantic connections with appearance:
<MemoryGraph
documents={documents}
appearance={{ documentNodes: 'stacked', connections: 'emphasized' }}
openDocumentOnEnter
onOpenDocument={openPersistedDocument}
/>documentNodes accepts 'icons' or 'stacked'; connections accepts 'default' or 'emphasized'. Both fields are optional and also work on GraphCanvas. Stacks stay inside existing node bounds and simplify at low zoom. Use colors.edgeDocument to configure document-source ink; older custom GraphCanvas palettes without that field use textSecondary in emphasized mode.
openDocumentOnEnter is off by default. With an onOpenDocument callback, MemoryGraph exposes a focused Enter action for the selected persisted document or one confirmed memory source. Typing, modifier shortcuts, repeats, composition and keys outside the graph do not open documents. Your callback retains responsibility for routing and access control; consumers without a callback receive no new opening behavior.
Backend API route
Create an API route to fetch documents from Supermemory:
// app/api/graph/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
const response = await fetch('https://api.supermemory.ai/v3/documents/documents', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.SUPERMEMORY_API_KEY}`,
},
body: JSON.stringify({
page: 1,
limit: 500,
sort: 'createdAt',
order: 'desc',
}),
});
const data = await response.json();
return NextResponse.json(data);
}// pages/api/graph.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const response = await fetch('https://api.supermemory.ai/v3/documents/documents', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.SUPERMEMORY_API_KEY}`,
},
body: JSON.stringify({ page: 1, limit: 500, sort: 'createdAt', order: 'desc' }),
});
const data = await response.json();
res.json(data);
}app.get('/api/graph', async (req, res) => {
const response = await fetch('https://api.supermemory.ai/v3/documents/documents', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.SUPERMEMORY_API_KEY}`,
},
body: JSON.stringify({ page: 1, limit: 500, sort: 'createdAt', order: 'desc' }),
});
const data = await response.json();
res.json(data);
});Variants
Console Variant - Full-featured dashboard view (0.8x zoom, space selector visible):
<MemoryGraph documents={documents} variant="console" />Consumer Variant - Embedded widget view (0.5x zoom, space selector hidden):
<MemoryGraph documents={documents} variant="consumer" />Examples
With pagination
'use client';
import { MemoryGraph } from '@supermemory/memory-graph';
import { useCallback, useEffect, useState } from 'react';
export default function PaginatedGraph() {
const [documents, setDocuments] = useState([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [isLoading, setIsLoading] = useState(true);
const [isLoadingMore, setIsLoadingMore] = useState(false);
useEffect(() => { fetchPage(1, false); }, []);
const fetchPage = async (pageNum, append) => {
pageNum === 1 ? setIsLoading(true) : setIsLoadingMore(true);
const res = await fetch(`/api/graph?page=${pageNum}&limit=100`);
const data = await res.json();
append ? setDocuments(prev => [...prev, ...data.documents]) : setDocuments(data.documents);
setHasMore(data.pagination.currentPage < data.pagination.totalPages);
setIsLoading(false);
setIsLoadingMore(false);
};
const loadMore = useCallback(async () => {
if (!isLoadingMore && hasMore) {
const nextPage = page + 1;
setPage(nextPage);
await fetchPage(nextPage, true);
}
}, [page, hasMore, isLoadingMore]);
return (
<MemoryGraph
documents={documents}
isLoading={isLoading}
isLoadingMore={isLoadingMore}
hasMore={hasMore}
totalLoaded={documents.length}
loadMoreDocuments={loadMore}
/>
);
}Highlighting search results
<MemoryGraph
documents={documents}
highlightDocumentIds={searchResults}
highlightsVisible={searchResults.length > 0}
/>Controlled space selection
<MemoryGraph
documents={documents}
selectedSpace={selectedSpace}
onSpaceChange={setSelectedSpace}
showSpacesSelector={false}
/>Custom empty state
<MemoryGraph documents={documents} isLoading={isLoading}>
<h2>No memories yet</h2>
Add content to see your knowledge graph
</MemoryGraph>Props reference
Core props
| Prop | Type | Default | Description |
|---|---|---|---|
documents |
DocumentWithMemories[] |
required | Array of documents to display |
isLoading |
boolean |
false |
Shows loading indicator |
error |
Error | null |
null |
Error to display |
variant |
"console" | "consumer" |
"console" |
Visual variant |
children |
ReactNode |
- | Custom empty state content |
Pagination props
| Prop | Type | Default | Description |
|---|---|---|---|
isLoadingMore |
boolean |
false |
Shows indicator when loading more |
hasMore |
boolean |
false |
Whether more documents available |
totalLoaded |
number |
- | Total documents currently loaded |
loadMoreDocuments |
() => Promise<void> |
- | Callback to load more |
autoLoadOnViewport |
boolean |
true |
Auto-load when 80% visible |
Display props
| Prop | Type | Default | Description |
|---|---|---|---|
showSpacesSelector |
boolean |
variant-based | Show space filter dropdown |
highlightDocumentIds |
string[] |
[] |
Document IDs to highlight |
highlightsVisible |
boolean |
true |
Whether highlights shown |
occludedRightPx |
number |
0 |
Pixels occluded on right |
Controlled state props
| Prop | Type | Description |
|---|---|---|
selectedSpace |
string |
Currently selected space (use "all" for all) |
onSpaceChange |
(spaceId: string) => void |
Callback when space changes |
memoryLimit |
number |
Max memories per document when space selected |
Data types
DocumentWithMemories
interface DocumentWithMemories {
id: string;
customId?: string | null;
title?: string | null;
content?: string | null;
summary?: string | null;
url?: string | null;
source?: string | null;
type?: string | null;
status: 'pending' | 'processing' | 'done' | 'failed';
metadata?: Record<string, string | number | boolean> | null;
createdAt: string | Date;
updatedAt: string | Date;
memoryEntries: MemoryEntry[];
}MemoryEntry
interface MemoryEntry {
id: string;
documentId: string;
content: string | null;
summary?: string | null;
title?: string | null;
type?: string | null;
metadata?: Record<string, string | number | boolean> | null;
createdAt: string | Date;
updatedAt: string | Date;
spaceContainerTag?: string | null;
relation?: 'updates' | 'extends' | 'derives' | null;
isLatest?: boolean;
spaceId?: string | null;
}Exports
Components
import {
MemoryGraph,
GraphCanvas,
Legend,
LoadingIndicator,
NodeDetailPanel,
SpacesDropdown
} from '@supermemory/memory-graph';Hooks
import { useGraphData, useGraphInteractions } from '@supermemory/memory-graph';Constants
import { colors, GRAPH_SETTINGS, LAYOUT_CONSTANTS } from '@supermemory/memory-graph';Performance
The graph handles hundreds of nodes efficiently through:
- Canvas-based rendering (not DOM elements)
- Viewport culling (only draws visible nodes)
- Level-of-detail optimization (simplifies when zoomed out)
- Change-based rendering (only redraws when state changes)
For very large datasets (1000+ documents), use pagination to load data in chunks.
Browser support
Works in all modern browsers supporting Canvas 2D API, ES2020, and CSS custom properties. Tested on Chrome, Firefox, Safari, and Edge.
Apps Docs Integrations Muse Code (`apps/docs/integrations/muse-code.mdx`)
title: "Muse Code" sidebarTitle: "Muse Code" description: "Persistent memory for Meta Muse Code with the muse-supermemory plugin." icon: "/images/muse-code-icon.svg"
muse-supermemory is a native Muse Code plugin that gives your agent persistent memory across sessions. Your agent remembers what you worked on — across sessions, across projects.
Install the plugin
Requires Node.js 18+ on your PATH — the memory hooks and MCP proxy run as Node scripts.
In a Muse session, open /plugins and install from the marketplace. Or from the CLI:
export MUSE_EXPERIMENTAL_PLUGINS=1
muse plugins marketplace add supermemory https://github.com/supermemoryai/muse-supermemory
muse plugins install supermemory@supermemory
muse plugins approve supermemoryapprove trusts the runtime capabilities (hooks + MCP). Without it, the plugin is installed but inert.
Local bundle (a clone of the repo):
export MUSE_EXPERIMENTAL_PLUGINS=1
muse plugins install . --scope user
muse plugins approve supermemoryAuthenticate
Muse runs hooks with a cleared environment, so an exported API key in your shell does not reach them. Auth is file-based.
The first session opens a browser login and writes ~/.supermemory-muse/credentials.json.
If you already logged in through the Claude Code plugin, that key is reused from ~/.supermemory-claude/credentials.json.
Or write the file yourself with a key from API Keys:
{ "apiKey": "sm_..." }Check the connection any time with /supermemory:status.
How it works
Two layers, same as the other coding plugins:
| Layer | When | What it does |
|---|---|---|
| Session profile | SessionStart |
Injects up to 5 persistent profile facts and 5 recent memories for this repo |
| Prompt recall | UserPromptSubmit |
Searches supermemory with the prompt, injects up to 5 new hits (similarity ≥ 0.55) |
| Auto-approve | PreToolUse |
Allows read-only supermemory MCP tools without a permission prompt |
| Auto-capture | Stop |
Saves new user prompts and assistant messages from Muse's session.jsonl |
| MCP tools | on demand | Search, save, list, and manage memories explicitly |
Recalled lines are marked ◪. When the model uses one, it keeps that mark and says "from supermemory" — never "from memory".
Slash commands and very short prompts skip recall. Timeouts fail open so a slow network never blocks the turn.
Memory scope
Container tags match Claude Code:
repo___<pro