Compare commits

...

29 commits

Author SHA1 Message Date
Mahesh Sanikommu
0f2b24b0f2
docs: full drift audit (2026-10-27) (#1855)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-28 23:34:56 -07:00
Mahesh Sanikommu
0da13138fa
fix(mcp): expose dreaming status and clarify document acceptance (#1844)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-10-28 12:20:29 -07:00
Muskan jain
ad443e155c
docs: add AGENTS.md for coding agents, imported from CLAUDE.md (#1836)
Co-authored-by: Muskan <muskan@Muskans-MacBook-Air.local>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:38:09 -07:00
Mahesh Sanikommu
225fbf0e0c
fix(deps): bump happy-dom to 21.0.4 and zod 4 copies to 4.6.7 (#1843)
Some checks failed
Publish Memory Graph / publish (push) Has been cancelled
Publish Tools / publish (push) Has been cancelled
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-26 13:16:46 -07:00
Muskan jain
9f55a37013
Docs: name the Mem0 dashboard in the migration guide without linking it (#1840)
Co-authored-by: Muskan <muskan@Muskans-MacBook-Air.local>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 12:53:44 -07:00
sreedharsreeram
f355dc8039 feat(mcp): validate API keys via /session (#1839)
## What

The Supermemory API now serves the session endpoint unversioned at `/session` (supermemoryai/mono#3626). `sm_` API-key validation in the MCP worker calls `/session` instead of `/v3/session`. One-line code change.

- `apps/mcp/src/server/auth/index.ts`: `fetchSession` requests `/session`.
- `apps/mcp/src/server/auth/index.test.ts`: asserts the new URL and that a refused key makes exactly one request.
- `apps/mcp/src/shared/types.ts`: comment updated.

## Deploy order

This worker depends on the API serving `/session`. **Deploy mono#3626 first.** Until then, production answers `/session` with 404 for full keys and 403 for scoped keys (the scope allowlist is checked before routing), and every `sm_` key would be rejected.

No fallback to `/v3/session` on purpose: the only "old server" in scope is that deploy window, and a failed-request fallback would blur 403/404 semantics for a case merge ordering already covers.

## Verification

- `bun run check-types`, `vitest run src` (29/29).
- Against the API with mono#3626 running: full key → `/session` 200 valid; scoped key → `/session` 200 valid; bad key → `/session` 401 rejected, single call.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-24 23:59:06 +00:00
sreedharsreeram
f3001517d1 docs: explain optional content in v5 document lists (#1830)
## Why

The document operations guide did not describe how to retrieve extracted text from a v5 document list, and the migration guide incorrectly said the list `include` object was for memories only.

## What changed

- Document `include.content`, its `false` default, and a cURL request for uploaded files in the same section pattern as the surrounding guide.
- Explain that list content is the last published extracted text, including the behavior before first publication and during failed replacement.
- Correct the v5 migration and reference overview copy.

```mermaid
flowchart LR
  A[Document operations guide] --> B[include.content request]
  B --> C[Published extracted text in list response]
```

## Validation

- Compared the guide and migration snippet with adjacent v5 docs for format and terminology.
- `git diff --check` — passed.
- `python3 -m json.tool apps/docs/docs.json` — passed.

Related API PR: https://github.com/supermemoryai/mono/pull/3606

The written docs should publish with or after the API change. The generated v5 reference reads the deployed OpenAPI source, so it updates when that source is available and the docs site refreshes.
2026-10-21 18:19:21 +00:00
Muskan jain
f81fad00e4
docs: permanent redirects for /self-hosting and /supermemory-mcp (#1821)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:37:33 -07:00
Mahesh Sanikommu
00f48d0378
fix(mcp): log expected auth rejections at warn and reserve error for auth outages (#1822)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-16 17:59:26 -07:00
Dhravya
d40b2f5a07 fix(mcp): share the accessible default with prompts and resources, prefer writable spaces
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-15 23:16:56 -07:00
Dhravya
5381882fb6 fix(mcp): only hand out IDs and spaces the caller can use
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-15 23:16:56 -07:00
MaheshtheDev
ccf0485869 fix(mcp): forget memories by ID instead of LLM matching (#1820)
forget_memory called the semantic forget-matching endpoint (two LLM calls plus a widening loop) to pick one memory, so most calls hit the 30s timeout since the v5 move. It now takes ids from search_memory and forgets them with the exact-ID endpoint (~0.5s on prod).

- Text-only calls forget only an exact text match; otherwise nothing is deleted and the closest memories come back with their IDs.

- Legacy add_memory action=forget keeps working through the same path.
2026-10-15 20:43:16 +00:00
sohamd22
23983bffae docs: align billing, analytics and legacy reference pages with the API (#1814)
Fixes billing, analytics, security and legacy reference pages.

- **Operations.** `rewriteQuery`, rerank/aggregate and forget-matching each bill one operation.
- **Profile metering.** v5 profile is documented as 1 search query per call.
- **Analytics:**
  - The errors response shape is fixed.
  - Usage `byKey` includes `tokensUsed`, and the log `output` example has the right shape.
  - The 90-day range limit doesn't exist.
- **Smaller fixes:**
  - Only 2 billing endpoints need an admin.
  - The `credits` response shape.
  - The 2FA gate is in the feature table.
  - The security page points to the one-call namespace delete for data erasure.
  - Container-tag merge takes several sources.
  - A stale "after API deploy" note is removed.

Checked against the current API and the published SDKs (TS 5.0.1, Python 5.0.0). Legacy v3/v4 docs are unchanged apart from factual fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-15 18:52:42 +00:00
sohamd22
acbb05f837 docs(self-hosting): align self-hosting docs with the server (#1813)
Fixes self-hosting docs to match the `supermemory-server` binary.

- **Ingest example.** `?dreaming=instant` was in the URL in the quickstart; it's now in the body.
- **Startup.** The banner example is accurate. The plaintext `env` file is legacy: it's moved into the encrypted `env.enc` and then deleted.
- **Providers:**
  - The provider order is OpenAI → Anthropic → Gemini → Vertex → Groq → Workers AI.
  - PDF extraction needs `GEMINI_API_KEY` (Vertex doesn't help).
  - URL ingestion uses Firecrawl and needs `FIRECRAWL_API_KEY`.
- **Smaller fixes:**
  - The SDK option is `baseUrl`.
  - Documented the environment switches the binary honors.
  - Embedding defaults are corrected.
  - Removed the claim that the hosted platform uses the same embedding stack.

Checked against the current API and the published SDKs (TS 5.0.1, Python 5.0.0). Legacy v3/v4 docs are unchanged apart from factual fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-15 18:52:41 +00:00
sohamd22
2a546ff654 docs(connectors): align connector docs with the v5 API (#1812)
Fixes connector docs.

- **`authorization.url` examples.** They now show the URL the API actually returns (`/v3/connections/auth/redirect?state=…`), not provider URLs.
- **Broken examples:**
  - TS `connectors.create` examples were missing commas.
  - Python dicts now use the SDK's snake_case keys.
  - `connector.createdAt` → `connector.system.createdAt`.
  - The list default `limit` is 10.
- **Pending connectors.** They are visible through get (`pending_authorization`), just not in list.
- **Gmail:**
  - It needs the Max plan.
  - Nothing syncs until labels are picked.
  - The watch covers the selected labels, not just INBOX.
  - `from` filters use `contains`.
- **Smaller fixes:**
  - The OAuth redirect-URI troubleshooting.
  - Notion property metadata keys.
  - Picker error codes.

Checked against the current API and the published SDKs (TS 5.0.1, Python 5.0.0). Legacy v3/v4 docs are unchanged apart from factual fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-15 18:52:41 +00:00
sohamd22
2197c04ac1 docs(api): fix namespace move, scoped keys and SDK call shape in v5 docs (#1811)
Fixes namespace, auth and SDK-shape docs.

- **Namespace move example deleted instead.** It sent `moveTo` in a JSON body on `DELETE /ns/{namespace}`. The route only reads `moveTo` from the query string and ignores the body, so following the docs permanently deleted the namespace. It now uses `?moveTo=`, and the overview warns about this.
- **SDK call shape.** URL values are positional (`namespace` first), followed by one options object; the docs described one object with `body`. Python 5.0.0 is the v5 client, so "no v5 Python SDK" is gone.
- **Scoped keys** (`authentication.mdx`):
  - The allowed-endpoints list now includes the `/ns/*` routes; all legacy routes are kept.
  - `containerTags` (several namespaces) is documented.
  - Namespace names don't allow dots.
- **Key permissions.** Read/write applies to the whole key; only restricted members have per-namespace permissions.
- **Smaller fixes:**
  - The move response has no `success` field.
  - `namespaceCount` only matches the namespace list for an unrestricted key.
  - `setup --json` does nothing.
  - The agent prompt's ban on `memories.*` now covers only the calls v5 removed.

Checked against the current API and the published SDKs (TS 5.0.1, Python 5.0.0). Legacy v3/v4 docs are unchanged apart from factual fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-15 18:52:41 +00:00
sohamd22
4347970d16 docs(api): align v5 ingestion and document operations docs with the API (#1810)
Fixes v5 ingestion and document-operation docs.

- **Query params return `400`.** `dreaming`, `fileType` and `mimeType` were documented as query params; they are JSON body or multipart form fields.
- **Errors.** Out-of-balance quota is `402`, not `429`; `429` is only rate limiting. Added the `409` returned during a namespace merge.
- **Statuses.** There is no `processing` status. The real ones are queued, extracting, chunking, embedding, done and failed; treat anything else as in progress.
- **`group` on updates.** `group` is ingestion-side: it picks which existing memories serve as context while content is processed. Changing it only takes effect on re-ingestion, so it's sent with `content`/`file`. The "group-only PATCH" claims are removed from the migration doc.
- **Lists, deletes and forget:**
  - Valid `sort` values per type, a `limit` max of 100, and `include.forgotten`.
  - Deletes skip documents that are still processing.
  - Forget-matching has a 100-match cap and a 2000-character `query` limit.
- **Failed documents stay `failed`.** The docs said they're deleted after 2 minutes. They actually stay `failed`, and resending the same `id` reprocesses them.
- **Smaller fixes:**
  - Metadata allows `string[]`.
  - The batch response field is `count`.
  - `audio` is a valid `fileType`.
  - Chunk overlap is 200 characters.
  - URL extraction no longer claims Markdowner.
  - Search examples no longer index the response object as an array.

Checked against the current API and the published SDKs (TS 5.0.1, Python 5.0.0). Legacy v3/v4 docs are unchanged apart from factual fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-15 18:52:40 +00:00
sohamd22
8452a00434 docs(api): keep the default preferences bucket and drop v4 profile labels in v5 docs (#1809)
Fixes v5 profile and bucket docs.

- **The default `preferences` bucket can't be replaced.** It's always part of the effective set. Custom buckets are added alongside it, `deleteBuckets` can't remove it, and defining a bucket named `preferences` only overrides its description.
- **No `[Summary]`/`[Recent]` labels in v5.** Only v4 adds them. Summaries are now told apart by an `aggregated_` id, and individual `dynamic` entries keep their `[YYYY-MM-DD]` date. Example responses are updated, and the migration snippet calls out the change.
- **Name clashes.** A namespace bucket with an org bucket's name returns `409`; the docs said the org's definition silently wins.
- **Smaller fixes:**
  - `filterPrompt` → `organizationalContext`.
  - An invalid `organization.update` example (stray `},`).
  - OAuth redirect URIs now use `/v3/connections/auth/callback/{provider}`.
  - The v4 `include` example in the migration snippet.
  - Broken anchors and links.

Checked against the current API and the published SDKs (TS 5.0.1, Python 5.0.0). Legacy v3/v4 docs are unchanged apart from factual fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-15 18:52:40 +00:00
sohamd22
ca89d161cc docs(api): move v5 search and ingest options into the request body (#1808)
Fixes v5 search and recall docs that didn't match the API.

- **Options in the URL.** Search examples passed `searchMode`/`limit` as query params, and ingest examples passed `dreaming`/`taskType` the same way. v5 rejects every query param on these routes with `400`. They now go in the JSON body (quickstart, rules, super-rag, recall/search).
- **`include`, not `attach`.** Renamed the search option. Its `forgotten` flag also admits forgotten memories into the main results, not just related context.
- **Search response.** Added the required `isInference`. `metadata` is always an object, and `system.createdAt` is optional.
- **Search add-ons are billed.** `rewriteQuery` and `rerank` each bill one extra operation; the docs said "no extra cost".
- **Removed claims that don't match current behavior:**
  - inferred memories being down-weighted in search;
  - AST code chunking via `code-chunk` (super-rag now describes how chunking actually works);
  - a 64-character metadata key limit.
- **Smaller fixes:**
  - The namespace is the SDK's first argument; the docs called it a top-level key.
  - "No v5 Python SDK" was stale.
  - Memory review: `total` semantics, the internal review stamp, and the missing 403.

Checked against the current API and the published SDKs (TS 5.0.1, Python 5.0.0). Legacy v3/v4 docs are unchanged apart from factual fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-15 18:52:40 +00:00
MaheshtheDev
5323f427d0 fix(python): community fixes for the openai, cartesia and pipecat wrappers (#1799)
Some checks failed
Publish Pipecat SDK Python / publish (push) Has been cancelled
Publish Cartesia SDK Python / publish (push) Has been cancelled
Publish OpenAI SDK Python / publish (push) Has been cancelled
Bundles 10 community fixes for the Python wrappers, each commit kept with its original author. Ships supermemory-openai-sdk 1.0.10, supermemory-cartesia 0.1.5 and supermemory-pipecat 0.1.5.

- openai-sdk-python: shared OpenAI clients no longer leak memories across tenants (#1503), memory search fails open instead of crashing the chat (#1288), the requests fallback runs off the event loop (#1660), get_profile works for users with no memories (#1759).
- cartesia: the wrapped agent runs exactly once per event (#1280, #1670), plain-string and chunk-only search results are kept (#1683, #1693).
- pipecat: memory text stays literal when replacing the injected block (#1647), `InputParams` is exported from the package root (#1575).
- #1503 was hand-ported onto main's newer middleware. #1751 is left out: the v5 agent-framework migration (#1786) appends to the conversation document by design.

Co-Authored-By: abhinav7x94 <204053250+abhinav7x94@users.noreply.github.com>
Co-Authored-By: shamAnimates <145093437+shamAnimates@users.noreply.github.com>
Co-Authored-By: aloktripathi1 <142301030+aloktripathi1@users.noreply.github.com>
Co-Authored-By: Cintu07 <178455858+Cintu07@users.noreply.github.com>
Co-Authored-By: Atharvsinh-codez <201381760+Atharvsinh-codez@users.noreply.github.com>
Co-Authored-By: abhay-codes07 <182421137+abhay-codes07@users.noreply.github.com>
Co-Authored-By: nkootstra <545768+nkootstra@users.noreply.github.com>
Co-Authored-By: Agnik47 <140933190+Agnik47@users.noreply.github.com>
2026-10-14 21:08:38 +00:00
Dhravya Shah
669d2697a9
fix(ci): remove retired AI SDK publishing workflow (#1777) 2026-10-14 14:04:27 -07:00
Mahesh Sanikommu
92f511df9b
chore: remove the Raycast extension (#1818) 2026-10-14 14:03:45 -07:00
rishibalanagu0108
b7534f9c50
fix(ui): remove unused dependencies (#1815) 2026-10-14 13:58:42 -07:00
MaheshtheDev
d42e71f8bb fix(docs-test): Windows venvs and failing TypeScript examples (#1804)
Community fixes for the docs-test runner.

- Python examples run on Windows: the runner picks `.venv/Scripts/python.exe` and reports a missing venv as a failed test (#1487).
- TypeScript examples exit 1 when `main()` rejects, so broken examples no longer count as passed (#1495).

Co-Authored-By: abhinav7x94 <204053250+abhinav7x94@users.noreply.github.com>
2026-10-13 19:59:35 +00:00
MaheshtheDev
87af03a55f docs: community fixes for self-hosting, contributing and the sidebar (#1802)
Community docs fixes, checked against the self-hosted server source.

- CONTRIBUTING.md: setup steps and project structure match the repo (#1745).
- README: link the self-hosting quickstart (#1713).
- Self-hosting: database snapshot behavior (#1687) and embedding env vars, local model dimensions and the v0.0.6 upgrade lock (#1659), trimmed to what the code does.
- Docs sidebar: hover and selected state CSS from #1784, without the duplicated bold icon set.
- `SUPERMEMORY_DATA_DIR` now defaults to `~/.supermemory` (server-v0.0.9).

Co-Authored-By: Agnik47 <140933190+Agnik47@users.noreply.github.com>
Co-Authored-By: Fullstop000 <fullstop1005@gmail.com>
Co-Authored-By: Adityakk9031 <143548997+Adityakk9031@users.noreply.github.com>
Co-Authored-By: parthivdzns <parthiv.des@gmail.com>
2026-10-13 04:41:23 +00:00
Dhravya Shah
575ee2eaf5
docs: add Docker installation and safe upgrade instructions (#1801)
Some checks failed
Publish Tools / publish (push) Has been cancelled
2026-10-12 16:54:58 -07:00
Mahesh Sanikommu
570bc1bf47
fix(tools): community fixes for 3.1.2 (#1798)
Co-authored-by: abhinav7x94 <raj.abhinav7x94@gmail.com>
Co-authored-by: Rohit <71192000+rohitsux@users.noreply.github.com>
Co-authored-by: Aniruddha Adak <aniruddhaadak80@users.noreply.github.com>
Co-authored-by: Aditya kumar singh <143548997+Adityakk9031@users.noreply.github.com>
Co-authored-by: Rikinshah787 <Rikinshah787@users.noreply.github.com>
Co-authored-by: Agnik47 <140933190+Agnik47@users.noreply.github.com>
2026-10-12 16:23:11 -07:00
sohamd22
6e5ec07819 docs(api): document v5 system.dreamingStatus (#1797)
Docs for supermemoryai/mono#3519: v5 document reads and list items now return `system.dreamingStatus` (`"dreaming" | "done"`), the signal for when a document's memories are ready, separate from `system.status`.

- Migration guide (content-management snippet): the field, why it differs from `status`, and the move from v3's top-level `dreamingStatus`.
- Document operations: a "Memory generation status" section, plus the field in the get, list, and processing-queue examples.
- One-line pointers in the SDK mapping table, batch ingest, and quickstart.

Draft until mono#3519 is deployed and the v5 SDK is regenerated: `supermemory@5.0.1` doesn't type `system.dreamingStatus` yet, so the new TS examples won't compile before then. The endpoint reference pages come from the live v5 spec and update on their own.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-12 21:56:52 +00:00
Mahesh Sanikommu
8c70ffdc8b
fix(tools): save AI SDK 7 images and keep provider peer optional (3.1.1) (#1796) 2026-10-12 13:39:50 -07:00
177 changed files with 4964 additions and 5648 deletions

View file

@ -1,102 +0,0 @@
name: Publish AI SDK
on:
push:
branches:
- main
paths:
- "packages/ai-sdk/package.json"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
defaults:
run:
working-directory: ./packages/ai-sdk
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- name: Upgrade npm for trusted publishing support
run: npm install -g npm@latest
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
working-directory: .
run: bun install --frozen-lockfile
- name: Check if version changed
id: version-check
run: |
PACKAGE_NAME=$(jq -r '.name' package.json)
LOCAL_VERSION=$(jq -r '.version' package.json)
if npm view "$PACKAGE_NAME@$LOCAL_VERSION" version >/dev/null 2>&1; then
echo "Version $LOCAL_VERSION already published, skipping."
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "Publishing $LOCAL_VERSION."
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Wait for the Tools dependency
if: steps.version-check.outputs.changed == 'true'
run: |
TOOLS_SPEC=$(jq -r '.dependencies["@supermemory/tools"]' package.json)
TOOLS_VERSION=${TOOLS_SPEC#^}
TOOLS_VERSION=${TOOLS_VERSION#~}
if [[ ! "$TOOLS_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]; then
echo "Unsupported @supermemory/tools dependency spec: $TOOLS_SPEC" >&2
exit 1
fi
for attempt in {1..20}; do
PUBLISHED_VERSION=$(npm view "@supermemory/tools@$TOOLS_VERSION" version 2>/dev/null || true)
if [ "$PUBLISHED_VERSION" = "$TOOLS_VERSION" ]; then
echo "@supermemory/tools@$TOOLS_VERSION is available on npm."
exit 0
fi
echo "Waiting for @supermemory/tools@$TOOLS_VERSION (attempt $attempt/20)."
sleep 15
done
echo "@supermemory/tools@$TOOLS_VERSION was not published within five minutes." >&2
exit 1
- name: Build Tools dependency
if: steps.version-check.outputs.changed == 'true'
run: bun run --cwd ../tools build
- name: Build AI SDK package
if: steps.version-check.outputs.changed == 'true'
run: bun run build
- name: Verify packed artifact
if: steps.version-check.outputs.changed == 'true'
run: |
npm pack --dry-run --json > "$RUNNER_TEMP/ai-sdk-pack.json"
jq -e '
(.[0].files | any(.path == "dist/index.js")) and
(.[0].files | any(.path == "dist/index.d.ts"))
' "$RUNNER_TEMP/ai-sdk-pack.json" >/dev/null
- name: Publish
if: steps.version-check.outputs.changed == 'true'
run: npm publish --access public --provenance

251
AGENTS.md Normal file
View file

@ -0,0 +1,251 @@
# AGENTS.md
Context for AI coding agents (Claude Code, Cursor, Codex, OpenCode, Copilot and others) working with supermemory.
**supermemory** is a memory engine for AI agents: a memory graph that updates and forgets facts, user profiles, and
retrieval over your documents, through one API.
[Documentation](https://supermemory.ai/docs) · [Docs index for agents](https://supermemory.ai/docs/llms.txt) ·
[Console and API keys](https://console.supermemory.ai)
Two jobs, two sections:
- **Adding supermemory to another project?** Read [Use supermemory in an app](#use-supermemory-in-an-app).
- **Changing code in this repository?** Read [Work in this repository](#work-in-this-repository).
## Use supermemory in an app
### Start here
- Check the [docs index](https://supermemory.ai/docs/llms.txt) or the docs MCP (below) before writing any endpoint.
- `npx supermemory setup --prompt` prints the integration prompt for the current project. Plain `npx supermemory setup`
launches a separate coding agent, so use `--prompt` when you are already an agent.
- `npx supermemory help --json` lists every CLI command in a machine-readable form.
- The official skill teaches the current (v5) API surface:
```bash
npx skills add https://github.com/supermemoryai/skills --skill supermemory
```
### Install and first calls
Set `SUPERMEMORY_API_KEY` (create a key in the [console](https://console.supermemory.ai)), then install SDK version 5
or later:
```bash
npm i supermemory # TypeScript / JavaScript
pip install -U supermemory # Python (3.10 or later)
```
Every memory call (add, search, profile, list, documents) takes a **namespace** first: one user, workspace or tenant.
```typescript
import { Supermemory } from "supermemory";
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY });
await supermemory.add("user_123", {
content: "User prefers TypeScript",
dreaming: "instant", // processed right away (one extra operation per document); the default "dynamic" batches extraction
});
const { results } = await supermemory.search("user_123", { query: "language preference" });
const { profile } = await supermemory.profile("user_123");
```
```python
import os
from supermemory import Supermemory
client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY"))
client.add("user_123", content="User prefers TypeScript", dreaming="instant")
response = client.search("user_123", query="language preference")
profile = client.profile("user_123").profile
```
### API rules
- **Auth:** `Authorization: Bearer $SUPERMEMORY_API_KEY` is the only supported header.
- **Write:** `POST https://api.supermemory.ai/ns/{namespace}/document`
- **Search:** `POST https://api.supermemory.ai/ns/{namespace}/search`
- **Profile:** `POST https://api.supermemory.ai/ns/{namespace}/profile`
- **Scoping:** every write and every search names exactly one namespace, in the URL path. Never in the body or a
header.
- **Search modes:** `"chunks"` for RAG, `"memories"` for extracted memories, `"hybrid"` (default) for both. The option is
`searchMode` in the API and TypeScript, `search_mode` in Python.
- **Readiness:** processing is async. Wait until `system.status` is `done` before searching chunks, and until
`system.dreamingStatus` is `done` before reading memories or the profile. These are the API and TypeScript field
names.
### Do NOT use
These are deprecated, undocumented or made up:
- **Endpoints:** `/v1/...`, `/v3/documents`, `/v3/search`, `/v4/search`, `/v4/profile`, `/v4/memories`
- **Headers:** `x-supermemory-api-key`, `x-api-key`, `x-sm-user-id`
- **Body keys:** `containerTag`, `containerTags`, `customId` (use `id`), `entityContext` (use `supportingContext`),
`filterByMetadata` (use `group`), `documentDate` (use `date`), `q` (use `query`), `filters` (use `filter`), `userId`,
`spaces`. One exception: scoped-key creation (`POST /v3/auth/scoped-key`) still takes `containerTag` or
`containerTags` ([API keys & auth](https://supermemory.ai/docs/authentication)).
- **SDK calls:** `client.add({ containerTag })`, `client.search.execute`, `client.memories.add`,
`client.memories.updateMemory`, `client.profile({ q })`
Code on v3/v4 moves with `npx supermemory@latest migrate --prompt`, which prints the migration prompt for you to follow
([migration guide](https://supermemory.ai/docs/migration/api-v5)).
### Docs MCP
Searches these docs while you code. Server: `https://supermemory.ai/docs/mcp`
```bash
claude mcp add --transport http supermemory-docs https://supermemory.ai/docs/mcp # Claude Code
codex mcp add supermemory-docs --url https://supermemory.ai/docs/mcp # Codex
```
Cursor (`~/.cursor/mcp.json`) and most other clients:
```json
{ "mcpServers": { "supermemory-docs": { "url": "https://supermemory.ai/docs/mcp" } } }
```
This is not the memory MCP. The docs MCP only searches documentation.
### Memory for the coding agent itself
One command installs the supermemory plugin for every coding agent it finds on this machine (Claude Code, Cursor,
OpenCode and Codex), then signs in through the browser:
```bash
npx supermemory plugin --all
```
For one agent or a few, name them with `--only` (`claude`, `cursor`, `opencode`, `codex`):
```bash
npx supermemory plugin --only claude
```
- Plain `npx supermemory plugin` asks which agents to install for.
- `--dry-run` shows what would run without installing anything.
- `--no-auth` skips the browser sign-in; run `npx supermemory plugin login` later.
To install a single plugin by hand, or for an agent the CLI doesn't cover (Hermes, OpenClaw, any MCP client):
| Agent | Install | Guide |
| --- | --- | --- |
| Claude Code | `/plugin marketplace add supermemoryai/claude-supermemory`, then `/plugin install supermemory` | [docs](https://supermemory.ai/docs/integrations/claude-code) |
| Cursor | `/add-plugin cursor-supermemory`, or Supermemory in the Cursor Marketplace | [docs](https://supermemory.ai/docs/integrations/cursor) |
| Codex | `npx codex-supermemory@latest install`, then restart Codex CLI | [docs](https://supermemory.ai/docs/integrations/codex) |
| OpenCode | `bunx opencode-supermemory@latest install --no-tui` | [docs](https://supermemory.ai/docs/integrations/opencode) |
| Hermes | `hermes plugins install supermemoryai/hermes-supermemory`, `hermes plugins enable supermemory`, then `hermes memory setup` | [repo](https://github.com/supermemoryai/hermes-supermemory) |
| OpenClaw | `openclaw plugins install @supermemory/openclaw-supermemory` | [docs](https://supermemory.ai/docs/integrations/openclaw) |
| Any MCP client | Remote server `https://mcp.supermemory.ai/mcp` (OAuth sign-in, no API key) | [docs](https://supermemory.ai/docs/supermemory-mcp/mcp) |
Each plugin handles its own sign-in (a browser login or an API key, not always `SUPERMEMORY_API_KEY`), so follow its
guide.
Framework guides (Vercel AI SDK, OpenAI SDK, OpenAI Agents SDK, LangChain, LangGraph, Mastra, CrewAI, Agno, Microsoft
Agent Framework, VoltAgent, Convex, Pipecat, LiveKit and more) are listed in the
[docs index](https://supermemory.ai/docs/llms.txt).
### Self-hosted
`npx supermemory local` runs a local server, at `http://localhost:6767` by default. Point the SDK at it with
`baseUrl: "http://localhost:6767"` (TypeScript) or `base_url="http://localhost:6767"` (Python). The v5 SDKs need
`supermemory-server` v0.0.9 or later. [Guide](https://supermemory.ai/docs/self-hosting/overview)
## Work in this repository
A Bun + Turbo monorepo. The `supermemory` SDKs on npm and PyPI are built in separate repositories, not here.
### Requirements
- Bun 1.3.6 (the `packageManager` field). Use Bun only; the lockfile is `bun.lock`.
- Node 20 or later.
- Python 3.10 or later for the `packages/*-python` packages and 3.11 or later for `apps/sdk-playground`, plus `uv` for
`packages/openai-sdk-python` and `apps/sdk-playground`.
### Do NOT
- Use npm, yarn or pnpm in this repo.
- Bump versions in `packages/tools/package.json`, `packages/memory-graph/package.json` or
`packages/*-python/pyproject.toml` unless asked. The `publish-*.yml` workflows publish when those change on `main`.
- Edit the workflows in `.github/workflows/` unless asked.
- Migrate the Python packages to the v5 SDK unless asked. `openai-sdk-python`, `pipecat-sdk-python` and
`cartesia-sdk-python` pin `supermemory<5` (the 3.x SDK) on purpose, and `livekit-sdk-python` still makes uncapped 3.x
SDK calls; all of them use `container_tag`. `agent-framework-python` 2.0 is already on v5 (`supermemory>=5.0.0,<6`)
and passes its `container_tag` as the namespace.
- Commit `.env` files, API keys or credentials.
### Repository map
| Path | What it is |
| --- | --- |
| `apps/web/` | Redirects to the console (needs no env) |
| `apps/mcp/` | The MCP server (workspace `supermemory-mcp`; not the npm package of that name). Hosted at `https://mcp.supermemory.ai/mcp` |
| `apps/docs/` | Documentation site |
| `apps/memory-graph-playground/` | Demo app for the memory graph component |
| `apps/sdk-playground/` | Playground for the SDK integrations |
| `packages/tools/` | `@supermemory/tools` (3.x, v5 API): memory tools and middleware for the AI SDK, OpenAI, Mastra, VoltAgent and Claude's memory tool |
| `packages/memory-graph/` | `@supermemory/memory-graph`: interactive memory graph component |
| `packages/openai-sdk-python/` | `supermemory-openai-sdk`: memory tools for OpenAI function calling |
| `packages/agent-framework-python/` | `supermemory-agent-framework`: Microsoft Agent Framework integration |
| `packages/pipecat-sdk-python/` | `supermemory-pipecat`: Pipecat integration |
| `packages/cartesia-sdk-python/` | `supermemory-cartesia`: Cartesia Line integration |
| `packages/livekit-sdk-python/` | `supermemory-livekit` (not on PyPI yet): LiveKit Agents integration |
| `packages/ui/` | Shared UI components |
| `packages/lib/` | Shared utilities and logic |
| `packages/hooks/` | Shared React hooks |
| `packages/validation/` | Zod schemas and validation |
| `packages/docs-test/` | Runs the code samples from the docs |
| `skills/` | Older copy of the supermemory skill, still on the v3/v4 API. The current skill is [supermemoryai/skills](https://github.com/supermemoryai/skills) |
`CLAUDE.md` in this repo predates the v5 API. Where it differs from this file, follow this file.
### Setup and commands
```bash
bun install
bun run dev:local # every app on localhost: web 3000, mcp 8788, docs 3003, graph playground 3004, sdk playground 3005
bun run check-types # TypeScript across the monorepo
bunx turbo run build # build everything
```
Use `dev:local`, not `bun run dev`: `dev` runs through portless, which binds port 443 and trusts a local CA. `dev:local`
also needs Python and `uv` (the sdk playground runs `uv run server.py`) and network access (the docs run
Mintlify). Use `bunx turbo run build` rather than `bun run build`: the root `postbuild` step uploads Sentry sourcemaps.
Checks that CI runs:
```bash
bun install --frozen-lockfile # fails if bun.lock is out of date
bunx biome ci --changed --since=origin/main --no-errors-on-unmatched # lint and format, changed files only
bun run --cwd packages/tools test:unit
bun run --cwd packages/tools build
bun run --cwd packages/tools test:compat
bun run --cwd apps/sdk-playground check-types:app
bun run --cwd apps/sdk-playground build:app
(cd apps/sdk-playground/python && uv sync --locked --python 3.12 \
&& .venv/bin/python -m py_compile server.py && .venv/bin/python -c "import server")
bun run --cwd packages/memory-graph check-types
bun run --cwd packages/memory-graph test
```
The tools steps run when `packages/tools` changes and the sdk-playground steps when `apps/sdk-playground` changes (the
tools build runs for either); memory-graph and Biome run on every pull request.
`bun run format-lint` rewrites files across the whole repo with Biome, so prefer the changed-files check above.
Python packages: CI builds each package's wheel, installs it with a pinned `supermemory` version and runs its pytest
suite from the package folder (`packages/openai-sdk-python` uses `uv` and its `uv.lock`).
### Before a pull request
- Branch from `main` (`feature/...` or `fix/...`) and keep it up to date with `main`.
- Run the CI checks above for the areas you touched, plus `bun run check-types`.
- Add tests if applicable, and update the docs if needed.
- CONTRIBUTING.md suggests `bun run format-lint` and `bun run build`; use the changed-files Biome check and
`bunx turbo run build` above instead.
- If CI fails on a branch in this repo, the Auto Fix CI Failures workflow (`claude-auto-fix-ci.yml`) can push fixes to
it, so pull before you push again.
- More in [CONTRIBUTING.md](CONTRIBUTING.md).

View file

@ -1,3 +1,5 @@
@AGENTS.md
# CLAUDE.md # CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

View file

@ -26,25 +26,17 @@ Before you begin, ensure you have the following installed:
bun install bun install
``` ```
3. **Set Up Environment Variables** 3. **Start the Development Server**
```bash
# Copy the example environment file
cp .env.example .env.local
# Edit the file with your configuration
# You'll need to add your API keys and database URLs
```
4. **Start the Development Server**
```bash ```bash
bun run dev:local bun run dev:local
``` ```
This starts each app on plain `localhost` ports (web on 3000, mcp on 8788, docs on 3003, graph on 3004) and points the web app at the public API at `https://api.supermemory.ai` via `NEXT_PUBLIC_BACKEND_URL` in `.env.example`. Sign in with magic-link/email-OTP — Google/GitHub OAuth sign-in won't round-trip back to localhost. This starts each app on plain `localhost` ports (web on 3000, mcp on 8788, docs on 3003, graph playground on 3004, sdk playground on 3005).
> **Internal team note:** `bun run dev` (without `:local`) routes through [portless](https://github.com/portless/portless) to give every app a stable `*.dev.supermemory.ai` HTTPS URL with shared cookies and a working OAuth proxy. That path requires `bun run setup:dev` once (binds port 443, trusts a local CA). OSS contributors don't need it. > **Note:** `apps/web` only redirects to the console at `https://console.supermemory.ai`, so it needs no env setup.
> **Internal team note:** `bun run dev` (without `:local`) routes through [portless](https://github.com/portless/portless) to give every app a stable `*.dev.supermemory.ai` HTTPS URL with shared cookies and a working OAuth proxy. That path needs portless set up once (it binds port 443 and trusts a local CA). OSS contributors don't need it.
## 📁 Project Structure ## 📁 Project Structure
@ -53,21 +45,25 @@ supermemory is organized as a monorepo using Turbo:
``` ```
supermemory/ supermemory/
├── apps/ ├── apps/
│ ├── web/ # Next.js web application │ ├── web/ # Redirects to the console
│ ├── browser-extension/ # Browser extension (WXT-based) │ ├── mcp/ # Model Context Protocol server
│ ├── docs/ # Documentation site │ ├── docs/ # Documentation site
│ └── raycast-extension/ # Raycast extension │ ├── memory-graph-playground/ # Demo app for the memory graph component
│ └── sdk-playground/ # Playground for the SDK integrations
├── packages/ ├── packages/
│ ├── tools/ # Memory tools for AI SDK, OpenAI, Voltagent and Mastra
│ ├── memory-graph/ # Interactive memory graph component
│ ├── openai-sdk-python/ # Memory tools for OpenAI function calling (Python)
│ ├── agent-framework-python/ # Microsoft Agent Framework integration (Python)
│ ├── pipecat-sdk-python/ # Pipecat integration (Python)
│ ├── cartesia-sdk-python/ # Cartesia Line integration (Python)
│ ├── livekit-sdk-python/ # LiveKit Agents integration (Python)
│ ├── ui/ # Shared UI components │ ├── ui/ # Shared UI components
│ ├── lib/ # Shared utilities and logic │ ├── lib/ # Shared utilities and logic
│ ├── hooks/ # Shared React hooks │ ├── hooks/ # Shared React hooks
│ ├── validation/ # Zod schemas and validation │ ├── validation/ # Zod schemas and validation
│ ├── ai-sdk/ # AI SDK for memory operations │ └── docs-test/ # Runs the code samples from the docs
│ ├── tools/ # Development tools and utilities ├── skills/ # Supermemory agent skill
│ ├── openai-sdk-python/ # Python SDK for OpenAI integration
│ ├── openai-sdk-ts/ # TypeScript SDK for OpenAI integration
│ ├── eslint-config/ # ESLint configurations
│ └── typescript-config/ # TypeScript configurations
├── turbo.json # Turbo configuration ├── turbo.json # Turbo configuration
├── biome.json # Biome configuration ├── biome.json # Biome configuration
└── package.json # Root package configuration └── package.json # Root package configuration
@ -101,7 +97,7 @@ bun run build
### Tech Stack ### Tech Stack
- **Frontend**: Next.js 15, React 19, TypeScript - **Frontend**: Next.js 16, React 19, TypeScript
- **Styling**: Tailwind CSS, Radix UI components - **Styling**: Tailwind CSS, Radix UI components
- **State Management**: Zustand, TanStack Query - **State Management**: Zustand, TanStack Query
- **Build Tool**: Turbo (monorepo) - **Build Tool**: Turbo (monorepo)

View file

@ -337,10 +337,28 @@ const client = new Supermemory({
- **Bring any model** — OpenAI, Anthropic, Gemini, Groq, or any OpenAI-compatible endpoint. An interactive wizard walks you through it on first boot. - **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.5` by default (no API key); optionally OpenAI, Gemini, or Ollama. Same provider stack as cloud. - **Embeddings** — local `Xenova/bge-base-en-v1.5` by 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:20b` works great), disable telemetry with `SUPERMEMORY_DISABLE_TELEMETRY=1`, and use local text or file inputs after the first embedding-model download. URL ingestion uses a hosted reader service. - **Offline text memories if you want** — point it at Ollama (`gpt-oss:20b` works great), disable telemetry with `SUPERMEMORY_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. - **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`. - **Same API as the platform** — prototype locally, ship on the hosted platform by changing `baseURL`.
Read the [self-hosting docs](https://supermemory.ai/docs/self-hosting/overview) — quickstart, [configuration](https://supermemory.ai/docs/self-hosting/configuration), [embeddings](https://supermemory.ai/docs/self-hosting/embeddings), and [local vs. Enterprise](https://supermemory.ai/docs/self-hosting/local-vs-enterprise). ### 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:
```bash
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 180
```
The 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](apps/docs/self-hosting/docker.mdx) 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](https://supermemory.ai/docs/self-hosting/overview) — [quickstart](https://supermemory.ai/docs/self-hosting/quickstart), [configuration](https://supermemory.ai/docs/self-hosting/configuration), [embeddings](https://supermemory.ai/docs/self-hosting/embeddings), and [local vs. Enterprise](https://supermemory.ai/docs/self-hosting/local-vs-enterprise).
--- ---

View file

@ -28,7 +28,6 @@ Useful for coding agents:
```bash ```bash
npx supermemory setup # detect project, launch/print integration flow npx supermemory setup # detect project, launch/print integration flow
npx supermemory setup --prompt # print integration prompt only npx supermemory setup --prompt # print integration prompt only
npx supermemory setup --json # machine-readable output
npx supermemory help --json # agent-readable command catalog npx supermemory help --json # agent-readable command catalog
npx supermemory help --all npx supermemory help --all
npx supermemory migrate # move a v3/v4 project to v5 with your agent npx supermemory migrate # move a v3/v4 project to v5 with your agent
@ -210,7 +209,7 @@ DO NOT USE — deprecated, undocumented, or fabricated:
- Endpoints: /v1/anything, /v3/documents, /v3/search, /v4/search, /v4/profile, /v4/memories (use /ns/{namespace}/...) - Endpoints: /v1/anything, /v3/documents, /v3/search, /v4/search, /v4/profile, /v4/memories (use /ns/{namespace}/...)
- Headers: x-supermemory-api-key, x-api-key, x-sm-user-id (for API auth) - Headers: x-supermemory-api-key, x-api-key, x-sm-user-id (for API auth)
- Body keys: containerTag, containerTags, customId (use `id`), entityContext (use `supportingContext`), filterByMetadata (use `group`), documentDate (use `date`), q (use `query`), filters (use `filter`), userId, spaces - Body keys: containerTag, containerTags, customId (use `id`), entityContext (use `supportingContext`), filterByMetadata (use `group`), documentDate (use `date`), q (use `query`), filters (use `filter`), userId, spaces
- SDK: `client.add({ containerTag })`, `client.search.execute`, `client.memories.*`, `client.profile({ q })` - SDK: `client.add({ containerTag })`, `client.search.execute`, `client.memories.add` / `client.memories.updateMemory`, `client.profile({ q })`
SCOPING IS LOAD-BEARING. Every write and every search MUST name one namespace in the path. SCOPING IS LOAD-BEARING. Every write and every search MUST name one namespace in the path.

View file

@ -13,10 +13,11 @@ v5 calls these **namespaces**. Same values. New integrations should use [v5 name
| Endpoint | Use when | | Endpoint | Use when |
| --- | --- | | --- | --- |
| `GET /v3/container-tags/list` | List every container tag |
| `GET /v3/container-tags/{containerTag}` | Read tag settings | | `GET /v3/container-tags/{containerTag}` | Read tag settings |
| `PATCH /v3/container-tags/{containerTag}` | Update tag settings | | `PATCH /v3/container-tags/{containerTag}` | Update tag settings |
| `DELETE /v3/container-tags/{containerTag}` | Delete a container and its data | | `DELETE /v3/container-tags/{containerTag}` | Delete a container and its data |
| `POST /v3/container-tags/merge` | Merge one tag into another | | `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 | | `GET /v3/container-tags/merge/{mergeId}` | Poll merge status |
**Guide:** [Container tags](/concepts/container-tags) · [Filtering](/concepts/filtering) **Guide:** [Container tags](/concepts/container-tags) · [Filtering](/concepts/filtering)

View file

@ -48,7 +48,7 @@ Same `containerTag` scopes ingest, search, and profiles — one engine, multiple
## Suggested order ## Suggested order
1. **Ingest** — `POST /v3/documents` (SDK: `client.add`) 1. **Ingest** — `POST /v3/documents` (SDK: `client.add`)
2. **Documents** — `GET /v3/documents/{id}` until `status: "done"` 2. **Documents** — `GET /v3/documents/{id}` until `status: "done"` for searchable content, and `dreamingStatus: "done"` before you read memories or profiles
3. **Search** — `POST /v4/search` 3. **Search** — `POST /v4/search`
4. **Profiles** — `POST /v4/profile` 4. **Profiles** — `POST /v4/profile`
@ -56,16 +56,14 @@ Full walkthrough with conversation + document examples: [Quickstart](/quickstart
## SDKs ## SDKs
Official clients wrap this API: The current `supermemory` SDKs (5.x) call the [v5 API](/v5/api-reference/overview), not these endpoints. To call v3/v4 through an SDK, pin a release from before 5.0.0:
- TypeScript: `npm install supermemory` - TypeScript: `npm install supermemory@4`
- Python: `pip install supermemory` - Python: `pip install "supermemory<5"`
See [Supermemory SDK](/integrations/supermemory-sdk). See [Supermemory SDK](/integrations/supermemory-sdk).
Playground snippets come from the OpenAPI spec: official **TypeScript / Python SDK** samples via `x-codeSamples`, plus cURL. (After API deploy — until then you may still see generic HTTP snippets.) The API's OpenAPI generator supplies **TypeScript / Python SDK** samples via `x-codeSamples`, plus cURL. On this legacy reference they use the pre-5 SDK methods. 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 ## OpenAPI

View file

@ -50,11 +50,15 @@ This works for Google Drive, Notion, and OneDrive. See the full setup in [Custom
## Scoped API keys ## Scoped API keys
Scoped keys are restricted to one namespace (what v3/v4 called a container tag). They can only access documents and search within that namespace — use them to give a client, session, or tenant limited access without shipping your org master key. 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.
<Warning>
Namespace scoping is not read-only access. Writable scoped keys can update and delete documents and forget memories. Deleting or moving a namespace additionally requires an org admin or owner with write access to the source and, for moves, the destination.
</Warning>
Pairs with [namespaces](/concepts/container-tags) for multi-tenant isolation. Pairs with [namespaces](/concepts/container-tags) for multi-tenant isolation.
**Allowed endpoints:** `/v3/documents`, `/v3/memories`, `/v4/memories`, `/v3/search`, `/v4/search`, `/v4/profile` **Allowed endpoints:** `/ns/{namespace}/document`, `/ns/{namespace}/list`, `/ns/{namespace}/search`, `/ns/{namespace}/profile`, and `/ns/{namespace}/memories`; `/namespaces` and `/ns/{namespace}`; `/session`; 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. Scoped keys **cannot** read billing, manage org settings, or mint further keys.
@ -63,7 +67,7 @@ Scoped keys **cannot** read billing, manage org settings, or mint further keys.
The scoped-key endpoint still takes the namespace in a field named `containerTag`. The scoped-key endpoint still takes the namespace in a field named `containerTag`.
```bash ```bash
curl https://api.supermemory.ai/v3/auth/scoped-key \ curl https://api.supermemory.ai/auth/scoped-key \
--request POST \ --request POST \
--header 'Content-Type: application/json' \ --header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Authorization: Bearer YOUR_API_KEY' \
@ -78,11 +82,13 @@ curl https://api.supermemory.ai/v3/auth/scoped-key \
| Parameter | Required | Default | Description | | Parameter | Required | Default | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `containerTag` | Yes | — | The namespace to scope the key to. Alphanumeric, hyphens, underscores, colons, dots | | `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 | | `name` | No | `scoped_{containerTag}` | Display name for the key |
| `expiresInDays` | No | — | 1–365 days | | `expiresInDays` | No | — | 1–365 days |
| `rateLimitMax` | No | `500` | Max requests per window (1–10,000) | | `rateLimitMax` | No | `500` | Max requests per minute (1–10,000), rounded up to the next tier: 500, 1,000, 2,000, 5,000, or 10,000 |
| `rateLimitTimeWindow` | No | `60000` | Window in milliseconds (1–3,600,000) |
Requests over the limit return `429` with a `Retry-After` header.
### Response ### Response
@ -92,8 +98,10 @@ curl https://api.supermemory.ai/v3/auth/scoped-key \
"id": "key-id", "id": "key-id",
"name": "scoped_my-project", "name": "scoped_my-project",
"containerTag": "my-project", "containerTag": "my-project",
"containerTags": ["my-project"],
"expiresAt": "2026-03-08T00:00:00.000Z", "expiresAt": "2026-03-08T00:00:00.000Z",
"allowedEndpoints": ["/v3/documents", "/v3/memories", "/v4/memories", "/v3/search", "/v4/search", "/v4/profile"] "allowedEndpoints": ["/v3/session", "/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/*$"],
"rateLimitMax": 500
} }
``` ```
@ -104,7 +112,7 @@ Use the returned key like a normal API key — it just will not work outside its
Revoke with the `id` from creation. Subsequent requests get `401`. Memories and namespaces are **not** deleted. Revoke with the `id` from creation. Subsequent requests get `401`. Memories and namespaces are **not** deleted.
```bash ```bash
curl https://api.supermemory.ai/v3/auth/scoped-key/KEY_ID \ curl https://api.supermemory.ai/auth/scoped-key/KEY_ID \
--request DELETE \ --request DELETE \
--header 'Authorization: Bearer YOUR_API_KEY' --header 'Authorization: Bearer YOUR_API_KEY'
``` ```

View file

@ -97,7 +97,7 @@ The colon is intentionally allowed so you can build **hierarchical** namespaces
## Scope lives in the URL ## 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 SDK call takes `namespace` as a top-level key next to `body`. 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 | | Legacy (v3/v4) | v5 |
|-----------|------| |-----------|------|
@ -138,8 +138,8 @@ await supermemory.list("project_q1", "documents");
Namespaces are also an **authorization boundary**, not just an organizational one. Two mechanisms can restrict which namespaces a given caller may touch: 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, with read or write permission per namespace. - **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. - **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: When a request is restricted, Supermemory validates the namespace in the URL against the caller's allowed set:
@ -159,11 +159,13 @@ The `namespaces.*` SDK methods read and change a namespace itself, as opposed to
| `supermemory.namespaces.list()` | Page through namespaces with `documentCount` and `memoryCount`. | | `supermemory.namespaces.list()` | Page through namespaces with `documentCount` and `memoryCount`. |
| `supermemory.namespaces.get(namespace)` | Read one namespace and its `supportingContext`. | | `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.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 and all its documents and memories. | | `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`. | | `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. `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](/overview/security).
<CodeGroup> <CodeGroup>
```typescript TypeScript ```typescript TypeScript
@ -178,7 +180,7 @@ const { namespaces, pagination } = await supermemory.namespaces.list();
await supermemory.namespaces.delete("project_research_old", { await supermemory.namespaces.delete("project_research_old", {
moveTo: "project_research", moveTo: "project_research",
}); });
// { success, status: "queued", operationId, namespace, moveTo } // { status: "queued", operationId, namespace, moveTo }
``` ```
```bash curl ```bash curl
@ -190,10 +192,8 @@ curl -X PATCH "https://api.supermemory.ai/ns/project_research" \
curl "https://api.supermemory.ai/namespaces" \ curl "https://api.supermemory.ai/namespaces" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" -H "Authorization: Bearer $SUPERMEMORY_API_KEY"
curl -X DELETE "https://api.supermemory.ai/ns/project_research_old" \ curl -X DELETE "https://api.supermemory.ai/ns/project_research_old?moveTo=project_research" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY"
-H "Content-Type: application/json" \
-d '{ "moveTo": "project_research" }'
``` ```
</CodeGroup> </CodeGroup>

View file

@ -5,7 +5,7 @@ description: "All the content formats Supermemory can ingest and process"
icon: "/icons/hugeicons/files-01.svg" 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 a top-level key. See [Add Memories](/ingestion/add-memories) to learn how to ingest content via the API. 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](/ingestion/add-memories) to learn how to ingest content via the API.
## Text content ## Text content
@ -31,7 +31,7 @@ await supermemory.add("documentation", {
}); });
``` ```
**Extracts:** Article text, headings, metadata. Strips navigation, ads, boilerplate. URL extraction is powered by [Markdowner](https://md.dhr.wtf). **Extracts:** Article text, headings, metadata. Strips navigation, ads, boilerplate.
--- ---
@ -102,13 +102,13 @@ await supermemory.add("user_123", {
**Extracts:** Structure, headings, code blocks with syntax awareness. **Extracts:** Structure, headings, code blocks with syntax awareness.
Code is chunked using [code-chunk](https://github.com/supermemoryai/code-chunk), which understands AST boundaries to keep functions, classes, and logical blocks intact. See [Super RAG](/concepts/super-rag) for how Supermemory optimizes chunking for each content type. See [Super RAG](/concepts/super-rag) for how Supermemory optimizes chunking for each content type.
--- ---
## Images ## Images
Set `fileType: "image"` and `mimeType` so Supermemory knows exactly how to process it. Both are query parameters, so they sit next to `namespace` rather than inside `body`: 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`:
```typescript ```typescript
await supermemory.documents.uploadFile("user_123", { await supermemory.documents.uploadFile("user_123", {
@ -127,7 +127,7 @@ await supermemory.documents.uploadFile("user_123", {
## Audio & video ## Audio & video
Video has a dedicated `fileType`; audio is uploaded the same way and detected from the file itself: Video and audio each have a dedicated `fileType` (`"video"`, `"audio"`); set it with `mimeType` when inference is not enough:
```typescript ```typescript
// Video // Video
@ -142,6 +142,7 @@ await supermemory.documents.uploadFile("user_123", {
await supermemory.documents.uploadFile("user_123", { await supermemory.documents.uploadFile("user_123", {
file: fs.createReadStream('call-recording.mp3'), file: fs.createReadStream('call-recording.mp3'),
metadata: JSON.stringify({ title: "Customer Call Recording" }), metadata: JSON.stringify({ title: "Customer Call Recording" }),
fileType: "audio",
mimeType: "audio/mpeg", mimeType: "audio/mpeg",
}); });
``` ```
@ -215,7 +216,7 @@ await supermemory.add("user_123", { content: "https://example.com/page" });
await supermemory.add("user_123", { content: "User said they prefer email contact" }); 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 and video. 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.
--- ---
@ -227,7 +228,7 @@ For files, `uploadFile` detects type from the file itself in most cases. `fileTy
| Files | 50MB | | Files | 50MB |
| URLs | Fetched content up to 10MB | | 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 chunked at the sentence level with a 2-sentence overlap between chunks. **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.
<Tip> <Tip>
For large files, consider chunking or using [connectors](/connectors/overview) for automatic sync. For large files, consider chunking or using [connectors](/connectors/overview) for automatic sync.

View file

@ -27,7 +27,6 @@ await supermemory.organization.update({
- Outdated brand materials (pre-2024) - Outdated brand materials (pre-2024)
- Internal discussions about brand changes - Internal discussions about brand changes
- Competitor analysis docs`, - Competitor analysis docs`,
},
}); });
``` ```
@ -103,6 +102,10 @@ Supporting context persists on the namespace and combines with the org-level org
--- ---
<Note>
The Chunk size and Connector branding sections below use the legacy settings resource. Their `client.settings.update(...)` examples require a legacy SDK that exposes `settings`; SDK 5.x users should call `PATCH /v3/settings` directly with an org admin or owner credential. These options are not fields on the v5 organization resource.
</Note>
## Chunk size ## Chunk size
Control how documents are split into searchable pieces. Smaller chunks = more precise retrieval but less context per result. Control how documents are split into searchable pieces. Smaller chunks = more precise retrieval but less context per result.
@ -133,7 +136,7 @@ Show "Log in to **YourApp**" instead of "Log in to Supermemory" when users conne
<AccordionGroup> <AccordionGroup>
<Accordion title="Google Drive"> <Accordion title="Google Drive">
1. Create OAuth credentials in [Google Cloud Console](https://console.cloud.google.com/) 1. Create OAuth credentials in [Google Cloud Console](https://console.cloud.google.com/)
2. Redirect URI: `https://api.supermemory.ai/v3/connections/google-drive/callback` 2. Redirect URI: `https://api.supermemory.ai/v3/connections/auth/callback/google-drive`
```typescript ```typescript
await client.settings.update({ await client.settings.update({
@ -145,7 +148,7 @@ Show "Log in to **YourApp**" instead of "Log in to Supermemory" when users conne
</Accordion> </Accordion>
<Accordion title="Notion"> <Accordion title="Notion">
1. Create integration at [Notion Developers](https://developers.notion.com/) 1. Create integration at [Notion Developers](https://developers.notion.com/)
2. Redirect URI: `https://api.supermemory.ai/v3/connections/notion/callback` 2. Redirect URI: `https://api.supermemory.ai/v3/connections/auth/callback/notion`
```typescript ```typescript
await client.settings.update({ await client.settings.update({
@ -157,7 +160,7 @@ Show "Log in to **YourApp**" instead of "Log in to Supermemory" when users conne
</Accordion> </Accordion>
<Accordion title="OneDrive"> <Accordion title="OneDrive">
1. Register app in [Azure Portal](https://portal.azure.com/) 1. Register app in [Azure Portal](https://portal.azure.com/)
2. Redirect URI: `https://api.supermemory.ai/v3/connections/onedrive/callback` 2. Redirect URI: `https://api.supermemory.ai/v3/connections/auth/callback/onedrive`
```typescript ```typescript
await client.settings.update({ await client.settings.update({
@ -169,6 +172,10 @@ Show "Log in to **YourApp**" instead of "Log in to Supermemory" when users conne
</Accordion> </Accordion>
</AccordionGroup> </AccordionGroup>
<Note>
Client secrets are write-only. `GET /v3/settings` and the `PATCH /v3/settings` response return each connector's client ID and `*CustomKeyEnabled` flag, but never its client secret.
</Note>
--- ---
## API reference ## API reference

View file

@ -22,7 +22,7 @@ Both can be used independently or together for precise filtering.
## Namespaces ## 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 every SDK call takes it as a top-level key. 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 ### Adding Memories to a Namespace
@ -42,7 +42,7 @@ const results = await supermemory.search("user_123", {
``` ```
<Note> <Note>
Each search is scoped to a single namespace. Passing `namespace: "user_123"` restricts results to memories stored in that namespace. Each search is scoped to a single namespace. Passing `"user_123"` as the namespace restricts results to memories stored in that namespace.
</Note> </Note>
### Recommended patterns ### Recommended patterns
@ -336,7 +336,6 @@ const results = await supermemory.search("user_123", { // Scopes results to this
### Metadata key rules ### Metadata key rules
- Allowed characters: `a-z`, `A-Z`, `0-9`, `_`, `-`, `.` - Allowed characters: `a-z`, `A-Z`, `0-9`, `_`, `-`, `.`
- Max length: 64 characters
- No spaces or special characters - No spaces or special characters
- Keys are literal: `customer.plan` is one key named `customer.plan`, not a nested path - Keys are literal: `customer.plan` is one key named `customer.plan`, not a nested path

View file

@ -27,7 +27,7 @@ await supermemory.add("user_123", {
dreaming: "instant", // memories land in the graph right away dreaming: "instant", // memories land in the graph right away
}); });
const results = await supermemory.search("user_123", { const { results } = await supermemory.search("user_123", {
query: "where does Alex work?", query: "where does Alex work?",
include: { related: true }, include: { related: true },
searchMode: "memories", searchMode: "memories",
@ -154,7 +154,7 @@ await supermemory.add("user_123", {
content: "Alex mentioned he just started at Stripe", content: "Alex mentioned he just started at Stripe",
}); });
const results = await supermemory.search("user_123", { const { results } = await supermemory.search("user_123", {
query: "where does Alex work?", query: "where does Alex work?",
include: { related: true }, include: { related: true },
searchMode: "memories", searchMode: "memories",

View file

@ -61,10 +61,10 @@ results = client.search("user_123", query="where does the user want to travel?")
``` ```
```bash curl ```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_123/document?dreaming=instant" \ curl -X POST "https://api.supermemory.ai/ns/user_123/document" \
-H "Authorization: Bearer sm_..." \ -H "Authorization: Bearer sm_..." \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ "content": "The user loves Paris." }' -d '{ "content": "The user loves Paris.", "dreaming": "instant" }'
curl -X POST "https://api.supermemory.ai/ns/user_123/search" \ curl -X POST "https://api.supermemory.ai/ns/user_123/search" \
-H "Authorization: Bearer sm_..." \ -H "Authorization: Bearer sm_..." \
@ -118,7 +118,7 @@ A document with status `done` has its chunks indexed for search. Memories (the g
This is when the content is passed through the memory model and merged, arranged and organized for the future. This is when the content is passed through the memory model and merged, arranged and organized for the future.
Pass `dreaming` on [add](/ingestion/add-memories) (a query parameter, so a top-level key in the SDK): Pass `dreaming` on [add](/ingestion/add-memories) (a field in the JSON body, next to `content` in the SDK):
| Mode | Default? | Behavior | When to use | | Mode | Default? | Behavior | When to use |
| --- | --- | --- | --- | | --- | --- | --- | --- |

View file

@ -30,7 +30,7 @@ Agents benefit most from having a _general_ idea of the topic alongside tools to
Two things about this table that trip people up. Two things about this table that trip people up.
**"Remember" and "look up" are both supermemory, but different reads.** You [ingest documents](/ingestion/add-memories); the pipeline derives memories from them and maintains a profile per [namespace](/concepts/container-tags). `supermemory.search({ searchMode: "memories" })` recalls the derived facts. `supermemory.search({ 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. **"Remember" and "look up" are both supermemory, but different reads.** You [ingest documents](/ingestion/add-memories); the pipeline derives memories from them and maintains a profile per [namespace](/concepts/container-tags). `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. **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.
@ -56,10 +56,10 @@ client.add(
``` ```
```bash curl ```bash curl
curl -X POST "https://api.supermemory.ai/ns/test/document?taskType=superrag" \ curl -X POST "https://api.supermemory.ai/ns/test/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ "content": "testing" }' -d '{ "content": "testing", "taskType": "superrag" }'
``` ```
</CodeGroup> </CodeGroup>
@ -86,10 +86,10 @@ results = client.search(
``` ```
```bash curl ```bash curl
curl -X POST "https://api.supermemory.ai/ns/test/search?searchMode=hybrid" \ curl -X POST "https://api.supermemory.ai/ns/test/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ "query": "test" }' -d '{ "query": "test", "searchMode": "hybrid" }'
``` ```
</CodeGroup> </CodeGroup>
@ -252,11 +252,12 @@ results = client.search(
``` ```
```bash curl ```bash curl
curl -X POST "https://api.supermemory.ai/ns/org-supermemory/search?searchMode=chunks" \ curl -X POST "https://api.supermemory.ai/ns/org-supermemory/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"query": "preferred language", "query": "preferred language",
"searchMode": "chunks",
"filter": { "field": "team", "operator": "eq", "value": "Research" } "filter": { "field": "team", "operator": "eq", "value": "Research" }
}' }'
``` ```

View file

@ -33,7 +33,7 @@ No chunking strategies to configure. No embedding models to choose. It just work
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. 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"` (a query parameter, so a top-level key in the SDK). It skips the memory pipeline entirely and only does the chunk → embed → index work needed to make the content searchable. 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.
<CodeGroup> <CodeGroup>
@ -53,10 +53,10 @@ client.add(
``` ```
```bash cURL ```bash cURL
curl -X POST "https://api.supermemory.ai/ns/docs_kb/document?taskType=superrag" \ curl -X POST "https://api.supermemory.ai/ns/docs_kb/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ "content": "..." }' -d '{ "content": "...", "taskType": "superrag" }'
``` ```
</CodeGroup> </CodeGroup>
@ -100,21 +100,9 @@ PDFs and documents are chunked by **semantic sections** — headers, paragraphs,
### Code ### Code
Code is chunked using [code-chunk](https://github.com/supermemoryai/code-chunk), our open-source library that understands AST (Abstract Syntax Tree) boundaries: 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.
- Functions and methods stay intact 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.
- Classes are chunked by method
- Import statements grouped separately
- Comments attached to their code blocks
```typescript
// A 500-line file becomes meaningful chunks:
// - Imports + type definitions
// - Each function as a separate chunk
// - Class methods individually indexed
```
This means searching for "authentication middleware" finds the actual function, not a random slice of code.
### Web pages ### Web pages

View file

@ -94,7 +94,7 @@ Recent context and temporary states:
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. 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. 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. 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.
```typescript ```typescript
// Define a namespace-level bucket (name → description) // Define a namespace-level bucket (name → description)
@ -110,7 +110,7 @@ console.log(profile.buckets.preferences); // [{ id, memory }, ...]
console.log(profile.buckets.goals); 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 [`filterPrompt`](/concepts/customization), which controls what gets ingested at all — buckets only organize facts that already made it into the profile. 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`](/concepts/customization#organizational-context) (called `filterPrompt` in v3/v4), which steers what gets extracted at all — buckets only organize facts that already made it into the profile.
<Card title="Profile Buckets reference" icon="/icons/hugeicons/tag-01.svg" href="/user-profiles/buckets"> <Card title="Profile Buckets reference" icon="/icons/hugeicons/tag-01.svg" href="/user-profiles/buckets">
Request bucketed profiles, create buckets at the org or namespace level, and see validation limits. Request bucketed profiles, create buckets at the org or namespace level, and see validation limits.

View file

@ -24,7 +24,7 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
const connector = await supermemory.connectors.create("user-123", { const connector = await supermemory.connectors.create("user-123", {
provider: "github", provider: "github",
redirectUrl: "https://yourapp.com/auth/github/callback" redirectUrl: "https://yourapp.com/auth/github/callback",
documentLimit: 5000, documentLimit: 5000,
}) })
@ -44,8 +44,8 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
"user-123", "user-123",
request={ request={
"provider": "github", "provider": "github",
"redirectUrl": "https://yourapp.com/auth/github/callback", "redirect_url": "https://yourapp.com/auth/github/callback",
"documentLimit": 5000, "document_limit": 5000,
}, },
) )
@ -68,7 +68,7 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
# Response: { # Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA", # "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": { # "authorization": {
# "url": "https://github.com/login/oauth/authorize?...", # "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z" # "expiresAt": "2024-01-15T11:30:00.000Z"
# } # }
# } # }
@ -85,7 +85,7 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
### 2. Handle OAuth callback ### 2. Handle OAuth callback
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, GitHub redirects to your `redirectUrl`. A pending OAuth connector is not visible in list or get until the user finishes authorization. Once it is visible, the user can select which repositories to sync. 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 ### 3. Select Repositories
@ -95,7 +95,7 @@ Unlike most connectors, GitHub requires repository selection before syncing begi
- **Direct selection:** if you already know the repository ids, pass them in `selection.repos` with `connectors.update`. A new selection replaces the old one and starts a sync. - **Direct selection:** if you already know the repository ids, pass them in `selection.repos` with `connectors.update`. A new selection replaces the old one and starts a sync.
<Note> <Note>
See [Managing Connector Selection](/connectors/managing-resources) for the generic selection API that GitHub, Gmail and Google Drive share. See [Managing Connector Selection](/connectors/managing-resources) for the generic selection API that GitHub, Gmail, Google Drive and OneDrive share.
</Note> </Note>
<Tabs> <Tabs>
@ -224,7 +224,9 @@ Webhooks are secured using HMAC-SHA256 signature validation with constant-time c
print(f"Webhooks: {connector.capabilities.webhooks}") print(f"Webhooks: {connector.capabilities.webhooks}")
print(f"Last synced: {connector.system.last_successful_sync_at}") print(f"Last synced: {connector.system.last_successful_sync_at}")
print(f"Last sync: {connector.latest_run.system.status} {connector.latest_run.error}") 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 [] repos = (connector.selection or {}).get("repos") or []
print(f"Repositories: {[r.name for r in repos]}") print(f"Repositories: {[r.name for r in repos]}")
print(f"Recent runs: {connector.syncs}") print(f"Recent runs: {connector.syncs}")
@ -255,7 +257,7 @@ Webhooks are secured using HMAC-SHA256 signature validation with constant-time c
console.log(`Provider: ${connector.provider}`) console.log(`Provider: ${connector.provider}`)
console.log(`ID: ${connector.id}`) console.log(`ID: ${connector.id}`)
console.log(`Account: ${connector.account}`) console.log(`Account: ${connector.account}`)
console.log(`Created: ${connector.createdAt}`) console.log(`Created: ${connector.system.createdAt}`)
console.log(`Document limit: ${connector.documentLimit}`) console.log(`Document limit: ${connector.documentLimit}`)
console.log(`Repositories: ${connector.selection?.repos?.length ?? 0}`) console.log(`Repositories: ${connector.selection?.repos?.length ?? 0}`)
console.log("---") console.log("---")
@ -271,7 +273,7 @@ Webhooks are secured using HMAC-SHA256 signature validation with constant-time c
print(f"Provider: {connector.provider}") print(f"Provider: {connector.provider}")
print(f"ID: {connector.id}") print(f"ID: {connector.id}")
print(f"Account: {connector.account}") print(f"Account: {connector.account}")
print(f"Created: {connector.created_at}") print(f"Created: {connector.system.created_at}")
print(f"Document limit: {connector.document_limit}") print(f"Document limit: {connector.document_limit}")
repos = (connector.selection or {}).get("repos") or [] repos = (connector.selection or {}).get("repos") or []
print(f"Repositories: {len(repos)}") print(f"Repositories: {len(repos)}")

View file

@ -24,7 +24,7 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
const connector = await supermemory.connectors.create("user-123", { const connector = await supermemory.connectors.create("user-123", {
provider: "gmail", provider: "gmail",
redirectUrl: "https://yourapp.com/auth/gmail/callback" redirectUrl: "https://yourapp.com/auth/gmail/callback",
documentLimit: 5000, documentLimit: 5000,
}) })
@ -44,8 +44,8 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
"user-123", "user-123",
request={ request={
"provider": "gmail", "provider": "gmail",
"redirectUrl": "https://yourapp.com/auth/gmail/callback", "redirect_url": "https://yourapp.com/auth/gmail/callback",
"documentLimit": 5000, "document_limit": 5000,
}, },
) )
@ -68,7 +68,7 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
# Response: { # Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA", # "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": { # "authorization": {
# "url": "https://accounts.google.com/o/oauth2/v2/auth?...", # "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z" # "expiresAt": "2024-01-15T11:30:00.000Z"
# } # }
# } # }
@ -78,7 +78,9 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
### 2. Handle OAuth callback ### 2. Handle OAuth callback
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Google redirects to your `redirectUrl` and the initial sync begins. A pending OAuth connector is not visible in list or get until the user finishes authorization. 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](/connectors/managing-resources).
### 3. Check Connector Status ### 3. Check Connector Status
@ -106,7 +108,7 @@ Send the user to `authorization.url` before `authorization.expiresAt`. After the
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"]) connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
print(f"Connected email: {connector.account}") print(f"Connected email: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status}") print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Threads synced: {connector.document_count}") print(f"Threads synced: {connector.document_count}")
# List synced email threads in the namespace # List synced email threads in the namespace
@ -155,8 +157,8 @@ Each synced thread includes searchable metadata:
| `type` | Always `gmail_thread` | | `type` | Always `gmail_thread` |
| `subject` | Email subject line | | `subject` | Email subject line |
| `threadId` | Gmail thread ID | | `threadId` | Gmail thread ID |
| `from` | Sender email address | | `from` | Sender, as the raw `From` header (for example `Team <team@company.com>`) |
| `to` | Recipient email addresses | | `to` | Recipient addresses, comma-separated |
| `date` | Date of first message | | `date` | Date of first message |
| `messageCount` | Number of messages in thread | | `messageCount` | Number of messages in thread |
| `attachmentCount` | Number of attachments (if any) | | `attachmentCount` | Number of attachments (if any) |
@ -171,7 +173,7 @@ const results = await supermemory.search("user-123", {
operator: "and", operator: "and",
operands: [ operands: [
{ field: "type", operator: "eq", value: "gmail_thread" }, { field: "type", operator: "eq", value: "gmail_thread" },
{ field: "from", operator: "eq", value: "team@company.com" }, { field: "from", operator: "contains", value: "team@company.com" },
], ],
}, },
searchMode: "chunks", searchMode: "chunks",
@ -192,7 +194,7 @@ const results = await supermemory.search("user-123", {
console.log(`Provider: ${connector.provider}`) console.log(`Provider: ${connector.provider}`)
console.log(`ID: ${connector.id}`) console.log(`ID: ${connector.id}`)
console.log(`Account: ${connector.account}`) console.log(`Account: ${connector.account}`)
console.log(`Created: ${connector.createdAt}`) console.log(`Created: ${connector.system.createdAt}`)
console.log(`Document limit: ${connector.documentLimit}`) console.log(`Document limit: ${connector.documentLimit}`)
console.log("---") console.log("---")
}) })
@ -207,7 +209,7 @@ const results = await supermemory.search("user-123", {
print(f"Provider: {connector.provider}") print(f"Provider: {connector.provider}")
print(f"ID: {connector.id}") print(f"ID: {connector.id}")
print(f"Account: {connector.account}") print(f"Account: {connector.account}")
print(f"Created: {connector.created_at}") print(f"Created: {connector.system.created_at}")
print(f"Document limit: {connector.document_limit}") print(f"Document limit: {connector.document_limit}")
print("---") print("---")
``` ```
@ -302,19 +304,18 @@ Gmail connector supports multiple sync methods:
| Feature | Behavior | | Feature | Behavior |
|---------|----------| |---------|----------|
| **Real-time sync** | Via Google Cloud Pub/Sub webhooks (7-day expiry, auto-renewed) | | **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 | | **Manual sync** | On-demand via API |
| **Incremental sync** | Uses Gmail `historyId` to fetch only changed threads | | **Incremental sync** | Uses Gmail `historyId` to fetch only changed threads |
### How real-time sync works ### How real-time sync works
1. When a connector is created, supermemory registers a Gmail API "watch" subscription 1. After labels are selected, supermemory registers a Gmail API "watch" subscription when Pub/Sub is configured
2. Gmail sends notifications to a Google Cloud Pub/Sub topic when emails change 2. Gmail sends notifications to a Google Cloud Pub/Sub topic when emails change
3. supermemory receives these notifications and fetches updated threads 3. supermemory receives these notifications and fetches updated threads
4. Watch subscriptions expire after 7 days and are automatically renewed 4. Watch subscriptions expire after 7 days and are automatically renewed
<Note> <Note>
Real-time sync monitors the **INBOX** label. Emails in other labels are synced via scheduled/manual sync. Real-time sync monitors the selected labels, including labels outside **INBOX**. If multiple connectors use the same mailbox, its watch covers their combined label selections.
</Note> </Note>
## Permissions & scopes ## Permissions & scopes
@ -335,7 +336,7 @@ The Gmail connector requests the following OAuth scopes:
<Warning> <Warning>
**Important Limitations:** **Important Limitations:**
- **Plan requirement**: Requires Max Plan or above - **Plan requirement**: Requires Max Plan or above
- **INBOX only** for real-time sync: Only INBOX label triggers real-time updates; other labels sync via scheduled sync - **Selected labels only**: Syncs and real-time updates cover the labels chosen in the picker. Nothing syncs until at least one label is selected
- **Watch expiration**: Gmail watch subscriptions expire after 7 days (automatically renewed by supermemory) - **Watch expiration**: Gmail watch subscriptions expire after 7 days (automatically renewed by supermemory)
- **Document limit**: Default limit is 10,000 threads per connector (configurable via `documentLimit` parameter) - **Document limit**: Default limit is 10,000 threads per connector (configurable via `documentLimit` parameter)
- **Attachments**: Attachment metadata is stored, but attachment content is not downloaded - **Attachments**: Attachment metadata is stored, but attachment content is not downloaded
@ -371,7 +372,7 @@ if (next.authorization) window.location.href = next.authorization.url
If real-time sync isn't working: If real-time sync isn't working:
- Scheduled sync (every 4 hours) and manual sync still work - Manual sync still works
- Real-time sync requires supermemory's Pub/Sub infrastructure - Real-time sync requires supermemory's Pub/Sub infrastructure
- Check if the connector was created recently (watch registration happens on creation) - Check if the connector was created recently (watch registration happens on creation)
- Trigger a manual sync to verify the connector is working - Trigger a manual sync to verify the connector is working

View file

@ -16,7 +16,7 @@ Connect Google Drive to sync documents into your Supermemory knowledge base with
**Explicit scoped mode:** set `config.syncScope` to `"selected"` for the picker flow, or rely on the default for new connects. **Explicit scoped mode:** set `config.syncScope` to `"selected"` for the picker flow, or rely on the default for new connects.
<Note> <Note>
If you use scoped sync and the user has not finished the picker yet, **scheduled or manual sync may skip that connector** until a selection is saved on the connector. If you use scoped sync and the user has not finished the picker yet, **sync may skip that connector** until a selection is saved on the connector.
</Note> </Note>
## Quick setup ## Quick setup
@ -32,7 +32,7 @@ If you use scoped sync and the user has not finished the picker yet, **scheduled
const connector = await supermemory.connectors.create("user-123", { const connector = await supermemory.connectors.create("user-123", {
provider: "google-drive", provider: "google-drive",
redirectUrl: "https://yourapp.com/auth/google-drive/callback" redirectUrl: "https://yourapp.com/auth/google-drive/callback",
documentLimit: 3000, documentLimit: 3000,
config: { syncScope: "selected" }, config: { syncScope: "selected" },
}) })
@ -53,9 +53,9 @@ If you use scoped sync and the user has not finished the picker yet, **scheduled
"user-123", "user-123",
request={ request={
"provider": "google-drive", "provider": "google-drive",
"redirectUrl": "https://yourapp.com/auth/google-drive/callback", "redirect_url": "https://yourapp.com/auth/google-drive/callback",
"documentLimit": 3000, "document_limit": 3000,
"config": {"syncScope": "selected"}, "config": {"sync_scope": "selected"},
}, },
) )
@ -79,7 +79,7 @@ If you use scoped sync and the user has not finished the picker yet, **scheduled
# Response: { # Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA", # "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": { # "authorization": {
# "url": "https://accounts.google.com/o/oauth2/v2/auth?...", # "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z" # "expiresAt": "2024-01-15T11:30:00.000Z"
# } # }
# } # }
@ -93,7 +93,7 @@ For **whole Drive** sync, send `"config": { "syncScope": "full" }` on the same `
### 2. Handle OAuth callback ### 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 visible in list or get until the user finishes authorization. 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. 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.
@ -173,7 +173,7 @@ If you already know the Drive file and folder ids, set them directly. A new sele
print(f"Account: {connector.account}") print(f"Account: {connector.account}")
print(f"Selection: {connector.selection}") print(f"Selection: {connector.selection}")
print(f"Last sync: {connector.latest_run.system.status}") print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
# List synced documents in the namespace # List synced documents in the namespace
documents = client.list("user-123", "documents").documents documents = client.list("user-123", "documents").documents
@ -205,6 +205,8 @@ Based on the API type definitions, Google Drive documents are identified with th
- `google_slide` - Google Slides - `google_slide` - Google Slides
- `google_sheet` - Google Sheets - `google_sheet` - Google Sheets
PDF and Markdown files stored in Drive are synced too.
<Note> <Note>
Drive documents are converted to markdown before ingestion. This conversion is lossy — some formatting may not be preserved. Drive documents are converted to markdown before ingestion. This conversion is lossy — some formatting may not be preserved.
</Note> </Note>
@ -223,7 +225,7 @@ Drive documents are converted to markdown before ingestion. This conversion is l
console.log(`Provider: ${connector.provider}`) console.log(`Provider: ${connector.provider}`)
console.log(`ID: ${connector.id}`) console.log(`ID: ${connector.id}`)
console.log(`Account: ${connector.account}`) console.log(`Account: ${connector.account}`)
console.log(`Created: ${connector.createdAt}`) console.log(`Created: ${connector.system.createdAt}`)
console.log(`Document limit: ${connector.documentLimit}`) console.log(`Document limit: ${connector.documentLimit}`)
console.log("---") console.log("---")
}) })
@ -238,7 +240,7 @@ Drive documents are converted to markdown before ingestion. This conversion is l
print(f"Provider: {connector.provider}") print(f"Provider: {connector.provider}")
print(f"ID: {connector.id}") print(f"ID: {connector.id}")
print(f"Account: {connector.account}") print(f"Account: {connector.account}")
print(f"Created: {connector.created_at}") print(f"Created: {connector.system.created_at}")
print(f"Document limit: {connector.document_limit}") print(f"Document limit: {connector.document_limit}")
print("---") print("---")
``` ```
@ -263,7 +265,7 @@ Drive documents are converted to markdown before ingestion. This conversion is l
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "..." } # "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "..." }
# } # }
# ], # ],
# "pagination": { "currentPage": 1, "limit": 50, "totalItems": 1, "totalPages": 1 } # "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
# } # }
``` ```
</Tab> </Tab>

View file

@ -61,9 +61,9 @@ The console limits connector setup to 500 documents. Use the API setup below for
request={ request={
"provider": "granola", "provider": "granola",
"config": { "config": {
"apiKey": os.environ["GRANOLA_API_KEY"], "api_key": os.environ["GRANOLA_API_KEY"],
}, },
"documentLimit": 1000, "document_limit": 1000,
}, },
) )

View file

@ -6,7 +6,7 @@ icon: 'folder-sync'
--- ---
<Note> <Note>
**Selectable connectors:** GitHub (repositories), Gmail (labels) and Google Drive (files and folders) let you choose what to sync. Other connectors sync everything and return `selection: null`. **Selectable connectors:** GitHub (repositories), Gmail (labels), Google Drive (files and folders) and OneDrive (folders) let you choose what to sync. Other connectors sync everything and return `selection: null`, as does a OneDrive connector set to sync the whole drive.
</Note> </Note>
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`. 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`.
@ -83,7 +83,7 @@ curl "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}?include=pi
``` ```
<Note> <Note>
The picker link works once, in any browser. `picker` is `null` for connectors that sync everything. The picker link works once, in any browser. `picker` is `null` for providers that do not support selection. A Google Drive or OneDrive connector with whole-drive sync can still open the picker; saving a selection switches it to scoped sync. Because the link changes what syncs, `include=picker` needs an admin key with write access to the namespace; otherwise the request returns `403`.
</Note> </Note>
## Set the Selection Directly ## Set the Selection Directly
@ -121,7 +121,7 @@ connector = client.connectors.update(
) )
print(f"Selection: {connector.selection}") print(f"Selection: {connector.selection}")
print(f"Last sync: {connector.latest_run.system.status}") print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
``` ```
```bash cURL ```bash cURL
@ -150,6 +150,7 @@ The selection key depends on the provider:
- GitHub: `repos` (repository ids) - GitHub: `repos` (repository ids)
- Gmail: `labels` (label ids, for example `INBOX`) - Gmail: `labels` (label ids, for example `INBOX`)
- Google Drive: `files` and `folders` (Drive ids) - Google Drive: `files` and `folders` (Drive ids)
- OneDrive: `folders` (OneDrive folder ids)
You can send `documentLimit` in the same body. Each call must include `selection`, `documentLimit`, or both. You can send `documentLimit` in the same body. Each call must include `selection`, `documentLimit`, or both.
@ -173,9 +174,11 @@ The updated connector object, including the saved `selection` as `{ id, name }`
### Error Responses ### Error Responses
- `400`: Connector does not support selection - `400`: Connector does not support selection, the selection is empty or has unknown ids, or the connector needs to be reconnected
- `401`: Unauthorized - `401`: Unauthorized
- `403`: The key lacks admin role or write access to the connector's namespaces
- `404`: Connector not found - `404`: Connector not found
- `409`: Connector is still waiting for the user to finish authorizing
<Note> <Note>
**Automatic Sync:** After a selection is saved, a sync starts for the connector. You don't need to call `connectors.sync` afterwards. **Automatic Sync:** After a selection is saved, a sync starts for the connector. You don't need to call `connectors.sync` afterwards.
@ -193,10 +196,11 @@ const created = await supermemory.connectors.create("user-123", {
redirectUrl: "https://yourapp.com/callback" redirectUrl: "https://yourapp.com/callback"
}) })
// 2. Send the user to GitHub; the connector becomes visible once they authorize // 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 if (created.authorization) window.location.href = created.authorization.url
// 3. After the callback, open the hosted picker so the user chooses repositories // 3. To change repositories later, open the hosted picker again
const connector = await supermemory.connectors.get("user-123", created.id, { const connector = await supermemory.connectors.get("user-123", created.id, {
include: "picker", include: "picker",
returnUrl: "https://yourapp.com/settings/integrations" returnUrl: "https://yourapp.com/settings/integrations"
@ -215,14 +219,15 @@ created = client.connectors.create(
"user-123", "user-123",
request={ request={
"provider": "github", "provider": "github",
"redirectUrl": "https://yourapp.com/callback", "redirect_url": "https://yourapp.com/callback",
}, },
) )
# 2. Send the user to GitHub; the connector becomes visible once they authorize # 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}") print(f"Redirect to: {created.authorization.url}")
# 3. After the callback, open the hosted picker so the user chooses repositories # 3. To change repositories later, open the hosted picker again
connector = client.connectors.get( connector = client.connectors.get(
"user-123", "user-123",
created.id, created.id,
@ -247,9 +252,9 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"provider": "github", "redirectUrl": "https://yourapp.com/callback"}' -d '{"provider": "github", "redirectUrl": "https://yourapp.com/callback"}'
# 2. ... (user authorizes at authorization.url) ... # 2. ... (user authorizes at authorization.url and picks repositories in the hosted picker) ...
# 3. Get a one-time hosted picker URL # 3. To change repositories later, get a one-time hosted picker URL
curl "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}?include=picker" \ curl "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}?include=picker" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" -H "Authorization: Bearer $SUPERMEMORY_API_KEY"

View file

@ -19,7 +19,7 @@ Connect Notion workspaces to automatically sync pages, databases, and content bl
const connector = await supermemory.connectors.create("user-123", { const connector = await supermemory.connectors.create("user-123", {
provider: "notion", provider: "notion",
redirectUrl: "https://yourapp.com/auth/notion/callback" redirectUrl: "https://yourapp.com/auth/notion/callback",
documentLimit: 2000, documentLimit: 2000,
}) })
@ -38,8 +38,8 @@ Connect Notion workspaces to automatically sync pages, databases, and content bl
"user-123", "user-123",
request={ request={
"provider": "notion", "provider": "notion",
"redirectUrl": "https://yourapp.com/auth/notion/callback", "redirect_url": "https://yourapp.com/auth/notion/callback",
"documentLimit": 2000, "document_limit": 2000,
}, },
) )
@ -61,7 +61,7 @@ Connect Notion workspaces to automatically sync pages, databases, and content bl
# Response: { # Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA", # "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": { # "authorization": {
# "url": "https://api.notion.com/v1/oauth/authorize?...", # "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z" # "expiresAt": "2024-01-15T11:30:00.000Z"
# } # }
# } # }
@ -71,7 +71,7 @@ Connect Notion workspaces to automatically sync pages, databases, and content bl
### 2. Handle OAuth flow ### 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 visible in list or get until the user finishes authorization. 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 ### 3. Monitor sync progress
@ -97,7 +97,7 @@ Send the user to `authorization.url` before `authorization.expiresAt`. After the
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"]) connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
print(f"Connected workspace: {connector.account}") print(f"Connected workspace: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status}") print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Documents synced: {connector.document_count}") print(f"Documents synced: {connector.document_count}")
# List synced pages and databases # List synced pages and databases
@ -268,15 +268,15 @@ Notion connector respects workspace permissions:
### Database properties ### Database properties
Notion database properties are mapped to metadata: Notion database properties are mapped to metadata keys named `notion_prop_<property name>`, 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"`.
```typescript ```typescript
// List documents in the namespace (there is no per-connector document list in v5) // List documents in the namespace (there is no per-connector document list in v5)
const { documents } = await supermemory.list("user-123", "documents") const { documents } = await supermemory.list("user-123", "documents")
// Find database entries // Find database entries by a property value
const projectEntries = documents.filter(doc => const projectEntries = documents.filter(doc =>
doc.metadata?.database === "Projects" doc.metadata?.notion_prop_status === "In Progress"
) )
// Database properties become searchable metadata // Database properties become searchable metadata
@ -285,8 +285,8 @@ const projectWithStatus = await supermemory.search("user-123", {
filter: { filter: {
operator: "and", operator: "and",
operands: [ operands: [
{ field: "status", operator: "eq", value: "In Progress" }, { field: "notion_prop_status", operator: "eq", value: "In Progress" },
{ field: "priority", operator: "eq", value: "High" }, { field: "notion_prop_priority", operator: "eq", value: "High" },
], ],
}, },
searchMode: "chunks", searchMode: "chunks",

View file

@ -1,12 +1,24 @@
--- ---
title: "OneDrive connector" title: "OneDrive connector"
sidebarTitle: "OneDrive" sidebarTitle: "OneDrive"
description: "Sync Microsoft Office documents from OneDrive with scheduled synchronization and business account support" description: "Sync Microsoft Office documents from OneDrive with real-time webhook updates and business account support"
icon: "/images/microsoft-icon.svg" 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. Connect OneDrive to automatically sync Word documents, Excel spreadsheets, and PowerPoint presentations into your Supermemory knowledge base. Supports both personal and business accounts with real-time updates via webhooks.
## Sync scope
**Default for new connectors:** after OAuth, the user picks **folders** in Supermemory's hosted folder picker. Only files in the selected folders are synced, and nothing syncs until at least one folder is saved.
**Whole OneDrive:** 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.
<Note>
Connectors created before folder selection was available keep syncing the whole drive. They can narrow to folders by opening the picker and saving a selection.
</Note>
## Quick setup ## Quick setup
@ -21,13 +33,14 @@ const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("user-123", { const connector = await supermemory.connectors.create("user-123", {
provider: "onedrive", provider: "onedrive",
redirectUrl: "https://yourapp.com/auth/onedrive/callback" redirectUrl: "https://yourapp.com/auth/onedrive/callback",
documentLimit: 1500, documentLimit: 1500,
config: { syncScope: "selected" },
}) })
// Send the user to Microsoft to authorize // Send the user to Microsoft to authorize
if (connector.authorization) window.location.href = connector.authorization.url if (connector.authorization) window.location.href = connector.authorization.url
// Output: Redirects to https://login.microsoftonline.com/oauth2/authorize?... // Output: Redirects to https://api.supermemory.ai/v3/connections/auth/redirect?state=...
``` ```
```python Python ```python Python
@ -40,14 +53,16 @@ connector = client.connectors.create(
"user-123", "user-123",
request={ request={
"provider": "onedrive", "provider": "onedrive",
"redirectUrl": "https://yourapp.com/auth/onedrive/callback", "redirect_url": "https://yourapp.com/auth/onedrive/callback",
"documentLimit": 1500, "document_limit": 1500,
"config": {"sync_scope": "selected"},
}, },
) )
# Send the user to Microsoft to authorize # Send the user to Microsoft to authorize
if connector.authorization:
print(f"Redirect to: {connector.authorization.url}") print(f"Redirect to: {connector.authorization.url}")
# Output: Redirect to: https://login.microsoftonline.com/oauth2/authorize?... # Output: Redirect to: https://api.supermemory.ai/v3/connections/auth/redirect?state=...
``` ```
```bash cURL ```bash cURL
@ -57,13 +72,14 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
-d '{ -d '{
"provider": "onedrive", "provider": "onedrive",
"redirectUrl": "https://yourapp.com/auth/onedrive/callback", "redirectUrl": "https://yourapp.com/auth/onedrive/callback",
"documentLimit": 1500 "documentLimit": 1500,
"config": { "syncScope": "selected" }
}' }'
# Response: { # Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA", # "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": { # "authorization": {
# "url": "https://login.microsoftonline.com/oauth2/authorize?...", # "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z" # "expiresAt": "2024-01-15T11:30:00.000Z"
# } # }
# } # }
@ -73,7 +89,7 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
### 2. Handle Microsoft OAuth ### 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 visible in list or get until the user finishes authorization. Send the user to `authorization.url` before `authorization.expiresAt`. With **scoped** sync (`syncScope` omitted or `"selected"`), Supermemory then sends the user to its hosted folder picker; once they save a selection, they return to your `redirectUrl` and the initial sync begins. With `syncScope: "full"`, Microsoft redirects through Supermemory to your `redirectUrl` and the initial sync begins right away. 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 ### 3. Monitor sync status
@ -96,7 +112,7 @@ Send the user to `authorization.url` before `authorization.expiresAt`. After the
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"]) connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
print(f"Account: {connector.account}") print(f"Account: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status}") print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Documents synced: {connector.document_count}") print(f"Documents synced: {connector.document_count}")
# List synced Office documents in the namespace # List synced Office documents in the namespace
@ -119,8 +135,12 @@ Send the user to `authorization.url` before `authorization.expiresAt`. After the
There is no per-connector document list in v5. `supermemory.list(namespace, "documents")` returns every document in the namespace. There is no per-connector document list in v5. `supermemory.list(namespace, "documents")` returns every document in the namespace.
</Note> </Note>
To change the folders later, read the connector with `include: "picker"` and send the user to `picker.url`, or set folder ids directly with `connectors.update(namespace, id, { selection: { folders: ["<folder-id>"] } })`. A new selection replaces the old one and starts a sync. See [Managing Connector Selection](/connectors/managing-resources).
## Supported document types ## Supported document types
Word, Excel, and PowerPoint files are converted to PDF by Microsoft Graph before extraction. Markdown, PDF, JSON, and plain-text files are downloaded as-is and go through the same extraction pipeline as uploads.
### Microsoft Word documents ### Microsoft Word documents
- **Rich text formatting** converted to markdown - **Rich text formatting** converted to markdown
- **Headers and styles** preserved as markdown hierarchy - **Headers and styles** preserved as markdown hierarchy

View file

@ -7,7 +7,7 @@ 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. 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.
Every connector belongs to one namespace. The namespace goes in the URL (`/ns/{namespace}/connectors`) and is a top-level `namespace` key in every SDK call. 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 ## Supported connectors
@ -33,7 +33,7 @@ Every connector belongs to one namespace. The namespace goes in the URL (`/ns/{n
<Card title="OneDrive" icon="/images/microsoft-icon.svg" href="/connectors/onedrive"> <Card title="OneDrive" icon="/images/microsoft-icon.svg" href="/connectors/onedrive">
**Word, Excel, PowerPoint** **Word, Excel, PowerPoint**
Scheduled sync every 4 hours. Supports personal and business accounts with file versioning. Real-time updates via webhooks. Supports personal and business accounts with file versioning.
</Card> </Card>
@ -69,7 +69,7 @@ const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("user-123", { const connector = await supermemory.connectors.create("user-123", {
provider: "notion", provider: "notion",
redirectUrl: "https://yourapp.com/callback" redirectUrl: "https://yourapp.com/callback",
documentLimit: 5000, documentLimit: 5000,
}) })
@ -77,7 +77,7 @@ const connector = await supermemory.connectors.create("user-123", {
console.log("Connector:", connector.id) console.log("Connector:", connector.id)
console.log("Auth URL:", connector.authorization?.url) console.log("Auth URL:", connector.authorization?.url)
console.log("Expires at:", connector.authorization?.expiresAt) console.log("Expires at:", connector.authorization?.expiresAt)
// Output: Auth URL: https://api.notion.com/v1/oauth/authorize?... // Output: Auth URL: https://api.supermemory.ai/v3/connections/auth/redirect?state=...
``` ```
```python Python ```python Python
@ -90,8 +90,8 @@ connector = client.connectors.create(
"user-123", "user-123",
request={ request={
"provider": "notion", "provider": "notion",
"redirectUrl": "https://yourapp.com/callback", "redirect_url": "https://yourapp.com/callback",
"documentLimit": 5000, "document_limit": 5000,
}, },
) )
@ -99,7 +99,7 @@ connector = client.connectors.create(
print(f"Connector: {connector.id}") print(f"Connector: {connector.id}")
print(f"Auth URL: {connector.authorization.url}") print(f"Auth URL: {connector.authorization.url}")
print(f"Expires at: {connector.authorization.expires_at}") print(f"Expires at: {connector.authorization.expires_at}")
# Output: Auth URL: https://api.notion.com/v1/oauth/authorize?... # Output: Auth URL: https://api.supermemory.ai/v3/connections/auth/redirect?state=...
``` ```
```bash cURL ```bash cURL
@ -115,7 +115,7 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
# Response: { # Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA", # "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": { # "authorization": {
# "url": "https://api.notion.com/v1/oauth/authorize?...", # "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z" # "expiresAt": "2024-01-15T11:30:00.000Z"
# } # }
# } # }
@ -125,7 +125,7 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
### 2. Handle OAuth callback ### 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. A pending OAuth connector is not visible in list or get until the user finishes authorization. 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 and Gmail, and Google Drive and OneDrive 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. Config providers (Web Crawler, S3, Granola) return `authorization: null` and start syncing immediately.
@ -167,7 +167,9 @@ connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=
print(f"Provider: {connector.provider}") print(f"Provider: {connector.provider}")
print(f"Account: {connector.account}") print(f"Account: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status} {connector.latest_run.error}") 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"Last synced at: {connector.system.last_successful_sync_at}")
print(f"Documents: {connector.document_count}") print(f"Documents: {connector.document_count}")
print(f"Recent runs: {connector.syncs}") print(f"Recent runs: {connector.syncs}")
@ -210,10 +212,10 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
### Authentication flow ### Authentication flow
1. **Create Connector**: Call `POST /ns/{namespace}/connectors` with a `provider`. OAuth providers return an `authorization` link; config providers (Web Crawler, S3, Granola) take a `config` object and return `authUrl: null` 1. **Create Connector**: Call `POST /ns/{namespace}/connectors` with a `provider`. OAuth providers return an `authorization` link; config providers (Web Crawler, S3, Granola) take a `config` object and return `authorization: null`
2. **User Authorization**: Send the user to `authorization.url` when the provider requires it 2. **User Authorization**: Send the user to `authorization.url` when the provider requires it
3. **Automatic Setup**: Connector established, sync begins immediately 3. **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
4. **Continuous Sync**: Real-time updates via webhooks + scheduled sync every 4 hours (or scheduled recrawling for Web Crawler) 4. **Continuous Sync**: Real-time updates via webhooks (or scheduled recrawling for Web Crawler), plus manual sync on demand
### Document processing pipeline ### Document processing pipeline
@ -232,11 +234,11 @@ graph TD
| Provider | Real-time Sync | Scheduled Sync | Manual Sync | | Provider | Real-time Sync | Scheduled Sync | Manual Sync |
|----------|---------------|----------------|-------------| |----------|---------------|----------------|-------------|
| **Google Drive** | ✅ Webhooks (7-day expiry) | ✅ Every 4 hours | ✅ On-demand | | **Google Drive** | ✅ Webhooks (7-day expiry) | ❌ Not supported | ✅ On-demand |
| **Gmail** | ✅ Pub/Sub (7-day expiry) | ✅ Every 4 hours | ✅ On-demand | | **Gmail** | ✅ Pub/Sub (7-day expiry) | ❌ Not supported | ✅ On-demand |
| **Notion** | ✅ Webhooks | ✅ Every 4 hours | ✅ On-demand | | **Notion** | ✅ Webhooks | ❌ Not supported | ✅ On-demand |
| **OneDrive** | ✅ Webhooks (30-day expiry) | ✅ Every 4 hours | ✅ On-demand | | **OneDrive** | ✅ Webhooks (30-day expiry) | ❌ Not supported | ✅ On-demand |
| **GitHub** | ✅ Webhooks | ✅ Every 4 hours | ✅ On-demand | | **GitHub** | ✅ Webhooks | ❌ Not supported | ✅ On-demand |
| **Granola** | ❌ Not supported | ❌ Not supported | ✅ On-demand | | **Granola** | ❌ Not supported | ❌ Not supported | ✅ On-demand |
| **Web Crawler** | ❌ Not supported | ✅ Scheduled recrawling (7+ days) | ✅ On-demand | | **Web Crawler** | ❌ Not supported | ✅ Scheduled recrawling (7+ days) | ✅ On-demand |
@ -301,7 +303,7 @@ curl "https://api.supermemory.ai/ns/org-123/connectors" \
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "2024-01-15T10:45:00.000Z" } # "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "2024-01-15T10:45:00.000Z" }
# } # }
# ], # ],
# "pagination": { "currentPage": 1, "limit": 50, "totalItems": 1, "totalPages": 1 } # "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
# } # }
# Connectors across every namespace in the organization # Connectors across every namespace in the organization

View file

@ -48,8 +48,8 @@ The S3 connector requires a **Scale Plan** or higher. You can also create S3 con
"config": { "config": {
"bucket": "my-documents-bucket", "bucket": "my-documents-bucket",
"region": "us-east-1", "region": "us-east-1",
"accessKeyId": os.environ["AWS_ACCESS_KEY_ID"], "access_key_id": os.environ["AWS_ACCESS_KEY_ID"],
"secretAccessKey": os.environ["AWS_SECRET_ACCESS_KEY"], "secret_access_key": os.environ["AWS_SECRET_ACCESS_KEY"],
}, },
}, },
) )
@ -270,7 +270,9 @@ Minimum required permissions:
## Error codes ## Error codes
| Code | Message | Solution | 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 | | 401 | Authentication failed | Verify access key and secret |
| 403 | Access denied | Check IAM permissions and bucket policy | | 403 | Access denied | Check IAM permissions and bucket policy |

View file

@ -72,7 +72,7 @@ curl "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA?i
curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \ curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"filter": {"field": "source", "operator": "eq", "value": "notion"}}' -d '{"filter": {"field": "type", "operator": "eq", "value": "notion_doc"}}'
``` ```
</CodeGroup> </CodeGroup>
@ -85,30 +85,26 @@ There is no per-connector document list in v5. `supermemory.list(namespace, "doc
### Connector Missing From List ### Connector Missing From List
**Problem:** `connectors.create` returned an `id`, but `connectors.list` and `connectors.get` do not show it **Problem:** `connectors.create` returned an `id`, but `connectors.list` does not show it
**Solution:** A pending OAuth connector is not visible in list or get until the user finishes authorization. Send the user to `authorization.url` before `authorization.expiresAt`. If the link expired, create the connector again. **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 ### OAuth callback fails
**Problem:** "Invalid redirect URI" error after user grants permissions **Problem:** "Invalid redirect URI" error after user grants permissions
**Solution:** Ensure your redirect URL matches EXACTLY what's configured in your OAuth app: **Solution:** The provider always redirects to Supermemory's callback, not to your `redirectUrl`. If you use a [custom OAuth application](/connectors/overview#custom-oauth-applications), 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:
```typescript ```typescript
// correct - exact match with OAuth app settings
const connector = await supermemory.connectors.create("user-123", { const connector = await supermemory.connectors.create("user-123", {
provider: "notion", provider: "notion",
redirectUrl: "https://yourapp.com/auth/notion/callback" redirectUrl: "https://yourapp.com/auth/notion/callback"
}) })
// Wrong - URL doesn't match
// redirectUrl: "https://yourapp.com/callback"
``` ```
**Prevention:** **Prevention:**
- Use HTTPS for production URLs - Use HTTPS for production URLs
- Copy the exact URL from your OAuth app settings - Register `https://api.supermemory.ai/v3/connections/auth/callback/{provider}` in your custom OAuth app settings
- Test the flow in development first - Test the flow in development first
### Documents not syncing ### Documents not syncing
@ -159,7 +155,7 @@ if (next.authorization) window.location.href = next.authorization.url
```typescript ```typescript
const connector = await supermemory.connectors.create("user-123", { const connector = await supermemory.connectors.create("user-123", {
provider: "onedrive", provider: "onedrive",
redirectUrl: "https://yourapp.com/callback" redirectUrl: "https://yourapp.com/callback",
documentLimit: 500, // Start with fewer documents documentLimit: 500, // Start with fewer documents
}) })
@ -216,11 +212,11 @@ Business accounts may have additional restrictions:
**Real-time Sync Not Working** **Real-time Sync Not Working**
If emails aren't syncing in real-time but scheduled/manual sync works: If emails aren't syncing in real-time but manual sync works:
1. Real-time sync uses Google Cloud Pub/Sub webhooks 1. Real-time sync uses Google Cloud Pub/Sub webhooks
2. Watch subscriptions expire after 7 days (supermemory auto-renews) 2. Watch subscriptions expire after 7 days (supermemory auto-renews)
3. Only INBOX label triggers real-time updates 3. Real-time updates cover only the labels selected for the connector
4. Trigger a manual sync to verify the connector is healthy: 4. Trigger a manual sync to verify the connector is healthy:
```typescript ```typescript
@ -247,11 +243,11 @@ const next = await supermemory.connectors.create("user-123", {
}) })
``` ```
**Scale Plan Required Error** **Max Plan Required Error**
Gmail connector requires Scale Plan or Enterprise Plan. If you see access errors: Gmail connector requires Max Plan or above. If you see access errors:
- Verify your organization has the required plan - Verify your organization has the required plan
- Contact support to upgrade your plan - Upgrade from **Billing** in the [console](https://console.supermemory.ai/billing)
## Best practices ## Best practices

View file

@ -48,16 +48,16 @@ The web crawler connector requires a **Scale Plan** or **Enterprise Plan**.
request={ request={
"provider": "web-crawler", "provider": "web-crawler",
"config": { "config": {
"startUrl": "https://docs.example.com", "start_url": "https://docs.example.com",
"crawlDepth": 3, "crawl_depth": 3,
}, },
"documentLimit": 5000, "document_limit": 5000,
}, },
) )
# Web crawler doesn't require OAuth; crawling starts immediately # Web crawler doesn't require OAuth; crawling starts immediately
print(f"Connector ID: {connector.id}") print(f"Connector ID: {connector.id}")
# Note: connector.authorization.url is None for web-crawler # Note: connector.authorization is None for web-crawler
``` ```
</Tab> </Tab>
<Tab title="cURL"> <Tab title="cURL">
@ -120,7 +120,7 @@ Unlike OAuth connectors, the web crawler doesn't require user authorization. `au
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"]) connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
print(f"Start URL: {(connector.config or {}).get('startUrl')}") print(f"Start URL: {(connector.config or {}).get('startUrl')}")
print(f"Last sync: {connector.latest_run.system.status}") print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Pages synced: {connector.document_count}") print(f"Pages synced: {connector.document_count}")
# List synced web pages in the namespace # List synced web pages in the namespace
@ -229,7 +229,7 @@ Start a crawl now. The call returns `409` while a sync for that connector is alr
webCrawlerConnectors.forEach(connector => { webCrawlerConnectors.forEach(connector => {
console.log(`Start URL: ${connector.config?.startUrl}`) console.log(`Start URL: ${connector.config?.startUrl}`)
console.log(`Connector ID: ${connector.id}`) console.log(`Connector ID: ${connector.id}`)
console.log(`Created: ${connector.createdAt}`) console.log(`Created: ${connector.system.createdAt}`)
}) })
``` ```
</Tab> </Tab>
@ -241,7 +241,7 @@ Start a crawl now. The call returns `409` while a sync for that connector is alr
for connector in connectors: for connector in connectors:
print(f"Start URL: {(connector.config or {}).get('startUrl')}") print(f"Start URL: {(connector.config or {}).get('startUrl')}")
print(f"Connector ID: {connector.id}") print(f"Connector ID: {connector.id}")
print(f"Created: {connector.created_at}") print(f"Created: {connector.system.created_at}")
``` ```
</Tab> </Tab>
<Tab title="cURL"> <Tab title="cURL">
@ -256,12 +256,12 @@ Start a crawl now. The call returns `409` while a sync for that connector is alr
# "id": "PTzGiUYei7pgzg5buzZHgA", # "id": "PTzGiUYei7pgzg5buzZHgA",
# "provider": "web-crawler", # "provider": "web-crawler",
# "namespace": "user-123", # "namespace": "user-123",
# "createdAt": "2024-01-15T10:30:00.000Z",
# "documentLimit": 5000, # "documentLimit": 5000,
# "config": {"startUrl": "https://docs.example.com", "crawlDepth": 3} # "config": {"startUrl": "https://docs.example.com", "crawlDepth": 3},
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "..." }
# } # }
# ], # ],
# "pagination": { "currentPage": 1, "limit": 50, "totalItems": 1, "totalPages": 1 } # "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
# } # }
``` ```
</Tab> </Tab>

View file

@ -216,6 +216,7 @@
"pages": [ "pages": [
"self-hosting/overview", "self-hosting/overview",
"self-hosting/quickstart", "self-hosting/quickstart",
"self-hosting/docker",
"self-hosting/configuration", "self-hosting/configuration",
"self-hosting/embeddings", "self-hosting/embeddings",
"self-hosting/providers", "self-hosting/providers",
@ -377,6 +378,7 @@
"icon": "/icons/hugeicons/tag-01.svg", "icon": "/icons/hugeicons/tag-01.svg",
"pages": [ "pages": [
"api-reference/container-tags", "api-reference/container-tags",
"GET /v3/container-tags/list",
"GET /v3/container-tags/{containerTag}", "GET /v3/container-tags/{containerTag}",
"PATCH /v3/container-tags/{containerTag}", "PATCH /v3/container-tags/{containerTag}",
"DELETE /v3/container-tags/{containerTag}", "DELETE /v3/container-tags/{containerTag}",
@ -447,6 +449,7 @@
"pages": [ "pages": [
"v5/api-reference/content-management", "v5/api-reference/content-management",
"GET /ns/{namespace}/document/{id}", "GET /ns/{namespace}/document/{id}",
"GET /ns/{namespace}/memories/{id}",
"POST /ns/{namespace}/list/{type}", "POST /ns/{namespace}/list/{type}",
"DELETE /ns/{namespace}/document", "DELETE /ns/{namespace}/document",
"DELETE /ns/{namespace}/memories", "DELETE /ns/{namespace}/memories",
@ -977,6 +980,11 @@
"permanent": true, "permanent": true,
"source": "/supermemory-mcp/technology" "source": "/supermemory-mcp/technology"
}, },
{
"destination": "/supermemory-mcp/mcp",
"permanent": true,
"source": "/supermemory-mcp"
},
{ {
"destination": "/", "destination": "/",
"permanent": true, "permanent": true,
@ -1213,6 +1221,11 @@
"source": "/self-hosting/embed", "source": "/self-hosting/embed",
"destination": "/self-hosting/configuration" "destination": "/self-hosting/configuration"
}, },
{
"source": "/self-hosting",
"destination": "/self-hosting/overview",
"permanent": true
},
{ {
"source": "/concepts/memory", "source": "/concepts/memory",
"destination": "/concepts/graph-memory" "destination": "/concepts/graph-memory"

View file

@ -66,7 +66,7 @@ Every write goes to one `namespace` (what v3/v4 called a container tag). The nam
``` ```
<Warning> <Warning>
If an irrecoverable processing error occurs, the document is automatically deleted after 2 minutes. If an irrecoverable processing error occurs, the document's status becomes `failed`. Send the content again with the same `id` to reprocess it.
</Warning> </Warning>
--- ---
@ -193,16 +193,16 @@ Scale and Enterprise include [advanced document extraction for PDFs](/concepts/c
## Parameters ## Parameters
`namespace`, `taskType`, and `dreaming` are top-level keys on the SDK call (`taskType` and `dreaming` are query parameters on the HTTP route). Everything else goes in `body`. `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 | | Parameter | Type | Description |
|-----------|------|-------------| |-----------|------|-------------|
| `namespace` | string | **Required.** Group by user/project. Goes in the URL path. Required for user profiles | | `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 | | `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 | | `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) | | `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](#filtered-writes) | | `group` | object | Filter which existing memories are used as context during ingestion. See [Filtered Writes](#filtered-writes) |
| `supportingContext` | string | Context for memory extraction on this namespace. Max 1500 chars. See [Customization](/concepts/customization#entity-context) | | `supportingContext` | string | Context for memory extraction on this namespace. Max 1500 chars. See [Customization](/concepts/customization#supporting-context) |
| `date` | string | ISO 8601 date the content was originally created. Use when backfilling. See [Backfill historical data](/ingestion/batch-ingest-historical-data) | | `date` | string | ISO 8601 date the content was originally created. Use when backfilling. See [Backfill historical data](/ingestion/batch-ingest-historical-data) |
| `dreaming` | `"dynamic" \| "instant"` | Processing mode. Default `"dynamic"`. `"instant"` processes each document on its own and bills one extra operation. See [Processing Modes](#processing-modes) | | `dreaming` | `"dynamic" \| "instant"` | Processing mode. Default `"dynamic"`. `"instant"` processes each document on its own and bills one extra operation. See [Processing Modes](#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](/concepts/super-rag#ingesting-as-pure-superrag-tasktype-superrag) | | `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](/concepts/super-rag#ingesting-as-pure-superrag-tasktype-superrag) |
@ -223,16 +223,16 @@ Scale and Enterprise include [advanced document extraction for PDFs](/concepts/c
{ content: "# Project Docs\n\n## Features\n- Real-time sync" } { content: "# Project Docs\n\n## Features\n- Real-time sync" }
``` ```
**Namespaces:** **Namespaces** (first argument; letters, numbers, `-`, `_`, and `:`, up to 100 characters):
```typescript ```typescript
// By user // By user
{ namespace: "user_123" } await supermemory.add("user_123", { content: "..." });
// By project // By project
{ namespace: "project_alpha" } await supermemory.add("project_alpha", { content: "..." });
// Hierarchical // Hierarchical
{ namespace: "org_456_team_backend" } await supermemory.add("org_456_team_backend", { content: "..." });
``` ```
**IDs (Recommended):** **IDs (Recommended):**
@ -262,8 +262,8 @@ Scale and Enterprise include [advanced document extraction for PDFs](/concepts/c
} }
} }
``` ```
- No nested objects or arrays - No nested objects
- Values: string, number, or boolean only - Values: string, number, boolean, or an array of strings
**Supporting Context:** **Supporting Context:**
```typescript ```typescript
@ -363,7 +363,7 @@ The metadata itself is still written to the document, but the memories will only
### How it works ### How it works
When `group` is provided: When `group` is provided:
- **Profile memories** (static context) are filtered to only those from documents matching the metadata - **Profile memories** (static context) are filtered by the existing memories' metadata
- **Similar memories** used as context during ingestion are filtered the same way - **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 - The new document's own metadata is written normally — the filter only affects which **existing** memories are used as context
@ -371,7 +371,7 @@ When `group` is provided:
| Key | Type | Description | | Key | Type | Description |
|-----|------|-------------| |-----|------|-------------|
| `group` | `Record<string, string \| number \| boolean \| string[]>` | Key-value pairs to filter existing memories by their source document metadata | | `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 - **Scalar values** (string, number, boolean) match exactly
- **Array values** match if **any** value in the array matches (OR logic) - **Array values** match if **any** value in the array matches (OR logic)
@ -404,7 +404,7 @@ When you add content, Supermemory:
Track progress with `GET /ns/{namespace}/document/{id}`: Track progress with `GET /ns/{namespace}/document/{id}`:
```typescript ```typescript
const doc = await supermemory.documents.get("user_123", "abc123"); const doc = await supermemory.documents.get("user_123", "abc123");
console.log(doc.system.status); // "queued" | "processing" | "done" console.log(doc.system.status); // "queued" | "extracting" | "chunking" | "embedding" | "done" | "failed"
``` ```
<AccordionGroup> <AccordionGroup>
@ -444,17 +444,19 @@ console.log(doc.system.status); // "queued" | "processing" | "done"
<Accordion title="Error handling"> <Accordion title="Error handling">
| Status | Error | Cause | | Status | Error | Cause |
|--------|-------|-------| |--------|-------|-------|
| 400 | Bad request | Missing required fields, invalid parameters | | 400 | Bad request | Missing required fields, invalid parameters, unexpected query parameters |
| 401 | Unauthorized | Invalid or missing API key | | 401 | Unauthorized | Invalid or missing API key |
| 403 | Forbidden | Insufficient permissions | | 402 | Payment required | Not enough credit balance for this ingest |
| 429 | Rate limited | Too many requests or quota exceeded | | 403 | Forbidden | No write access to this namespace |
| 409 | Conflict | A namespace merge is in progress |
| 429 | Rate limited | Too many requests |
| 500 | Internal error | Processing failure | | 500 | Internal error | Processing failure |
```typescript ```typescript
try { try {
await supermemory.add("user_123", { content: "..." }); await supermemory.add("user_123", { content: "..." });
} catch (error) { } catch (error) {
// Back off on 429, fix the request on 400, retry later on 5xx // Back off on 429, top up on 402, fix the request on 400, retry later on 5xx
console.error("add failed:", error); console.error("add failed:", error);
} }
``` ```

View file

@ -102,13 +102,13 @@ curl -X POST "https://api.supermemory.ai/ns/historical_import/document/batch" \
</CodeGroup> </CodeGroup>
Batch results preserve input order. Inspect `success`, `failed`, and every item in `results`; a batch can contain successful and failed items together. `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 ## Optional: wait for processing to finish
**Endpoint:** [`GET /ns/{namespace}/document/{id}`](/api-reference/documents/get-document) **Endpoint:** [`GET /ns/{namespace}/document/{id}`](/api-reference/documents/get-document)
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. 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.
<CodeGroup> <CodeGroup>

View file

@ -49,9 +49,12 @@ Retrieve paginated documents with filtering. One `list` call serves documents, c
"id": "doc_abc123", "id": "doc_abc123",
"title": "Meeting notes", "title": "Meeting notes",
"type": "text", "type": "text",
"summary": "Team sync covering the Q3 roadmap",
"metadata": { "source": "slack" }, "metadata": { "source": "slack" },
"url": null,
"system": { "system": {
"status": "done", "status": "done",
"dreamingStatus": "done",
"createdAt": "2024-01-15T10:30:00Z", "createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z" "updatedAt": "2024-01-15T10:30:00Z"
} }
@ -72,16 +75,31 @@ Every response contains `documents`, `chunks`, `memories`, and `pagination`. Onl
### Parameters ### Parameters
`namespace` and `type` are required. Pagination and sorting are query parameters (top-level keys in the SDK); `filter` goes in `body`. `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 | | Parameter | Type | Default | Description |
|-----------|------|---------|-------------| |-----------|------|---------|-------------|
| `type` | string | required | `documents`, `chunks`, or `memories` | | `type` | string | required | `documents`, `chunks`, or `memories` |
| `limit` | number | 10 | Items per page | | `limit` | number | 10 | Items per page (maximum 100) |
| `page` | number | 1 | Page number | | `page` | number | 1 | Page number |
| `sort` | string | `createdAt` | Sort by `createdAt`, `updatedAt`, or `position` | | `sort` | string | `createdAt` | `createdAt` or `updatedAt` for documents and memories; `createdAt` or `position` for chunks |
| `order` | string | `desc` | `desc` (newest) or `asc` (oldest) | | `order` | string | `desc` | `desc` (newest) or `asc` (oldest) |
| `body.filter` | object | — | Metadata filter. See [Organizing & Filtering](/concepts/filtering) | | `body.filter` | object | — | Metadata filter. See [Organizing & Filtering](/concepts/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:
```bash
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.
<Accordion title="Pagination example"> <Accordion title="Pagination example">
```typescript ```typescript
@ -132,7 +150,7 @@ Get a specific document with its processing status. Pass `include` to include it
include: ["chunks", "memories"], // optional include: ["chunks", "memories"], // optional
}); });
console.log(doc.system.status); // "queued" | "processing" | "done" | "failed" console.log(doc.system.status); // "queued" | "extracting" | ... | "done" | "failed"
console.log(doc.content); console.log(doc.content);
``` ```
</Tab> </Tab>
@ -144,7 +162,7 @@ Get a specific document with its processing status. Pass `include` to include it
include=["chunks", "memories"], # optional include=["chunks", "memories"], # optional
) )
print(doc.system.status) # "queued" | "processing" | "done" | "failed" print(doc.system.status) # "queued" | "extracting" | ... | "done" | "failed"
print(doc.content) print(doc.content)
``` ```
</Tab> </Tab>
@ -158,6 +176,10 @@ Get a specific document with its processing status. Pass `include` to include it
The `id` may be the Supermemory document ID or the `id` you supplied at ingestion. It resolves only inside the namespace in the path. The `id` may be the Supermemory document ID or the `id` you supplied at ingestion. It resolves only inside the namespace in the path.
<Note>
The current HTTP response also includes `system.dreamingStatus`. TypeScript SDK 5.0.1 and Python SDK 5.0.0 do not expose that field in their generated models; Python's `system.dreaming_status` is not available. Use the raw HTTP response when monitoring memory generation with these versions, or upgrade the TypeScript SDK to 5.0.2 for typed `system.dreamingStatus`. `system.status: "done"` alone does not mean memories are ready.
</Note>
### Processing status ### Processing status
| Status | Description | | Status | Description |
@ -169,6 +191,19 @@ The `id` may be the Supermemory document ID or the `id` you supplied at ingestio
| `done` | Ready for search | | `done` | Ready for search |
| `failed` | Processing failed | | `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.
<Accordion title="Poll for completion"> <Accordion title="Poll for completion">
```typescript ```typescript
async function waitForProcessing(namespace: string, id: string) { async function waitForProcessing(namespace: string, id: string) {
@ -188,7 +223,7 @@ The `id` may be the Supermemory document ID or the `id` you supplied at ingestio
## Update document ## 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`, `group`, or `date`. 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> <Tabs>
<Tab title="TypeScript"> <Tab title="TypeScript">
@ -277,7 +312,7 @@ Permanently remove documents. One call deletes 1 to 100 documents by Supermemory
</Tab> </Tab>
</Tabs> </Tabs>
Inspect both `count` and per-ID `errors`; HTTP success can include partial failures. 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.
<Warning> <Warning>
Deletes are permanent — no recovery. Deletes are permanent — no recovery.
@ -287,7 +322,7 @@ Deletes are permanent — no recovery.
## Processing queue ## Processing queue
There is no separate processing endpoint. List documents and read `system.status` on each item. 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.
<Tabs> <Tabs>
<Tab title="TypeScript"> <Tab title="TypeScript">

View file

@ -1,6 +1,8 @@
You are integrating Supermemory into my application. Supermemory provides user memory, semantic search, and automatic knowledge extraction for AI applications. 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**. You can always reference the documentation by using the **supermemory-docs MCP** (`https://supermemory.ai/docs/mcp`) or running a web search tool for content on **supermemory.ai/docs**. Prefer `npx supermemory setup` / `npx supermemory help --json` when scaffolding.
This prompt targets the v5 API and the 5.x SDKs. Already on Supermemory v3/v4? Run `npx supermemory@latest migrate` instead of rewriting calls by hand.
## STEP 1: ASK ME THESE QUESTIONS ## STEP 1: ASK ME THESE QUESTIONS
@ -18,105 +20,116 @@ You can always reference the documentation by using the **SearchSupermemoryDocs
- Direct API calls - Direct API calls
3. Data model? 3. Data model?
- Individual users only → containerTag: userId - Individual users only → namespace: userId
- Organizations only → containerTag: orgId - Organizations only → namespace: orgId
- Both users AND orgs → ask for strategy - Both users AND orgs → ask for strategy
4. Do you want USER PROFILES? 4. Do you want USER PROFILES?
User profiles are automatically-maintained facts about users (what they like, what they're working on, preferences). 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 - Yes (RECOMMENDED) → Use `client.profile(namespace)` to get context
- No → Just use search - No → Just use search
5. How should I retrieve context? 5. How should I retrieve context?
- OPTION A: One call with search included → `profile({ containerTag, q: userMessage })` - OPTION A: Profile plus search → `profile(namespace)` for facts, then `search(namespace, { query: userMessage })` for relevant memories
- OPTION B: Separate calls → `profile()` for facts, `search()` for memories - OPTION B: Search only → `search(namespace, { query: userMessage })`
## STEP 2: INSTALL ## STEP 2: INSTALL
```bash ```bash
# Get API key: https://console.supermemory.ai # Get API key: https://console.supermemory.ai
npm install supermemory # or: pip install supermemory npm install supermemory # or: pip install supermemory (version 5 or later)
# For Vercel AI SDK: npm install @supermemory/tools # For Vercel AI SDK: npm install @supermemory/tools
export SUPERMEMORY_API_KEY="sm_..." export SUPERMEMORY_API_KEY="sm_..."
``` ```
## STEP 3: CONFIGURE SETTINGS (DO THIS FIRST) ## STEP 3: CANONICAL API SURFACE (use these, nothing else)
- Auth header: `Authorization: Bearer $SUPERMEMORY_API_KEY` (recommended; `x-api-key` and `x-supermemory-api-key` still work but are deprecated)
- Write content: `POST https://api.supermemory.ai/ns/{namespace}/document`
- Upload a file: `POST https://api.supermemory.ai/ns/{namespace}/document/file`
- Search: `POST https://api.supermemory.ai/ns/{namespace}/search`
- Profile: `POST https://api.supermemory.ai/ns/{namespace}/profile`
- Org settings: `PATCH https://api.supermemory.ai/organization`
- Scoping: one `namespace` in the URL path — never in the body or a header
DO NOT USE — deprecated or removed in v5:
- Endpoints: `/v3/documents`, `/v3/search`, `/v3/settings`, `/v4/search`, `/v4/profile`, `/v4/memories` (use `/ns/{namespace}/...` and `/organization`)
- Body keys: `containerTag`, `containerTags`, `customId` (use `id`), `entityContext` (use `supportingContext`), `documentDate` (use `date`), `q` (use `query`), `filters` (use `filter`)
- SDK: `client.add({ containerTag })`, `client.search.execute`, `client.memories.add`, `client.profile({ containerTag, q })`
## STEP 4: CONFIGURE ORGANIZATION CONTEXT (OPTIONAL)
Describe your app so extraction focuses on what matters:
```typescript ```typescript
// PATCH https://api.supermemory.ai/v3/settings import { Supermemory } from "supermemory"
fetch('https://api.supermemory.ai/v3/settings', {
method: 'PATCH', const client = new Supermemory()
headers: {
'Authorization': `Bearer ${process.env.SUPERMEMORY_API_KEY}`, await client.organization.update({
'Content-Type': 'application/json', organizationalContext: "This is a [your app description]. Each namespace is a [userId/orgId]. We store [what data].",
},
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 ## STEP 5: NAMESPACE STRATEGY
Based on their data model answer: Based on their data model answer:
**USER-ONLY APP:** **USER-ONLY APP:**
```typescript ```typescript
containerTag: userId const namespace = userId
``` ```
**ORG-ONLY APP:** **ORG-ONLY APP:**
```typescript ```typescript
containerTag: orgId // Org members share memories const namespace = orgId // Org members share memories
``` ```
**BOTH (ask which):** **BOTH (ask which):**
```typescript ```typescript
// Option A: Unique per user-org combination // Option A: Unique per user-org combination
containerTag: `${userId}-${orgId}` const namespace = `${orgId}_${userId}`
// Option B: Org-scoped with user metadata // Option B: Org-scoped with user metadata
containerTag: orgId, metadata: { userId } await client.add(orgId, { content, metadata: { userId } })
// Option C: User-scoped with org metadata // Option C: User-scoped with org metadata
containerTag: userId, metadata: { orgId } await client.add(userId, { content, metadata: { orgId } })
``` ```
## STEP 5: INTEGRATION CODE ## STEP 6: INTEGRATION CODE
Based on their integration choice: Based on their integration choice:
### VERCEL AI SDK ### VERCEL AI SDK
```typescript ```typescript
import { streamText } from 'ai' import { generateText } from "ai"
import { anthropic } from '@ai-sdk/anthropic' import { openai } from "@ai-sdk/openai"
import { supermemoryTools } from '@supermemory/tools/ai-sdk' import { supermemoryTools, withSupermemory } from "@supermemory/tools/ai-sdk"
// Option 1: Agent tools (recommended for agentic flows) // 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({ const result = await generateText({
model: openai("gpt-5"),
prompt: userMessage,
tools: supermemoryTools(process.env.SUPERMEMORY_API_KEY!, {
namespace: userId,
}),
})
// Option 2: Middleware (automatic context injection)
const modelWithMemory = withSupermemory(openai("gpt-5"), {
namespace: userId,
id: "conversation-1",
})
const answer = await generateText({
model: modelWithMemory, model: modelWithMemory,
messages: [{ role: 'user', content: userMessage }] messages: [{ role: "user", content: userMessage }],
}) })
// Profile is automatically injected into context // Profile is automatically injected into context
``` ```
@ -124,65 +137,71 @@ const result = await generateText({
### DIRECT SDK (WITH PROFILES) ### DIRECT SDK (WITH PROFILES)
```typescript ```typescript
import Supermemory from 'supermemory' import { Supermemory } from "supermemory"
const client = new Supermemory() const client = new Supermemory()
// Before each LLM call: // Before each LLM call:
const { profile, searchResults } = await client.profile({ const { profile } = await client.profile(userId)
containerTag: userId, const related = await client.search(userId, {
q: userMessage // Include this if they chose OPTION A (one call) query: userMessage,
// Omit if they chose OPTION B (separate calls) searchMode: "memories",
limit: 5,
}) })
// Build context // Build context
const context = ` const context = [
Static facts: ${profile.static.join('\n')} "Static facts:",
Recent context: ${profile.dynamic.join('\n')} ...profile.static.map((m) => m.memory),
${searchResults ? `Memories: ${searchResults.results.map(r => r.content).join('\n')}` : ''} "Recent context:",
` ...profile.dynamic.map((m) => m.memory),
"Memories:",
...related.results.map((r) => r.memory).filter(Boolean),
].join("\n")
// Send to LLM // Send to LLM
const messages = [ const messages = [
{ role: 'system', content: `User context:\n${context}` }, { role: "system", content: `User context:\n${context}` },
{ role: 'user', content: userMessage } { role: "user", content: userMessage },
] ]
// After LLM responds: // After LLM responds, append the turn to one document per conversation:
await client.memories.add({ await client.add(userId, {
content: `user: ${userMessage}\nassistant: ${response}`, content: `user: ${userMessage}\nassistant: ${response}`,
containerTag: userId id: conversationId,
}) })
``` ```
### DIRECT SDK (NO PROFILES) ### DIRECT SDK (NO PROFILES)
```typescript ```typescript
import Supermemory from 'supermemory' import { Supermemory } from "supermemory"
const client = new Supermemory() const client = new Supermemory()
// Search for relevant memories // Search memories and document chunks
const results = await client.search({ const results = await client.search(userId, {
q: userMessage, query: userMessage,
containerTag: userId, searchMode: "hybrid",
searchMode: 'hybrid', // Searches memories + document chunks limit: 5,
limit: 5
}) })
// Build context // Build context
const context = results.results.map(r => r.content).join('\n') const context = results.results
.map((r) => r.memory ?? r.chunk)
.filter(Boolean)
.join("\n")
// Send to LLM with context // Send to LLM with context
const messages = [ const messages = [
{ role: 'system', content: `Relevant context:\n${context}` }, { role: "system", content: `Relevant context:\n${context}` },
{ role: 'user', content: userMessage } { role: "user", content: userMessage },
] ]
// Store the conversation // Store the conversation
await client.memories.add({ await client.add(userId, {
content: `user: ${userMessage}\nassistant: ${response}`, content: `user: ${userMessage}\nassistant: ${response}`,
containerTag: userId id: conversationId,
}) })
``` ```
@ -194,124 +213,134 @@ from supermemory import Supermemory
client = Supermemory() client = Supermemory()
# With profiles (if they want it) # With profiles (if they want it)
profile_data = client.profile( profile = client.profile(user_id).profile
container_tag=user_id, related = client.search(user_id, query=user_message, search_mode="memories", limit=5)
q=user_message # Include if OPTION A, omit if OPTION B
context = "\n".join(
[
"Static:",
*[m.memory for m in profile.static],
"Dynamic:",
*[m.memory for m in profile.dynamic],
"Memories:",
*[r.memory for r in related.results if r.memory],
]
) )
context = f"""
Static: {chr(10).join(profile_data.profile.static)}
Dynamic: {chr(10).join(profile_data.profile.dynamic)}
"""
# Store conversation # Store conversation
client.add(content=f"user: {user_message}\\nassistant: {response}", container_tag=user_id) client.add(
user_id,
content=f"user: {user_message}\nassistant: {response}",
id=conversation_id,
)
``` ```
### DIRECT API ### DIRECT API
```bash ```bash
# Add memory # Add memory
curl -X POST https://api.supermemory.ai/v3/documents \ curl -X POST https://api.supermemory.ai/ns/user_123/document \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"content": "conversation", "containerTag": "userId"}' -d '{"content": "conversation", "id": "conversation_1"}'
# Get profile # Get profile
curl -X POST https://api.supermemory.ai/v4/profile \ curl -X POST https://api.supermemory.ai/ns/user_123/profile \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"containerTag": "userId", "q": "search query"}' -d '{}'
# Search # Search
curl -X POST https://api.supermemory.ai/v4/search \ curl -X POST https://api.supermemory.ai/ns/user_123/search \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"q": "query", "containerTag": "userId", "searchMode": "hybrid"}' -d '{"query": "query", "searchMode": "hybrid"}'
``` ```
## STEP 6: FILE UPLOADS (if they need it) ## STEP 7: FILE UPLOADS (if they need it)
```typescript ```typescript
// Files are automatically extracted (PDFs, images with OCR, videos with transcription) // Files are automatically extracted (PDFs, images with OCR, videos with transcription)
const formData = new FormData() const formData = new FormData()
formData.append('file', fileBlob) formData.append("file", fileBlob)
formData.append('containerTag', userId)
await fetch('https://api.supermemory.ai/v3/documents/file', { await fetch(`https://api.supermemory.ai/ns/${userId}/document/file`, {
method: 'POST', method: "POST",
headers: { headers: {
'Authorization': `Bearer ${process.env.SUPERMEMORY_API_KEY}`, Authorization: `Bearer ${process.env.SUPERMEMORY_API_KEY}`,
'Content-Type': 'application/json',
}, },
body: formData body: formData,
}) })
// Processing is async - check status before assuming searchable // Processing is async: client.documents.get(userId, documentId)
// GET /v3/documents/{documentId} // wait for system.status === "done" for chunks (searchable content), and system.dreamingStatus === "done" for memories
``` ```
## STEP 7: SEARCH MODES ## STEP 8: SEARCH MODES
```typescript ```typescript
// HYBRID (recommended) - searches memories + document chunks // HYBRID (default) - learned memories plus source chunks
searchMode: 'hybrid' searchMode: "hybrid"
// MEMORIES ONLY - just extracted memories, no original text // MEMORIES ONLY - learned memories, no original text
searchMode: 'memories' searchMode: "memories"
// CHUNKS ONLY - source passages for RAG
searchMode: "chunks"
``` ```
## STEP 8: METADATA FILTERS (if they need secondary filtering) ## STEP 9: METADATA FILTERS (if they need secondary filtering)
```typescript ```typescript
await client.search({ await client.search(userId, {
q: query, query,
containerTag: userId, filter: {
filters: { operator: "and",
AND: [ operands: [
{ key: 'type', value: 'conversation', type: 'string_equal' }, { field: "type", operator: "eq", value: "conversation" },
{ key: 'timestamp', value: '2024', type: 'string_contains' } { field: "title", operator: "contains", value: "2024" },
] ],
} },
}) })
``` ```
## KEY POINTS: ## KEY POINTS:
1. Configure settings FIRST with filterPrompt 1. Every write and every search names one namespace in the URL path
2. User profiles = automatic facts about users (profile.static + profile.dynamic) 2. User profiles = automatic facts about users (profile.static + profile.dynamic)
3. profile({ containerTag, q }) combines profile + search in ONE call 3. `profile(namespace)` takes no query; call `search(namespace, { query })` for message-specific memories
4. Search modes: 'hybrid' (recommended) or 'memories' 4. Search modes: 'hybrid' (default), 'memories', or 'chunks'
5. File extraction is automatic - no config needed 5. File extraction is automatic - no config needed
6. Store conversations after each interaction 6. Store conversations after each interaction under a stable `id` per conversation
7. containerTag should match what you put in filterPrompt 7. Processing is async: `system.status` tracks extraction, and memories are ready when `system.dreamingStatus` is `done` (it reads `dreaming` while they are generated). Pass `dreaming: "instant"` only when memories must be generated right away (it bills one extra operation per document)
## TESTING: ## TESTING:
```bash ```bash
# 1. Configure settings # 1. Add test memory, processed right away
curl -X PATCH https://api.supermemory.ai/v3/settings \ curl -X POST https://api.supermemory.ai/ns/test_user/document \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"shouldLLMFilter": true, "filterPrompt": "..."}' -d '{"content": "Test", "dreaming": "instant"}'
# 2. Add test memory # 2. Get profile
curl -X POST https://api.supermemory.ai/v3/documents \ curl -X POST https://api.supermemory.ai/ns/test_user/profile \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"content": "Test", "containerTag": "test_user"}' -d '{}'
```
# 3. Get profile Or smoke-test from the terminal with the CLI:
curl -X POST https://api.supermemory.ai/v4/profile \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ ```bash
-H "Content-Type: application/json" \ npx supermemory add "Test" --namespace test_user --dreaming instant
-d '{"containerTag": "test_user"}' npx supermemory profile --namespace test_user
``` ```
## NOW: ## NOW:
1. Ask me the 5 questions above 1. Ask me the 5 questions above
2. Generate complete working code based on my answers 2. Generate complete working code based on my answers
3. Include installation, settings config, and full integration 3. Include installation, namespace strategy, and full integration
**DOCS:** https://supermemory.ai/docs **DOCS:** https://supermemory.ai/docs

View file

@ -156,7 +156,7 @@ const model = withSupermemory(openai("gpt-5"), {
## Memory tools ## Memory tools
Add memory capabilities to AI agents with search, add, and fetch operations. Add memory capabilities to AI agents. `supermemoryTools` returns seven tools: `searchMemories`, `addMemory`, `getProfile`, `documentList`, `documentAdd`, `documentDelete`, and `memoryForget`.
```typescript ```typescript
import { streamText } from "ai" import { streamText } from "ai"
@ -223,6 +223,6 @@ const result = await streamText({
{ success: true, results: [...], count: 5 } { success: true, results: [...], count: 5 }
// addMemory result // addMemory result
{ success: true, memory: { id: "mem_123", ... } } { success: true, memory: { id: "doc_123", status: "queued" } }
``` ```

View file

@ -166,6 +166,7 @@ Container tags allow you to organize memories across multiple dimensions:
memory_agent = SupermemoryCartesiaAgent( memory_agent = SupermemoryCartesiaAgent(
agent=base_agent, agent=base_agent,
container_tag="user-alice", # Primary: user ID container_tag="user-alice", # Primary: user ID
custom_id=call_request.call_id, # Required: groups all messages
container_tags=["org-acme", "prod"], # Additional: organization, environment container_tags=["org-acme", "prod"], # Additional: organization, environment
) )
``` ```

View file

@ -42,7 +42,9 @@ icon: "/images/claude-code-icon.svg"
## Authenticate ## Authenticate
Create a Supermemory API key from the [API Keys](https://console.supermemory.ai/keys) page in the console, then add it to your shell profile: Start a Claude Code session and the plugin opens a browser login, then saves the key to `~/.supermemory-claude/credentials.json`.
Or create a Supermemory API key from the [API Keys](https://console.supermemory.ai/keys) page in the console and add it to your shell profile:
<Tabs> <Tabs>
<Tab title="macOS / Linux (zsh)"> <Tab title="macOS / Linux (zsh)">
@ -69,27 +71,28 @@ Create a Supermemory API key from the [API Keys](https://console.supermemory.ai/
Once installed, the plugin runs automatically: 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. - **Session context** — At session start, profile facts for this project are loaded into context.
- **Auto-capture** — Conversations and important tool usage are saved for later sessions. - **Direct recall** — Before Claude sees each substantive prompt, the hook searches Supermemory and injects fresh matches. Set `recallDirective` to switch to advisory mode, where Claude decides when to search.
- **Team memory** — Project knowledge is shared separately from personal memories. - **Auto-capture** — After each Claude response (the Stop hook), conversation content added since the last save is captured in the background.
- **Explicit skills** — Ask Claude to search or save memories when you need control. - **Shared repo memory** — Captures go to one repository container shared with the Codex, OpenCode, and Cursor plugins, tagged with `sm_scope: personal` metadata.
- **MCP tools** — The hosted Supermemory MCP tools (`search_memory`, `add_memory`, and more) use the same credentials. `search_memory` and the legacy camelCase read tools (`listMemories`, `listDocuments`, `getDocument`, `listSpaces`, `whoAmI`) are approved automatically; other tools, including `list_memories`, `list_documents`, `get_document` and `get_profile`, still ask for approval. For deeper history, Claude can also use the `context-gatherer` agent.
## Commands ## Commands
| Command | Description | | Command | Description |
| --- | --- | | --- | --- |
| `/supermemory:index` | Index codebase architecture and patterns | | `/supermemory:index` | Index codebase architecture, conventions, and how to run it into the project container |
| `/supermemory:project-config` | Configure project-level settings | | `/supermemory:status` | Show authentication status, API and MCP reachability, and the active project container |
| `/supermemory:logout` | Clear saved credentials |
| `/supermemory:session` | Show a clickable URL for the current session document |
| `/supermemory:status` | Show authentication status |
## Configuration ## Configuration
### Environment variables ### Environment variables
```bash ```bash
SUPERMEMORY_CC_API_KEY=sm_... # Required SUPERMEMORY_CC_API_KEY=sm_... # Optional: browser login is used when unset
SUPERMEMORY_API_URL=... # Optional: override the REST API base URL
SUPERMEMORY_REPO_TAG=... # Optional: override the project container
SUPERMEMORY_ISOLATE_WORKTREES=true # Optional: key the container on the worktree path
SUPERMEMORY_DEBUG=true # Optional: enable debug logging SUPERMEMORY_DEBUG=true # Optional: enable debug logging
``` ```
@ -118,13 +121,13 @@ Create `~/.supermemory-claude/settings.json`:
### Project config ### Project config
Per-repo overrides in `.claude/.supermemory-claude/config.json`. Run `/supermemory:project-config` or create manually: Per-repo overrides in `.claude/.supermemory-claude/config.json`. Create the file manually:
```json ```json
{ {
"apiKey": "sm_...", "apiKey": "sm_...",
"baseUrl": "https://api.supermemory.ai", "baseUrl": "https://api.supermemory.ai",
"repoContainerTag": "my-team-project", "namespace": "my-team-project",
"signalExtraction": true "signalExtraction": true
} }
``` ```
@ -133,8 +136,11 @@ Per-repo overrides in `.claude/.supermemory-claude/config.json`. Run `/supermemo
| --- | --- | | --- | --- |
| `apiKey` | Project-specific API key | | `apiKey` | Project-specific API key |
| `baseUrl` | Supermemory API URL (use for self-hosted) | | `baseUrl` | Supermemory API URL (use for self-hosted) |
| `personalContainerTag` | Override personal container | | `apiVersion` | `v5` or `legacy`. The hosted API uses v5; custom `baseUrl` values default to `legacy`. Set `v5` to use v5 with a self-hosted server 0.0.9 or newer |
| `repoContainerTag` | Override team container tag | | `namespace` | Override the project container. Takes precedence over `repoContainerTag` |
| `repoContainerTag` | Override the project container (checked before `SUPERMEMORY_REPO_TAG`) |
API key precedence is `SUPERMEMORY_CC_API_KEY`, project `apiKey`, then `~/.supermemory-claude/credentials.json`.
## Next steps ## Next steps

View file

@ -84,14 +84,9 @@ console.log(response.content[0])
import { createClaudeMemoryTool } from "@supermemory/tools/claude-memory" import { createClaudeMemoryTool } from "@supermemory/tools/claude-memory"
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, { const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
// Scope memories to a project or user // Scope memories to a project or user (default: "sm_project_default")
namespace: "my-app",
// Or use container tags for more flexibility
namespace: "user-123", namespace: "user-123",
// Custom memory container prefix (default: "claude_memory")
// Custom API endpoint // Custom API endpoint
baseUrl: "https://custom.api.com", baseUrl: "https://custom.api.com",
}) })
@ -121,7 +116,7 @@ All memory paths must start with `/memories/`:
``` ```
<Note> <Note>
Paths are normalized for storage: `/memories/preferences` is stored as `--memories--preferences`. Each file is stored as one document in the namespace, tagged with `metadata.source = "claude-memory"`. Its document ID is a reversible encoding of the path: `/memories/preferences.txt` is stored as `cm_memories_s_preferences_d_txt`.
</Note> </Note>
## Commands reference ## Commands reference
@ -182,7 +177,7 @@ Paths are normalized for storage: `/memories/preferences` is stored as `--memori
```typescript ```typescript
{ {
command: "rename", command: "rename",
path: "/memories/old-name.txt", old_path: "/memories/old-name.txt",
new_path: "/memories/new-name.txt" new_path: "/memories/new-name.txt"
} }
``` ```
@ -199,7 +194,7 @@ const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
}) })
async function runConversation() { async function runConversation() {
const messages: Anthropic.MessageParam[] = [] const messages: Anthropic.Beta.BetaMessageParam[] = []
// Helper to chat with memory // Helper to chat with memory
async function chat(userMessage: string) { async function chat(userMessage: string) {

View file

@ -5,10 +5,10 @@ description: "Persistent memory for the OpenAI Codex CLI with the codex-supermem
icon: "/images/codex.svg" icon: "/images/codex.svg"
--- ---
[codex-supermemory](https://github.com/supermemoryai/codex-supermemory) wires Supermemory into the [OpenAI Codex CLI](https://github.com/openai/codex) via hooks and skills. Your agent gets **two layers of memory**: [codex-supermemory](https://github.com/supermemoryai/codex-supermemory) wires Supermemory into the [OpenAI Codex CLI](https://github.com/openai/codex) via hooks and the hosted Supermemory MCP server. Your agent gets **two layers of memory**:
- **Implicit** (hooks) — automatically recalls context before each prompt and captures conversations incrementally during the session. - **Implicit** (hooks) — automatically recalls context before each prompt and captures completed turns in the background.
- **Explicit** (skills) — lets you or the agent save, search, and manage memories on demand. - **Explicit** (MCP tools) — lets you or the agent search, save, list, and forget memories on demand.
<Tip> <Tip>
**Prefer to keep everything on your machine?** This plugin works with [self-hosted Supermemory](/self-hosting/overview) — run `npx supermemory local`, then `export SUPERMEMORY_API_URL="http://localhost:6767"` (or set `baseUrl` in `~/.codex/supermemory.json`) and use the API key printed on first boot. **Prefer to keep everything on your machine?** This plugin works with [self-hosted Supermemory](/self-hosting/overview) — run `npx supermemory local`, then `export SUPERMEMORY_API_URL="http://localhost:6767"` (or set `baseUrl` in `~/.codex/supermemory.json`) and use the API key printed on first boot.
@ -22,21 +22,18 @@ npx codex-supermemory@latest install
This command: This command:
- Copies hook and skill scripts to `~/.codex/supermemory/` - Copies the pre-bundled hook scripts and MCP proxy to `~/.codex/supermemory/`
- Enables `codex_hooks` in `~/.codex/config.toml` - Registers the `supermemory` MCP server in `~/.codex/config.toml`
- Registers `UserPromptSubmit` (recall) and `Stop` (flush) hooks in `~/.codex/hooks.json` - Registers `SessionStart`, `UserPromptSubmit`, `PreToolUse`, and `Stop` hooks in `~/.codex/hooks.json`
- Installs explicit memory skills under `~/.codex/skills/` - Installs the `supermemory-status` and `supermemory-index` skills under `~/.codex/skills/`
Restart Codex CLI after installing. Restart Codex CLI after installing.
## Authenticate ## Authenticate
**Browser auth is preferred.** Start Codex CLI — on your first prompt a browser window opens to authenticate with Supermemory. **Browser auth is preferred.** Start Codex CLI — a browser window opens to authenticate with Supermemory.
Alternatively: Alternatively, set an API key from [API Keys](https://console.supermemory.ai/keys):
- Use `$supermemory-login` / `/supermemory-login` inside Codex
- Or set an API key from [API Keys](https://console.supermemory.ai/keys):
<Tabs> <Tabs>
<Tab title="macOS / Linux (zsh)"> <Tab title="macOS / Linux (zsh)">
@ -65,41 +62,42 @@ Once installed, the plugin runs on every Codex session:
| Hook | Event | What it does | | Hook | Event | What it does |
|------|-------|--------------| |------|-------|--------------|
| `recall` | `UserPromptSubmit` | Captures new turns (every N prompts), searches Supermemory, injects memories + profile as `additionalContext` | | `session-start` | `SessionStart` | Loads persistent and recent profile context for the session |
| `flush` | `Stop` | Captures any remaining turns at session end so nothing is lost | | `recall` | `UserPromptSubmit` | Searches Supermemory directly for substantive prompts and injects fresh relevant memories |
| `recall-approve` | `PreToolUse` | Auto-allows `search_memory` and the legacy camelCase read tools (`listMemories`, `listDocuments`, `getDocument`, `listSpaces`, `whoAmI`); other Supermemory MCP tools still ask |
| `flush` | `Stop` | Captures completed turns in the background |
- **Incremental capture** — Memories are saved every N turns (default: 3) so mid-session context is available for later prompts in the same session. - **Recall modes** — Set `recallMode` to `"advisory"` to let the model decide when to search, or `"off"` to disable prompt recall.
- **Privacy** — Content wrapped in `<private>...</private>` is redacted before storage. - **Privacy** — Content wrapped in `<private>...</private>` is redacted before storage.
### Memory scopes ### Memory scopes
| Tag | Derived from | Description | Codex, Claude Code, and OpenCode use one container per repository:
|-----|-------------|-------------|
| 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: ```text
repo_<project-name>__<remote-hash>
```
Automatic captures go to this container with `sm_scope: personal` metadata. Explicit saves through the MCP tools are passed to the hosted server unchanged, so they land in your account's active space, not the repository container. The hash comes from the normalized Git remote, so clones and linked worktrees share memory while same-named repositories do not collide. Codex still reads the older `codex_user_*`, `codex_project_*`, and other per-tool containers, so existing memories stay searchable.
Override the container in `~/.codex/supermemory.json` if needed:
```json ```json
{ {
"userContainerTag": "my-custom-user-tag",
"projectContainerTag": "my-custom-project-tag" "projectContainerTag": "my-custom-project-tag"
} }
``` ```
Set `SUPERMEMORY_ISOLATE_WORKTREES=true` to keep each worktree isolated. Set `SUPERMEMORY_ISOLATE_WORKTREES=true` to keep each worktree isolated.
## Explicit memory skills ## Explicit memory
Search, save, list, and forget go through the hosted [Supermemory MCP](/supermemory-mcp/mcp) tools, using the same credentials as the hooks. The installer also adds two skills:
| Skill | Description | | Skill | Description |
|-------|-------------| |-------|-------------|
| `supermemory-search` | Search memories by natural-language query | | `supermemory-status` | Check the saved credential, API reachability, active project container, and account |
| `supermemory-save` | Save important project knowledge | | `supermemory-index` | Explore the repository and save focused memories about architecture, conventions, and how to run it |
| `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: Example prompts:
@ -122,7 +120,7 @@ npx codex-supermemory status
npx codex-supermemory uninstall npx codex-supermemory uninstall
``` ```
This removes hook registrations and skill scripts. Your existing memories in Supermemory are preserved. This removes the hook registrations, MCP server entry, and skills. Your existing memories in Supermemory are preserved.
## Configuration ## Configuration
@ -136,8 +134,7 @@ Create `~/.codex/supermemory.json` to override defaults:
"maxMemories": 5, "maxMemories": 5,
"maxProfileItems": 5, "maxProfileItems": 5,
"injectProfile": true, "injectProfile": true,
"containerTagPrefix": "codex", "recallMode": "direct",
"autoSaveEveryTurns": 3,
"signalExtraction": false, "signalExtraction": false,
"debug": false "debug": false
} }
@ -147,12 +144,14 @@ Create `~/.codex/supermemory.json` to override defaults:
|--------|---------|-------------| |--------|---------|-------------|
| `apiKey` | — | API key (env / browser auth preferred) | | `apiKey` | — | API key (env / browser auth preferred) |
| `baseUrl` | `https://api.supermemory.ai` | API base URL (`SUPERMEMORY_API_URL` overrides) | | `baseUrl` | `https://api.supermemory.ai` | API base URL (`SUPERMEMORY_API_URL` overrides) |
| `apiVersion` | auto | `v5` or `legacy`. The hosted API uses v5; custom URLs default to `legacy`. Set `v5` to use v5 with a self-hosted server 0.0.9 or newer |
| `similarityThreshold` | `0.6` | Minimum match score for recall (0–1) | | `similarityThreshold` | `0.6` | Minimum match score for recall (0–1) |
| `maxMemories` | `5` | Max memories injected per prompt | | `maxMemories` | `5` | Max memories injected per prompt |
| `maxProfileItems` | `5` | Max profile facts injected per prompt | | `maxProfileItems` | `5` | Max profile facts injected per prompt |
| `injectProfile` | `true` | Include user profile in context | | `injectProfile` | `true` | Include user profile in context |
| `containerTagPrefix` | `"codex"` | Prefix for auto-generated container tags | | `projectContainerTag` | auto (per repo) | Override the unified project container |
| `autoSaveEveryTurns` | `3` | Save memories every N turns | | `recallMode` | `"direct"` | `"direct"`, `"advisory"`, or `"off"` |
| `recallDirective` | built-in | Context injected when `recallMode` is `"advisory"` |
| `signalExtraction` | `false` | Only capture turns with signal keywords | | `signalExtraction` | `false` | Only capture turns with signal keywords |
| `debug` | `false` | Write debug logs to `~/.codex-supermemory.log` | | `debug` | `false` | Write debug logs to `~/.codex-supermemory.log` |

View file

@ -81,7 +81,7 @@ Credentials are stored in `~/.supermemory-cursor/credentials.json`.
| Session profile | Loads your persistent profile when a Cursor conversation starts | | 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 | | 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 | | Incremental capture | Saves each completed turn, and retries unsaved transcript deltas at session end |
| MCP tools | Explicit memory control from any Cursor AI session | | MCP tools | The hosted Supermemory MCP tools, proxied over stdio with the same credentials as the hooks |
| Context gatherer | Fans out targeted searches before substantial work | | Context gatherer | Fans out targeted searches before substantial work |
| Always-on rule | Makes the agent recall relevant history proactively | | Always-on rule | Makes the agent recall relevant history proactively |
@ -93,6 +93,7 @@ Credentials are stored in `~/.supermemory-cursor/credentials.json`.
| `memory-save` | Skill | Save an insight, decision, or solution worth keeping | | `memory-save` | Skill | Save an insight, decision, or solution worth keeping |
| `memory-search` | Skill | Search memory for past work, bugs, and decisions | | `memory-search` | Skill | Search memory for past work, bugs, and decisions |
| `supermemory-context-gatherer` | Agent | Gather deep background before substantial work | | `supermemory-context-gatherer` | Agent | Gather deep background before substantial work |
| `supermemory-index` | Command | Index codebase architecture and patterns into Supermemory |
| `supermemory-setup` | Command | Connect Supermemory to Cursor | | `supermemory-setup` | Command | Connect Supermemory to Cursor |
| `supermemory-status` | Command | Check authentication and live connectivity | | `supermemory-status` | Command | Check authentication and live connectivity |
| `supermemory-config` | Command | Create or edit the project config file | | `supermemory-config` | Command | Create or edit the project config file |
@ -100,25 +101,18 @@ Credentials are stored in `~/.supermemory-cursor/credentials.json`.
## MCP tools ## MCP tools
The plugin proxies the hosted [Supermemory MCP](/supermemory-mcp/mcp) server (`https://mcp.supermemory.ai/mcp`) over stdio, using the same credentials as the hooks. Claude Code and Codex use the same server, so all three agents see one tool set. The bundled rule, skills, and agent mainly use these:
| Tool | Description | | Tool | Description |
|------|-------------| |------|-------------|
| `supermemory_get_config` | Show current config, resolved container tags, and config file paths | | `search_memory` | Search memories in one space |
| `supermemory_set_config` | Update config at project or global scope | | `add_memory` | Save a memory |
| `supermemory_containers` | Show what `user` and `project` container tags resolve to | | `forget_memory` | Forget outdated memories |
| `supermemory_search` | Search memories by query | | `list_memories` | List recent memories with their IDs |
| `supermemory_add` | Save new information to memory | | `list_documents` / `get_document` | Browse and read stored documents |
| `supermemory_list` | List stored memories | | `list_spaces` / `who_am_i` | Resolve a named space, or report the active account and space |
| `supermemory_forget` | Delete a memory by id or content |
| `supermemory_profile` | Get your user profile summary |
Every tool that takes a `container` argument accepts: Pass `containerTag` on every call, using the tag from the `<supermemory-context>` block the session-start hook injects. Without it, the hosted server uses the account's active space, and this project's hook recall will not find the memory. Config lives in files rather than tools: see [Configuration](#configuration) or run `/supermemory-config`.
- `"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 ## Container tags
@ -144,6 +138,9 @@ The plugin still reads the former `cursor_user_*` and `cursor_project_*` tags, a
|----------|-------------| |----------|-------------|
| `SUPERMEMORY_API_KEY` | API key (overrides all other sources) | | `SUPERMEMORY_API_KEY` | API key (overrides all other sources) |
| `SUPERMEMORY_API_URL` | Override the Supermemory API base URL | | `SUPERMEMORY_API_URL` | Override the Supermemory API base URL |
| `SUPERMEMORY_BASE_URL` | API base URL fallback when `SUPERMEMORY_API_URL` is unset |
| `SUPERMEMORY_API_VERSION` | `v5` or `legacy` REST contract; overrides config `apiVersion` |
| `SUPERMEMORY_MCP_URL` | Override the hosted MCP endpoint the proxy connects to |
| `SUPERMEMORY_REPO_TAG` | Override the unified repository container tag | | `SUPERMEMORY_REPO_TAG` | Override the unified repository container tag |
| `SUPERMEMORY_USER_TAG` | Legacy Cursor personal container to continue reading | | `SUPERMEMORY_USER_TAG` | Legacy Cursor personal container to continue reading |
| `SUPERMEMORY_PROJECT_TAG` | Legacy Cursor project container to continue reading | | `SUPERMEMORY_PROJECT_TAG` | Legacy Cursor project container to continue reading |
@ -183,6 +180,7 @@ The plugin still reads the former `cursor_user_*` and `cursor_project_*` tags, a
|--------|---------|-------------| |--------|---------|-------------|
| `apiKey` | — | Project-specific API key | | `apiKey` | — | Project-specific API key |
| `baseUrl` | Supermemory API | Override the Supermemory API base URL | | `baseUrl` | Supermemory API | Override the Supermemory API base URL |
| `apiVersion` | v5 for the hosted API, `legacy` for custom URLs | Set `v5` to use v5 with a self-hosted server 0.0.9 or newer |
| `repoContainerTag` | derived from normalized Git remote or project path | Override the unified repository container | | `repoContainerTag` | derived from normalized Git remote or project path | Override the unified repository container |
| `userContainerTag` | — | Legacy Cursor personal container to continue reading | | `userContainerTag` | — | Legacy Cursor personal container to continue reading |
| `projectContainerTag` | — | Legacy Cursor project container to continue reading | | `projectContainerTag` | — | Legacy Cursor project container to continue reading |
@ -193,7 +191,7 @@ The plugin still reads the former `cursor_user_*` and `cursor_project_*` tags, a
| `signalKeywords` | `remember`, `architecture`, `decision`, `bug`, `fix` | Keywords that trigger signal-based capture | | `signalKeywords` | `remember`, `architecture`, `decision`, `bug`, `fix` | Keywords that trigger signal-based capture |
| `signalTurnsBefore` | `3` | Number of nearby turns retained around a signal | | `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. Edit the file by hand, or run the `/supermemory-config` command.
## Log out ## Log out

View file

@ -6,7 +6,7 @@ icon: "/images/hermes.svg"
--- ---
[Hermes agent](https://github.com/NousResearch/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. The [hermes-supermemory](https://github.com/supermemoryai/hermes-supermemory) plugin is the **supermemory** memory provider for [Hermes agent](https://github.com/NousResearch/hermes-agent): semantic long-term memory, profile recall, search, explicit memory tools, and per-turn conversation capture — not a bolt-on script the model might skip. It previously shipped inside Hermes core and is now maintained by Supermemory as a Hermes plugin. 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. 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.
@ -18,13 +18,13 @@ Create a supermemory API key from the [API Keys](https://console.supermemory.ai/
## Install the memory provider ## Install the memory provider
```bash ```bash
pip install supermemory hermes plugins install supermemoryai/hermes-supermemory
``` hermes plugins enable supermemory
```bash
hermes memory setup hermes memory setup
``` ```
Hermes installs the plugin's dependency, the `supermemory` Python SDK (`>=5.0.0,<6`), into its own environment.
Select **supermemory** when prompted and paste your API key. Select **supermemory** when prompted and paste your API key.
Or set the provider and key manually: Or set the provider and key manually:
@ -41,9 +41,8 @@ echo 'SUPERMEMORY_API_KEY=sm_...' >> ~/.hermes/.env
Once configured, the provider runs through Hermes’s normal memory lifecycle: Once configured, the provider runs through Hermes’s normal memory lifecycle:
- **Prefetch** — Relevant memory context can be loaded before each turn. - **Prefetch** — Relevant memory context can be loaded before each turn.
- **Turn capture** — Cleaned user/assistant turns can be stored after each completed response. - **Turn capture** — After each completed turn, the conversation is saved as one document per conversation, rewritten in full, so edits and context compaction never store it twice. Remaining turns are flushed at session end.
- **Session ingest** — The full session can be ingested at session end for richer graph updates. - **Explicit tools** — Search, store, forget, profile, and read-document tools are available to the model when appropriate.
- **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). - **Built-in file memory** — This does not replace `MEMORY.md` / `USER.md`; mirroring behavior depends on Hermes version and config (see upstream README).
## Tools ## Tools
@ -56,6 +55,7 @@ Kebab-case names are registered for the agent; snake_case aliases remain support
| `supermemory-search` | `supermemory_search` | Search by semantic similarity. | | `supermemory-search` | `supermemory_search` | Search by semantic similarity. |
| `supermemory-forget` | `supermemory_forget` | Forget a memory by ID or best-match query. | | `supermemory-forget` | `supermemory_forget` | Forget a memory by ID or best-match query. |
| `supermemory-profile` | `supermemory_profile` | Retrieve persistent profile and recent context. | | `supermemory-profile` | `supermemory_profile` | Retrieve persistent profile and recent context. |
| `supermemory-read-document` | `supermemory_read_document` | Read a stored document, such as a past conversation, by the `document_id` from search results. |
## Commands ## Commands
@ -72,6 +72,7 @@ These variables configure the supermemory provider (for example in your shell or
```bash ```bash
SUPERMEMORY_API_KEY=sm_... # Required SUPERMEMORY_API_KEY=sm_... # Required
SUPERMEMORY_CONTAINER_TAG=hermes-work # Optional: overrides container tag from config SUPERMEMORY_CONTAINER_TAG=hermes-work # Optional: overrides container tag from config
SUPERMEMORY_BASE_URL=http://localhost:6767 # Optional: API endpoint when base_url is not set in config
``` ```
## Multi-container tags ## Multi-container tags
@ -103,7 +104,7 @@ Example:
} }
``` ```
See the [upstream plugin README](https://github.com/NousResearch/hermes-agent/tree/main/plugins/memory/supermemory) for the exact schema and any newer options. See the [plugin README](https://github.com/supermemoryai/hermes-supermemory) for the exact schema and any newer options.
## Config file ## Config file
@ -111,13 +112,16 @@ Create or edit `$HERMES_HOME/supermemory.json`. Common keys:
| Key | Default | Description | | Key | Default | Description |
|-----|---------|-------------| |-----|---------|-------------|
| `base_url` | `https://api.supermemory.ai` | API endpoint for hosted or self-hosted Supermemory. Takes priority over `SUPERMEMORY_BASE_URL`. |
| `container_tag` | `hermes` | Tag for search/writes; use `{identity}` for profile-scoped tags (e.g. `hermes-{identity}` → `hermes-coder`). | | `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_recall` | `true` | Inject memory context before turns. |
| `auto_capture` | `true` | Store turns after each response. | | `auto_capture` | `true` | Store turns after each response. |
| `max_recall_results` | `10` | Max items merged into context. | | `max_recall_results` | `10` | Max items merged into context. |
| `profile_frequency` | `50` | Profile on first turn and every N turns. | | `profile_frequency` | `50` | Profile on first turn and every N turns. |
| `capture_mode` | `all` | How aggressively turns are captured. | | `capture_mode` | `all` | How aggressively turns are captured. |
| `search_mode` | `hybrid` | `hybrid`, `memories`, or `documents`. | | `search_mode` | `hybrid` | `hybrid`, `memories`, or `chunks` (`documents` is still accepted as an alias for `chunks`). |
| `entity_context` | built-in | Extraction guidance passed to Supermemory. |
| `dreaming` | `dynamic` | `dynamic` groups related conversations before forming memories; `instant` processes each one right away (billed as one extra operation). |
| `api_timeout` | `5.0` | SDK / ingest timeout (seconds). | | `api_timeout` | `5.0` | SDK / ingest timeout (seconds). |
| `enable_custom_container_tags` | `false` | Set `true` to allow extra namespaces; see [Multi-container tags](#multi-container-tags) above. | | `enable_custom_container_tags` | `false` | Set `true` to allow extra namespaces; see [Multi-container tags](#multi-container-tags) above. |
| `custom_containers` | — | Allowlisted tags beyond the primary `container_tag`. | | `custom_containers` | — | Allowlisted tags beyond the primary `container_tag`. |
@ -136,12 +140,20 @@ Example profile-scoped container:
## Self-hosted API ## 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](https://github.com/NousResearch/hermes-agent/tree/main/plugins/memory/supermemory) — alongside your key and container settings. If you run your own supermemory API, start it with `npx supermemory local` and note the API key it prints on first boot. Before running `hermes memory setup`, set **`base_url`** in `$HERMES_HOME/supermemory.json` so the setup connection probe also stays local:
```json
{
"base_url": "http://localhost:6767"
}
```
Then run `hermes memory setup` and enter the local server's API key.
## Next steps ## Next steps
<CardGroup cols={2}> <CardGroup cols={2}>
<Card title="Hermes + plugin README" icon="/images/github-icon.svg" href="https://github.com/NousResearch/hermes-agent/tree/main/plugins/memory/supermemory"> <Card title="Hermes plugin README" icon="/images/github-icon.svg" href="https://github.com/supermemoryai/hermes-supermemory">
Full config table, env vars, and multi-container details. Full config table, env vars, and multi-container details.
</Card> </Card>

View file

@ -53,7 +53,7 @@ const response = await agent.generate("What do you know about me?")
```typescript ```typescript
const agent = new Agent(withSupermemory( const agent = new Agent(withSupermemory(
{ id: "my-assistant", model: openai("gpt-4o"), ... }, { id: "my-assistant", name: "My Assistant", model: openai("gpt-4o"), ... },
{ {
namespace: "user-123", namespace: "user-123",
id: "conv-456", id: "conv-456",
@ -140,6 +140,7 @@ const agent = new Agent(withSupermemory(config, {
id: "conv-456", id: "conv-456",
mode: "full", mode: "full",
})) }))
```
### Mode Comparison ### Mode Comparison
@ -157,7 +158,7 @@ Conversation saving is enabled by default (`addMemory: "always"`). Messages are
```typescript ```typescript
const agent = new Agent(withSupermemory( const agent = new Agent(withSupermemory(
{ id: "my-assistant", model: openai("gpt-4o"), instructions: "..." }, { id: "my-assistant", name: "My Assistant", model: openai("gpt-4o"), instructions: "..." },
{ {
namespace: "user-123", namespace: "user-123",
id: "conv-456", // Required: groups messages for contextual memory id: "conv-456", // Required: groups messages for contextual memory
@ -173,7 +174,7 @@ To disable automatic saving:
```typescript ```typescript
const agent = new Agent(withSupermemory( const agent = new Agent(withSupermemory(
{ id: "my-assistant", model: openai("gpt-4o"), instructions: "..." }, { id: "my-assistant", name: "My Assistant", model: openai("gpt-4o"), instructions: "..." },
{ {
namespace: "user-123", namespace: "user-123",
id: "conv-456", id: "conv-456",
@ -192,6 +193,7 @@ Customize how memories are formatted and injected. The template receives `userMe
import { Agent } from "@mastra/core/agent" import { Agent } from "@mastra/core/agent"
import { withSupermemory } from "@supermemory/tools/mastra" import { withSupermemory } from "@supermemory/tools/mastra"
import type { MemoryPromptData } from "@supermemory/tools/mastra" import type { MemoryPromptData } from "@supermemory/tools/mastra"
import { openai } from "@ai-sdk/openai"
const claudePrompt = (data: MemoryPromptData) => ` const claudePrompt = (data: MemoryPromptData) => `
<context> <context>
@ -205,7 +207,7 @@ const claudePrompt = (data: MemoryPromptData) => `
`.trim() `.trim()
const agent = new Agent(withSupermemory( const agent = new Agent(withSupermemory(
{ id: "my-assistant", model: openai("gpt-4o"), instructions: "..." }, { id: "my-assistant", name: "My Assistant", model: openai("gpt-4o"), instructions: "..." },
{ {
namespace: "user-123", namespace: "user-123",
id: "conv-456", id: "conv-456",
@ -306,7 +308,7 @@ import { withSupermemory } from "@supermemory/tools/mastra"
import { openai } from "@ai-sdk/openai" import { openai } from "@ai-sdk/openai"
const agent = new Agent(withSupermemory( const agent = new Agent(withSupermemory(
{ id: "my-assistant", model: openai("gpt-4o"), instructions: "..." }, { id: "my-assistant", name: "My Assistant", model: openai("gpt-4o"), instructions: "..." },
{ {
namespace: "user-123", namespace: "user-123",
id: "fallback-conv", // Used only when RequestContext doesn't provide a threadId id: "fallback-conv", // Used only when RequestContext doesn't provide a threadId
@ -334,7 +336,7 @@ Enable detailed logging for debugging:
```typescript ```typescript
const agent = new Agent(withSupermemory( const agent = new Agent(withSupermemory(
{ id: "my-assistant", model: openai("gpt-4o"), instructions: "..." }, { id: "my-assistant", name: "My Assistant", model: openai("gpt-4o"), instructions: "..." },
{ {
namespace: "user-123", namespace: "user-123",
id: "conv-456", id: "conv-456",
@ -361,6 +363,8 @@ The wrapper correctly merges with existing processors in the config:
const agent = new Agent(withSupermemory( const agent = new Agent(withSupermemory(
{ {
id: "my-assistant", id: "my-assistant",
name: "My Assistant",
instructions: "You are a helpful assistant.",
model: openai("gpt-4o"), model: openai("gpt-4o"),
inputProcessors: [myLoggingProcessor], inputProcessors: [myLoggingProcessor],
outputProcessors: [myAnalyticsProcessor], outputProcessors: [myAnalyticsProcessor],
@ -461,7 +465,7 @@ Processors gracefully handle errors without breaking the agent:
```typescript ```typescript
// Missing API key throws immediately // Missing API key throws immediately
const agent = new Agent(withSupermemory( const agent = new Agent(withSupermemory(
{ id: "my-assistant", model: openai("gpt-4o"), instructions: "..." }, { id: "my-assistant", name: "My Assistant", model: openai("gpt-4o"), instructions: "..." },
{ {
namespace: "user-123", namespace: "user-123",
id: "conv-456", id: "conv-456",

View file

@ -25,11 +25,11 @@ npm install @supermemory/memory-graph
'use client'; // For Next.js App Router 'use client'; // For Next.js App Router
import { MemoryGraph } from '@supermemory/memory-graph'; import { MemoryGraph } from '@supermemory/memory-graph';
import type { DocumentWithMemories } from '@supermemory/memory-graph'; import type { GraphApiDocument } from '@supermemory/memory-graph';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
export default function GraphPage() { export default function GraphPage() {
const [documents, setDocuments] = useState<DocumentWithMemories[]>([]); const [documents, setDocuments] = useState<GraphApiDocument[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null); const [error, setError] = useState<Error | null>(null);
@ -78,7 +78,7 @@ Existing icon-based document nodes and connection styling remain the defaults. O
## Backend API route ## Backend API route
Create an API route to fetch documents from Supermemory: Create an API route to fetch documents from Supermemory. The endpoint returns each document's memories as `memoryEntries` and its type as `type`; `MemoryGraph` reads `memories` and `documentType`, so map each document before returning it:
<CodeGroup> <CodeGroup>
@ -102,7 +102,14 @@ export async function GET() {
}); });
const data = await response.json(); const data = await response.json();
return NextResponse.json(data); return NextResponse.json({
...data,
documents: data.documents.map((doc: { type: string; memoryEntries: unknown[] }) => ({
...doc,
documentType: doc.type,
memories: doc.memoryEntries,
})),
});
} }
``` ```
@ -121,7 +128,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}); });
const data = await response.json(); const data = await response.json();
res.json(data); res.json({
...data,
documents: data.documents.map((doc: { type: string; memoryEntries: unknown[] }) => ({
...doc,
documentType: doc.type,
memories: doc.memoryEntries,
})),
});
} }
``` ```
@ -137,7 +151,14 @@ app.get('/api/graph', async (req, res) => {
}); });
const data = await response.json(); const data = await response.json();
res.json(data); res.json({
...data,
documents: data.documents.map((doc) => ({
...doc,
documentType: doc.type,
memories: doc.memoryEntries,
})),
});
}); });
``` ```
@ -151,13 +172,13 @@ app.get('/api/graph', async (req, res) => {
## Variants ## Variants
**Console Variant** - Full-featured dashboard view (0.8x zoom, space selector visible): **Console Variant** - Full dashboard view: themed surface with a dot grid, nodes colored by theme:
```tsx ```tsx
<MemoryGraph documents={documents} variant="console" /> <MemoryGraph documents={documents} variant="console" />
``` ```
**Consumer Variant** - Embedded widget view (0.5x zoom, space selector hidden): **Consumer Variant** - Embedded widget view: transparent surface, nodes colored by cluster:
```tsx ```tsx
<MemoryGraph documents={documents} variant="consumer" /> <MemoryGraph documents={documents} variant="consumer" />
@ -173,10 +194,11 @@ app.get('/api/graph', async (req, res) => {
'use client'; 'use client';
import { MemoryGraph } from '@supermemory/memory-graph'; import { MemoryGraph } from '@supermemory/memory-graph';
import type { GraphApiDocument } from '@supermemory/memory-graph';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
export default function PaginatedGraph() { export default function PaginatedGraph() {
const [documents, setDocuments] = useState([]); const [documents, setDocuments] = useState<GraphApiDocument[]>([]);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true); const [hasMore, setHasMore] = useState(true);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
@ -184,7 +206,7 @@ export default function PaginatedGraph() {
useEffect(() => { fetchPage(1, false); }, []); useEffect(() => { fetchPage(1, false); }, []);
const fetchPage = async (pageNum, append) => { const fetchPage = async (pageNum: number, append: boolean) => {
pageNum === 1 ? setIsLoading(true) : setIsLoadingMore(true); pageNum === 1 ? setIsLoading(true) : setIsLoadingMore(true);
const res = await fetch(`/api/graph?page=${pageNum}&limit=100`); const res = await fetch(`/api/graph?page=${pageNum}&limit=100`);
@ -210,8 +232,7 @@ export default function PaginatedGraph() {
isLoading={isLoading} isLoading={isLoading}
isLoadingMore={isLoadingMore} isLoadingMore={isLoadingMore}
hasMore={hasMore} hasMore={hasMore}
totalLoaded={documents.length} onLoadMore={loadMore}
loadMoreDocuments={loadMore}
/> />
); );
} }
@ -227,17 +248,6 @@ export default function PaginatedGraph() {
/> />
``` ```
### Controlled space selection
```tsx
<MemoryGraph
documents={documents}
selectedSpace={selectedSpace}
onSpaceChange={setSelectedSpace}
showSpacesSelector={false}
/>
```
### Custom empty state ### Custom empty state
```tsx ```tsx
@ -257,7 +267,7 @@ export default function PaginatedGraph() {
| Prop | Type | Default | Description | | Prop | Type | Default | Description |
|------|------|---------|-------------| |------|------|---------|-------------|
| `documents` | `DocumentWithMemories[]` | required | Array of documents to display | | `documents` | `GraphApiDocument[]` | `[]` | Array of documents to display |
| `isLoading` | `boolean` | `false` | Shows loading indicator | | `isLoading` | `boolean` | `false` | Shows loading indicator |
| `error` | `Error \| null` | `null` | Error to display | | `error` | `Error \| null` | `null` | Error to display |
| `variant` | `"console" \| "consumer"` | `"console"` | Visual variant | | `variant` | `"console" \| "consumer"` | `"console"` | Visual variant |
@ -269,71 +279,68 @@ export default function PaginatedGraph() {
|------|------|---------|-------------| |------|------|---------|-------------|
| `isLoadingMore` | `boolean` | `false` | Shows indicator when loading more | | `isLoadingMore` | `boolean` | `false` | Shows indicator when loading more |
| `hasMore` | `boolean` | `false` | Whether more documents available | | `hasMore` | `boolean` | `false` | Whether more documents available |
| `totalLoaded` | `number` | - | Total documents currently loaded | | `onLoadMore` | `() => void` | - | Called to load more, for example when the user zooms out past the loaded nodes |
| `loadMoreDocuments` | `() => Promise<void>` | - | Callback to load more | | `totalCount` | `number` | - | Total document count shown by the loading indicator |
| `autoLoadOnViewport` | `boolean` | `true` | Auto-load when 80% visible |
### Display props ### Display props
| Prop | Type | Default | Description | | Prop | Type | Default | Description |
|------|------|---------|-------------| |------|------|---------|-------------|
| `showSpacesSelector` | `boolean` | variant-based | Show space filter dropdown |
| `highlightDocumentIds` | `string[]` | `[]` | Document IDs to highlight | | `highlightDocumentIds` | `string[]` | `[]` | Document IDs to highlight |
| `highlightsVisible` | `boolean` | `true` | Whether highlights shown | | `highlightsVisible` | `boolean` | `true` | Whether highlights shown |
| `occludedRightPx` | `number` | `0` | Pixels occluded on right | | `maxNodes` | `number` | - | Maximum number of nodes to display |
| `colors` | `Partial<GraphThemeColors>` | - | Theme color overrides |
### Controlled state props | `labels` | `MemoryGraphLabels` | - | User-facing label overrides |
| `appearance` | `GraphAppearance` | - | Opt-in document node and connection styles (see above) |
| Prop | Type | Description | | `openDocumentOnEnter` | `boolean` | `false` | Enable the Enter action that calls `onOpenDocument` |
|------|------|-------------| | `onOpenDocument` | `(documentId: string) => void` | - | Called when the user opens a document |
| `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 ## Data types
### DocumentWithMemories ### GraphApiDocument
```typescript ```typescript
interface DocumentWithMemories { interface GraphApiDocument {
id: string; id: string;
customId?: string | null; title: string | null;
title?: string | null; summary: string | null;
documentType: string;
createdAt: string;
updatedAt: string;
memories: GraphApiMemory[];
}
```
### GraphApiMemory
```typescript
interface GraphApiMemory {
id: string;
memory: string;
content?: string | null; content?: string | null;
summary?: string | null; isStatic: boolean;
url?: string | null; spaceId: string;
source?: string | null; isLatest: boolean;
type?: string | null; isForgotten: boolean;
status: 'pending' | 'processing' | 'done' | 'failed'; forgetAfter: string | null;
metadata?: Record<string, string | number | boolean> | null; forgetReason: string | null;
createdAt: string | Date; version: number;
updatedAt: string | Date; parentMemoryId: string | null;
memoryEntries: MemoryEntry[]; rootMemoryId: string | null;
} createdAt: string;
``` updatedAt: string;
### MemoryEntry
```typescript
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; relation?: 'updates' | 'extends' | 'derives' | null;
isLatest?: boolean; updatesMemoryId?: string | null;
spaceId?: string | null; nextVersionId?: string | null;
memoryRelations?: Record<string, 'updates' | 'extends' | 'derives'> | null;
spaceContainerTag?: string | null;
} }
``` ```
`DocumentWithMemories` and `MemoryEntry` are still exported as backward-compatible types.
--- ---
## Exports ## Exports
@ -341,26 +348,36 @@ interface MemoryEntry {
### Components ### Components
```typescript ```typescript
import { import { MemoryGraph, GraphCanvas } from '@supermemory/memory-graph';
MemoryGraph,
GraphCanvas,
Legend,
LoadingIndicator,
NodeDetailPanel,
SpacesDropdown
} from '@supermemory/memory-graph';
``` ```
### Hooks ### Hooks
```typescript ```typescript
import { useGraphData, useGraphInteractions } from '@supermemory/memory-graph'; import { useGraphData, useGraphTheme } from '@supermemory/memory-graph';
```
### Engine classes
```typescript
import {
ForceSimulation,
ViewportState,
SpatialIndex,
VersionChainIndex
} from '@supermemory/memory-graph';
``` ```
### Constants ### Constants
```typescript ```typescript
import { colors, GRAPH_SETTINGS, LAYOUT_CONSTANTS } from '@supermemory/memory-graph'; import {
DEFAULT_COLORS,
DEFAULT_HOVER_POPOVER_Z_INDEX,
DEFAULT_LABELS,
FORCE_CONFIG,
GRAPH_SETTINGS
} from '@supermemory/memory-graph';
``` ```
--- ---

View file

@ -271,9 +271,8 @@ Search through user memories using semantic search:
result = await tools.search_memories( result = await tools.search_memories(
information_to_get="user preferences", information_to_get="user preferences",
limit=10, limit=10,
include_full_docs=True
) )
print(f"Found {len(result.memories)} memories") print(f"Found {result.get('count', 0)} memories")
``` ```
```typescript JavaScript ```typescript JavaScript
@ -282,7 +281,7 @@ const searchResult = await tools.searchMemories({
informationToGet: "user preferences", informationToGet: "user preferences",
limit: 10, limit: 10,
}) })
console.log(`Found ${searchResult.memories.length} memories`) console.log(`Found ${searchResult.count} memories`)
``` ```
</CodeGroup> </CodeGroup>
@ -298,7 +297,8 @@ Store new information in memory:
result = await tools.add_memory( result = await tools.add_memory(
memory="User prefers tea over coffee" memory="User prefers tea over coffee"
) )
print(f"Added memory with ID: {result.memory.id}") memory = result.get("memory") or {}
print(f"Added memory with ID: {memory.get('id')}")
``` ```
```typescript JavaScript ```typescript JavaScript
@ -306,7 +306,7 @@ print(f"Added memory with ID: {result.memory.id}")
const addResult = await tools.addMemory({ const addResult = await tools.addMemory({
memory: "User prefers dark roast coffee", memory: "User prefers dark roast coffee",
}) })
console.log(`Added memory with ID: ${addResult.memory.id}`) console.log(`Added memory with ID: ${addResult.memory?.id}`)
``` ```
</CodeGroup> </CodeGroup>
@ -466,7 +466,7 @@ async function chatWithMemory() {
messages.push({ messages.push({
role: "tool", role: "tool",
tool_call_id: toolCall.id, tool_call_id: toolCall.id,
content: JSON.stringify(result), content: result,
}) })
} }
@ -567,7 +567,7 @@ SupermemoryTools(
**Methods** **Methods**
- `get_tool_definitions()` - Get OpenAI function definitions - `get_tool_definitions()` - Get OpenAI function definitions
- `search_memories(information_to_get, limit, include_full_docs)` - Search user memories - `search_memories(information_to_get, limit=10)` - Search user memories. Pass `limit` by keyword: the second positional parameter is the deprecated, ignored `include_full_docs`
- `add_memory(memory)` - Add new memory - `add_memory(memory)` - Add new memory
- `execute_tool_call(tool_call)` - Execute individual tool call - `execute_tool_call(tool_call)` - Execute individual tool call
@ -598,7 +598,7 @@ supermemoryTools(
createToolCallExecutor( createToolCallExecutor(
apiKey: string, apiKey: string,
config?: { namespace?: string; baseUrl?: string } config?: { namespace?: string; baseUrl?: string }
) -> (toolCall: OpenAI.Chat.ChatCompletionMessageToolCall) => Promise<any> ) -> (toolCall: OpenAI.Chat.ChatCompletionMessageToolCall) => Promise<string> // JSON-encoded result
``` ```
## Environment variables ## Environment variables
@ -608,7 +608,7 @@ Set these environment variables:
```bash ```bash
SUPERMEMORY_API_KEY=your_supermemory_key SUPERMEMORY_API_KEY=your_supermemory_key
OPENAI_API_KEY=your_openai_key OPENAI_API_KEY=your_openai_key
SUPERMEMORY_BASE_URL=https://custom-endpoint.com # optional SUPERMEMORY_BASE_URL=https://custom-endpoint.com # optional, Python only; in TypeScript pass baseUrl
``` ```
## Development ## Development
@ -645,10 +645,7 @@ npm install
npm test npm test
# Type checking # Type checking
npm run type-check npm run check-types
# Linting
npm run lint
``` ```
## Next steps ## Next steps

View file

@ -61,10 +61,12 @@ The AI can use these tools autonomously during conversations. With custom contai
| Tool | Description | | Tool | Description |
|------|-------------| |------|-------------|
| `supermemory_store` | Save information to long-term memory. | | `supermemory-save` | Save information to long-term memory. |
| `supermemory_search` | Search memories by query with similarity scores. | | `supermemory-search` | Search memories by query with similarity scores. |
| `supermemory_forget` | Delete a memory by query or ID. | | `supermemory-forget` | Delete a memory by query or ID. |
| `supermemory_profile` | View the user profile — persistent facts and recent context. | | `supermemory-profile` | View the user profile — persistent facts and recent context. |
The older `supermemory_store`, `supermemory_search`, `supermemory_forget`, and `supermemory_profile` names still work but are deprecated. Update tool allowlists to the hyphenated names.
### Slash commands ### Slash commands
@ -74,6 +76,9 @@ Users can interact with memory directly in chat.
|---------|-------------| |---------|-------------|
| `/remember [text]` | Manually save something to memory. | | `/remember [text]` | Manually save something to memory. |
| `/recall [query]` | Search memories and see results with similarity scores. | | `/recall [query]` | Search memories and see results with similarity scores. |
| `/memory-usage` | Toggle showing how many memories were loaded and used in responses. |
The bundled `supermemory-index` skill indexes the current project: ask the agent to index or memorize the codebase, and it explores the workspace and saves focused memories.
### CLI commands ### CLI commands
@ -107,13 +112,16 @@ openclaw supermemory wipe # Delete all memories (requires confirma
|-----|------|---------|-------------| |-----|------|---------|-------------|
| `apiKey` | `string` | — | Supermemory API key. | | `apiKey` | `string` | — | Supermemory API key. |
| `baseUrl` | `string` | `https://api.supermemory.ai` | API endpoint (self-hosted / local). | | `baseUrl` | `string` | `https://api.supermemory.ai` | API endpoint (self-hosted / local). |
| `apiVersion` | `string` | auto | `"v5"` or `"legacy"`. The hosted API uses v5; custom endpoints default to legacy. Set `"v5"` to use v5 with a self-hosted server 0.0.9 or newer. |
| `containerTag` | `string` | `openclaw_{hostname}` | Root memory namespace. | | `containerTag` | `string` | `openclaw_{hostname}` | Root memory namespace. |
| `autoRecall` | `boolean` | `true` | Inject relevant memories before every AI turn. | | `autoRecall` | `boolean` | `true` | Inject relevant memories before every AI turn. |
| `autoCapture` | `boolean` | `true` | Store conversation content after every turn. | | `autoCapture` | `boolean` | `true` | Store conversation content after every turn. |
| `maxRecallResults` | `number` | `10` | Max memories injected into context per turn. | | `maxRecallResults` | `number` | `10` | Max memories injected into context per turn. |
| `profileFrequency` | `number` | `50` | Inject full user profile every N turns. | | `profileFrequency` | `number` | `50` | Inject full user profile every N turns. |
| `captureMode` | `string` | `"all"` | `"all"` filters noise. `"everything"` captures all messages. | | `captureMode` | `string` | `"all"` | `"all"` filters noise. `"everything"` captures all messages. |
| `entityContext` | `string` | built-in | Instructions that guide what memories are extracted from conversations (max 1,500 characters). |
| `debug` | `boolean` | `false` | Verbose debug logs. | | `debug` | `boolean` | `false` | Verbose debug logs. |
| `showMemoryUsage` | `boolean` | `true` | Show how many memories were loaded and used in AI responses. |
| `enableCustomContainerTags` | `boolean` | `false` | Enable custom container routing. | | `enableCustomContainerTags` | `boolean` | `false` | Enable custom container routing. |
| `customContainers` | `array` | `[]` | Custom containers with `tag` and `description`. | | `customContainers` | `array` | `[]` | Custom containers with `tag` and `description`. |
| `customContainerInstructions` | `string` | `""` | Instructions for AI on container routing. | | `customContainerInstructions` | `string` | `""` | Instructions for AI on container routing. |
@ -123,9 +131,16 @@ openclaw supermemory wipe # Delete all memories (requires confirma
```json ```json
{ {
"plugins": { "plugins": {
"slots": {
"memory": "openclaw-supermemory"
},
"entries": { "entries": {
"openclaw-supermemory": { "openclaw-supermemory": {
"enabled": true, "enabled": true,
"hooks": {
"allowPromptInjection": true,
"allowConversationAccess": true
},
"config": { "config": {
"apiKey": "${SUPERMEMORY_OPENCLAW_API_KEY}", "apiKey": "${SUPERMEMORY_OPENCLAW_API_KEY}",
"containerTag": "my_memory", "containerTag": "my_memory",

View file

@ -60,29 +60,53 @@ Or set an API key manually from the [API Keys](https://console.supermemory.ai/ke
</Tab> </Tab>
</Tabs> </Tabs>
Ensure your `~/.config/opencode/opencode.jsonc` contains: OpenCode V1 and OpenCode 2 read different keys from `~/.config/opencode/opencode.jsonc`. Use the config for the version you run:
```json <Tabs>
<Tab title="OpenCode V1">
```jsonc
{ {
"plugin": ["opencode-supermemory"] "plugin": ["opencode-supermemory@latest"]
} }
``` ```
</Tab>
<Tab title="OpenCode 2">
```jsonc
{
"plugins": ["opencode-supermemory"],
"permissions": [
{ "action": "supermemory_recall", "resource": "*", "effect": "allow" }
]
}
```
The `permissions` rule lets the read-only `supermemory_recall` tool run without prompting.
</Tab>
</Tabs>
<Warning>
The installer writes all three keys (`plugin`, `plugins`, and `permissions`) so that one file covers both versions. OpenCode V1 refuses to start when the file has a top-level `permissions` field ("V2 permissions are not supported by OpenCode V1"), so V1 users must remove `permissions` after installing. V1 ignores `plugins` with a warning, so you can remove it too.
</Warning>
## How it works ## How it works
Once installed, the plugin runs automatically: Once installed, the plugin runs automatically:
- **Context Injection** — On session start, relevant memories are fetched and injected into the agent's context (user profile, project knowledge, semantic matches). - **Context Injection** — On a session's first message, your profile facts are injected into the agent's context.
- **Keyword Detection** — Phrases like "remember" or "save this" trigger automatic storage. - **Direct Recall** — On every substantive prompt, the plugin searches the current project and injects up to five fresh matches. Set `recallMode` to `"advisory"` to let the agent decide when to search, or `"off"` to disable it.
- **Smart Compaction** — At 80% context capacity, sessions are summarized and saved as memories. - **Automatic Capture** — Completed turns are saved every `captureEveryNTurns` turns (fresh installs use `0`, which saves only at the end), and any remainder is flushed when the session ends or OpenCode shuts down.
- **Keyword Detection** — Phrases like "remember" or "save this" nudge the agent to save to memory.
- **Smart Compaction** — On OpenCode V1, sessions are summarized at 80% context capacity; on OpenCode 2, the plugin enriches native compaction. Both save the summary as a memory.
- **Privacy Protection** — Content within `<private>` tags never persists. - **Privacy Protection** — Content within `<private>` tags never persists.
### Memory scopes ### Memory scopes
Both scopes write to one repository container, `repo_{project-name}__{repository-hash}`, shared with the Claude Code, Codex, and Cursor plugins. `sm_scope` metadata keeps them apart.
| Scope | Description | | Scope | Description |
|-------|-------------| |-------|-------------|
| `user` | Memories that persist across all projects | | `user` | Personal memories for the current project |
| `project` | Memories isolated to the current project (default) | | `project` | Project knowledge for the current project (default) |
### Memory types ### Memory types
@ -97,13 +121,14 @@ Once installed, the plugin runs automatically:
## Commands ## Commands
### /supermemory-init The installer adds these in-chat commands:
Explore and index your codebase structure into memory: | Command | Description |
|---------|-------------|
``` | `/supermemory-index` | Explore and index your codebase structure into memory (`/supermemory-init` is an alias) |
/supermemory-init | `/supermemory-login` | Authenticate with Supermemory in the browser |
``` | `/supermemory-logout` | Clear stored credentials |
| `/supermemory-status` | Show the Supermemory connection status |
## Tools ## Tools
@ -116,6 +141,7 @@ The agent has access to a `supermemory` tool with these modes:
| `profile` | query? | View user preferences | | `profile` | query? | View user preferences |
| `list` | scope?, limit? | Display stored items | | `list` | scope?, limit? | Display stored items |
| `forget` | memoryId, scope? | Remove memory | | `forget` | memoryId, scope? | Remove memory |
| `help` | none | List available modes |
## Configuration ## Configuration
@ -124,16 +150,18 @@ Create `~/.config/opencode/supermemory.jsonc`:
```jsonc ```jsonc
{ {
"apiKey": "sm_...", // Or use SUPERMEMORY_API_KEY / browser login "apiKey": "sm_...", // Or use SUPERMEMORY_API_KEY / browser login
"similarityThreshold": 0.6, // Minimum match score (0-1) "baseUrl": "https://api.supermemory.ai", // Or SUPERMEMORY_API_URL (self-hosted)
"similarityThreshold": 0.55, // Minimum match score (0-1)
"maxMemories": 5, // Memories per injection "maxMemories": 5, // Memories per injection
"maxProjectMemories": 10, // Project memory listings "maxProjectMemories": 10, // Project memory listings
"maxProfileItems": 5, // Profile facts injected "maxProfileItems": 5, // Profile facts injected
"injectProfile": true, // Include user preferences in context "injectProfile": true, // Include user preferences in context
"containerTagPrefix": "opencode", // Tag prefix for scoping "projectContainerTag": "my-project-tag", // Optional: override the repository container
"userContainerTag": "my-user-tag", // Optional override
"projectContainerTag": "my-project-tag", // Optional override
"keywordPatterns": ["log\\s+this"], // Extra auto-save triggers "keywordPatterns": ["log\\s+this"], // Extra auto-save triggers
"compactionThreshold": 0.80 // Context usage ratio for summarization "recallMode": "direct", // "direct", "advisory", or "off"
"captureEveryNTurns": 3, // Save completed turns every N turns (0 = session end only)
"compactionThreshold": 0.80, // OpenCode V1: context usage ratio for summarization
"compactionEnabled": true // OpenCode 2: enrich native compaction and save summaries
} }
``` ```

View file

@ -28,8 +28,7 @@ You can obtain an API key from [console.supermemory.ai](https://console.supermem
Supermemory integration is provided through the `SupermemoryPipecatService` class in Pipecat: Supermemory integration is provided through the `SupermemoryPipecatService` class in Pipecat:
```python ```python
from supermemory_pipecat import SupermemoryPipecatService from supermemory_pipecat import InputParams, SupermemoryPipecatService
from supermemory_pipecat.service import InputParams
memory = SupermemoryPipecatService( memory = SupermemoryPipecatService(
api_key=os.getenv("SUPERMEMORY_API_KEY"), api_key=os.getenv("SUPERMEMORY_API_KEY"),
@ -106,7 +105,7 @@ InputParams(
| `search_limit` | int | 10 | Maximum number of memories to retrieve per query | | `search_limit` | int | 10 | Maximum number of memories to retrieve per query |
| `search_threshold` | float | 0.1 | Minimum similarity threshold for memory retrieval | | `search_threshold` | float | 0.1 | Minimum similarity threshold for memory retrieval |
| `mode` | str | "full" | Memory retrieval mode: `"profile"`, `"query"`, or `"full"` | | `mode` | str | "full" | Memory retrieval mode: `"profile"`, `"query"`, or `"full"` |
| `system_prompt` | str | "Based on previous conversations:\n\n" | Prefix text for memory context | | `system_prompt` | str | "Based on previous conversations, I recall:\n\n" | Prefix text for memory context |
| `inject_mode` | str | "auto" | How memories are injected: `"auto"`, `"system"`, or `"user"` | | `inject_mode` | str | "auto" | How memories are injected: `"auto"`, `"system"`, or `"user"` |
## Injection modes ## Injection modes
@ -157,8 +156,7 @@ from pipecat.transports.websocket.fastapi import (
FastAPIWebsocketTransport, FastAPIWebsocketTransport,
) )
from supermemory_pipecat import SupermemoryPipecatService from supermemory_pipecat import InputParams, SupermemoryPipecatService
from supermemory_pipecat.service import InputParams
app = FastAPI() app = FastAPI()

View file

@ -81,6 +81,8 @@ Both SDKs also work against [self-hosted Supermemory](/self-hosting/overview) ru
console.log(profile.static); console.log(profile.static);
console.log(profile.dynamic); console.log(profile.dynamic);
console.log(profile.buckets); console.log(profile.buckets);
const markdown = await supermemory.profileMarkdown("user_123");
``` ```
Buckets group profile facts under names you define: Buckets group profile facts under names you define:
@ -143,6 +145,10 @@ Both SDKs also work against [self-hosted Supermemory](/self-hosting/overview) ru
## Memories ## Memories
```typescript ```typescript
const memory = await supermemory.memories.get("user_123", "mem_123", {
include: ["related", "documents"],
});
const preview = await supermemory.memories.forgetMatching("user_123", { const preview = await supermemory.memories.forgetMatching("user_123", {
query: "old office address", query: "old office address",
dryRun: true, dryRun: true,
@ -333,6 +339,9 @@ await supermemory.search("user_123", { query: "planning notes" }, {
# Delete documents # Delete documents
result = client.documents.delete("user_123", ids=["meeting-q1"]) result = client.documents.delete("user_123", ids=["meeting-q1"])
# Get one memory with its related memories and source document
memory = client.memories.get("user_123", "mem_123", include=["related", "documents"])
# Forget memories: preview, then apply # Forget memories: preview, then apply
preview = client.memories.forget_matching("user_123", query="old office address", dry_run=True) preview = client.memories.forget_matching("user_123", query="old office address", dry_run=True)
client.memories.forget("user_123", ids=[m.id for m in preview.matches]) client.memories.forget("user_123", ids=[m.id for m in preview.matches])
@ -347,7 +356,7 @@ await supermemory.search("user_123", { query: "planning notes" }, {
# Connectors # Connectors
setup = client.connectors.create( setup = client.connectors.create(
"user_123", "user_123",
request={"provider": "notion", "redirectUrl": "https://app.example.com/connected"}, request={"provider": "notion", "redirect_url": "https://app.example.com/connected"},
) )
# send the user to setup.authorization.url before setup.authorization.expires_at # send the user to setup.authorization.url before setup.authorization.expires_at
connector = client.connectors.get("user_123", setup.id, include=["syncs", "picker"]) connector = client.connectors.get("user_123", setup.id, include=["syncs", "picker"])

View file

@ -128,10 +128,10 @@ const configWithMemory = withSupermemory({
id: "conv-456", // Groups messages into a conversation id: "conv-456", // Groups messages into a conversation
// Search tuning // Search tuning
searchMode: "hybrid", // "memories" | "documents" | "hybrid" searchMode: "hybrid", // "memories" | "chunks" | "hybrid"
threshold: 0.6, // 0.0-1.0 (higher = more accurate) threshold: 0.6, // 0.0-1.0 (higher = more accurate)
limit: 10, // Integer from 1 to 100 limit: 10, // Integer from 1 to 100
rerank: true, // Rerank for best relevance rerank: "order", // "none" | "order" | "aggregate"
rewriteQuery: false, // AI-rewrite query (+400ms latency) rewriteQuery: false, // AI-rewrite query (+400ms latency)
// Context // Context
@ -150,12 +150,14 @@ const configWithMemory = withSupermemory({
| `mode` | string | `"profile"` | Memory retrieval mode | | `mode` | string | `"profile"` | Memory retrieval mode |
| `addMemory` | string | `"always"` | Whether to save conversations after each response | | `addMemory` | string | `"always"` | Whether to save conversations after each response |
| `id` | string | **required** | Custom ID to group messages into a conversation | | `id` | string | **required** | Custom ID to group messages into a conversation |
| `searchMode` | string | — | `"memories"`, `"documents"`, or `"hybrid"` | | `searchMode` | string | — | `"memories"`, `"chunks"`, or `"hybrid"` |
| `threshold` | number | — | Similarity threshold (0 = more results, 1 = more accurate) | | `threshold` | number | — | Similarity threshold (0 = more results, 1 = more accurate) |
| `limit` | number | — | Maximum number of memory results (integer from 1 to 100) | | `limit` | number | — | Maximum number of memory results (integer from 1 to 100) |
| `rerank` | boolean | `false` | Rerank results for relevance | | `rerank` | string | `"none"` | Post-retrieval ranking: `"none"`, `"order"`, or `"aggregate"` |
| `rewriteQuery` | boolean | `false` | AI-rewrite query for better results (+400ms) | | `rewriteQuery` | boolean | `false` | AI-rewrite query for better results (+400ms) |
| `entityContext` | string | — | Deprecated and ignored. [Configure it on the container tag instead](/concepts/customization#entity-context). | | `filter` | object | — | Typed metadata filter applied before ranking |
| `include` | object | — | Extra data per search result (`documents`, `related`, `forgotten`) |
| `supportingContext` | string | — | Context that guides memory extraction for saved conversations (max 1500 characters) |
| `metadata` | object | — | Custom metadata attached to saved conversations | | `metadata` | object | — | Custom metadata attached to saved conversations |
| `promptTemplate` | function | — | Custom function to format memory data into prompt | | `promptTemplate` | function | — | Custom function to format memory data into prompt |
@ -168,5 +170,5 @@ The `searchMode` option controls what type of results are searched:
| Mode | Description | | Mode | Description |
| ------------- | ------------------------------------------------------ | | ------------- | ------------------------------------------------------ |
| `"memories"` | Search only memory entries (atomic facts about the user) | | `"memories"` | Search only memory entries (atomic facts about the user) |
| `"documents"` | Search only document chunks | | `"chunks"` | Search only document chunks |
| `"hybrid"` | Search both memories AND document chunks (recommended) | | `"hybrid"` | Search both memories AND document chunks (recommended) |

View file

@ -44,9 +44,14 @@ If you'd rather write the adapter yourself, every provider implements the same i
interface Provider { interface Provider {
name: string name: string
prompts?: ProviderPrompts prompts?: ProviderPrompts
concurrency?: ConcurrencyConfig
initialize(config: ProviderConfig): Promise<void> initialize(config: ProviderConfig): Promise<void>
ingest(sessions: UnifiedSession[], options: IngestOptions): Promise<IngestResult> ingest(sessions: UnifiedSession[], options: IngestOptions): Promise<IngestResult>
awaitIndexing(result: IngestResult, containerTag: string): Promise<void> awaitIndexing(
result: IngestResult,
containerTag: string,
onProgress?: IndexingProgressCallback
): Promise<void>
search(query: string, options: SearchOptions): Promise<unknown[]> search(query: string, options: SearchOptions): Promise<unknown[]>
clear(containerTag: string): Promise<void> clear(containerTag: string): Promise<void>
} }

View file

@ -11,7 +11,7 @@ Migrating from Mem0 to Supermemory is straightforward. This guide walks you thro
Supermemory offers enhanced capabilities over Mem0: Supermemory offers enhanced capabilities over Mem0:
- **Knowledge graph** architecture for better context relationships - **Knowledge graph** architecture for better context relationships
- **Multiple content types** (URLs, PDFs, images, videos) - **Multiple content types** (URLs, PDFs, images, videos)
- **Generous free tier** (100k tokens) with affordable pricing - **Free plan** with $5 of free credits every month; Free orgs upgrade to a paid plan once they run out (top-ups are paid-plan only)
- **Multiple integration options** (API, MCP, SDKs) - **Multiple integration options** (API, MCP, SDKs)
## Quick migration (all-in-one) ## Quick migration (all-in-one)
@ -49,7 +49,7 @@ print("Migration complete!")
Mem0 provides two ways to export your memories: Mem0 provides two ways to export your memories:
### Option 1: Export via dashboard (recommended) ### Option 1: Export via dashboard (recommended)
1. Log into your [Mem0 dashboard](https://app.mem0.ai) 1. Log into your Mem0 dashboard (app.mem0.ai)
2. Navigate to the export section 2. Navigate to the export section
3. Download your memories as JSON 3. Download your memories as JSON

View file

@ -11,6 +11,10 @@ sidebarTitle: 'Tools: v1.4 → 2.0'
production. production.
</Note> </Note>
<Info>
This guide covers 1.4.x → 2.0. The current major is 3.x: after this upgrade, follow the [3.0 upgrade guide](/migration/tools-v3-upgrade).
</Info>
## What changed at a glance ## What changed at a glance
| Area | v1.4.x | v2.0.0 | | Area | v1.4.x | v2.0.0 |

View file

@ -12,7 +12,7 @@ The Analytics API provides comprehensive insights into your Supermemory usage:
- **Usage Statistics**: Track API calls by type, hourly trends, and per-API key breakdown - **Usage Statistics**: Track API calls by type, hourly trends, and per-API key breakdown
- **Error Monitoring**: Identify top error types and patterns - **Error Monitoring**: Identify top error types and patterns
- **Detailed Logs**: Access complete request/response logs for debugging - **Detailed Logs**: Inspect stored request logs and compacted response data for debugging
- **Performance Metrics**: Monitor average response times and processing duration - **Performance Metrics**: Monitor average response times and processing duration
<Note> <Note>
@ -33,7 +33,7 @@ Get comprehensive usage statistics including hourly breakdowns and per-key metri
|-----------|------|-------------| |-----------|------|-------------|
| `from` | string (ISO 8601) | Start date/time for the period | | `from` | string (ISO 8601) | Start date/time for the period |
| `to` | string (ISO 8601) | End date/time for the period | | `to` | string (ISO 8601) | End date/time for the period |
| `period` | string | Alternative to `from`: `1h`, `24h`, `7d`, `30d` | | `period` | string | Alternative to `from`: `24h`, `7d`, `30d`, `all`. Use `from` and `to` for other windows |
| `page` | integer | Page number for pagination (default: 1) | | `page` | integer | Page number for pagination (default: 1) |
| `limit` | integer | Items per page (default: 20, max: 100) | | `limit` | integer | Items per page (default: 20, max: 100) |
@ -105,7 +105,8 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/usage?from=2024-01-01T00:00
"keyName": "Production API", "keyName": "Production API",
"count": 2341, "count": 2341,
"avgDuration": 98.7, "avgDuration": 98.7,
"lastUsed": "2024-01-15T14:35:00Z" "lastUsed": "2024-01-15T14:35:00Z",
"tokensUsed": 182340
} }
], ],
"totalMemories": 45678, "totalMemories": 45678,
@ -118,6 +119,8 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/usage?from=2024-01-01T00:00
} }
``` ```
`tokensUsed` sums recorded `finalTokens` for the listed keys, including chat usage. It is not the billable ingestion-token meter, and ordinary memory request logs do not necessarily populate it.
## Error monitoring ## Error monitoring
Track and analyze errors to identify issues and improve reliability. Track and analyze errors to identify issues and improve reliability.
@ -148,7 +151,7 @@ const data = await errors.json();
```python Python ```python Python
# Monitor errors and alert on spikes # Monitor errors and alert on spikes
response = requests.get( response = requests.get(
'https://api.supermemory.ai/v3/analytics/errors?period=1h', 'https://api.supermemory.ai/v3/analytics/errors?period=24h',
headers={'Authorization': f'Bearer {SUPERMEMORY_API_KEY}'} headers={'Authorization': f'Bearer {SUPERMEMORY_API_KEY}'}
) )
@ -167,41 +170,53 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/errors?period=7d" \
```json ```json
{ {
"totalErrors": 234, "summary": [
"errorRate": 0.023,
"topErrors": [
{ {
"type": "ValidationError", "type": "search_v4",
"totalRequests": 3421,
"successRate": 97.4,
"errorRate": 2.6,
"lastRequest": "2024-01-15T14:35:00Z"
}
],
"errors": [
{
"type": "search_v4",
"statusCode": 400,
"count": 89, "count": 89,
"statusCodes": [400], "percentage": 100
"lastOccurred": "2024-01-15T14:30:00Z"
},
{
"type": "RateLimitError",
"count": 45,
"statusCodes": [429],
"lastOccurred": "2024-01-15T13:15:00Z"
} }
], ],
"timeline": [ "hourly": [
{ {
"time": "2024-01-15T14:00:00Z", "hour": "2024-01-15T14:00:00Z",
"count": 12, "count": 156,
"types": ["ValidationError", "NotFoundError"] "errorCount": 4,
"errorRate": 2.56
} }
], ],
"byStatusCode": { "byKey": [
"400": 89, {
"404": 34, "keyId": "key_abc123",
"429": 45, "keyName": "Production API",
"500": 66 "count": 2341,
"errorCount": 61,
"errorRate": 2.61,
"lastUsed": "2024-01-15T14:35:00Z"
}
],
"pagination": {
"currentPage": 1,
"limit": 20,
"totalItems": 3421,
"totalPages": 172
} }
} }
``` ```
## Detailed logs ## Detailed logs
Access complete request/response logs for debugging and auditing. Inspect stored request logs for debugging. Response data is compacted: strings, arrays, fields, and nesting can be truncated. The ordinary request tracker skips HTTP `402`, `404`, and `500` responses, so this is not a complete audit trail.
### Endpoint ### Endpoint
@ -209,18 +224,14 @@ Access complete request/response logs for debugging and auditing.
### Parameters ### Parameters
Same as usage endpoint, plus optional filters: Use `from`, `to`, `period`, `page`, and `limit` as above. The current `/logs` handler filters by organization and time only; it does not apply `type`, `statusCode`, or `keyId` filters.
- `type`: Filter by request type (add, search, update, delete)
- `statusCode`: Filter by HTTP status code
- `keyId`: Filter by specific API key
### Example request ### Example request
<CodeGroup> <CodeGroup>
```typescript TypeScript ```typescript TypeScript
// Get recent failed requests const logs = await fetch('https://api.supermemory.ai/v3/analytics/logs?period=24h', {
const logs = await fetch('https://api.supermemory.ai/v3/analytics/logs?period=1h&statusCode=500', {
headers: { headers: {
'Authorization': `Bearer ${SUPERMEMORY_API_KEY}` 'Authorization': `Bearer ${SUPERMEMORY_API_KEY}`
} }
@ -230,11 +241,9 @@ const data = await logs.json();
``` ```
```python Python ```python Python
# Debug specific API key usage
response = requests.get( response = requests.get(
'https://api.supermemory.ai/v3/analytics/logs', 'https://api.supermemory.ai/v3/analytics/logs',
params={ params={
'keyId': 'key_abc123',
'period': '24h' 'period': '24h'
}, },
headers={'Authorization': f'Bearer {SUPERMEMORY_API_KEY}'} headers={'Authorization': f'Bearer {SUPERMEMORY_API_KEY}'}
@ -246,7 +255,7 @@ logs = response.json()['logs']
```bash cURL ```bash cURL
# Get all logs for debugging # Get all logs for debugging
curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=1h&limit=50" \ curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=24h&limit=50" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" -H "Authorization: Bearer $SUPERMEMORY_API_KEY"
``` ```
@ -263,13 +272,10 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=1h&limit=50" \
"type": "search", "type": "search",
"statusCode": 200, "statusCode": 200,
"duration": 89, "duration": 89,
"input": { "input": "{\"q\":\"user query\",\"limit\":10}",
"q": "user query",
"limit": 10
},
"output": { "output": {
"results": 10, "type": "search",
"processingTime": 85 "response": { "results": [], "timing": 85, "total": 0 }
} }
} }
], ],
@ -282,13 +288,10 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=1h&limit=50" \
} }
``` ```
## Rate limits ## Limits and retention
Analytics endpoints have the following rate limits: The maximum page size is 100 items. Normal API request rate limits also apply; this page does not establish a separate analytics-specific allowance.
- 100 requests per minute per organization
- Maximum time range: 90 days
- Maximum page size: 100 items
<Warning> <Warning>
Analytics data is retained for 90 days. For longer retention, export and store the data in your own systems. An accepted date range does not guarantee that historical logs are still retained. Confirm the applicable service retention policy with Supermemory, and export data to your own systems when you need longer retention.
</Warning> </Warning>

View file

@ -29,7 +29,7 @@ USD CREDIT BALANCE ──draw──► METERS
1. You hold a **USD credit balance** (`usd_credits`). 1. You hold a **USD credit balance** (`usd_credits`).
2. Product activity increments **meters** (tokens, queries, operations). 2. Product activity increments **meters** (tokens, queries, operations).
3. Each meter unit multiplies by a **USD-per-unit** rate and debits the balance. 3. Each meter unit multiplies by a **USD-per-unit** rate and debits the balance.
4. **Re-ingesting the same document under the same `id` only bills the net-new token delta** — already-seen content is effectively **fully discounted**. 4. **Re-ingesting a document under the same `id` can reuse unchanged content** — token charges follow added or changed text, not simply growth in the document's total length.
--- ---
@ -41,8 +41,8 @@ USD CREDIT BALANCE ──draw──► METERS
| `sm_tokens_rich` | Memory tokens (rich) | Billable **new** tokens on Memory-path ingest for multimodal / extraction-heavy types (PDF, media, etc.) | tokens | | `sm_tokens_rich` | Memory tokens (rich) | Billable **new** tokens on Memory-path ingest for multimodal / extraction-heavy types (PDF, media, etc.) | tokens |
| `sm_superrag_text` | SuperRAG tokens (text) | Billable **new** tokens when the task type is SuperRAG (text) | tokens | | `sm_superrag_text` | SuperRAG tokens (text) | Billable **new** tokens when the task type is SuperRAG (text) | tokens |
| `sm_superrag_rich` | SuperRAG tokens (rich) | Billable **new** tokens when the task type is SuperRAG (rich) | tokens | | `sm_superrag_rich` | SuperRAG tokens (rich) | Billable **new** tokens when the task type is SuperRAG (rich) | tokens |
| `sm_search_queries` | Search queries | Each search / profile call that runs retrieval (v3 search, v4 search, v4 profile with search) | queries | | `sm_search_queries` | Search queries | Gated v5 namespace search, v3/v4 search, and v4 profile calls that include a non-blank `q` query | queries |
| `sm_operations` | Operations | Counted platform operations not fully attributed to token meters (e.g. certain ingest modes such as **instant dreaming**, and other non-token platform actions) | operations | | `sm_operations` | Operations | Counted platform operations not attributed to token or query meters: **instant dreaming**, and search add-ons such as query rewriting, reranking and aggregation | operations |
### List rates (USD) ### List rates (USD)
@ -73,7 +73,8 @@ Text-like document types bill on the **text** meters; everything else bills as *
**`sm_operations`** is the meter for discrete platform work that is not “how many tokens did we embed.” Examples include: **`sm_operations`** is the meter for discrete platform work that is not “how many tokens did we embed.” Examples include:
- `dreaming: "instant"` extracts a document's memories immediately instead of waiting for the next dynamic batch. It costs **one extra operation per document** on top of normal token metering. See [processing modes](/ingestion/add-memories#processing-modes). - `dreaming: "instant"` extracts a document's memories immediately instead of waiting for the next dynamic batch. It costs **one extra operation per document** on top of normal token metering. See [processing modes](/ingestion/add-memories#processing-modes).
- Other non-token platform actions the product attributes to the operations meter (console: “counted platform operations not attributed to token or search meters”). - Search add-ons: query rewriting (`rewriteQuery`), reranking and aggregation (v5 `rerank: "order"` / `"aggregate"`, v3/v4 `rerank`, v4 `aggregate`) each add **one operation** when they run successfully. Failed or skipped add-ons do not necessarily charge an operation.
- Query-based forgetting (`DELETE /ns/{namespace}/memories/semantic`, legacy `POST /v4/memories/forget-matching`) adds **one operation per search round**, up to five, including `dryRun` previews. Forgetting an explicit `ids` list bypasses that query-operation charge.
Search is primarily metered as **`sm_search_queries`** (one unit per gated search/profile request). Prefer thinking of operations as **extra platform work**, not as a synonym for “API call.” Search is primarily metered as **`sm_search_queries`** (one unit per gated search/profile request). Prefer thinking of operations as **extra platform work**, not as a synonym for “API call.”
@ -81,14 +82,18 @@ Search is primarily metered as **`sm_search_queries`** (one unit per gated searc
| Call | Typical meter | | Call | Typical meter |
|---|---| |---|---|
| `POST /ns/{namespace}/search`, `supermemory.search()` | `sm_search_queries` (+1) | | `POST /ns/{namespace}/search`, `supermemory.search()` | `sm_search_queries` (+1), plus `sm_operations` for any add-ons that run |
| `POST /ns/{namespace}/profile`, `supermemory.profile()` when it runs retrieval | `sm_search_queries` (+1) | | `POST /ns/{namespace}/profile`, `supermemory.profile()` | No search-query meter in the current API implementation |
| Legacy `POST /v3/search`, `POST /v4/search` | `sm_search_queries` (+1), plus `sm_operations` for any add-ons that run |
| Legacy `POST /v4/profile` with a non-empty `q` | `sm_search_queries` (+1) |
If the org is out of balance, search/profile can return **402** (payment required) depending on gate configuration. Blocking metered cloud calls can return **402** (payment required) when the applicable balance or quota is exhausted. Billing exclusions, unlimited contracts, disabled billing, and gate configuration can differ. The current v5 profile route does not install a search-query billing gate; this is not a guarantee of future pricing.
--- ---
## Full discount on already-seen tokens (diff billing) <span id="full-discount-on-already-seen-tokens-diff-billing" />
## Reusing unchanged content (diff billing)
This is the most important cost control in production agents. This is the most important cost control in production agents.
@ -96,17 +101,13 @@ This is the most important cost control in production agents.
When you re-add content under the **same `id`** (same org / document identity), the pipeline: When you re-add content under the **same `id`** (same org / document identity), the pipeline:
1. Loads the **previous extracted content** for that document 1. Loads the document's active chunks and plans the updated content.
2. Computes **full** token count of the merged/updated document 2. Reuses unchanged chunks where possible.
3. Bills only: 3. Meters newly appended text on an append path, or added replacement chunks on a replace path.
```text This is not a subtraction of the old total token count from the new one. A same-length replacement can still incur charges if its content changed; an identical replacement can reuse its chunks without new token charges. Chunk boundaries and overlap can also affect the billable delta.
billableTokens = max(0, fullTokenCount - previousTokenCount)
```
So **tokens Supermemory has already processed are not billed again**. Unchanged content costs nothing on the token meters; only the net-new delta, and any new extraction on it, draws credits. Stable IDs let long-lived conversations and repeated document syncs reuse unchanged work rather than billing the whole history every time. Operation surcharges can still apply.
This is why long-lived agent loops stay cheap: re-sync the same conversation `id`, re-upload the same policy doc, or connector re-sync with stable IDs — you pay for **new** material, not the whole history every time.
### Requirements ### Requirements
@ -114,7 +115,7 @@ This is why long-lived agent loops stay cheap: re-sync the same conversation `id
|---|---| |---|---|
| **Stable `id`** | Identity for “this is the same document.” Max length 255. | | **Stable `id`** | Identity for “this is the same document.” Max length 255. |
| **Same org / key** | Documents are org-scoped. | | **Same org / key** | Documents are org-scoped. |
| **Update path, not full replace** | Full replace clears previous content for billing purposes (`isFullReplace` treats previous tokens as 0). Prefer append/update with the same `id` for chat. | | **Choose append or replace deliberately** | Append meters the newly appended text; replace compares chunks and can reuse unchanged ones. Changed replacement chunks can bill even without net document growth. |
### Practical patterns ### Practical patterns
@ -127,12 +128,12 @@ await supermemory.add(userId, {
}); });
``` ```
Connectors already use stable IDs (e.g. Drive file id, Gmail thread id, `s3://bucket/key`) so re-syncs diff-bill automatically. Connectors use stable IDs (e.g. Drive file id, Gmail thread id, `s3://bucket/key`), so re-syncs can reuse unchanged content. Changed replacement chunks can still incur token charges.
### What is not free ### What is not free
- **First** ingest of content still bills full token count - **First** ingest of content still bills full token count
- **Search / profile** still bill query meters every call - **Gated searches** still bill the query meter every call; legacy v4 profile does so only with a non-blank `q`. The current v5 profile implementation does not charge this meter.
- **Instant dreaming** still bills the **operation** surcharge when used - **Instant dreaming** still bills the **operation** surcharge when used
- **Brand-new `id`** = brand-new document = full tokens - **Brand-new `id`** = brand-new document = full tokens
@ -185,13 +186,15 @@ Usage meters are shared; **feature gates** differ by tier.
| Standard PDF extraction, including OCR for scans | Yes | Yes | Yes | Yes | Yes | | Standard PDF extraction, including OCR for scans | Yes | Yes | Yes | Yes | Yes |
| [Advanced document extraction for PDFs](/concepts/content-types#advanced-document-extraction) (figure and diagram descriptions) | — | — | — | Yes | Yes | | [Advanced document extraction for PDFs](/concepts/content-types#advanced-document-extraction) (figure and diagram descriptions) | — | — | — | Yes | Yes |
| Diff / delta token billing | Yes | Yes | Yes | Yes | Yes | | Diff / delta token billing | Yes | Yes | Yes | Yes | Yes |
| Coding plugins (Claude Code, Codex, Cursor, OpenCode, OpenClaw, Hermes) | Yes | Yes | Yes | Yes | Yes | | Coding plugins (Claude Code, Codex, Cursor, OpenCode, OpenClaw, Hermes, Muse Code) | Yes | Yes | Yes | Yes | Yes |
| Team management | — | Yes | Yes | Yes | Yes | | Team management | — | Yes | Yes | Yes | Yes |
| Google Drive, OneDrive, Notion, Granola connectors | — | Yes | Yes | Yes | Yes | | Google Drive, OneDrive, Notion, Granola connectors | — | Yes | Yes | Yes | Yes |
| Gmail connector | — | — | Yes | Yes | Yes | | Gmail connector | — | — | Yes | Yes | Yes |
| GitHub, S3, Web Crawler connectors | — | — | — | Yes | Yes | | GitHub, S3, Web Crawler connectors | — | — | — | Yes | Yes |
| User Insights | — | — | — | Yes | Yes | | User Insights | — | — | — | Yes | Yes |
| Two-factor authentication | — | — | — | Yes | Yes |
| Org seat limit (members) | 1 | 3 | 3 | Unlimited | Unlimited | | Org seat limit (members) | 1 | 3 | 3 | Unlimited | Unlimited |
| Restricted members (per-tag access) | — | — | — | Yes | Yes |
| Auto top-up in the console | — | Yes | Yes | Yes | Contract | | Auto top-up in the console | — | Yes | Yes | Yes | Contract |
| Auto top-up purchase limits | — | Yes | Yes | Yes | Contract | | Auto top-up purchase limits | — | Yes | Yes | Yes | Contract |
| Custom metering / SSO / dedicated deploy | — | — | — | — | Yes | | Custom metering / SSO / dedicated deploy | — | — | — | — | Yes |
@ -204,14 +207,23 @@ Overrides can be applied per org in metadata (`featureOverrides`) for enterprise
### HTTP behavior when gated ### HTTP behavior when gated
- **403** — plan too low for a **feature** (e.g. connector not on Free) - **403** — plan too low for a **feature** (e.g. connector not on Free). The `error` message names the minimum plan, for example `This feature requires Max plan or higher`.
- **402** — **quota / credits** exhausted for a metered call (search, tokens, etc.) - **402** — **quota / credits** exhausted for a metered call (search, tokens, etc.). The body names the meter:
```json
{
"error": "Search query limit reached",
"details": "You've run out of credits. Top up to continue."
}
```
- **429** — the API key's own request rate limit (500 requests per minute by default), not a plan quota. The response carries a `Retry-After` header and `{ "code": "RATE_LIMITED", "retryAfterSeconds": … }`.
--- ---
## Programmatic access ## Programmatic access
All of the following require an **org-admin capable** credential. **Scoped API keys cannot read billing** (403). **Scoped API keys cannot read billing** (403). `GET /v3/auth/billing` and `POST /v3/auth/billing/auto-topups` also require an org **admin or owner**.
Base: `https://api.supermemory.ai` Base: `https://api.supermemory.ai`
Auth: `Authorization: Bearer YOUR_API_KEY` Auth: `Authorization: Bearer YOUR_API_KEY`
@ -234,7 +246,7 @@ Typical fields:
"tokens": { "used": 120000, "limit": 0 }, "tokens": { "used": 120000, "limit": 0 },
"queries": { "used": 4200, "limit": 0 } "queries": { "used": 4200, "limit": 0 }
}, },
"credits": {}, "credits": { "balance": 14.62, "label": "USD" },
"resetDate": "2026-08-01T00:00:00.000Z", "resetDate": "2026-08-01T00:00:00.000Z",
"orgName": "Acme", "orgName": "Acme",
"billingEmail": "billing@acme.com" "billingEmail": "billing@acme.com"

View file

@ -41,7 +41,7 @@ Supermemory is infrastructure for *your* agents. Your customer content is never
### Data residency and deployment options ### Data residency and deployment options
- **Managed cloud** — default multi-tenant SaaS. - **Managed cloud** — default multi-tenant SaaS.
- **Self-hosted binary** — full engine on your machine or VPC; embeddings and storage stay where you run it. See [Self-hosting](/self-hosting/overview). - **Self-hosted binary** — the engine, storage, and default local embeddings run on your machine or VPC. If you configure remote LLM or embedding providers, their processing inputs are sent to those providers. See [Self-hosting](/self-hosting/overview).
- **Enterprise / dedicated** — for stricter residency, air-gap, or custom deployment requirements. See [Local vs Enterprise](/self-hosting/local-vs-enterprise). - **Enterprise / dedicated** — for stricter residency, air-gap, or custom deployment requirements. See [Local vs Enterprise](/self-hosting/local-vs-enterprise).
## Privacy operations you should design for ## Privacy operations you should design for
@ -51,14 +51,14 @@ Supermemory is infrastructure for *your* agents. Your customer content is never
The practical GDPR-style path for app builders: The practical GDPR-style path for app builders:
1. Scope each end-user (or tenant) to a **namespace**. 1. Scope each end-user (or tenant) to a **namespace**.
2. When the user requests deletion, delete that namespace’s content via the API / console workflows for documents and memories under that namespace. 2. When the user requests deletion, use `DELETE /ns/{namespace}` with an org admin or owner credential that has write access. It deletes the namespace, its namespace-owned memory records, and documents exclusive to it; documents shared with other namespaces are retained. The default `sm_project_default` namespace cannot be deleted.
3. Revoke any **scoped keys** issued for that user. 3. Revoke any **scoped keys** issued for that user.
Designing isolation up front makes erasure a single boundary operation instead of a forensic search. Designing isolation up front still makes identifying the user's content easier.
### Connectors and third-party sources ### Connectors and third-party sources
OAuth connectors (Drive, Notion, Gmail, and others) pull content your users authorize. Disconnecting a connector stops future sync; you still control whether already-ingested documents remain in the memory store. Treat connector scope and retention as part of your product privacy policy. OAuth connectors (Drive, Notion, Gmail, and others) pull content your users authorize. Disconnecting a connector stops future sync and, by default, deletes the documents it imported; pass `deleteDocuments=false` to keep them. Treat connector scope and retention as part of your product privacy policy.
### Self-host when cloud is not enough ### Self-host when cloud is not enough

View file

@ -97,13 +97,14 @@ print(conv.id, conv.status) # e.g. "queued"
``` ```
```bash curl ```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document?dreaming=instant" \ curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -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.", "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", "id": "chat_offsite_2026",
"metadata": { "type": "conversation" } "metadata": { "type": "conversation" },
"dreaming": "instant"
}' }'
``` ```
</CodeGroup> </CodeGroup>
@ -164,20 +165,21 @@ print(doc.id, doc.status)
``` ```
```bash curl ```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document?taskType=superrag" \ curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -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.", "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", "id": "doc_gift_policy",
"metadata": { "type": "document", "source": "handbook" } "metadata": { "type": "document", "source": "handbook" },
"taskType": "superrag"
}' }'
``` ```
</CodeGroup> </CodeGroup>
## 3. Wait until both are `done` ## 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`). Poll each document. `system.status` reaches `done` once the content is indexed and searchable (`queued → extracting → … → done`). Memories are ready when `system.dreamingStatus` is also `done`; it reads `dreaming` while they are generated. **`dreaming: "instant"`** starts memory generation right away instead of waiting for the next batch.
> 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. > 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.
@ -186,7 +188,8 @@ Poll document status. With **`dreaming: "instant"`**, once `system.status` is `d
async function waitUntilDone(id: string) { async function waitUntilDone(id: string) {
for (;;) { for (;;) {
const d = await supermemory.documents.get(user, id); const d = await supermemory.documents.get(user, id);
if (d.system.status === "done" || d.system.status === "failed") return d; if (d.system.status === "failed") return d;
if (d.system.status === "done" && d.system.dreamingStatus === "done") return d;
await new Promise((r) => setTimeout(r, 1500)); await new Promise((r) => setTimeout(r, 1500));
} }
} }
@ -202,7 +205,9 @@ import time
def wait_until_done(doc_id: str): def wait_until_done(doc_id: str):
while True: while True:
d = client.documents.get(user, doc_id) d = client.documents.get(user, doc_id)
if d.system.status in ("done", "failed"): if d.system.status == "failed":
return d
if d.system.status == "done" and d.system.dreaming_status == "done":
return d return d
time.sleep(1.5) time.sleep(1.5)
@ -215,11 +220,11 @@ print("ready to search")
# replace DOC_ID with each document id from the add responses # replace DOC_ID with each document id from the add responses
curl "https://api.supermemory.ai/ns/user_4f8a/document/DOC_ID" \ curl "https://api.supermemory.ai/ns/user_4f8a/document/DOC_ID" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" -H "Authorization: Bearer $SUPERMEMORY_API_KEY"
# repeat until "system": { "status": "done" } # repeat until "system": { "status": "done", "dreamingStatus": "done" }
``` ```
</CodeGroup> </CodeGroup>
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. 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 ## 4. Three ways to get context back
@ -253,10 +258,14 @@ for hit in rag.results:
``` ```
```bash curl ```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search?searchMode=chunks&limit=3" \ curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ "query": "gift ideas for a VP promotion after a Tokyo offsite" }' -d '{
"query": "gift ideas for a VP promotion after a Tokyo offsite",
"searchMode": "chunks",
"limit": 3
}'
``` ```
</CodeGroup> </CodeGroup>
@ -300,12 +309,14 @@ print(memories.to_json())
``` ```
```bash curl ```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search?searchMode=memories&limit=5" \ curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"query": "What gift should I get, and why?", "query": "What gift should I get, and why?",
"include": { "related": true } "searchMode": "memories",
"include": { "related": true },
"limit": 5
}' }'
``` ```
</CodeGroup> </CodeGroup>

View file

@ -29,13 +29,14 @@ v5 has no direct memory-create route. Ingest a document instead and Supermemory
</Tab> </Tab>
<Tab title="cURL"> <Tab title="cURL">
```bash ```bash
curl -X POST "https://api.supermemory.ai/ns/user_123/document?dreaming=instant" \ curl -X POST "https://api.supermemory.ai/ns/user_123/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"content": "John prefers dark mode.\nJohn is from Seattle.", "content": "John prefers dark mode.\nJohn is from Seattle.",
"id": "prefs_john", "id": "prefs_john",
"metadata": { "source": "user_preference" } "metadata": { "source": "user_preference" },
"dreaming": "instant"
}' }'
``` ```
</Tab> </Tab>
@ -88,7 +89,7 @@ Soft-delete memories by ID. Forgotten memories are excluded from search results
|-------|------|-------------| |-------|------|-------------|
| `count` | number | Number of memories forgotten; always equals `matches.length` | | `count` | number | Number of memories forgotten; always equals `matches.length` |
| `matches` | array | The memories that were forgotten (`{ id, memory }`) | | `matches` | array | The memories that were forgotten (`{ id, memory }`) |
| `errors` | array | IDs that were missing or not eligible (`{ id, error }`) | | `errors` | array | IDs that were missing, already forgotten, or in another namespace (`{ id, error }`) |
HTTP success does not imply every requested ID changed. Check `errors`. HTTP success does not imply every requested ID changed. Check `errors`.
@ -99,7 +100,7 @@ HTTP success does not imply every requested ID changed. Check `errors`.
Forget by meaning. Give a `query` (a prompt or topic) and the service semantically searches the namespace, decides which memories are genuinely about your target, and soft-deletes those — use this for "forget everything about X". Forget by meaning. Give a `query` (a prompt or topic) and the service semantically searches the namespace, decides which memories are genuinely about your target, and soft-deletes those — use this for "forget everything about X".
<Warning> <Warning>
This is a bulk, destructive operation. `dryRun` is required. Call with **`dryRun: true`** first to review what would be forgotten, then apply. The match is semantic, so a too-broad query can select more than you intend. This is a bulk, destructive operation. `dryRun` is required. Call with **`dryRun: true`** first to review what would be forgotten, then apply. The match is semantic, so a too-broad query can select more than you intend. Each request selects at most 100 memories. After applying a batch, repeat the preview-and-apply workflow to find more; repeated previews alone do not paginate and may return the same matches.
</Warning> </Warning>
<Tabs> <Tabs>
@ -144,7 +145,7 @@ This is a bulk, destructive operation. `dryRun` is required. Call with **`dryRun
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| |-----------|------|----------|-------------|
| `namespace` | string | yes | Namespace to scope the operation to (URL path) | | `namespace` | string | yes | Namespace to scope the operation to (URL path) |
| `query` | string | yes | What to forget — a natural-language instruction ("forget everything about Project Titan") or a bare topic ("Project Titan") | | `query` | string | yes | What to forget — a natural-language instruction ("forget everything about Project Titan") or a bare topic ("Project Titan"). 1 to 2000 characters |
| `dryRun` | boolean | yes | `true` returns what *would* be forgotten without changing anything. `false` forgets the memories selected when that request runs | | `dryRun` | boolean | yes | `true` returns what *would* be forgotten without changing anything. `false` forgets the memories selected when that request runs |
### Response ### Response
@ -161,7 +162,7 @@ Both dry-run and applied requests return the same shape as [Forget Memories](#fo
} }
``` ```
The payload does not say which mode was sent, so keep your own record of `dryRun` next to audit logs. `errors` is always empty for semantic requests. The payload does not say which mode was sent, so keep your own record of `dryRun` next to audit logs.
<Tip> <Tip>
**Exact, bound deletes.** Re-running `forgetMatching` with `dryRun: false` re-runs the semantic match, so the result can drift from the preview if the namespace changed in between. To forget *precisely* what you reviewed, take the `id`s from the `dryRun` preview and send them to `memories.forget` — the delete is then bound to exactly that set. **Exact, bound deletes.** Re-running `forgetMatching` with `dryRun: false` re-runs the semantic match, so the result can drift from the preview if the namespace changed in between. To forget *precisely* what you reviewed, take the `id`s from the `dryRun` preview and send them to `memories.forget` — the delete is then bound to exactly that set.

View file

@ -7,8 +7,8 @@ icon: "/icons/hugeicons/check-list.svg"
Supermemory's graph derives new facts from patterns across your existing memories Supermemory's graph derives new facts from patterns across your existing memories
(see [graph memory](/concepts/graph-memory)). Because nobody stated these facts (see [graph memory](/concepts/graph-memory)). Because nobody stated these facts
directly, they are flagged as inferred (`isInference: true`) and **down-weighted in directly, they are flagged as inferred (`isInference: true`) until someone confirms
search until someone confirms them**. them.
These two endpoints let you build a review flow on top of that queue: list the These two endpoints let you build a review flow on top of that queue: list the
inferred memories awaiting review, then approve, decline or undo a decision on each one. inferred memories awaiting review, then approve, decline or undo a decision on each one.
@ -20,19 +20,17 @@ These endpoints are scoped to a single [namespace](/concepts/container-tags)
path parameter. path parameter.
</Info> </Info>
## How review affects ranking ## What review changes
While a memory is unreviewed and inferred it is down-weighted in search, so the Reviewing an inferred memory resolves it either way:
engine's guesses rank below facts you stated explicitly. Reviewing it resolves that
either way:
| Action | Result | Effect on search | | Action | Result | Effect on search |
|--------|--------|------------------| |--------|--------|------------------|
| **Approve** | `isInference` cleared | Ranks like a stated fact — no longer down-weighted | | **Approve** | `isInference` cleared | Stays in search, no longer flagged as inferred |
| **Decline** | `isForgotten` set | Removed from search entirely — a rejected guess is forgotten | | **Decline** | `isForgotten` set | Removed from search entirely — a rejected guess is forgotten |
| **Undo** | back to unreviewed | Returns to the queue; inferred and down-weighted again | | **Undo** | back to unreviewed | Returns to the queue, flagged as inferred again |
A reviewed memory is stamped with `reviewStatus` in its metadata so it drops out of the A reviewed memory gets an internal review stamp in its metadata so it drops out of the
review queue (declined memories also leave search, since they're forgotten). **Undo** review queue (declined memories also leave search, since they're forgotten). **Undo**
clears that stamp — and un-forgets a declined memory — bringing it back. clears that stamp — and un-forgets a declined memory — bringing it back.
@ -98,7 +96,7 @@ GET /v3/container-tags/{containerTag}/inferred
| `memories[].createdAt` | string | ISO 8601 timestamp | | `memories[].createdAt` | string | ISO 8601 timestamp |
| `memories[].updatedAt` | string | ISO 8601 timestamp | | `memories[].updatedAt` | string | ISO 8601 timestamp |
| `memories[].metadata` | object \| null | Arbitrary metadata stored on the memory | | `memories[].metadata` | object \| null | Arbitrary metadata stored on the memory |
| `total` | number | Count of unreviewed inferred memories returned | | `total` | number | Total unreviewed inferred memories in the queue; can exceed the number returned in `memories` |
<Note> <Note>
The queue returns up to **50** memories, ordered by `parentCount` descending (most The queue returns up to **50** memories, ordered by `parentCount` descending (most
@ -166,8 +164,8 @@ The reject action is named **`decline`**. There is no `reject` value.
**Action semantics:** **Action semantics:**
- **`approve`** — Promote the memory: clears `isInference`, so it ranks like a stated - **`approve`** — Promote the memory: clears `isInference`, so it's treated as a
fact instead of a down-weighted guess. Stamps `reviewStatus: "approved"`. confirmed fact. Stamps `reviewStatus: "approved"`.
- **`decline`** — Reject the suggestion: the memory is **forgotten** - **`decline`** — Reject the suggestion: the memory is **forgotten**
(`isForgotten: true`) and stamped `reviewStatus: "declined"`, so it leaves both (`isForgotten: true`) and stamped `reviewStatus: "declined"`, so it leaves both
search and the review queue. search and the review queue.
@ -198,6 +196,7 @@ The reject action is named **`decline`**. There is no `reject` value.
| Status | When | | Status | When |
|--------|------| |--------|------|
| `401` | Missing or invalid authentication | | `401` | Missing or invalid authentication |
| `403` | Your key can't access this namespace, or lacks write access to it |
| `404` | The namespace or memory was not found in your organization | | `404` | The namespace or memory was not found in your organization |
| `409` | The memory isn't reviewable for this action — it's not an inferred memory, or there's no prior review to undo | | `409` | The memory isn't reviewable for this action — it's not an inferred memory, or there's no prior review to undo |
@ -286,4 +285,4 @@ a request and the memory simply stays in the queue for a later session.
- [Graph Memory](/concepts/graph-memory) — How inferred (`derive`) memories are created - [Graph Memory](/concepts/graph-memory) — How inferred (`derive`) memories are created
- [Memory Operations](/recall/memory-operations) — Forget memories - [Memory Operations](/recall/memory-operations) — Forget memories
- [Search](/recall/search) — How inferred memories are ranked in results - [Search](/recall/search) — Results flag inferred memories with `isInference`

View file

@ -12,7 +12,7 @@ Search through your memories and documents with a single API call. Each search r
</Tip> </Tip>
<Note> <Note>
**TypeScript SDK:** one call, `supermemory.search(namespace, { query, searchMode })`. `searchMode` (`"memories"`, `"chunks"`, or `"hybrid"`) picks what comes back. There is no v5 Python SDK yet; the Python tab shows the legacy client. **SDKs:** one call, `supermemory.search(namespace, { query, searchMode })` in TypeScript or `client.search(namespace, query=..., search_mode=...)` in Python. `searchMode` (`"memories"`, `"chunks"`, or `"hybrid"`) picks what comes back.
</Note> </Note>
## Quick start ## Quick start
@ -54,10 +54,10 @@ Search through your memories and documents with a single API call. Each search r
</Tab> </Tab>
<Tab title="cURL"> <Tab title="cURL">
```bash ```bash
curl -X POST "https://api.supermemory.ai/ns/user_123/search?searchMode=hybrid&limit=5" \ curl -X POST "https://api.supermemory.ai/ns/user_123/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ "query": "machine learning" }' -d '{ "query": "machine learning", "searchMode": "hybrid", "limit": 5 }'
``` ```
</Tab> </Tab>
</Tabs> </Tabs>
@ -72,6 +72,7 @@ Search through your memories and documents with a single API call. Each search r
"similarity": 0.91, "similarity": 0.91,
"metadata": { "topic": "interests" }, "metadata": { "topic": "interests" },
"isLatest": true, "isLatest": true,
"isInference": false,
"system": { "createdAt": "2024-01-15T10:30:00.000Z", "updatedAt": "2024-01-15T10:30:00.000Z" } "system": { "createdAt": "2024-01-15T10:30:00.000Z", "updatedAt": "2024-01-15T10:30:00.000Z" }
}, },
{ {
@ -80,6 +81,7 @@ Search through your memories and documents with a single API call. Each search r
"similarity": 0.87, "similarity": 0.87,
"metadata": { "source": "onboarding_doc" }, "metadata": { "source": "onboarding_doc" },
"isLatest": true, "isLatest": true,
"isInference": false,
"system": { "createdAt": "2024-01-14T09:15:00.000Z", "updatedAt": "2024-01-14T09:15:00.000Z" } "system": { "createdAt": "2024-01-14T09:15:00.000Z", "updatedAt": "2024-01-14T09:15:00.000Z" }
} }
], ],
@ -95,7 +97,7 @@ In hybrid mode, results contain either a `memory` field (extracted facts) or a `
## Parameters ## Parameters
`namespace` is the URL path. Every other option goes in the JSON body (the second argument in the SDKs). `namespace` is the URL path. Every other option goes in the JSON body: the second argument in TypeScript, or keyword arguments in Python. Search takes no query-string parameters; sending any returns `400`.
| Parameter | Type | Default | Description | | Parameter | Type | Default | Description |
|-----------|------|---------|-------------| |-----------|------|---------|-------------|
@ -104,10 +106,10 @@ In hybrid mode, results contain either a `memory` field (extracted facts) or a `
| `searchMode` | string | `"hybrid"` | `"hybrid"` (recommended), `"memories"`, or `"chunks"` | | `searchMode` | string | `"hybrid"` | `"hybrid"` (recommended), `"memories"`, or `"chunks"` |
| `limit` | number | 10 | Max results (max 100) | | `limit` | number | 10 | Max results (max 100) |
| `threshold` | 0-1 | 0.3 | Similarity cutoff (higher = fewer, better results) | | `threshold` | 0-1 | 0.3 | Similarity cutoff (higher = fewer, better results) |
| `rerank` | string | `"none"` | `"none"`, `"order"` (re-score for relevance, +100ms), or `"aggregate"` | | `rerank` | string | `"none"` | `"none"`, `"order"` (re-score for relevance, +100ms), or `"aggregate"`. Successful reranking or aggregation can bill one extra [operation](/overview/billing#what-is-an-operation); setting the option alone does not guarantee it runs |
| `rewriteQuery` | boolean | false | Generate multiple rewrites, search all of them, and merge results. No extra cost, but adds latency | | `rewriteQuery` | boolean | false | Generate multiple rewrites, search all of them, and merge results. Adds latency and can bill one extra [operation](/overview/billing#what-is-an-operation) when rewriting runs successfully |
| `filter` | object | — | Metadata filter expression. See [Filtering](#filtering) | | `filter` | object | — | Metadata filter expression. See [Filtering](#filtering) |
| `include` | object | — | `{ documents, related, forgotten }` — opt in to extra context per result | | `include` | object | all `false` | `{ documents, related, forgotten }` — opt in to extra context per result |
<Note> <Note>
Defaults changed from v4: `searchMode` was `memories` and is now `hybrid`; `threshold` was `0.6` and is now `0.3`. Set both explicitly if you are comparing against v4 results. Defaults changed from v4: `searchMode` was `memories` and is now `hybrid`; `threshold` was `0.6` and is now `0.3`. Set both explicitly if you are comparing against v4 results.
@ -196,9 +198,9 @@ await supermemory.search("user_123", { query: "...", threshold: 0.8 });
### Attachments ### Attachments
- `attach.documents` adds the most relevant source document to each result. - `include.documents` adds the most recently updated source document to each result when one is available.
- `attach.related` adds parent, child, and sibling memories (see [graph memory](/concepts/graph-memory)). - `include.related` adds parent, child, and sibling memories (see [graph memory](/concepts/graph-memory)).
- `attach.forgotten` allows forgotten memories in related context. By default, search excludes memories that have been forgotten or have passed their `forgetAfter` expiration. - `include.forgotten` lets forgotten and expired memories appear in results. By default, search excludes memories that have been forgotten or have passed their `forgetAfter` expiration.
```typescript ```typescript
await supermemory.search("user_123", { await supermemory.search("user_123", {
@ -238,10 +240,11 @@ async function getContext(userId: string, message: string) {
memory?: string; // Present for memory results memory?: string; // Present for memory results
chunk?: string; // Present for document chunk results chunk?: string; // Present for document chunk results
similarity: number; // 0-1 similarity: number; // 0-1
metadata: object | null; metadata: Record<string, unknown>; // {} when none
isLatest: boolean; isLatest: boolean;
system: { createdAt: string; updatedAt: string }; isInference: boolean; // false for chunks
included?: { // Only when you pass attach system: { createdAt?: string; updatedAt: string };
included?: { // Only when you pass include
document?: object; document?: object;
related?: { parents: object[]; children: object[]; siblings: object[] }; related?: { parents: object[]; children: object[]; siblings: object[] };
}; };

View file

@ -62,16 +62,18 @@ Profiles are built automatically as you [ingest content](/ingestion/add-memories
{ "id": "mem_3", "memory": "User prefers dark mode interfaces" } { "id": "mem_3", "memory": "User prefers dark mode interfaces" }
], ],
"dynamic": [ "dynamic": [
{ "id": "mem_4", "memory": "User is working on Project Alpha" }, { "id": "mem_4", "memory": "[2026-10-02] User is working on Project Alpha" },
{ "id": "mem_5", "memory": "User recently started learning Rust" }, { "id": "mem_5", "memory": "[2026-10-05] User recently started learning Rust" },
{ "id": "mem_6", "memory": "User is debugging authentication issues" } { "id": "mem_6", "memory": "[2026-10-09] User is debugging authentication issues" }
], ],
"buckets": {} "buckets": {
"preferences": []
}
} }
} }
``` ```
`static` and `dynamic` are always returned. Each entry is `{ id, memory }`; the `id` is the memory ID, so you can [forget](/recall/memory-operations#forget-memories) a profile entry directly. `static`, `dynamic`, and `buckets` are always returned. Omitting the request's `buckets` returns every effective bucket, including the built-in `preferences` bucket; specifying names returns only those buckets, and an empty list returns an empty bucket section. Each entry is `{ id, memory }`; the `id` is the memory ID, so you can [forget](/recall/memory-operations#forget-memories) a profile entry directly. The exception is synthesized summaries of older memories, whose `id` starts with `aggregated_` — they don't map to a single memory. Individual `dynamic` entries start with a `[YYYY-MM-DD]` date.
--- ---
@ -123,7 +125,7 @@ The v5 profile call takes no query. When you also need query-ranked memories, ru
## Parameters ## Parameters
`namespace` is a top-level key (URL path). `body` is optional and accepts only `filter` and `buckets`. `namespace` is the first argument (URL path). The options object is optional and accepts only `filter` and `buckets`.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| |-----------|------|----------|-------------|

View file

@ -7,17 +7,19 @@ icon: "/icons/hugeicons/settings-01.svg"
The self-hosted server aims for **zero configuration** — the only required input is one LLM provider key, which the first-boot wizard collects interactively (or set via env var for non-interactive deployments). Embeddings default to local English; you can pick another provider in the optional wizard step or via env. Everything else below is opt-in. The self-hosted server aims for **zero configuration** — the only required input is one LLM provider key, which the first-boot wizard collects interactively (or set via env var for non-interactive deployments). Embeddings default to local English; you can pick another provider in the optional wizard step or via env. Everything else below is opt-in.
The installer writes API keys to `~/.supermemory/env`, which is loaded on every launch. You can also set variables in your shell or a process manager. The installer writes API keys to `~/.supermemory/env`, and the first-boot wizard saves them encrypted to `env.enc` in the data directory. Saved keys are loaded on every launch. When the data directory is `~/.supermemory`, the server copies the installer's parsed plaintext `env` values into `env.enc` on the next launch before attempting to remove `env`. A failed migration or removal can leave the plaintext file in place. You can also set variables in your shell or a process manager.
## Core ## Core
| Variable | Purpose | Default | | Variable | Purpose | Default |
|---|---|---| |---|---|---|
| `PORT` (or `SUPERMEMORY_PORT`) | HTTP listen port | `6767` | | `PORT` (or `SUPERMEMORY_PORT`) | HTTP listen port | `6767` |
| `SUPERMEMORY_HOST` | In the next release, controls the listen address; not supported by v0.0.8 | `127.0.0.1` in the next release | | `SUPERMEMORY_HOST` | Listen address (v0.0.9 and later). Set `0.0.0.0` only when you protect the server with TLS and firewall rules | `127.0.0.1` |
| `SUPERMEMORY_DATA_DIR` | Where the graph engine's data, auth secret, and model cache live | `./.supermemory` | | `SUPERMEMORY_DATA_DIR` | Where the graph engine's data, auth secret, and model cache live | `~/.supermemory` |
**The currently published v0.0.8 binds all interfaces and its implicit local authentication is unsafe when exposed to untrusted networks. Do not expose it; restrict access with a firewall or run it on an isolated machine.** The next release will bind loopback and require the generated key for every API request, including requests from localhost. Its browser welcome page will no longer reveal the key; enter the key printed at first boot in the Memory tab. If you later expose that release through a reverse proxy, protect the endpoint with TLS and access controls. From v0.0.9, an unset `SUPERMEMORY_DATA_DIR` uses `~/.supermemory`. An existing `./.supermemory` store in the current directory is still used if `~/.supermemory` has none. If both have a store, the server refuses to start until you set `SUPERMEMORY_DATA_DIR`. v0.0.8 and older always use `./.supermemory`.
From v0.0.9, the server binds `127.0.0.1` and every API request needs the generated key, including requests from localhost. The welcome page does not show the key; enter the key printed at first boot in the Memory tab. If you expose the server through a reverse proxy, protect it with TLS and access controls. **v0.0.8 and older bind all interfaces with implicit local authentication. Do not expose them; upgrade, or restrict access with a firewall.**
## LLM providers ## LLM providers
@ -28,18 +30,20 @@ In production, Supermemory uses its own proprietary models tuned for long-horizo
| `OPENAI_API_KEY` | OpenAI — or any OpenAI-compatible endpoint, see below | | `OPENAI_API_KEY` | OpenAI — or any OpenAI-compatible endpoint, see below |
| `ANTHROPIC_API_KEY` | Anthropic | | `ANTHROPIC_API_KEY` | Anthropic |
| `GEMINI_API_KEY` | Google AI Studio (Gemini) | | `GEMINI_API_KEY` | Google AI Studio (Gemini) |
| `GOOGLE_VERTEX_PROJECT_ID` + `GOOGLE_VERTEX_LOCATION` | GCP Vertex AI |
| `GROQ_API_KEY` | Groq | | `GROQ_API_KEY` | Groq |
| `WORKERS_AI_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` | Cloudflare Workers AI | | `WORKERS_AI_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` | Cloudflare Workers AI |
| `GOOGLE_VERTEX_PROJECT_ID` + `GOOGLE_VERTEX_LOCATION` | GCP Vertex AI |
<Tip> <Tip>
No key set? The server walks you through it. On first boot, an interactive setup wizard asks which provider you want, securely prompts for the key, and saves it encrypted — including a custom base URL and model name if you pick an OpenAI-compatible endpoint. No key set? The server walks you through it. On first boot, an interactive setup wizard asks which provider you want, securely prompts for the key, and saves it encrypted — including a custom base URL and model name if you pick an OpenAI-compatible endpoint.
</Tip> </Tip>
With multiple providers configured, the first one in the order above is used. For fast/text LLM tasks, the first configured provider in the order above is used. Image and video tasks use Vertex AI first, then Gemini.
<Note> <Note>
Image, video, and high-fidelity PDF understanding require a Gemini or Vertex AI key. Text ingestion, memory extraction, and search work with any provider. Image and video understanding require a Gemini or Vertex AI key. PDF extraction requires `GEMINI_API_KEY`; Vertex AI doesn't cover it. Text ingestion, memory extraction, and search work with any provider.
Webpage ingestion fetches pages through Firecrawl and needs `FIRECRAWL_API_KEY`. Without it, webpage documents fail. URLs pointing to PDFs or media use their corresponding extractors instead.
</Note> </Note>
### Fully offline with local models ### Fully offline with local models
@ -60,22 +64,26 @@ OPENAI_MODEL=gpt-oss:20b
| `OPENAI_FAST_MODEL` | Override for fast/light tasks | `OPENAI_MODEL` | | `OPENAI_FAST_MODEL` | Override for fast/light tasks | `OPENAI_MODEL` |
| `OPENAI_TEXT_MODEL` | Override for heavier text tasks | `OPENAI_MODEL` | | `OPENAI_TEXT_MODEL` | Override for heavier text tasks | `OPENAI_MODEL` |
## File storage ## Storage & persistence
### File storage
Uploaded files (PDFs, images) are stored on local disk inside `$SUPERMEMORY_DATA_DIR` and served by the server at `/files/:key`.
### Database snapshots & resource usage
The server saves its database and vector indices as one encrypted snapshot file at `$SUPERMEMORY_DATA_DIR/data`.
- **Idle (v0.0.7 and later):** if nothing changed since the last snapshot, the server skips it, so an idle server does no snapshot work.
- **While writing:** each snapshot is a full dump, taken every 10 seconds while writes continue. The interval is not configurable.
- **Shutdown:** on `SIGINT` or `SIGTERM`, pending changes are saved before the process exits.
Nothing to configure. Uploaded files (PDFs, images) are stored on local disk inside `$SUPERMEMORY_DATA_DIR` and served by the server at `/files/:key`.
## Embeddings ## Embeddings
By default, vectors are computed locally with `Xenova/bge-base-en-v1.5` (768d) — no embedding API key. On interactive first boot you can pick a different provider after the LLM key step; for Docker/CI set env vars instead. By default, vectors are computed locally with `Xenova/bge-base-en-v1.5` (768d) — no embedding API key. On interactive first boot you can pick a different provider after the LLM key step; for Docker/CI set env vars instead.
The full provider table, multilingual guidance, remote examples (OpenAI, Gemini, Ollama) and the dimension-lock warning are in [embeddings for self-hosting](/self-hosting/embeddings). The `SUPERMEMORY_EMBEDDING_*` env vars, the local model table, remote examples (OpenAI, Gemini, Ollama) and the dimension lock are in [embeddings for self-hosting](/self-hosting/embeddings).
| Variable | Purpose | Default |
|---|---|---|
| `SUPERMEMORY_EMBEDDING_PROVIDER` | `local`, `openai`, `gemini`, or OpenAI-compatible remote | `local` |
| `SUPERMEMORY_EMBEDDING_MODEL` | Model id for the chosen provider | `Xenova/bge-base-en-v1.5` |
| `SUPERMEMORY_EMBEDDING_DIMENSIONS` | Vector size; must match model and stored data | `768` |
| `SUPERMEMORY_EMBEDDING_BASE_URL` | Base URL for OpenAI-compatible embedding APIs | unset |
### Embedding performance ### Embedding performance
@ -132,7 +140,7 @@ These exist in the codebase but are exclusive to the [hosted platform](https://c
- **Optimized memory extraction** — the platform's extraction pipeline is tuned for higher quality at lower cost than bring-your-own-key - **Optimized memory extraction** — the platform's extraction pipeline is tuned for higher quality at lower cost than bring-your-own-key
- **Managed scale** — globally distributed infrastructure, no capacity planning - **Managed scale** — globally distributed infrastructure, no capacity planning
Any other environment variables you may find referenced in the codebase are platform-only: the self-hosted binary ignores them even when set. Besides the variables on this page, the binary honors a few operational switches: `SUPERMEMORY_VERBOSE=1` (detailed logs), `SUPERMEMORY_HIDE_API_KEY_IN_LOGS=1` (omit the key from the startup banner), `SUPERMEMORY_NO_OPEN=1` (don't open a browser on boot), and `SUPERMEMORY_NO_UPDATE_CHECK=1` (skip the update notification). The Docker image sets the last three.
## Example: production-ish `.env` ## Example: production-ish `.env`

View file

@ -0,0 +1,109 @@
---
title: "Run Supermemory with Docker"
sidebarTitle: "Docker"
description: "Start the prebuilt Local image, keep your data, and upgrade an existing Compose installation."
icon: "docker"
---
Run Supermemory Local with the prebuilt Docker image. Docker chooses the Linux amd64 or arm64 image for your machine. You don’t need the server source, a local build, or a registry login.
You need Docker with the Compose plugin. Docker Desktop on macOS or Windows must run Linux containers. These shell examples use Bash or a compatible shell.
## Start a new installation
Use an empty directory for this installation. If you already run Supermemory with Compose, follow [Upgrade an existing installation](#upgrade-an-existing-installation) instead of overwriting your configuration.
Download the Compose file from the [0.0.9 release](https://github.com/supermemoryai/supermemory/releases/tag/server-v0.0.9):
```bash
release_url=https://github.com/supermemoryai/supermemory/releases
curl -fL "$release_url/download/server-v0.0.9/compose.yaml" -o compose.yaml
```
Create a `.env` file beside `compose.yaml`, replacing the placeholder with your provider key:
```dotenv
OPENAI_API_KEY=your_openai_api_key_here
SUPERMEMORY_VERSION=0.0.9
```
You can use `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, or `GROQ_API_KEY` instead of OpenAI. Keep `.env` private and don’t commit it. Docker runs non-interactively, so there’s no first-boot provider wizard.
Protect the file, pull the image, and start the service:
```bash
chmod 600 .env
unset SUPERMEMORY_VERSION
docker compose pull
docker compose up -d --wait --wait-timeout 180
docker compose ps
```
The published image is `docker.io/supermemory/supermemory`. `SUPERMEMORY_VERSION=0.0.9` pins this release; `latest` follows stable releases. The Compose file binds port `6767` to `127.0.0.1`, not the public network. Allow the first startup to finish before checking health:
```bash
curl --fail http://127.0.0.1:6767/v3/health
```
Local embeddings are the default. The first model download needs network access, and your chosen LLM provider may make external requests.
## Authenticate API requests
The server creates a local API key, separate from your LLM provider key. The Docker image hides that key from startup logs, so read it from the container:
```bash
SUPERMEMORY_API_KEY="$(docker compose exec -T supermemory cat /data/api-key)"
curl --fail -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
http://127.0.0.1:6767/v3/projects
```
Treat this key as a secret. API requests require it even on localhost. Use the same key with your SDK and set `baseUrl` (Python: `base_url`) to `http://localhost:6767`.
## Preserve data and configuration
The Compose project name is fixed as `supermemory`. Its named volume, `supermemory_supermemory-data`, mounts at `/data` and contains your store, API key, encryption identity, configuration, and model cache. Container replacement keeps that volume.
<Warning>
Don’t run `docker compose down -v` or delete the volume: that removes your stored data and credentials. Keep the project name and volume mapping unchanged when upgrading. Changing the provider or embedding settings is a separate operation.
</Warning>
The Compose file passes through the provider keys listed above and `FIRECRAWL_API_KEY`. To disable telemetry, add `SUPERMEMORY_DISABLE_TELEMETRY=1` to `.env`. Other [configuration variables](/self-hosting/configuration) need entries in the service’s `environment` block; setting them only in the host shell doesn’t pass them into the container.
## Upgrade an existing installation
Back up the complete stopped store before replacing its image. Run these commands from the directory containing your existing Compose file.
### Back up the store
Stop the service, then copy everything under `/data`, including its keys and identity files:
```bash
docker compose stop
backup_dir="backups/$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$backup_dir"
chmod 700 "$backup_dir"
docker compose cp supermemory:/data/. "$backup_dir/"
```
Keep this backup and your existing `.env` file private. Confirm the copy completed before upgrading. These steps cover an existing Docker store; moving a native installation into Docker requires preserving that installation’s complete store and keys separately.
### Pull and start the new image
Keep your existing Compose configuration. If it uses an older GHCR image reference, change only its `image` line to the public Docker Hub image:
```yaml
image: docker.io/supermemory/supermemory:${SUPERMEMORY_VERSION:-latest}
```
Set `SUPERMEMORY_VERSION=0.0.9` in your existing `.env`, then pull and recreate the service without deleting its volume. Unset a previously exported version so `.env` takes precedence:
```bash
unset SUPERMEMORY_VERSION
docker compose pull
docker compose up -d --wait --wait-timeout 180
curl --fail http://127.0.0.1:6767/v3/health
```
Local verifies a complete original backup before migrating an existing store. Existing cleanup can remove older duplicate documents and their source links. Keep your manual backup too; don’t point an older image at a migrated store. To roll back, restore the complete pre-upgrade backup and use its matching previous version.
Native Windows remains on 0.0.8; these Docker images run Linux containers.

View file

@ -5,7 +5,7 @@ description: "Local and remote embedding providers for Supermemory local: defaul
icon: "/icons/hugeicons/route-02.svg" icon: "/icons/hugeicons/route-02.svg"
--- ---
Self-hosted Supermemory uses the **same embedding provider stack** as the hosted platform: local ONNX models, OpenAI, Gemini, or any OpenAI-compatible embeddings endpoint (including Ollama). LLM keys power extraction and summarization; embeddings are configured separately. Self-hosted Supermemory supports local ONNX models, OpenAI, Gemini, or any OpenAI-compatible embeddings endpoint (including Ollama). LLM keys power extraction and summarization; embeddings are configured separately.
## Defaults ## Defaults
@ -28,7 +28,7 @@ On first boot with a TTY, Supermemory asks for an LLM API key (required), then o
1. Choose or paste an LLM provider key (OpenAI, Anthropic, Gemini, Groq, or OpenAI-compatible). 1. Choose or paste an LLM provider key (OpenAI, Anthropic, Gemini, Groq, or OpenAI-compatible).
2. Optionally pick an embedding provider/model. **Press Enter to keep the local English model.** 2. Optionally pick an embedding provider/model. **Press Enter to keep the local English model.**
3. Choices are saved encrypted under your data directory (`$SUPERMEMORY_DATA_DIR`, typically `./.supermemory` / `~/.supermemory`). 3. Choices are saved encrypted under your data directory (`$SUPERMEMORY_DATA_DIR`, `~/.supermemory` by default from v0.0.9, `./.supermemory` before).
Boot order is intentional: LLM keys load first so remote embedding options can reuse them (for example OpenAI or Gemini embeddings with the same key). Boot order is intentional: LLM keys load first so remote embedding options can reuse them (for example OpenAI or Gemini embeddings with the same key).
@ -42,10 +42,11 @@ For Docker, CI, or any non-interactive deploy, set env vars — there is **no in
| Variable | Purpose | Default | | Variable | Purpose | Default |
|---|---|---| |---|---|---|
| `SUPERMEMORY_EMBEDDING_PROVIDER` | Embedding backend: `local`, `openai`, `gemini`, or an OpenAI-compatible remote (`ollama` / custom base URL) | `local` | | `SUPERMEMORY_EMBEDDING_PROVIDER` | Embedding backend: `local`, `openai`, `gemini`, or `openai-compatible` (Ollama, vLLM, or any custom base URL) | `local` |
| `SUPERMEMORY_EMBEDDING_MODEL` | Model id for the chosen provider | `Xenova/bge-base-en-v1.5` (local) | | `SUPERMEMORY_EMBEDDING_MODEL` | Model id for the chosen provider (e.g. `Xenova/bge-m3`, `text-embedding-3-small`) | `Xenova/bge-base-en-v1.5` (local), `text-embedding-3-small` (OpenAI and compatible), `gemini-embedding-001` (Gemini) |
| `SUPERMEMORY_EMBEDDING_DIMENSIONS` | Vector size; must match the model and any already-stored data | `768` (local default) | | `SUPERMEMORY_EMBEDDING_DIMENSIONS` | Vector size; must match the model's output size (native, or a reduced size for `text-embedding-3-*` and Gemini) and any stored data. Required for models not in the [table below](#local-models-with-known-dimensions) or the defaults | Inferred for known models (`768` for the local default) |
| `SUPERMEMORY_EMBEDDING_BASE_URL` | Base URL for OpenAI-compatible embedding APIs (Ollama, vLLM, etc.) | unset | | `SUPERMEMORY_EMBEDDING_BASE_URL` | Base URL for OpenAI-compatible embedding APIs (Ollama, vLLM, etc.) | unset (`openai-compatible` falls back to `OPENAI_BASE_URL`) |
| `SUPERMEMORY_EMBEDDING_API_KEY` | API key for embedding endpoint (falls back to `OPENAI_API_KEY` / `GEMINI_API_KEY`; `openai-compatible` needs this or `OPENAI_API_KEY`; use any non-empty string if the endpoint has no auth) | unset |
| `OPENAI_API_KEY` | Used when provider is `openai` (or compatible) if not otherwise supplied | unset | | `OPENAI_API_KEY` | Used when provider is `openai` (or compatible) if not otherwise supplied | unset |
| `GEMINI_API_KEY` | Used when provider is `gemini` | unset | | `GEMINI_API_KEY` | Used when provider is `gemini` | unset |
@ -91,6 +92,22 @@ SUPERMEMORY_EMBEDDING_MODEL=Xenova/bge-base-en-v1.5
SUPERMEMORY_EMBEDDING_DIMENSIONS=768 SUPERMEMORY_EMBEDDING_DIMENSIONS=768
``` ```
#### Local models with known dimensions
Local models do not support dimension reduction, so `SUPERMEMORY_EMBEDDING_DIMENSIONS` must match the model's native size:
| Model ID | Dimensions | Notes |
|---|---|---|
| `Xenova/bge-base-en-v1.5` | `768` | Default local model (English) |
| `Xenova/bge-m3` | `1024` | Multilingual (uses `cls` pooling) |
| `Xenova/bge-small-en-v1.5` | `384` | English (lightweight) |
| `Xenova/bge-large-en-v1.5` | `1024` | English (high capacity) |
| `Xenova/multilingual-e5-small` | `384` | Multilingual (lightweight) |
| `Xenova/multilingual-e5-base` | `768` | Multilingual |
| `Xenova/multilingual-e5-large` | `1024` | Multilingual (high capacity) |
| `Xenova/all-MiniLM-L6-v2` | `384` | English general purpose |
| `Xenova/paraphrase-multilingual-MiniLM-L12-v2` | `384` | Multilingual sentence similarity |
### OpenAI ### OpenAI
```bash ```bash
@ -105,16 +122,16 @@ SUPERMEMORY_EMBEDDING_DIMENSIONS=1536
```bash ```bash
GEMINI_API_KEY=... GEMINI_API_KEY=...
SUPERMEMORY_EMBEDDING_PROVIDER=gemini SUPERMEMORY_EMBEDDING_PROVIDER=gemini
SUPERMEMORY_EMBEDDING_MODEL=text-embedding-004 SUPERMEMORY_EMBEDDING_MODEL=gemini-embedding-001
SUPERMEMORY_EMBEDDING_DIMENSIONS=768 SUPERMEMORY_EMBEDDING_DIMENSIONS=768
``` ```
### Ollama (OpenAI-compatible) ### Ollama (OpenAI-compatible)
```bash ```bash
SUPERMEMORY_EMBEDDING_PROVIDER=openai SUPERMEMORY_EMBEDDING_PROVIDER=openai-compatible
SUPERMEMORY_EMBEDDING_BASE_URL=http://localhost:11434/v1 SUPERMEMORY_EMBEDDING_BASE_URL=http://localhost:11434/v1
OPENAI_API_KEY=ollama SUPERMEMORY_EMBEDDING_API_KEY=ollama
SUPERMEMORY_EMBEDDING_MODEL=nomic-embed-text SUPERMEMORY_EMBEDDING_MODEL=nomic-embed-text
SUPERMEMORY_EMBEDDING_DIMENSIONS=768 SUPERMEMORY_EMBEDDING_DIMENSIONS=768
``` ```
@ -127,15 +144,22 @@ Use the dimension published for your chosen model. A mismatch with vectors alrea
**Not supported in place.** Embeddings from different models (or different dimensions) are not comparable. Start from a fresh data directory or re-ingest all content so vectors stay in one space. If configured dimensions disagree with stored data, the server **refuses to boot**. **Not supported in place.** Embeddings from different models (or different dimensions) are not comparable. Start from a fresh data directory or re-ingest all content so vectors stay in one space. If configured dimensions disagree with stored data, the server **refuses to boot**.
</Warning> </Warning>
**Changing embeddings later:** Not supported in place. Start from a fresh data directory or re-ingest all content so vectors stay comparable. <Note>
**Embedding env vars are ignored in v0.0.6 and v0.0.7-rc.2**
> [!IMPORTANT] Those two releases ignore `SUPERMEMORY_EMBEDDING_*` and always use local `Xenova/bge-base-en-v1.5`. The env vars work in `v0.0.7` and later.
> **Model Mixing Bug in v0.0.5 (Exact match returns nothing)**
> A data directory created on `v0.0.6` has no `embedding-plan.json`. From `v0.0.7`, if such a directory already has embeddings, the server locks it to `local · Xenova/bge-base-en-v1.5 · 768d`. To use another provider or model, point `SUPERMEMORY_DATA_DIR` at a fresh directory (or delete the old one) before starting with the new env vars.
> In version `v0.0.5`, there was a bug where the server could mix different embedding models between write and read paths (e.g., document ingestion using OpenAI but memory queries using local default embeddings). In multilingual contexts like Japanese (which lacks space tokenization for fallback lexical FTS matching), this caused exact-text memory searches through `/v4/search` and `/v4/profile` to silently return `{"results":[],"total":0}`. </Note>
>
> **Resolution:** <Note>
> This was fully resolved in `v0.0.7` by locking the embedding plan uniformly across all document and query embedding paths (enforced via a locked plan in the database store). If you are running `v0.0.5` and experiencing this issue, you should upgrade to `v0.0.7` or later. **Model Mixing Bug in v0.0.5 (Exact match returns nothing)**
In version `v0.0.5`, there was a bug where the server could mix different embedding models between write and read paths (e.g., document ingestion using OpenAI but memory queries using local default embeddings). In multilingual contexts like Japanese (which lacks space tokenization for fallback lexical FTS matching), this caused exact-text memory searches through `/v4/search` and `/v4/profile` to silently return `{"results":[],"total":0}`.
**Resolution:**
This was fully resolved in `v0.0.7` by locking the embedding plan uniformly across all document and query embedding paths (enforced via a locked plan in the database store). If you are running `v0.0.5` and experiencing this issue, you should upgrade to `v0.0.7` or later.
</Note>
## Related ## Related

View file

@ -5,7 +5,7 @@ description: "Supermemory local is for builders. Supermemory Enterprise is for o
icon: "/icons/hugeicons/building-03.svg" icon: "/icons/hugeicons/building-03.svg"
--- ---
Supermemory local — the self-hosted binary — is free within its lite license limit and built for individual developers: local-first workflows, prototyping, and privacy-sensitive side projects. Its server source is not in the public repository. Text-memory processing can run offline after the first embedding-model download; URL ingestion uses a hosted reader service. Supermemory local — the self-hosted binary — is free within its lite license limit of 10,000 stored documents and built for individual developers: local-first workflows, prototyping, and privacy-sensitive side projects. Its server source is not in the public repository. Text-memory processing can run offline after the first embedding-model download when the LLM is also local; webpage ingestion goes through Firecrawl and needs `FIRECRAWL_API_KEY`.
**Supermemory Enterprise** is the full platform, run for your organization: the same memory engine with proprietary models, organizational controls, and infrastructure that scales with you — without you operating any of it. **Supermemory Enterprise** is the full platform, run for your organization: the same memory engine with proprietary models, organizational controls, and infrastructure that scales with you — without you operating any of it.
@ -44,7 +44,7 @@ Local is bounded by one machine — which is the point. Enterprise runs on globa
## Moving between them ## Moving between them
The two speak the same API. Code written against your local server moves to Enterprise by changing the `baseUrl` — and vice versa. Prototype locally, ship on Enterprise. The two speak the same API. Point your code at the destination's `baseUrl` and use a credential issued there. Changing the connection does not transfer your local data. Prototype locally, ship on Enterprise.
<CardGroup cols={2}> <CardGroup cols={2}>
<Card title="Talk to us" icon="/icons/hugeicons/mail-01.svg" href="mailto:dhravya@supermemory.com"> <Card title="Talk to us" icon="/icons/hugeicons/mail-01.svg" href="mailto:dhravya@supermemory.com">

View file

@ -24,7 +24,7 @@ No Docker. No database to provision. No config files. It boots in seconds with e
Run the binary with nothing set and you get a complete memory system: 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. - **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. Same provider stack as cloud if you opt into OpenAI, Gemini, or Ollama — see [Embeddings](/self-hosting/embeddings). - **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](/self-hosting/embeddings).
- **An API key, generated for you** — printed on first boot, ready to paste into any SDK. - **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 full Memory API** — `/ns/{namespace}/document`, `/ns/{namespace}/search`, `/ns/{namespace}/profile`, namespaces, the works.
@ -41,7 +41,7 @@ OPENAI_MODEL=gpt-oss:20b \
supermemory-server supermemory-server
``` ```
Local 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`. URL ingestion uses a hosted reader service; use local text or file inputs for offline operation. Local 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 ## Drop-in with your existing code
@ -62,20 +62,20 @@ Everything in the [Memory API docs](/quickstart) works the same way. The coding
## Self-hosted vs. the platform ## 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 is free within its lite license limit of 10,000 stored documents 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 | | | Self-hosted | Platform |
|---|---|---| |---|---|---|
| Full Memory API | ✅ | ✅ | | Full Memory API | ✅ | ✅ |
| Hybrid semantic search | ✅ | ✅ | | Hybrid semantic search | ✅ | ✅ |
| Embeddings | Local default (or OpenAI / Gemini / Ollama) | Same provider stack, managed | | Embeddings | Local default (or OpenAI / Gemini / Ollama) | Managed |
| File ingestion (PDFs, images) | ✅ | ✅ | | File ingestion (PDFs, images) | ✅ (PDFs need `GEMINI_API_KEY`; images use Gemini or Vertex AI) | ✅ |
| [Connectors](/connectors/overview) (Google Drive, Notion, Gmail, OneDrive) | — | ✅ | | [Connectors](/connectors/overview) (Google Drive, Notion, Gmail, OneDrive) | — | ✅ |
| [Supermemory MCP](/supermemory-mcp/mcp) | — | ✅ | | [Supermemory MCP](/supermemory-mcp/mcp) | — | ✅ |
| Memory extraction | Your model, your key | Proprietary long-horizon models — higher quality, cheaper at scale | | Memory extraction | Your model, your key | Proprietary long-horizon models — higher quality, cheaper at scale |
| Infrastructure | Your machine | Globally distributed, scales with you | | Infrastructure | Your machine | Globally distributed, scales with you |
If you outgrow a single machine — or want connectors, MCP, and the best-tuned extraction pipeline — [the platform](https://console.supermemory.ai) is one `serverURL` change away. Running this for a team or organization? See [Local vs. Enterprise](/self-hosting/local-vs-enterprise). If you outgrow a single machine — or want connectors, MCP, and the best-tuned extraction pipeline — switch to [the platform](https://console.supermemory.ai) 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](/self-hosting/local-vs-enterprise).
## Next steps ## Next steps

View file

@ -45,7 +45,7 @@ Runs on `claude-haiku-4-5` — this one isn't currently configurable via env var
GEMINI_API_KEY=... GEMINI_API_KEY=...
``` ```
Runs on `gemini-3.1-flash-lite-preview` — also not currently overridable. This is the only key that also unlocks image, video, and high-fidelity PDF understanding (see the [full provider table](/self-hosting/configuration#llm-providers)). Runs on `gemini-3.1-flash-lite-preview` — also not currently overridable. This is the only key on this page that also unlocks image, video, and PDF understanding (see the [full provider table](/self-hosting/configuration#llm-providers)).
</Tab> </Tab>
<Tab title="OpenRouter"> <Tab title="OpenRouter">

View file

@ -58,12 +58,18 @@ supermemory-server
First boot sets everything up — the embedded Supermemory graph engine, local embeddings, and your credentials: First boot sets everything up — the embedded Supermemory graph engine, local embeddings, and your credentials:
``` ```
┌──────────────────────────────────────────────────┐ ╭───────────────────────────────────────────────────────────────╮
│ → supermemory ready │
│ │
│ url http://localhost:6767 │ │ url http://localhost:6767 │
│ database ./.supermemory │ │ database encrypted local storage (~/.supermemory) │
│ embeddings local · Xenova/bge-base-en-v1.5 · 768d │
│ workflow rivet │
│ api key sm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx │ │ api key sm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx │
│ org id xxxxxxxxxxxxxxxxxxxxxx │ │ 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. Save that API key — it's your bearer token for every request.
@ -76,6 +82,8 @@ In production, Supermemory runs proprietary models tuned for long-horizon data u
**Docker / non-interactive:** set an LLM key via env and, if you don’t want local embeddings, set `SUPERMEMORY_EMBEDDING_PROVIDER` / `MODEL` / `DIMENSIONS`. There is no wizard without a TTY. **Docker / non-interactive:** set an LLM key via env and, if you don’t want local embeddings, set `SUPERMEMORY_EMBEDDING_PROVIDER` / `MODEL` / `DIMENSIONS`. There is no wizard without a TTY.
</Tip> </Tip>
For the prebuilt Docker image, follow [Run Supermemory with Docker](/self-hosting/docker) for installation, authentication, backups, and upgrades.
## Add your first memory ## Add your first memory
<Note> <Note>
@ -116,11 +124,12 @@ client.add(
</Tab> </Tab>
<Tab title="curl"> <Tab title="curl">
```bash ```bash
curl "http://localhost:6767/ns/user_dhravya/document?dreaming=instant" \ curl http://localhost:6767/ns/user_dhravya/document \
-H "Authorization: Bearer sm_..." \ -H "Authorization: Bearer sm_..." \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"content": "I am Dhravya. I love building dev tools and I am allergic to peanuts." "content": "I am Dhravya. I love building dev tools and I am allergic to peanuts.",
"dreaming": "instant"
}' }'
``` ```
</Tab> </Tab>
@ -161,8 +170,9 @@ By default, all state lives in a single directory you can back up or move:
| Path | Contents | | Path | Contents |
|---|---| |---|---|
| `./.supermemory/` (or `$SUPERMEMORY_DATA_DIR`) | The Supermemory graph engine's data, auth secret, embedding model cache | | `~/.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` | API keys saved by the installer, loaded on every launch | | `~/.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 ## Next steps

View file

@ -226,9 +226,9 @@ print(r.stdout)
## Commands the agent can run ## Commands the agent can run
The Python tool exposes the same command surface as the TypeScript version: standard Unix builtins (`pwd`, `cd`, `ls`, `cat`, `stat`, `mkdir`, `rm`, `mv`, `cp`, `echo`), search and text utilities (`grep`, `find`, `head`, `tail`, `wc`, `sort`, `sed`, `awk`), plus the custom `sgrep <query> [path]` for semantic search across the container. Pipes, redirects, conditionals, loops, and file tests all work. The Python tool runs its own built-in commands: filesystem builtins (`pwd`, `cd`, `ls`, `cat`, `stat`, `mkdir`, `rm`, `rmdir`, `mv`, `cp`, `touch`, `echo`, `printf`, `tee`, `basename`, `dirname`), search and text utilities (`grep`, `find`, `head`, `tail`, `wc`, `sort`, `uniq`, `sed`, `cut`, `tr`, `seq`, `date`), file tests (`test`, `[`), plus the custom `sgrep <query> [path]` for semantic search across the container.
See the [TypeScript Bash Tool reference](/smfs/bash-tool#commands-the-agent-can-run) for the full list. Pipes (`|`), redirects (`>`, `>>`, `<`), and `&&` / `||` chains work. Only simple commands run: loops (`for`, `while`), `if` blocks, and `awk` are not supported in the Python tool. Use the [TypeScript Bash Tool](/smfs/bash-tool#commands-the-agent-can-run) if your agent needs them.
## Configuration ## Configuration
@ -239,7 +239,7 @@ See the [TypeScript Bash Tool reference](/smfs/bash-tool#commands-the-agent-can-
| `base_url` | `None` | Override the API endpoint | | `base_url` | `None` | Override the API endpoint |
| `eager_load` | `True` | Warm the path index when the instance starts | | `eager_load` | `True` | Warm the path index when the instance starts |
| `eager_content` | `True` | Also warm the content cache during eager load | | `eager_content` | `True` | Also warm the content cache during eager load |
| `cwd` | `"/home/user"` | Initial working directory | | `cwd` | `"/"` | Initial working directory |
| `env` | `None` | Extra environment variables | | `env` | `None` | Extra environment variables |
| `cache_ttl_ms` | `150_000` | Content cache TTL in ms. `None` = never expires (single-writer). `0` = no cache. | | `cache_ttl_ms` | `150_000` | Content cache TTL in ms. `None` = never expires (single-writer). `0` = no cache. |
@ -247,6 +247,6 @@ The container is what defines the filesystem; setting `cwd` or extra `env` from
## Limitations ## Limitations
- `chmod`, `utimes`, and symlinks (`ln -s`, `readlink`) raise `ENOSYS`. - `chmod`, `ln`, and `readlink` aren't available; the shell returns `command not found`.
- `/dev/null` as a redirect target isn't supported. Write to `/tmp/discard.log` instead. - Redirecting to `/dev/null` discards the output; nothing is written to the container.
- Binary uploads aren't supported. Text is extracted server-side. - Binary uploads aren't supported. Text is extracted server-side.

View file

@ -193,8 +193,9 @@ Standard Unix surface, plus one custom command. Each does what you'd expect.
| `eagerLoad` | `true` | Warm the path index when the instance starts | | `eagerLoad` | `true` | Warm the path index when the instance starts |
| `eagerContent` | `true` | Also warm the content cache during eager load | | `eagerContent` | `true` | Also warm the content cache during eager load |
| `cacheTtlMs` | `150_000` | Content cache TTL in ms. `null` = never expires (single-writer). `0` = no cache. | | `cacheTtlMs` | `150_000` | Content cache TTL in ms. `null` = never expires (single-writer). `0` = no cache. |
| `includeDocIds` | `false` | Append a `[doc:<id>]` annotation to each `sgrep` result so you can extract source document IDs from stdout |
Other options (`customCommands`, `logger`, plus `just-bash` pass-throughs like `executionLimits`, `network`, `python`, `javascript`, `cwd`, `env`) exist but aren't part of the supported surface for the SMFS use case. The container is what defines the filesystem; setting `cwd` or extra `env` from the host doesn't change that. Other options (`logger`, plus the `just-bash` pass-throughs `executionLimits` and `env`) exist but aren't part of the supported surface for the SMFS use case. The container is what defines the filesystem; setting extra `env` from the host doesn't change that.
## Limitations ## Limitations

View file

@ -118,6 +118,7 @@ Pushes pending writes and pulls remote changes immediately. Useful right before
| `--foreground` | Run the daemon inline instead of detaching. | | `--foreground` | Run the daemon inline instead of detaching. |
| `--backend <name>` | Linux only. `fuse` (default) or `nfs`. | | `--backend <name>` | Linux only. `fuse` (default) or `nfs`. |
| `--key <key>` | Pass an API key explicitly. Saved to project credentials. | | `--key <key>` | Pass an API key explicitly. Saved to project credentials. |
| `--api-url <url>` | Override the API base URL. Also read from `SUPERMEMORY_API_URL`. |
## Multiple agents and multiple containers ## Multiple agents and multiple containers

View file

@ -16,11 +16,13 @@ GET /ns/{namespace}/document/{id}?include=chunks,memories
Pass `include` as a comma-separated list to return chunks, memories, or both. Keys you omit are absent; requested keys with no results are empty arrays. Lifecycle fields move under `system`: Pass `include` as a comma-separated list to return chunks, memories, or both. Keys you omit are absent; requested keys with no results are empty arrays. Lifecycle fields move under `system`:
```json ```json
{"system":{"status":"done","createdAt":"...","updatedAt":"..."}} {"system":{"status":"done","dreamingStatus":"dreaming","createdAt":"...","updatedAt":"..."}}
``` ```
`system.status` is one of `unknown`, `queued`, `extracting`, `chunking`, `embedding`, `indexing`, `done`, or `failed`. `system.status` is one of `unknown`, `queued`, `extracting`, `chunking`, `embedding`, `indexing`, `done`, or `failed`.
`system.dreamingStatus` tracks memory generation separately. It is `dreaming` while memories for the latest content are queued, being generated, or waiting for a retry, and `done` once they are generated or when the document needed none. Under the default `dreaming: "dynamic"`, `status` can reach `done` minutes before `dreamingStatus` does, so wait on `dreamingStatus` when the next step reads memories or profiles. Legacy `GET /v3/documents/{id}` returned it as a top-level `dreamingStatus`; v5 nests it under `system` on document reads and on document list items.
### Retrieve a memory and its history ### Retrieve a memory and its history
```bash v5 ```bash v5
@ -29,7 +31,7 @@ GET /ns/{namespace}/memories/{id}?include=related,documents&relatedLimit=10
Returns one memory with `id`, `memory`, `metadata`, `isStatic`, `isInference`, `isLatest`, `isForgotten`, `version`, and `system.createdAt/updatedAt`. A memory from another namespace returns `404`; a forgotten memory, or one past its `forget_after`, is still returned with `isForgotten: true`. Returns one memory with `id`, `memory`, `metadata`, `isStatic`, `isInference`, `isLatest`, `isForgotten`, `version`, and `system.createdAt/updatedAt`. A memory from another namespace returns `404`; a forgotten memory, or one past its `forget_after`, is still returned with `isForgotten: true`.
- `include=related` adds `included.related.parents` (earlier versions), `children` (newer versions), and `siblings` (memories connected by `extends` or `derives`). Each list walks outward from the memory until it holds `relatedLimit` items (default `10`, maximum `100`). Forgotten memories and memories in other namespaces are left out. - `include=related` adds `included.related.parents` (earlier versions), `children` (newer versions), and `siblings` (memories directly connected by `extends` or `derives`). Parents and children reach up to three versions away, nearest first. Each list holds at most `relatedLimit` items (default `10`, maximum `100`). Forgotten memories and memories in other namespaces are left out.
- `include=documents` adds `included.document`, the most recently updated source document. With both values, every related memory also carries its own `document`. - `include=documents` adds `included.document`, the most recently updated source document. With both values, every related memory also carries its own `document`.
### List documents, chunks, or memories ### List documents, chunks, or memories
@ -46,9 +48,13 @@ POST /ns/{namespace}/list/{type}?page=1&limit=100&sort=createdAt&order=desc
``` ```
</CodeGroup> </CodeGroup>
Set `type` to `documents`, `chunks`, or `memories`. Pagination and sorting move to the query string as plain integers and values; the body takes an optional `filter` and, for memories, `include`. Defaults are `page=1`, `limit=10` (maximum 100), `sort=createdAt`, and `order=desc`. Set `type` to `documents`, `chunks`, or `memories`. Pagination and sorting move to the query string as plain integers and values; the body takes an optional `filter` and `include` object. Defaults are `page=1`, `limit=10` (maximum 100), `sort=createdAt`, and `order=desc`. `sort` accepts `createdAt` or `updatedAt` for documents and memories, and `createdAt` or `position` for chunks; any other combination returns `400`.
Memory lists hide forgotten memories and memories past their `forget_after`. Send `"include": {"forgotten": true}` to list them too; they come back with `isForgotten: true`. Document memories (`include=memories` on `GET /ns/{namespace}/document/{id}`) report the same flag. Document lists omit `content` by default. Send `{"include":{"content":true}}` to return the last published extracted text, including text from uploaded files. `content` is `null` before the first extraction publishes. During a replacement, previous text remains until new text publishes and also remains if extraction fails. Including content can make responses large.
Document list items also carry `system.memoryCount`, the number of live memories linked to the document in this namespace. Send `{"include":{"memoryCount":false}}` to skip counting.
Memory lists return only the latest version of each memory and hide forgotten memories and memories past their `forget_after`. Sending `"include": {"forgotten": true}` admits forgotten or expired rows only if they remain latest; they come back with `isForgotten: true`. Normal forgetting makes a memory non-latest, so this list does not recover it even with that flag. To search such memories, use search with `include.forgotten`. Document memories (`include=memories` on `GET /ns/{namespace}/document/{id}`) report the same forgotten flag.
Every response contains `documents`, `chunks`, `memories`, and `pagination`. Only the selected resource array is populated. Replace legacy `memories` assumptions in document-list callers with `documents`. Every response contains `documents`, `chunks`, `memories`, and `pagination`. Only the selected resource array is populated. Replace legacy `memories` assumptions in document-list callers with `documents`.
@ -66,7 +72,7 @@ DELETE /ns/{namespace}/document
``` ```
</CodeGroup> </CodeGroup>
The v5 array accepts 1–100 Supermemory or caller-defined IDs. Inspect both `count` and per-ID `errors`; HTTP success can include partial failures. The v5 array accepts 1–100 Supermemory or caller-defined IDs. Inspect both `count` and per-ID `errors`; HTTP success can include partial failures. Only documents whose `system.status` is `done` or `failed` are deleted; one that is still processing comes back in `errors` as `Document is still processing`.
### With the SDK ### With the SDK
@ -98,7 +104,7 @@ const { count, errors } = await supermemory.documents.delete("user_1", {
``` ```
</CodeGroup> </CodeGroup>
There is no separate processing list. Read `system.status` on each item returned by `supermemory.list(namespace, "documents")`. Chunks come from `supermemory.list(namespace, "chunks")` or `documents.get` with `include: ["chunks"]`. There is no separate processing list. Read `system.status` and `system.dreamingStatus` on each item returned by `supermemory.list(namespace, "documents")`. Chunks come from `supermemory.list(namespace, "chunks")` or `documents.get` with `include: ["chunks"]`.
### Forget memories ### Forget memories
@ -114,6 +120,7 @@ See [memory forgetting](/migration/api-v5-memory-forgetting) for the complete dr
### Verification ### Verification
- Assert requested empty includes are `[]`, while omitted includes are absent. - Assert requested empty includes are `[]`, while omitted includes are absent.
- Move readers of the legacy top-level `dreamingStatus` to `system.dreamingStatus`, and gate memory or profile reads on it rather than on `system.status`.
- Paginate each resource type until `currentPage >= totalPages`; unselected arrays stay empty. - Paginate each resource type until `currentPage >= totalPages`; unselected arrays stay empty.
- Verify chunk rows contain their parent `documentId`. - Verify chunk rows contain their parent `documentId`.
- Exercise partial document-delete failures and semantic dry runs. - Exercise partial document-delete failures and semantic dry runs.

View file

@ -6,7 +6,7 @@ v5 makes the difference between adding new information and replacing the canonic
| --- | --- | --- | | --- | --- | --- |
| Add information to a stable caller ID | `POST /ns/{namespace}/document` | Append/diff | | Add information to a stable caller ID | `POST /ns/{namespace}/document` | Append/diff |
| Replace text or URL content | `PATCH /ns/{namespace}/document/{id}` | Replace and reprocess | | Replace text or URL content | `PATCH /ns/{namespace}/document/{id}` | Replace and reprocess |
| Update only supporting fields | Same `PATCH` without `content` | Canonical content unchanged | | Update only metadata, context, or date | Same `PATCH` without `content` | Canonical content unchanged |
| Replace a source file and its user metadata | `POST /ns/{namespace}/document/file/{id}` | Full replacement and reprocess | | Replace a source file and its user metadata | `POST /ns/{namespace}/document/file/{id}` | Full replacement and reprocess |
| Partially update a file or its supporting fields | `PATCH /ns/{namespace}/document/file/{id}` | Omitted fields remain unchanged | | Partially update a file or its supporting fields | `PATCH /ns/{namespace}/document/file/{id}` | Omitted fields remain unchanged |
@ -24,7 +24,7 @@ PATCH /ns/user_1/document/doc_1
``` ```
</CodeGroup> </CodeGroup>
The v5 body accepts any non-empty subset of `content`, `supportingContext`, `metadata`, `group`, or `date`, plus optional `taskType` and `dreaming` (which alone do not count as a change). Supplying `content` makes it the new canonical source; facts supported only by the previous source can disappear after reprocessing. The v5 body accepts any non-empty subset of `content`, `supportingContext`, `metadata`, or `date`, plus optional `taskType` and `dreaming` (which alone do not count as a change). `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`; a `group` without `content` returns `400`. Supplying `content` makes it the new canonical source; facts supported only by the previous source can disappear after reprocessing.
### Replace a file-backed document ### Replace a file-backed document
@ -37,7 +37,7 @@ metadata={"revision":2}
dreaming=dynamic dreaming=dynamic
``` ```
POST requires `file` and replaces the canonical source plus user-controlled metadata, grouping, context, and date. Omitted supporting fields are cleared. Use it when the submitted request is the complete new representation of the file-backed document. POST requires `file` and replaces the canonical source plus caller metadata, including `date`. Omitted metadata and date are cleared. `supportingContext` is stored on the namespace, so omitting it leaves the namespace's existing context unchanged. Use it when the submitted request is the complete new representation of the file-backed document.
### Partially update a file-backed document ### Partially update a file-backed document
@ -49,7 +49,7 @@ metadata={"reviewed":true}
dreaming=dynamic dreaming=dynamic
``` ```
PATCH changes only supplied fields. Include `file` to replace the source while retaining omitted supporting fields, or omit `file` for metadata-, group-, context-, or date-only changes. `metadata` and `group` are JSON-encoded strings; `supportingContext` and `date` are plain strings. PATCH changes only supplied fields. Include `file` to replace the source while retaining omitted supporting fields, or omit `file` for metadata-, context-, or date-only changes. Like the JSON update, `group` only takes effect when the file is re-ingested, so send it together with `file`; without one the request returns `400`. `metadata` and `group` are JSON-encoded strings; `supportingContext` and `date` are plain strings.
<Warning> <Warning>
There is no public v5 `PUT /ns/{namespace}/document/file/{id}` operation. Use POST for a complete replacement and PATCH for a partial update. There is no public v5 `PUT /ns/{namespace}/document/file/{id}` operation. Use POST for a complete replacement and PATCH for a partial update.
@ -90,7 +90,7 @@ The path `id` may be the Supermemory document ID or your caller-defined ID. It i
### Processing and conflicts ### Processing and conflicts
Content or file replacement is accepted before downstream processing completes. A document still processing, a namespace conflict, or a conflicting internal file path can return `409`; retry only after the conflicting operation reaches a terminal state. Content or file replacement is accepted before downstream processing completes. A namespace merge in progress or a conflicting internal file path returns `409`; retry a merge conflict after the merge finishes.
### Verification ### Verification

View file

@ -83,7 +83,7 @@ await supermemory.search("user_1", {
``` ```
</CodeGroup> </CodeGroup>
`filter` is a typed object under `body`, not a JSON string. The same shape works in `supermemory.list` and `supermemory.profile`. Keys are literal: `customer.plan` is one field name, not a nested path. `filter` is a typed object in the request body (a field of the options object in the SDKs), not a JSON string. The same shape works in `supermemory.list` and `supermemory.profile`. Keys are literal: `customer.plan` is one field name, not a nested path.
### Deterministic conversion ### Deterministic conversion

View file

@ -16,7 +16,7 @@ Content-Type: application/json
{"ids":["mem_1","mem_2"]} {"ids":["mem_1","mem_2"]}
``` ```
Send 1–500 IDs. The response reports successful IDs in `matches` and missing or ineligible IDs in `errors`, so HTTP success does not imply every requested ID changed. Send 1–500 IDs. The response reports successful IDs in `matches` and missing, already forgotten, or out-of-namespace IDs in `errors`, so HTTP success does not imply every requested ID changed.
### Preview a semantic request ### Preview a semantic request
@ -27,7 +27,7 @@ Content-Type: application/json
{"query":"outdated home address","dryRun":true} {"query":"outdated home address","dryRun":true}
``` ```
`dryRun: true` performs selection without changing memory state. `dryRun: false` forgets the memories selected when that request executes. `dryRun: true` performs selection without changing memory state. `dryRun: false` forgets the memories selected when that request executes. `query` is 1–2000 characters, and each request selects at most 100 memories. After applying a batch, repeat the preview-and-apply workflow to find more; repeated previews alone do not paginate and may return the same matches.
### Avoid selection drift ### Avoid selection drift
@ -53,7 +53,7 @@ Use this workflow when a human or policy must approve the exact set. Re-running
} }
``` ```
`count` always equals `matches.length`. Both dry-run and applied semantic requests use this shape; the payload alone does not replace your record of which mode was sent. `count` always equals `matches.length`. Semantic requests always return an empty `errors` array. Both dry-run and applied semantic requests use this shape; the payload alone does not replace your record of which mode was sent.
### With the SDK ### With the SDK

View file

@ -14,7 +14,7 @@ v5 renames the public isolation boundary from container tag to namespace. Existi
### List namespaces ### List namespaces
The list is paginated: `GET /namespaces?page=1&limit=10` returns `{namespaces, pagination}`, newest first, with `limit` up to 100. Each entry now exposes `id`, `namespace`, `documentCount`, `memoryCount`, nullable `description`, and `system.createdAt/updatedAt`. Update readers that still expect `containerTag`, flat timestamps, or unbounded internal settings. The list is paginated: `GET /namespaces?page=1&limit=10` returns `{namespaces, pagination}`, newest first, with `limit` up to 100. Add `q` to keep only namespaces whose name contains that text, case-insensitively. Each entry now exposes `id`, `namespace`, `documentCount`, `memoryCount`, nullable `description`, and `system.createdAt/updatedAt`. Update readers that still expect `containerTag`, flat timestamps, or unbounded internal settings.
### Read and update settings ### Read and update settings
@ -50,7 +50,7 @@ DELETE /ns/project_alpha?moveTo=project_archive
`moveTo` is a query parameter. This returns `202` with `status: "queued"` and `operationId`. The destination must differ from the source. Do not treat acceptance as completed migration. `moveTo` is a query parameter. This returns `202` with `status: "queued"` and `operationId`. The destination must differ from the source. Do not treat acceptance as completed migration.
Legacy merge can accept multiple sources; v5 moves one source per request. Run multi-source migrations sequentially and record each operation independently. Legacy merge takes exactly two tags and can merge both into a new target; v5 moves one source namespace per request. Run multi-source migrations sequentially and record each operation independently.
### With the SDK ### With the SDK

View file

@ -69,6 +69,6 @@ await supermemory.organization.update({
- Compare the v5 context with the legacy `filterPrompt` before cutover. - Compare the v5 context with the legacy `filterPrompt` before cutover.
- Set, replace, and clear organizational context. - Set, replace, and clear organizational context.
- Confirm `namespaceCount` agrees with `pagination.totalItems` from `GET /namespaces` for the same credentials. - Confirm `namespaceCount` agrees with `pagination.totalItems` from `GET /namespaces` when called with an unrestricted org key. `namespaceCount` always counts every namespace in the organization, while `GET /namespaces` only counts the namespaces the caller can read.
- Verify non-admin callers receive `403` on PATCH. - Verify non-admin callers receive `403` on PATCH.
- Confirm removed fields are not required by downstream configuration code. - Confirm removed fields are not required by downstream configuration code.

View file

@ -6,7 +6,7 @@
The API base URL and bearer keys do not change. 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. Routes reject options sent in the wrong place with `400`. v5 application routes are unversioned; [`/v5/reference`](https://api.supermemory.ai/v5/reference) is the interactive v5 reference, not an API path prefix. **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`](https://api.supermemory.ai/v5/reference) is the interactive v5 reference, not an API path prefix.
## Recommended migration process ## Recommended migration process
@ -55,7 +55,7 @@ await supermemory.add("user_1", { content: "new turn" })
``` ```
</CodeGroup> </CodeGroup>
Every v5 call takes one object. `namespace`, path IDs, and query parameters are top-level keys; the JSON body goes under `body`. Legacy `containerTag` never appears in a body again. There is no v5 Python SDK yet. 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 ## Endpoint map

View file

@ -5,7 +5,7 @@ v5 returns a maintained profile directly and gives profile bucket definitions th
<CodeGroup> <CodeGroup>
```bash Legacy ```bash Legacy
POST /v4/profile POST /v4/profile
{"containerTag":"user_1","q":"work preferences","threshold":0.6,"include":{}} {"containerTag":"user_1","q":"work preferences","threshold":0.6,"include":["static","dynamic","buckets"]}
``` ```
```bash v5 ```bash v5
@ -22,13 +22,18 @@ Remove legacy `q`, `threshold`, and `include`. If the caller needs query-ranked
{ {
"profile": { "profile": {
"static": [{ "id": "mem_1", "memory": "The user works in design" }], "static": [{ "id": "mem_1", "memory": "The user works in design" }],
"dynamic": [{ "id": "mem_2", "memory": "The user is preparing a launch" }], "dynamic": [{ "id": "mem_2", "memory": "[2026-10-01] The user is preparing a launch" }],
"buckets": { "work": [{ "id": "mem_3", "memory": "Prefers concise project updates" }] } "buckets": {
"preferences": [],
"work": [{ "id": "mem_3", "memory": "Prefers concise project updates" }]
}
} }
} }
``` ```
`static` and `dynamic` are always returned and cannot be disabled. Omit `buckets` in the request to return every effective custom bucket; pass up to 50 names to narrow only the bucket section. `static` and `dynamic` are always returned and cannot be disabled. Omit `buckets` in the request to return every effective bucket, including the built-in `preferences` bucket; pass up to 50 names to narrow only the bucket section.
Entries change from v4 strings to `{ id, memory }` objects, and the v4 `[Recent]`/`[Summary]` text prefixes are dropped. Synthesized summaries of older memories now carry an `id` starting with `aggregated_`; individual `dynamic` entries keep their `[YYYY-MM-DD]` date prefix.
### Read bucket definitions ### Read bucket definitions
@ -65,7 +70,7 @@ DELETE /ns/user_1/profile/buckets
{"buckets":["work"]} {"buckets":["work"]}
``` ```
Names must be unique. Organization-owned buckets can appear in the effective GET response but cannot be changed or removed through namespace PUT or DELETE calls. Names must be unique. Organization-owned buckets can appear in the effective GET response but cannot be changed or removed through namespace PUT or DELETE calls — naming one returns `409`. The built-in `preferences` bucket always stays in the effective set; DELETE only removes namespace-owned buckets.
### With the SDK ### With the SDK
@ -92,7 +97,7 @@ await supermemory.profiles.deleteBuckets("user_1", {
``` ```
</CodeGroup> </CodeGroup>
`supermemory.profile` takes no query. `body` is optional and accepts only `filter` and `buckets`. Read `profile.static`, `profile.dynamic`, and `profile.buckets[name]` as arrays of `{ id, memory }`. `supermemory.profile(namespace, options?)` takes no query. The options object is optional and accepts only `filter` and `buckets`. Read `profile.static`, `profile.dynamic`, and `profile.buckets[name]` as arrays of `{ id, memory }`.
### Verification ### Verification

View file

@ -1,4 +1,4 @@
Check which client you call the API through before you translate requests. Both official SDKs speak v5; the integration packages do not yet. Check which client you call the API through before you translate requests. Both official SDKs and `@supermemory/tools` 3.x speak v5; check the table before upgrading anything else.
| Client | v5 support | What to do | | Client | v5 support | What to do |
| --- | --- | --- | | --- | --- | --- |
@ -31,7 +31,7 @@ If your code passes a `containerTags` array, pick one namespace. A v5 document l
| v4 | v5 | | v4 | v5 |
| --- | --- | | --- | --- |
| `client.search.memories({ q, containerTag })` | `client.search(namespace, { query })` | | `client.search.memories({ q, containerTag })` | `client.search(namespace, { query, searchMode: "memories" })` |
| `client.profile({ containerTag, q })` | `client.profile(namespace)`; profile no longer takes a query, call `client.search` separately if you need results | | `client.profile({ containerTag, q })` | `client.profile(namespace)`; profile no longer takes a query, call `client.search` separately if you need results |
| `client.documents.list(...)`, `client.memories.list(...)` | `client.list(namespace, "documents" \| "chunks" \| "memories", { filter? })` | | `client.documents.list(...)`, `client.memories.list(...)` | `client.list(namespace, "documents" \| "chunks" \| "memories", { filter? })` |
| `client.containerTags.*` | `client.namespaces.*` | | `client.containerTags.*` | `client.namespaces.*` |
@ -50,7 +50,7 @@ Some v4 methods are gone from the v5 SDK. Most have a v5 way to do the same thin
| `memories.updateMemory(...)` | Update the source document with `client.documents.update(namespace, id, { content })`. | | `memories.updateMemory(...)` | Update the source document with `client.documents.update(namespace, id, { content })`. |
| `documents.search(...)` | `client.search(namespace, { query, searchMode: "chunks" })` | | `documents.search(...)` | `client.search(namespace, { query, searchMode: "chunks" })` |
| `documents.chunks(id)` | `client.documents.get(namespace, id, { include: ["chunks"] })` | | `documents.chunks(id)` | `client.documents.get(namespace, id, { include: ["chunks"] })` |
| `documents.listProcessing()` | `client.list(namespace, "documents")` and read `system.status` on each item | | `documents.listProcessing()` | `client.list(namespace, "documents")` and read `system.status` and `system.dreamingStatus` on each item |
| `containerTags.merge(...)`, `mergeStatus(...)` | `client.namespaces.delete(source, { moveTo: target })`. One source per call; the response carries an `operationId`. | | `containerTags.merge(...)`, `mergeStatus(...)` | `client.namespaces.delete(source, { moveTo: target })`. One source per call; the response carries an `operationId`. |
| `memories.forget({ content })` | `client.memories.forgetMatching(namespace, { query, dryRun: true })` to preview, then `memories.forget(namespace, { ids })` | | `memories.forget({ content })` | `client.memories.forgetMatching(namespace, { query, dryRun: true })` to preview, then `memories.forget(namespace, { ids })` |

View file

@ -1781,6 +1781,97 @@ a[class*="-ml-10"] {
font-weight: inherit !important; font-weight: inherit !important;
} }
/* Mintlify paints group-hover:bg-* behind anchor icons; on an <img> that fills the whole box */
img[src*="/icons/hugeicons/"] {
background-color: transparent !important;
}
/* Icons are black/white <img>s; opacity tones them to the label colour (#3e3f3f light, #9e9f9f dark) */
#sidebar-content {
--sm-nav-icon-rest: 0.76;
}
.dark #sidebar-content {
--sm-nav-icon-rest: 0.62;
}
#sidebar-content a img[src*="/icons/hugeicons/"] {
opacity: var(--sm-nav-icon-rest);
transition: opacity var(--sm-dur-hover) var(--sm-ease-out) !important;
}
#sidebar-content a:hover img[src*="/icons/hugeicons/"],
#sidebar-content a[aria-current] img[src*="/icons/hugeicons/"] {
opacity: 1;
}
/* Anchors ship as a bare 24px row; give them the page items' 32px box (36px pitch kept via the margin) */
#sidebar-content a.nav-anchor {
column-gap: 12px;
padding: 6px 12px 6px 16px !important;
margin-bottom: 4px !important;
line-height: 20px !important;
border-radius: var(--sm-radius-lg) !important;
transition:
color var(--sm-dur-hover) var(--sm-ease-out),
background-color var(--sm-dur-hover) var(--sm-ease-out);
}
#sidebar-content a.nav-anchor:not([aria-current]):hover {
background-color: var(--sm-control-hover) !important;
color: var(--sm-text) !important;
}
/* Mintlify fakes bold on selected items with a 0.2px text-shadow; keep true Geist Medium */
#navigation-items a[aria-current],
#navigation-items a[aria-current] *,
a.nav-tabs-item,
a.nav-tabs-item * {
text-shadow: none !important;
}
/* Inactive top tabs ship in the active ink, so hover had nothing to change */
a.nav-tabs-item:not(:has(> div[class*="bg-primary"])) {
color: var(--sm-text-2) !important;
}
a.nav-tabs-item:not(:has(> div[class*="bg-primary"])):hover {
color: var(--sm-text) !important;
}
a.nav-tabs-item:not(:has(> div[class*="bg-primary"])) img {
opacity: var(--sm-nav-icon-rest, 0.76);
transition: opacity var(--sm-dur-hover) var(--sm-ease-out) !important;
}
.dark a.nav-tabs-item img {
--sm-nav-icon-rest: 0.62;
}
a.nav-tabs-item:not(:has(> div[class*="bg-primary"])):hover img {
opacity: 1;
}
#search-bar-entry:hover {
background-color: rgb(9 17 32 / 0.06) !important;
}
.dark #search-bar-entry:hover {
background-color: rgb(255 255 255 / 0.06) !important;
}
.dark [class*="text-tooltip-foreground"] {
background-color: #262626 !important;
}
.sm-home-link-icon {
transition: background-color var(--sm-dur-hover) var(--sm-ease-out);
}
.sm-home-link:hover .sm-home-link-icon {
background: var(--sm-surface);
}
.dark img[src*="/icons/hugeicons/"], .dark img[src*="/icons/hugeicons/"],
.sm-scene .m-ic.is-on img[src*="/icons/hugeicons/"], .sm-scene .m-ic.is-on img[src*="/icons/hugeicons/"],
.sm-scene .m-badge img[src*="/icons/hugeicons/"] { .sm-scene .m-badge img[src*="/icons/hugeicons/"] {

View file

@ -170,7 +170,7 @@ ChatGPT opens the **Add Supermemory MCP to ChatGPT** dialog. Select **Sign in wi
On the Supermemory authorization page: On the Supermemory authorization page:
- Select **Read + Write** to let ChatGPT search and save Supermemory context. Select **Read only** if you only want search access. - Select **Read and write** to let ChatGPT search and save Supermemory context. Select **Read only** if you only want search access.
- Select **Full access** to use every available space, or **Scoped** to choose specific spaces. - Select **Full access** to use every available space, or **Scoped** to choose specific spaces.
- Choose how long the connection should remain active. - Choose how long the connection should remain active.
- Select **Approve**. - Select **Approve**.
@ -196,6 +196,8 @@ After approval, ChatGPT returns to the Supermemory MCP plugin page. Select **Try
Supermemory MCP is now ready to use in ChatGPT Web. You can manage or disconnect it later from the [Plugins page](https://chatgpt.com/plugins). Supermemory MCP is now ready to use in ChatGPT Web. You can manage or disconnect it later from the [Plugins page](https://chatgpt.com/plugins).
To switch between **Read and write** and **Read only**, or change which spaces ChatGPT can use, open the [Agents page](https://console.supermemory.ai/agents) in the Supermemory console and choose **Edit access**. Reconnecting from ChatGPT does not show the authorization page again.
## Frequently asked questions ## Frequently asked questions
<AccordionGroup> <AccordionGroup>

View file

@ -34,13 +34,13 @@ Supermemory appears under your connectors. Click **Connect**.
## Step 4: Authorize ## Step 4: Authorize
You’ll be redirected to Supermemory to sign in and choose access scopes (for example **Read + Write** or **Full access**). Select the scopes you want, then click **Authorize**. You’ll be redirected to Supermemory to sign in and choose the permission (**Read and write** or **Read only**) and the scope (**All memories** or **Only some tags**). Select the access you want, then click **Authorize**.
![Authorize MCP screen with scope options and Authorize button](/images/supermemory-mcp/claude-desktop/step-4.png) ![Authorize MCP screen with scope options and Authorize button](/images/supermemory-mcp/claude-desktop/step-4.png)
## Done ## Done
Supermemory is connected and ready to use in Claude. You can change or revoke access later from **Settings → Connectors**. Supermemory is connected and ready to use in Claude. You can change or revoke access later from **Settings → Connectors**, or from the [Agents page](https://console.supermemory.ai/agents) in the Supermemory console with **Edit access** or **Disconnect memory**.
--- ---

View file

@ -46,13 +46,13 @@ Your assistant chooses these tools automatically. Use this table when you need t
| Tool | Use it for | Inputs | Result | | Tool | Use it for | Inputs | Result |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `search_memory` | Semantic recall from one space | `query` (required), `containerTag` | Matching memories | | `search_memory` | Semantic recall from one space | `query` (required), `namespace` | Matching memories with memory IDs and source document IDs |
| `get_profile` | Stable and recent profile context for one space | `containerTag` | Profile facts | | `get_profile` | Stable and recent profile context for one space | `namespace` | Profile facts |
| `add_memory` | Save information | `content` (required), `namespace` | Save confirmation | | `add_memory` | Save information | `content` (required), `namespace` | Save confirmation |
| `forget_memory` | Forget outdated information | `content` (required), `namespace` | Forget confirmation | | `forget_memory` | Forget outdated information | `ids` or `content`, `namespace` | Forget confirmation, or the closest matches when nothing was forgotten |
| `list_documents` | Browse stored source documents and their summaries | `page`, `limit`, `containerTag` | Document IDs, titles, types, status, dates, and summaries | | `list_documents` | Browse stored source documents and their summaries | `page`, `limit`, `namespace` | Document IDs, titles, types, status, dates, and summaries |
| `get_document` | Read the available content of one document | `documentId` (required) | Document metadata, summary, and available content | | `get_document` | Read the available content of one document | `documentId` (required), `namespace` | Document metadata, summary, and available content |
| `list_memories` | Browse recent extracted memory entries and their source document IDs | `page`, `limit`, `containerTag` | Memory IDs, text, versions, and source document IDs | | `list_memories` | Browse recent extracted memory entries and their source document IDs | `page`, `limit`, `namespace` | Memory IDs, text, versions, and source document IDs |
| `list_spaces` | List accessible spaces and resolve a space name to its key | None | Formatted list plus structured `spaces` and `count` fields | | `list_spaces` | List accessible spaces and resolve a space name to its key | None | Formatted list plus structured `spaces` and `count` fields |
| `who_am_i` | Inspect the authenticated account, permissions, scope, and active space | None | Account and access context | | `who_am_i` | Inspect the authenticated account, permissions, scope, and active space | None | Account and access context |
@ -71,7 +71,7 @@ Use the retrieval tools for different questions:
### Save or forget ### Save or forget
`add_memory` saves the supplied `content`. Use `forget_memory` when a fact is outdated or should be removed. Both tools use the active space unless you pass `namespace`. The deprecated `containerTag` input and `add_memory` with `action: "forget"` remain supported for existing clients. Because the legacy action can remove information, `add_memory` retains its destructive safety annotation. `add_memory` saves the supplied `content`. Use `forget_memory` when a fact is outdated or should be removed: find the memory IDs with `search_memory`, then pass them in `ids`. If you pass only `content`, an exact text match is forgotten; otherwise nothing is forgotten and the closest memories are returned with their IDs so you can choose. Space-aware tools use the active space unless you pass `namespace`. The deprecated `containerTag` input and `add_memory` with `action: "forget"` remain supported for existing clients. Because the legacy action can remove information, `add_memory` retains its destructive safety annotation.
If the content is already final, the assistant should use `add_memory`. If you want to review, edit, or choose a space before saving, it should open the `guided-save` widget instead. If the content is already final, the assistant should use `add_memory`. If you want to review, edit, or choose a space before saving, it should open the `guided-save` widget instead.
@ -79,6 +79,8 @@ If the content is already final, the assistant should use `add_memory`. If you w
`who_am_i` returns the current identity, role, access type, granted scope, and active space. `list_spaces` returns only spaces the authenticated account can access, with names, keys, document and memory counts, and recent activity. `who_am_i` returns the current identity, role, access type, granted scope, and active space. `list_spaces` returns only spaces the authenticated account can access, with names, keys, document and memory counts, and recent activity.
Reconnecting a client does not show the authorization page again. To change a connected app's permission or scope, open the [Agents page](https://console.supermemory.ai/agents) in the console and choose **Edit access** from the app's row. Choose **Disconnect memory** to revoke its access; the next connection asks for approval again.
## Interactive widgets ## Interactive widgets
Clients that support [MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview) can open these Supermemory widgets directly inside the conversation. Clients that support [MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview) can open these Supermemory widgets directly inside the conversation.

View file

@ -11,7 +11,7 @@ Buckets are **custom topical categories** for a profile — an axis that sits al
New to buckets? Read the [conceptual overview](/concepts/user-profiles#buckets) first — this page is the API reference for reading, creating, and managing them. New to buckets? Read the [conceptual overview](/concepts/user-profiles#buckets) first — this page is the API reference for reading, creating, and managing them.
</Tip> </Tip>
Every org starts with a built-in `preferences` bucket. You can define your own at the organization level, and add more per namespace (what v3/v4 called a container tag) — all covered below. Every org starts with a built-in `preferences` bucket that is always present — buckets you define at the organization level, or add per namespace (what v3/v4 called a container tag), are added alongside it. All covered below.
--- ---
@ -50,11 +50,11 @@ Every org starts with a built-in `preferences` bucket. You can define your own a
"dynamic": [], "dynamic": [],
"buckets": { "buckets": {
"preferences": [ "preferences": [
{ "id": "mem_1", "memory": "[Summary] Prefers concise, technical answers and dark-mode tooling" }, { "id": "mem_2", "memory": "Switched their editor to Zed" },
{ "id": "mem_2", "memory": "[Recent] Switched their editor to Zed" } { "id": "aggregated_0_1760000000000", "memory": "Tooling and style: Prefers concise, technical answers and dark-mode tooling" }
], ],
"goals": [ "goals": [
{ "id": "mem_3", "memory": "[Recent] Wants to ship the billing revamp this quarter" } { "id": "mem_3", "memory": "Wants to ship the billing revamp this quarter" }
] ]
} }
} }
@ -62,7 +62,7 @@ Every org starts with a built-in `preferences` bucket. You can define your own a
``` ```
<Note> <Note>
**`[Recent]` and `[Summary]` labels.** To keep profiles dense, an entity's older memories are periodically aggregated into a short synthesis. Entries prefixed `[Summary]` are that aggregated context; entries prefixed `[Recent]` were ingested since the last aggregation and aren't summarized yet. The `dynamic` section uses the same `[Recent]` prefix (plus a `[YYYY-MM-DD]` date). Strip the prefixes if you only want raw text, or keep them to signal recency to your model. **Summaries vs. individual memories.** To keep profiles dense, once a bucket grows large enough its older memories are periodically aggregated into a short synthesis. Synthesized entries have an `id` starting with `aggregated_` and don't map to a single memory; entries with a regular memory ID were ingested since the last aggregation and aren't summarized yet. v5 returns plain text — the `[Recent]`/`[Summary]` prefixes from v4 are not included, so use the `aggregated_` ID prefix to tell summaries apart. Individual `dynamic` entries still start with a `[YYYY-MM-DD]` date.
</Note> </Note>
### List bucket definitions ### List bucket definitions
@ -106,7 +106,7 @@ Bucket definitions live at two levels: **organization** (the default set every n
### Namespace buckets ### Namespace buckets
`setBuckets` sends 1 to 50 name-to-description entries. Existing namespace names are updated, new names are added, and omitted namespace buckets remain unchanged. These are **add-only** on top of org buckets — a namespace always keeps every org bucket, and if a namespace bucket's name collides with an org bucket, the org's definition wins in the merged, effective set used at ingestion and read time. `setBuckets` sends 1 to 50 name-to-description entries. Existing namespace names are updated, new names are added, and omitted namespace buckets remain unchanged. These are **add-only** on top of org buckets — a namespace always keeps every org bucket. Sending a name the org already defines returns `409`, and if an org bucket is added later with the same name as a namespace bucket, the org's definition wins in the merged, effective set used at ingestion and read time.
<Tabs> <Tabs>
<Tab title="TypeScript"> <Tab title="TypeScript">
@ -179,7 +179,7 @@ If you'd rather start from a template than write descriptions from scratch, thes
### Default bucket ### Default bucket
If neither the org nor the namespace has configured any buckets, ingestion falls back to a single built-in `preferences` bucket, scoped tightly to explicit first-person statements ("prefers X over Y", "always uses W") — not inferred traits or general observations. Configuring your own buckets replaces this default. Every namespace has a built-in `preferences` bucket, scoped tightly to explicit first-person statements ("prefers X over Y", "always uses W") — not inferred traits or general observations. It is always part of the effective set: configuring your own buckets adds to it rather than replacing it, and `deleteBuckets` can't remove it. Defining a bucket named `preferences` only overrides its description; an empty description falls back to the built-in one.
--- ---
@ -203,7 +203,7 @@ Bucket descriptions steer classification. A precise description ("Explicit first
### Instructions ### Instructions
Bucket descriptions only steer classification *within* a bucket — they don't tell the model anything about the namespace itself. For that, set [`supportingContext`](/concepts/customization#entity-context) on the namespace: a free-text field that's appended alongside the org-level `organizationalContext` into the same prompt the extraction/classification step uses, so it shapes bucket assignment too, not just fact extraction. Bucket descriptions only steer classification *within* a bucket — they don't tell the model anything about the namespace itself. For that, set [`supportingContext`](/concepts/customization#supporting-context) on the namespace: a free-text field that's appended alongside the org-level `organizationalContext` into the same prompt the extraction/classification step uses, so it shapes bucket assignment too, not just fact extraction.
<Tabs> <Tabs>
<Tab title="TypeScript"> <Tab title="TypeScript">

View file

@ -12,8 +12,8 @@ icon: "/icons/hugeicons/book-open-01.svg"
| `GET /ns/{namespace}/connectors/{id}` | Read one connector, with `include=syncs` for recent sync runs or `include=picker` for a hosted picker link | | `GET /ns/{namespace}/connectors/{id}` | Read one connector, with `include=syncs` for recent sync runs or `include=picker` for a hosted picker link |
| `PATCH /ns/{namespace}/connectors/{id}` | Change what it syncs: `selection` and `documentLimit` | | `PATCH /ns/{namespace}/connectors/{id}` | Change what it syncs: `selection` and `documentLimit` |
| `POST /ns/{namespace}/connectors/{id}/sync` | Trigger a sync now. Returns 409 while one is already running | | `POST /ns/{namespace}/connectors/{id}/sync` | Trigger a sync now. Returns 409 while one is already running |
| `DELETE /ns/{namespace}/connectors/{id}` | Disconnect, optionally deleting the documents it imported | | `DELETE /ns/{namespace}/connectors/{id}` | Disconnect. Also deletes the documents it imported unless you pass `deleteDocuments=false` |
| `GET /connectors` | List every connector in the organization, across namespaces | | `GET /connectors` | List connectors across every namespace this key can read |
Providers: `notion`, `google-drive`, `onedrive`, `gmail`, `github` authenticate with OAuth. `web-crawler`, `s3`, and `granola` take their configuration in the create body and need no login. Providers: `notion`, `google-drive`, `onedrive`, `gmail`, `github` authenticate with OAuth. `web-crawler`, `s3`, and `granola` take their configuration in the create body and need no login.
@ -32,7 +32,7 @@ const { id, authorization } = await supermemory.connectors.create("user_123", {
// Config provider: starts syncing immediately, authorization is null // Config provider: starts syncing immediately, authorization is null
const crawler = await supermemory.connectors.create("user_123", { const crawler = await supermemory.connectors.create("user_123", {
provider: "web-crawler", provider: "web-crawler",
config: { startUrl: "https://docs.example.com" } config: { startUrl: "https://docs.example.com" },
documentLimit: 200, documentLimit: 200,
}) })
@ -48,7 +48,7 @@ curl -X POST "https://api.supermemory.ai/ns/user_123/connectors" \
</CodeGroup> </CodeGroup>
<Note> <Note>
A pending OAuth connector is not returned by list or get until the user completes authorization. The `authorization.url` expires after one hour (see `authorization.expiresAt`). A pending OAuth connector is not returned by list until the user completes authorization. Get still returns it, with `system.status: "pending_authorization"`. The `authorization.url` expires after one hour (see `authorization.expiresAt`).
</Note> </Note>
See the [connector guides](/connectors/overview) for per-provider setup, selection rules, and troubleshooting. See the [connector guides](/connectors/overview) for per-provider setup, selection rules, and troubleshooting.

View file

@ -41,4 +41,6 @@ curl -X POST "https://api.supermemory.ai/ns/user_1/list/documents?page=1&limit=5
``` ```
</CodeGroup> </CodeGroup>
Semantic forgetting supports `dryRun: true`, so you can review matches before deleting them. See [document read migration](/migration/api-v5-document-reads) and [memory migration](/migration/api-v5-recall). Document lists omit `content` by default. Send `{"include":{"content":true}}` to return published extracted text, including text from uploaded files. See [Document operations](/ingestion/document-operations) for response and processing details.
Semantic forgetting supports `dryRun: true`, so you can review matches before deleting them. See [document read migration](/migration/api-v5-document-reads) and [memory forgetting migration](/migration/api-v5-memory-forgetting).

View file

@ -27,10 +27,10 @@ const { id, status } = await supermemory.add("user_1", {
``` ```
```bash curl ```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_1/document?dreaming=instant" \ curl -X POST "https://api.supermemory.ai/ns/user_1/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"content":"The user prefers concise project updates","id":"conv_1","metadata":{"source":"chat"}}' -d '{"content":"The user prefers concise project updates","id":"conv_1","metadata":{"source":"chat"},"dreaming":"instant"}'
``` ```
</CodeGroup> </CodeGroup>

View file

@ -1,7 +1,7 @@
--- ---
title: "API reference" title: "API reference"
sidebarTitle: "Overview" sidebarTitle: "Overview"
description: "Documents, search, profiles, lists, memories, namespaces, and organization settings in the latest Supermemory API" description: "Documents, search, profiles, lists, memories, connectors, namespaces, and organization settings in the latest Supermemory API"
icon: "/icons/hugeicons/plug-socket.svg" icon: "/icons/hugeicons/plug-socket.svg"
--- ---
@ -16,6 +16,8 @@ https://api.supermemory.ai
/ns/{namespace}/profile /ns/{namespace}/profile
/ns/{namespace}/list/{type} /ns/{namespace}/list/{type}
/ns/{namespace}/memories /ns/{namespace}/memories
/ns/{namespace}/connectors
/connectors
/namespaces /namespaces
/organization /organization
``` ```
@ -36,7 +38,7 @@ import { Supermemory } from "supermemory"
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY }) const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
``` ```
Every SDK call takes one object. `namespace`, path IDs, and query parameters are top-level keys; the JSON body goes under `body`. Each reference page below shows the SDK call next to the equivalent `curl`. There is no v5 Python SDK yet. 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="...")`.
<CardGroup cols={2}> <CardGroup cols={2}>
<Card title="Migrate to v5" icon="arrow-up-right" href="/migration/api-v5"> <Card title="Migrate to v5" icon="arrow-up-right" href="/migration/api-v5">

View file

@ -36,4 +36,4 @@ curl -X PUT "https://api.supermemory.ai/ns/user_1/profile/buckets" \
Profiles always contain static and dynamic sections, giving applications both durable knowledge and fresh context without composing them manually. Organization-owned buckets may be inherited but cannot be changed through namespace bucket endpoints. Profiles always contain static and dynamic sections, giving applications both durable knowledge and fresh context without composing them manually. Organization-owned buckets may be inherited but cannot be changed through namespace bucket endpoints.
See [profile migration](/migration/api-v5-recall). See [profile migration](/migration/api-v5-profiles).

View file

@ -55,9 +55,22 @@ The client discovers the OAuth authorization server through
| `list_memories` | List extracted memory entries and their source document IDs | | `list_memories` | List extracted memory entries and their source document IDs |
| `list_spaces` | List spaces visible to the authenticated account | | `list_spaces` | List spaces visible to the authenticated account |
| `who_am_i` | Return identity, access, and active-space context | | `who_am_i` | Return identity, access, and active-space context |
| `add_memory` | Save a memory (legacy `action: "forget"` remains supported) | | `add_memory` | Accept a document for asynchronous memory extraction (legacy `action: "forget"` remains supported) |
| `forget_memory` | Forget a memory matching the supplied content | | `forget_memory` | Forget a memory matching the supplied content |
### Document status
`list_documents` and `get_document` return two status fields:
| Field | Value | Meaning |
| --- | --- | --- |
| `status` | `done` | Content is processed and readable |
| `dreamingStatus` | `dreaming` | Memory extraction is pending, running, or retrying |
| `dreamingStatus` | `done` | Memory extraction settled, possibly with no memories |
`dreamingStatus` appears only when the API returns it. Neither `done` means
memories are searchable yet.
### MCP App launchers ### MCP App launchers
| Tool | Purpose | | Tool | Purpose |

View file

@ -37,7 +37,7 @@ describe.skipIf(!OAUTH_CREDENTIALS_AVAILABLE)("MCP — memory behaviors", () =>
action: "save", action: "save",
}) })
expect(save.isError).toBeFalsy() expect(save.isError).toBeFalsy()
expect(textOf(save)).toMatch(/Memory saved/i) expect(textOf(save)).toMatch(/Document accepted/i)
const found = await recallUntil(s.client, "test fruit dragonfruit", marker) const found = await recallUntil(s.client, "test fruit dragonfruit", marker)
expect(found, `recall never returned marker ${marker}`).not.toBeNull() expect(found, `recall never returned marker ${marker}`).not.toBeNull()

View file

@ -41,7 +41,7 @@
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"supermemory": "^5.0.0", "supermemory": "^5.0.0",
"tailwind-merge": "^3.4.0", "tailwind-merge": "^3.4.0",
"zod": "^4.4.3" "zod": "^4.6.6"
}, },
"devDependencies": { "devDependencies": {
"@cloudflare/workers-types": "^4.20250620.0", "@cloudflare/workers-types": "^4.20250620.0",

View file

@ -83,16 +83,21 @@ describe("MCP authentication", () => {
}) })
it("rejects a token issued for a different audience", async () => { it("rejects a token issued for a different audience", async () => {
vi.spyOn(console, "error").mockImplementation(() => {}) const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
const token = await signToken({ audience: "https://api.example.com" }) const token = await signToken({ audience: "https://api.example.com" })
await expect( await expect(
validateOAuthToken(token, API_URL, MCP_RESOURCE, keySet), validateOAuthToken(token, API_URL, MCP_RESOURCE, keySet),
).resolves.toBeNull() ).resolves.toBeNull()
expect(warn).toHaveBeenCalledTimes(1)
expect(warn.mock.calls[0]?.[0]).toMatch(/^OAuth token rejected: /)
expect(error).not.toHaveBeenCalled()
}) })
it("rejects expired tokens and tokens without a subject", async () => { it("rejects expired tokens and tokens without a subject", async () => {
vi.spyOn(console, "error").mockImplementation(() => {}) const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
const expired = await signToken({ expiresIn: "-1s" }) const expired = await signToken({ expiresIn: "-1s" })
const noSubject = await signToken({ subject: "" }) const noSubject = await signToken({ subject: "" })
@ -102,10 +107,34 @@ describe("MCP authentication", () => {
await expect( await expect(
validateOAuthToken(noSubject, API_URL, MCP_RESOURCE, keySet), validateOAuthToken(noSubject, API_URL, MCP_RESOURCE, keySet),
).resolves.toBeNull() ).resolves.toBeNull()
expect(warn).toHaveBeenCalledTimes(1)
expect(warn.mock.calls[0]?.[0]).toMatch(/^OAuth token rejected: .*exp/)
expect(error).not.toHaveBeenCalled()
})
it("logs a JWKS failure as an auth backend outage and surfaces TransientAuthError", async () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
const token = await signToken()
const failingKeySet = () =>
Promise.reject(
Object.assign(new Error("JWKS endpoint unavailable"), {
code: "ERR_JWKS_TIMEOUT",
}),
)
await expect(
validateOAuthToken(token, API_URL, MCP_RESOURCE, failingKeySet),
).rejects.toThrow(TransientAuthError)
expect(error).toHaveBeenCalledTimes(1)
expect(error.mock.calls[0]?.[0]).toBe(
"Auth backend unavailable during OAuth token validation: JWKS fetch failed: JWKS endpoint unavailable",
)
expect(warn).not.toHaveBeenCalled()
}) })
it("rejects opaque API keys without an API request", async () => { it("rejects opaque API keys without an API request", async () => {
vi.spyOn(console, "error").mockImplementation(() => {}) vi.spyOn(console, "warn").mockImplementation(() => {})
const fetchSpy = vi.fn() const fetchSpy = vi.fn()
vi.stubGlobal("fetch", fetchSpy) vi.stubGlobal("fetch", fetchSpy)
@ -148,7 +177,7 @@ describe("MCP authentication", () => {
scopes: [], scopes: [],
}) })
expect(fetchSpy).toHaveBeenCalledWith( expect(fetchSpy).toHaveBeenCalledWith(
`${API_URL}/v3/session`, `${API_URL}/session`,
expect.objectContaining({ expect.objectContaining({
headers: { Authorization: `Bearer ${key}` }, headers: { Authorization: `Bearer ${key}` },
}), }),
@ -166,15 +195,23 @@ describe("MCP authentication", () => {
}) })
it("rejects an API key the session endpoint refuses", async () => { it("rejects an API key the session endpoint refuses", async () => {
vi.spyOn(console, "error").mockImplementation(() => {}) const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
vi.stubGlobal( const error = vi.spyOn(console, "error").mockImplementation(() => {})
"fetch", const fetchSpy = vi
vi.fn().mockResolvedValue(new Response(null, { status: 401 })), .fn()
) .mockResolvedValue(new Response(null, { status: 401 }))
vi.stubGlobal("fetch", fetchSpy)
await expect( await expect(
validateApiKey("sm_revoked_key_0123456789abcdef", API_URL), validateApiKey("sm_revoked_key_0123456789abcdef", API_URL),
).resolves.toBeNull() ).resolves.toBeNull()
expect(fetchSpy).toHaveBeenCalledTimes(1)
expect(fetchSpy.mock.calls[0]?.[0]).toBe(`${API_URL}/session`)
expect(warn).toHaveBeenCalledTimes(1)
expect(warn).toHaveBeenCalledWith(
"API key rejected: Session request failed with status 401",
)
expect(error).not.toHaveBeenCalled()
}) })
it("rejects malformed API keys without an API request", async () => { it("rejects malformed API keys without an API request", async () => {
@ -187,7 +224,8 @@ describe("MCP authentication", () => {
}) })
it("surfaces a 500 from the session endpoint as TransientAuthError, not invalid token", async () => { it("surfaces a 500 from the session endpoint as TransientAuthError, not invalid token", async () => {
vi.spyOn(console, "error").mockImplementation(() => {}) const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
vi.stubGlobal( vi.stubGlobal(
"fetch", "fetch",
vi.fn().mockResolvedValue(new Response(null, { status: 500 })), vi.fn().mockResolvedValue(new Response(null, { status: 500 })),
@ -196,10 +234,16 @@ describe("MCP authentication", () => {
await expect( await expect(
validateApiKey("sm_outage_key_0123456789abcdef", API_URL), validateApiKey("sm_outage_key_0123456789abcdef", API_URL),
).rejects.toThrow(TransientAuthError) ).rejects.toThrow(TransientAuthError)
expect(error).toHaveBeenCalledTimes(1)
expect(error.mock.calls[0]?.[0]).toBe(
"Auth backend unavailable during API key validation: Session endpoint returned 500",
)
expect(warn).not.toHaveBeenCalled()
}) })
it("surfaces a session-endpoint timeout as TransientAuthError", async () => { it("surfaces a session-endpoint timeout as TransientAuthError", async () => {
vi.spyOn(console, "error").mockImplementation(() => {}) const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
vi.stubGlobal( vi.stubGlobal(
"fetch", "fetch",
vi.fn().mockRejectedValue( vi.fn().mockRejectedValue(
@ -212,5 +256,7 @@ describe("MCP authentication", () => {
await expect( await expect(
validateApiKey("sm_timeout_key_0123456789abcd", API_URL), validateApiKey("sm_timeout_key_0123456789abcd", API_URL),
).rejects.toThrow(TransientAuthError) ).rejects.toThrow(TransientAuthError)
expect(error).toHaveBeenCalledTimes(1)
expect(warn).not.toHaveBeenCalled()
}) })
}) })

View file

@ -31,7 +31,7 @@ export async function fetchSession(
bearerToken: string, bearerToken: string,
apiUrl: string, apiUrl: string,
): Promise<SessionInfo> { ): Promise<SessionInfo> {
const response = await fetch(`${apiUrl.replace(/\/+$/, "")}/v3/session`, { const response = await fetch(`${apiUrl.replace(/\/+$/, "")}/session`, {
method: "GET", method: "GET",
headers: { Authorization: `Bearer ${bearerToken}` }, headers: { Authorization: `Bearer ${bearerToken}` },
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
@ -132,9 +132,17 @@ export async function validateApiKey(
}) })
return user return user
} catch (error) { } catch (error) {
console.error("API key validation error:", error)
const transient = transientAuthErrorFor(error) const transient = transientAuthErrorFor(error)
if (transient) throw transient if (transient) {
console.error(
`Auth backend unavailable during API key validation: ${transient.message}`,
error,
)
throw transient
}
console.warn(
`API key rejected: ${error instanceof Error ? error.message : String(error)}`,
)
return null return null
} }
} }
@ -183,9 +191,17 @@ export async function validateOAuthToken(
expiresAt: payload.exp, expiresAt: payload.exp,
} }
} catch (error) { } catch (error) {
console.error("OAuth token validation error:", error)
const transient = transientAuthErrorFor(error) const transient = transientAuthErrorFor(error)
if (transient) throw transient if (transient) {
console.error(
`Auth backend unavailable during OAuth token validation: ${transient.message}`,
error,
)
throw transient
}
console.warn(
`OAuth token rejected: ${error instanceof Error ? error.message : String(error)}`,
)
return null return null
} }
} }

View file

@ -35,18 +35,23 @@ export type {
NamespaceInfo, NamespaceInfo,
} }
type DreamingStatus = "dreaming" | "done"
export interface DocumentSummary { export interface DocumentSummary {
id: string id: string
title: string | null title: string | null
type: string type: string
status: string status: string
dreamingStatus?: DreamingStatus
createdAt: string createdAt: string
updatedAt: string updatedAt: string
summary: string | null summary: string | null
url: string | null url: string | null
} }
export type DocumentDetails = GetDocumentsResponse export type DocumentDetails = GetDocumentsResponse & {
system: { dreamingStatus?: DreamingStatus }
}
export interface DocumentsListResponse { export interface DocumentsListResponse {
documents: DocumentSummary[] documents: DocumentSummary[]
@ -57,8 +62,8 @@ export type { MemoryEntry, MemoryEntryHistory }
export type MemoryEntriesResponse = MemoriesList export type MemoryEntriesResponse = MemoriesList
export type Memory = export type Memory =
| { id: string; memory: string; similarity: number } | { id: string; memory: string; similarity: number; documentId?: string }
| { id: string; chunk: string; similarity: number } | { id: string; chunk: string; similarity: number; documentId?: string }
export interface SearchResult { export interface SearchResult {
results: Memory[] results: Memory[]
@ -85,29 +90,35 @@ function limitByChars(text: string, maxChars = MAX_CHARS): string {
function mapSearchResults(results: SearchResponseResultsItem[]): Memory[] { function mapSearchResults(results: SearchResponseResultsItem[]): Memory[] {
return results.map((result) => { return results.map((result) => {
const documentId = result.included?.document?.id
if (result.chunk && !result.memory) { if (result.chunk && !result.memory) {
return { return {
id: result.id, id: result.id,
similarity: result.similarity, similarity: result.similarity,
chunk: limitByChars(result.chunk), chunk: limitByChars(result.chunk),
...(documentId && { documentId }),
} }
} }
return { return {
id: result.id, id: result.id,
similarity: result.similarity, similarity: result.similarity,
memory: limitByChars(result.memory ?? ""), memory: limitByChars(result.memory ?? ""),
...(documentId && { documentId }),
} }
}) })
} }
function mapDocumentSummary( function mapDocumentSummary(
document: ListResponseDocumentsItem, document: ListResponseDocumentsItem & {
system: { dreamingStatus?: DreamingStatus }
},
): DocumentSummary { ): DocumentSummary {
return { return {
id: document.id, id: document.id,
title: document.title, title: document.title,
type: document.type, type: document.type,
status: document.system.status, status: document.system.status,
dreamingStatus: document.system.dreamingStatus,
createdAt: document.system.createdAt, createdAt: document.system.createdAt,
updatedAt: document.system.updatedAt, updatedAt: document.system.updatedAt,
summary: document.summary, summary: document.summary,
@ -207,39 +218,24 @@ export class SupermemoryClient {
} }
} }
// v5 has no exact-content forget: preview semantic matches, then forget the best one by ID. // Retrying a committed mutation can turn its success into a misleading not-found result.
async forgetMemory( async forgetByIds(ids: string[]): Promise<{
content: string, forgotten: { id: string; memory: string }[]
): Promise<{ success: boolean; message: string; namespace: string }> { errors: { id: string; error: string }[]
namespace: string
}> {
try { try {
const preview = await this.client.memories.forgetMatching( const result = await this.client.memories.forget(
this.namespace, this.namespace,
{ query: content, dryRun: true }, { ids },
{ maxRetries: 0 },
) )
const match = preview.matches[0]
if (!match) {
return { return {
success: false, forgotten: result.matches.map((m) => ({
message: "No matching memory found to forget.", id: m.id,
namespace: this.namespace, memory: limitByChars(m.memory, 200),
} })),
} errors: result.errors,
const result = await this.client.memories.forget(this.namespace, {
ids: [match.id],
})
if (result.count === 0) {
const reason = result.errors[0]?.error ?? "unknown error"
return {
success: false,
message: `Could not forget memory ${match.id}: ${reason}`,
namespace: this.namespace,
}
}
return {
success: true,
message: `Forgot memory (ID: ${match.id}): "${limitByChars(match.memory, 100)}"`,
namespace: this.namespace, namespace: this.namespace,
} }
} catch (error) { } catch (error) {
@ -252,8 +248,9 @@ export class SupermemoryClient {
const result = await this.client.search(this.namespace, { const result = await this.client.search(this.namespace, {
query, query,
limit, limit,
searchMode: "hybrid", searchMode: "memories",
threshold: SEARCH_THRESHOLD, threshold: SEARCH_THRESHOLD,
include: { documents: true },
}) })
const results = mapSearchResults(result.results) const results = mapSearchResults(result.results)
return { return {
@ -371,9 +368,25 @@ export class SupermemoryClient {
} }
} }
// Older search results handed out memory IDs as document IDs, so resolve those to their source document.
async getDocument(id: string): Promise<DocumentDetails> { async getDocument(id: string): Promise<DocumentDetails> {
try { try {
return await this.client.documents.get(this.namespace, id) return await this.client.documents.get(this.namespace, id)
} catch (error) {
if (objectProperty(error, "statusCode") !== 404) this.handleError(error)
}
const documentId = await this.client.memories
.get(this.namespace, id, { include: "documents" })
.then((memory) => memory.included?.document?.id)
.catch(() => undefined)
if (!documentId) {
throw new Error(
`No document with ID "${id}" in space "${this.namespace}". Use a document ID from list_documents, or the document ID shown on a search_memory or list_memories result.`,
)
}
try {
return await this.client.documents.get(this.namespace, documentId)
} catch (error) { } catch (error) {
this.handleError(error) this.handleError(error)
} }

View file

@ -18,6 +18,10 @@ function day(value: string | null | undefined): string {
return value?.slice(0, 10) ?? "" return value?.slice(0, 10) ?? ""
} }
function memoryExtractionLabel(status: "dreaming" | "done"): string {
return status === "dreaming" ? "in progress" : "done"
}
function paginationSummary( function paginationSummary(
currentPage: number, currentPage: number,
totalPages: number, totalPages: number,
@ -43,6 +47,11 @@ export function formatDocumentsList(response: DocumentsListResponse): string {
const lines = [ const lines = [
`- [${document.id}] "${title}" (${document.type}, ${document.status}, ${day(document.createdAt)})`, `- [${document.id}] "${title}" (${document.type}, ${document.status}, ${day(document.createdAt)})`,
] ]
if (document.dreamingStatus) {
lines.push(
` Memory extraction: ${memoryExtractionLabel(document.dreamingStatus)}`,
)
}
if (document.summary?.trim()) { if (document.summary?.trim()) {
lines.push(` Summary: ${compactText(document.summary)}`) lines.push(` Summary: ${compactText(document.summary)}`)
} }
@ -148,6 +157,11 @@ export function formatDocument(document: DocumentDetails): string {
`Document ID: ${document.id}`, `Document ID: ${document.id}`,
`Type: ${document.type}`, `Type: ${document.type}`,
`Status: ${document.system.status}`, `Status: ${document.system.status}`,
...(document.system.dreamingStatus
? [
`Memory extraction: ${memoryExtractionLabel(document.system.dreamingStatus)}`,
]
: []),
`Created: ${document.system.createdAt}`, `Created: ${document.system.createdAt}`,
`Updated: ${document.system.updatedAt}`, `Updated: ${document.system.updatedAt}`,
] ]

View file

@ -1,5 +1,5 @@
import type { McpServer } from "@modelcontextprotocol/server" import type { McpServer } from "@modelcontextprotocol/server"
import { DEFAULT_NAMESPACE, type SupermemoryClient } from "../client" import type { SupermemoryClient } from "../client"
import { import {
compactDescription, compactDescription,
formatFactSection, formatFactSection,
@ -15,6 +15,7 @@ export function registerContextPrompt(
server: McpServer, server: McpServer,
getClient: (namespace?: string) => SupermemoryClient, getClient: (namespace?: string) => SupermemoryClient,
resolveNamespace: () => Promise<string | undefined>, resolveNamespace: () => Promise<string | undefined>,
getDefaultNamespace: () => Promise<string>,
) { ) {
server.registerPrompt( server.registerPrompt(
"context", "context",
@ -24,7 +25,7 @@ export function registerContextPrompt(
async () => { async () => {
try { try {
const selectedNamespace = await resolveNamespace() const selectedNamespace = await resolveNamespace()
const activeKey = selectedNamespace ?? DEFAULT_NAMESPACE const activeKey = selectedNamespace ?? (await getDefaultNamespace())
const [profileResult, spaces] = await Promise.all([ const [profileResult, spaces] = await Promise.all([
getClient(activeKey).getProfile(), getClient(activeKey).getProfile(),
getClient().listNamespaces(), getClient().listNamespaces(),

View file

@ -1,11 +1,12 @@
import type { McpServer } from "@modelcontextprotocol/server" import type { McpServer } from "@modelcontextprotocol/server"
import { DEFAULT_NAMESPACE, type SupermemoryClient } from "../client" import type { SupermemoryClient } from "../client"
import { formatSpaceRow, sortSpaces } from "../space-presentation" import { formatSpaceRow, sortSpaces } from "../space-presentation"
export function registerNamespacesResource( export function registerNamespacesResource(
server: McpServer, server: McpServer,
getClient: () => SupermemoryClient, getClient: () => SupermemoryClient,
resolveNamespace: () => Promise<string | undefined>, resolveNamespace: () => Promise<string | undefined>,
getDefaultNamespace: () => Promise<string>,
) { ) {
server.registerResource("My Spaces", "supermemory://spaces", {}, async () => { server.registerResource("My Spaces", "supermemory://spaces", {}, async () => {
const client = getClient() const client = getClient()
@ -13,7 +14,7 @@ export function registerNamespacesResource(
client.listNamespaces(), client.listNamespaces(),
resolveNamespace(), resolveNamespace(),
]) ])
const activeKey = selectedNamespace ?? DEFAULT_NAMESPACE const activeKey = selectedNamespace ?? (await getDefaultNamespace())
const rows = sortSpaces(namespaces, activeKey).map((space) => const rows = sortSpaces(namespaces, activeKey).map((space) =>
formatSpaceRow(space, activeKey), formatSpaceRow(space, activeKey),
) )

View file

@ -1,5 +1,5 @@
import type { McpServer } from "@modelcontextprotocol/server" import type { McpServer } from "@modelcontextprotocol/server"
import { DEFAULT_NAMESPACE, type SupermemoryClient } from "../client" import type { SupermemoryClient } from "../client"
import { import {
compactDescription, compactDescription,
formatFactSection, formatFactSection,
@ -12,6 +12,7 @@ export function registerProfileResource(
server: McpServer, server: McpServer,
getClient: (namespace?: string) => SupermemoryClient, getClient: (namespace?: string) => SupermemoryClient,
resolveNamespace: () => Promise<string | undefined>, resolveNamespace: () => Promise<string | undefined>,
getDefaultNamespace: () => Promise<string>,
) { ) {
server.registerResource( server.registerResource(
"Active Space Profile", "Active Space Profile",
@ -19,7 +20,7 @@ export function registerProfileResource(
{}, {},
async () => { async () => {
const selectedNamespace = await resolveNamespace() const selectedNamespace = await resolveNamespace()
const activeKey = selectedNamespace ?? DEFAULT_NAMESPACE const activeKey = selectedNamespace ?? (await getDefaultNamespace())
const [profileResult, spaces] = await Promise.all([ const [profileResult, spaces] = await Promise.all([
getClient(activeKey).getProfile(), getClient(activeKey).getProfile(),
getClient().listNamespaces(), getClient().listNamespaces(),

View file

@ -14,6 +14,7 @@ import { registerAllTools } from "./tools"
import { errorResult } from "./tools/types" import { errorResult } from "./tools/types"
import type { ActorContext, ServerEnv } from "./types" import type { ActorContext, ServerEnv } from "./types"
import { import {
accessibleDefaultNamespace,
resolveNamespace as resolveSpaceNamespace, resolveNamespace as resolveSpaceNamespace,
spaceStateName, spaceStateName,
} from "./space" } from "./space"
@ -24,8 +25,39 @@ const UPLOAD_SESSION_TTL_MS = 2 * 60 * 1000
const SERVER_INSTRUCTIONS = const SERVER_INSTRUCTIONS =
"Supermemory is the authenticated user's persistent memory and knowledge layer across conversations and spaces. Use these tools whenever the user wants to recall something they may have saved, inspect stored sources or extracted memories, remember or upload new information, check their Supermemory account or access, change their active space, or explore their memory graph, even if they do not mention Supermemory by name. Use the active or account-default space when none is named. Resolve a named space with list_spaces and pass its key to the relevant tool; change the active space only when the user explicitly asks." "Supermemory is the authenticated user's persistent memory and knowledge layer across conversations and spaces. Use these tools whenever the user wants to recall something they may have saved, inspect stored sources or extracted memories, remember or upload new information, check their Supermemory account or access, change their active space, or explore their memory graph, even if they do not mention Supermemory by name. Use the active or account-default space when none is named. Resolve a named space with list_spaces and pass its key to the relevant tool; change the active space only when the user explicitly asks."
const DEFAULT_NAMESPACE_CACHE_TTL_MS = 60_000
const DEFAULT_NAMESPACE_CACHE_MAX_ENTRIES = 1000
type ClientInfo = { name: string; version?: string } type ClientInfo = { name: string; version?: string }
const defaultNamespaceCache = new Map<
string,
{ namespace: string; expiresAt: number }
>()
// Stateless HTTP builds a server per request, so cache per isolate to keep the session lookup off most calls.
async function defaultNamespaceFor(
bearerToken: string,
apiUrl: string,
): Promise<string> {
const cached = defaultNamespaceCache.get(bearerToken)
if (cached && cached.expiresAt > Date.now()) return cached.namespace
const namespace = await fetchSession(bearerToken, apiUrl)
.then(accessibleDefaultNamespace)
.catch(() => undefined)
if (!namespace) return DEFAULT_NAMESPACE
if (defaultNamespaceCache.size >= DEFAULT_NAMESPACE_CACHE_MAX_ENTRIES) {
defaultNamespaceCache.clear()
}
defaultNamespaceCache.set(bearerToken, {
namespace,
expiresAt: Date.now() + DEFAULT_NAMESPACE_CACHE_TTL_MS,
})
return namespace
}
function clientInfoFromContext(context: ServerContext): ClientInfo | null { function clientInfoFromContext(context: ServerContext): ClientInfo | null {
const envelope = context.mcpReq.envelope const envelope = context.mcpReq.envelope
if (!envelope) return null if (!envelope) return null
@ -66,8 +98,10 @@ export function createSupermemoryServer(
spaceState.setActiveNamespace(namespace) spaceState.setActiveNamespace(namespace)
const resolveSelectedNamespace = (explicit?: string) => const resolveSelectedNamespace = (explicit?: string) =>
resolveSpaceNamespace(explicit, getActiveNamespace) resolveSpaceNamespace(explicit, getActiveNamespace)
const getDefaultNamespace = () =>
defaultNamespaceFor(actor.bearerToken, apiUrl)
const resolveNamespace = async (explicit?: string) => const resolveNamespace = async (explicit?: string) =>
(await resolveSelectedNamespace(explicit)) ?? DEFAULT_NAMESPACE (await resolveSelectedNamespace(explicit)) ?? (await getDefaultNamespace())
const createUploadSession = async (namespace: string) => { const createUploadSession = async (namespace: string) => {
const uploadId = crypto.randomUUID() const uploadId = crypto.randomUUID()
const uploadToken = [crypto.randomUUID(), crypto.randomUUID()] const uploadToken = [crypto.randomUUID(), crypto.randomUUID()]
@ -106,14 +140,25 @@ export function createSupermemoryServer(
} catch {} } catch {}
} }
registerProfileResource(server, getClient, resolveSelectedNamespace) registerProfileResource(
server,
getClient,
resolveSelectedNamespace,
getDefaultNamespace,
)
registerNamespacesResource( registerNamespacesResource(
server, server,
() => getClient(), () => getClient(),
resolveSelectedNamespace, resolveSelectedNamespace,
getDefaultNamespace,
) )
registerWidgetResource(server, mcpOrigin) registerWidgetResource(server, mcpOrigin)
registerContextPrompt(server, getClient, resolveSelectedNamespace) registerContextPrompt(
server,
getClient,
resolveSelectedNamespace,
getDefaultNamespace,
)
return server return server
} }

Some files were not shown because too many files have changed in this diff Show more