Compare commits

..

No commits in common. "main" and "server-v0.0.9" have entirely different histories.

149 changed files with 5218 additions and 4311 deletions

102
.github/workflows/publish-ai-sdk.yml vendored Normal file
View file

@ -0,0 +1,102 @@
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
View file

@ -1,251 +0,0 @@
# 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,5 +1,3 @@
@AGENTS.md
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

View file

@ -26,17 +26,25 @@ Before you begin, ensure you have the following installed:
bun install
```
3. **Start the Development Server**
3. **Set Up Environment Variables**
```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
bun run dev:local
```
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).
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.
> **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.
> **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.
## 📁 Project Structure
@ -45,28 +53,24 @@ supermemory is organized as a monorepo using Turbo:
```
supermemory/
├── apps/
│ ├── web/ # Redirects to the console
│ ├── mcp/ # Model Context Protocol server
│ ├── docs/ # Documentation site
│ ├── memory-graph-playground/ # Demo app for the memory graph component
│ └── sdk-playground/ # Playground for the SDK integrations
│ ├── web/ # Next.js web application
│ ├── browser-extension/ # Browser extension (WXT-based)
│ ├── docs/ # Documentation site
│ └── raycast-extension/ # Raycast extension
├── 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
│ ├── lib/ # Shared utilities and logic
│ ├── hooks/ # Shared React hooks
│ ├── validation/ # Zod schemas and validation
│ └── docs-test/ # Runs the code samples from the docs
├── skills/ # Supermemory agent skill
├── turbo.json # Turbo configuration
├── biome.json # Biome configuration
└── package.json # Root package configuration
│ ├── ui/ # Shared UI components
│ ├── lib/ # Shared utilities and logic
│ ├── hooks/ # Shared React hooks
│ ├── validation/ # Zod schemas and validation
│ ├── ai-sdk/ # AI SDK for memory operations
│ ├── tools/ # Development tools and utilities
│ ├── 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
├── biome.json # Biome configuration
└── package.json # Root package configuration
```
## 🛠️ Development Workflow
@ -97,7 +101,7 @@ bun run build
### Tech Stack
- **Frontend**: Next.js 16, React 19, TypeScript
- **Frontend**: Next.js 15, React 19, TypeScript
- **Styling**: Tailwind CSS, Radix UI components
- **State Management**: Zustand, TanStack Query
- **Build Tool**: Turbo (monorepo)

View file

@ -337,28 +337,10 @@ 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.
- **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.
- **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`.
### 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).
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).
---

View file

@ -28,6 +28,7 @@ Useful for coding agents:
```bash
npx supermemory setup # detect project, launch/print integration flow
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 --all
npx supermemory migrate # move a v3/v4 project to v5 with your agent
@ -209,7 +210,7 @@ DO NOT USE — deprecated, undocumented, or fabricated:
- 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)
- 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.add` / `client.memories.updateMemory`, `client.profile({ q })`
- SDK: `client.add({ containerTag })`, `client.search.execute`, `client.memories.*`, `client.profile({ q })`
SCOPING IS LOAD-BEARING. Every write and every search MUST name one namespace in the path.

View file

@ -16,7 +16,7 @@ v5 calls these **namespaces**. Same values. New integrations should use [v5 name
| `GET /v3/container-tags/{containerTag}` | Read tag settings |
| `PATCH /v3/container-tags/{containerTag}` | Update tag settings |
| `DELETE /v3/container-tags/{containerTag}` | Delete a container and its data |
| `POST /v3/container-tags/merge` | Merge exactly two listed tags into either listed tag or a new target tag |
| `POST /v3/container-tags/merge` | Merge one tag into another |
| `GET /v3/container-tags/merge/{mergeId}` | Poll merge status |
**Guide:** [Container tags](/concepts/container-tags) · [Filtering](/concepts/filtering)

View file

@ -63,7 +63,7 @@ Official clients wrap this API:
See [Supermemory SDK](/integrations/supermemory-sdk).
The API's OpenAPI generator supplies official **TypeScript / Python SDK** samples via `x-codeSamples`, plus cURL. Playground snippets use the deployed spec, so their availability depends on the API version serving it.
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.)
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.

View file

@ -50,15 +50,11 @@ This works for Google Drive, Notion, and OneDrive. See the full setup in [Custom
## Scoped API keys
Scoped keys are restricted to one namespace (what v3/v4 called a container tag), or to a fixed list of namespaces. They can read and write content only inside those namespaces, subject to the key's read/write permission and member restrictions — use them to give a client, session, or tenant limited access without shipping your org master key.
<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>
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.
Pairs with [namespaces](/concepts/container-tags) for multi-tenant isolation.
**Allowed endpoints:** `/ns/{namespace}/document`, `/ns/{namespace}/list`, `/ns/{namespace}/search`, `/ns/{namespace}/profile`, and `/ns/{namespace}/memories`; `/namespaces` and `/ns/{namespace}`; and the legacy `/v3/documents`, `/v3/memories`, `/v4/memories`, `/v4/conversations`, `/v3/search`, `/v4/search`, `/v4/profile`, `/v3/container-tags/list`, and `/v3/session` routes. Any other route returns `403`.
**Allowed endpoints:** `/v3/documents`, `/v3/memories`, `/v4/memories`, `/v3/search`, `/v4/search`, `/v4/profile`
Scoped keys **cannot** read billing, manage org settings, or mint further keys.
@ -82,8 +78,7 @@ curl https://api.supermemory.ai/v3/auth/scoped-key \
| Parameter | Required | Default | Description |
| --- | --- | --- | --- |
| `containerTag` | Yes, unless `containerTags` is set | — | The namespace to scope the key to. Up to 100 characters: letters, numbers, hyphens, underscores, colons |
| `containerTags` | No | — | Scope the key to several namespaces instead of one. Takes precedence over `containerTag` |
| `containerTag` | Yes | — | The namespace to scope the key to. Alphanumeric, hyphens, underscores, colons, dots |
| `name` | No | `scoped_{containerTag}` | Display name for the key |
| `expiresInDays` | No | — | 1–365 days |
| `rateLimitMax` | No | `500` | Max requests per window (1–10,000) |
@ -97,9 +92,8 @@ curl https://api.supermemory.ai/v3/auth/scoped-key \
"id": "key-id",
"name": "scoped_my-project",
"containerTag": "my-project",
"containerTags": ["my-project"],
"expiresAt": "2026-03-08T00:00:00.000Z",
"allowedEndpoints": ["/v3/session", "/v3/documents", "/v3/memories", "/v4/memories", "/v4/conversations", "/ns/*/memories", "/v3/search", "/v4/search", "/v4/profile", "/v3/container-tags/list", "/ns/*/document", "/ns/*/list", "/ns/*/search", "/ns/*/profile", "/namespaces$", "/ns$", "/ns/*$"]
"allowedEndpoints": ["/v3/documents", "/v3/memories", "/v4/memories", "/v3/search", "/v4/search", "/v4/profile"]
}
```
@ -117,4 +111,4 @@ curl https://api.supermemory.ai/v3/auth/scoped-key/KEY_ID \
```json
{ "success": true }
```
```

View file

@ -97,7 +97,7 @@ The colon is intentionally allowed so you can build **hierarchical** namespaces
## Scope lives in the URL
In v5 the namespace is a path segment, not a body field. Every scoped endpoint starts with `/ns/{namespace}/`, and every scoped SDK call takes `namespace` as its first argument.
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`.
| 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:
- **API key scopes** — an API key can be limited to a specific set of namespaces, and a key can be read-only.
- **Member restrictions** — an organization member can be granted access to only certain namespaces, with read or write permission per namespace.
- **API key scopes** — an API key can be limited to a specific set of namespaces, with read or write permission per namespace.
- **Member restrictions** — an organization member can be granted access to only certain namespaces.
When a request is restricted, Supermemory validates the namespace in the URL against the caller's allowed set:
@ -159,13 +159,11 @@ 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.get(namespace)` | Read one namespace and its `supportingContext`. |
| `supermemory.namespaces.update(namespace, { supportingContext })` | Set a context prompt applied when processing documents in this namespace. Useful for steering extraction per project or tenant. |
| `supermemory.namespaces.delete(namespace)` | Delete the namespace, its namespace-owned memory records, and documents exclusive to it. Shared documents are retained. |
| `supermemory.namespaces.delete(namespace)` | Delete the namespace and all its documents and memories. |
| `supermemory.namespaces.delete(namespace, { moveTo })` | Move the content into another namespace, then remove this one. Queued; returns an `operationId`. |
`supportingContext` is what v3/v4 called `entityContext`. The legacy "merge" operation is now a delete with `moveTo`, one source namespace per request.
Namespace deletion and moves require an org admin or owner with write access to the source namespace; moves also require write access to the destination. The default `sm_project_default` namespace cannot be permanently deleted. See [deletion scope and erasure limits](/overview/security).
<CodeGroup>
```typescript TypeScript
@ -180,7 +178,7 @@ const { namespaces, pagination } = await supermemory.namespaces.list();
await supermemory.namespaces.delete("project_research_old", {
moveTo: "project_research",
});
// { status: "queued", operationId, namespace, moveTo }
// { success, status: "queued", operationId, namespace, moveTo }
```
```bash curl
@ -192,8 +190,10 @@ curl -X PATCH "https://api.supermemory.ai/ns/project_research" \
curl "https://api.supermemory.ai/namespaces" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
curl -X DELETE "https://api.supermemory.ai/ns/project_research_old?moveTo=project_research" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
curl -X DELETE "https://api.supermemory.ai/ns/project_research_old" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "moveTo": "project_research" }'
```
</CodeGroup>

View file

@ -5,7 +5,7 @@ description: "All the content formats Supermemory can ingest and process"
icon: "/icons/hugeicons/files-01.svg"
---
Supermemory automatically extracts and indexes content from various formats. There are two entry points: `supermemory.add()` for text and URLs, `supermemory.documents.uploadFile()` for actual files. Both take the target `namespace` as the first argument. See [Add Memories](/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 a top-level key. See [Add Memories](/ingestion/add-memories) to learn how to ingest content via the API.
## Text content
@ -31,7 +31,7 @@ await supermemory.add("documentation", {
});
```
**Extracts:** Article text, headings, metadata. Strips navigation, ads, boilerplate.
**Extracts:** Article text, headings, metadata. Strips navigation, ads, boilerplate. URL extraction is powered by [Markdowner](https://md.dhr.wtf).
---
@ -102,13 +102,13 @@ await supermemory.add("user_123", {
**Extracts:** Structure, headings, code blocks with syntax awareness.
See [Super RAG](/concepts/super-rag) for how Supermemory optimizes chunking for each content type.
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.
---
## Images
Set `fileType: "image"` and `mimeType` so Supermemory knows exactly how to process it. Both are multipart form fields, sent in the same object as `file` and `metadata`:
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`:
```typescript
await supermemory.documents.uploadFile("user_123", {
@ -127,7 +127,7 @@ await supermemory.documents.uploadFile("user_123", {
## Audio & video
Video and audio each have a dedicated `fileType` (`"video"`, `"audio"`); set it with `mimeType` when inference is not enough:
Video has a dedicated `fileType`; audio is uploaded the same way and detected from the file itself:
```typescript
// Video
@ -142,7 +142,6 @@ await supermemory.documents.uploadFile("user_123", {
await supermemory.documents.uploadFile("user_123", {
file: fs.createReadStream('call-recording.mp3'),
metadata: JSON.stringify({ title: "Customer Call Recording" }),
fileType: "audio",
mimeType: "audio/mpeg",
});
```
@ -216,7 +215,7 @@ await supermemory.add("user_123", { content: "https://example.com/page" });
await supermemory.add("user_123", { content: "User said they prefer email contact" });
```
For files, `uploadFile` detects type from the file itself in most cases. `fileType` and `mimeType` only exist to force specific processing when inference is not enough — set them for images, video, and audio.
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.
---
@ -228,7 +227,7 @@ For files, `uploadFile` detects type from the file itself in most cases. `fileTy
| Files | 50MB |
| URLs | Fetched content up to 10MB |
**Typical processing time:** text is near-instant; PDFs take 1-5s; images 2-10s; video 10s+; webpages 1-3s. Text content is split on sentence boundaries, with about 200 characters of overlap carried between chunks.
**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.
<Tip>
For large files, consider chunking or using [connectors](/connectors/overview) for automatic sync.

View file

@ -27,6 +27,7 @@ await supermemory.organization.update({
- Outdated brand materials (pre-2024)
- Internal discussions about brand changes
- Competitor analysis docs`,
},
});
```
@ -102,10 +103,6 @@ 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
Control how documents are split into searchable pieces. Smaller chunks = more precise retrieval but less context per result.
@ -136,7 +133,7 @@ Show "Log in to **YourApp**" instead of "Log in to Supermemory" when users conne
<AccordionGroup>
<Accordion title="Google Drive">
1. Create OAuth credentials in [Google Cloud Console](https://console.cloud.google.com/)
2. Redirect URI: `https://api.supermemory.ai/v3/connections/auth/callback/google-drive`
2. Redirect URI: `https://api.supermemory.ai/v3/connections/google-drive/callback`
```typescript
await client.settings.update({
@ -148,7 +145,7 @@ Show "Log in to **YourApp**" instead of "Log in to Supermemory" when users conne
</Accordion>
<Accordion title="Notion">
1. Create integration at [Notion Developers](https://developers.notion.com/)
2. Redirect URI: `https://api.supermemory.ai/v3/connections/auth/callback/notion`
2. Redirect URI: `https://api.supermemory.ai/v3/connections/notion/callback`
```typescript
await client.settings.update({
@ -160,7 +157,7 @@ Show "Log in to **YourApp**" instead of "Log in to Supermemory" when users conne
</Accordion>
<Accordion title="OneDrive">
1. Register app in [Azure Portal](https://portal.azure.com/)
2. Redirect URI: `https://api.supermemory.ai/v3/connections/auth/callback/onedrive`
2. Redirect URI: `https://api.supermemory.ai/v3/connections/onedrive/callback`
```typescript
await client.settings.update({

View file

@ -22,7 +22,7 @@ Both can be used independently or together for precise filtering.
## Namespaces
Namespaces create isolated memory spaces. Use them to separate memories by user, project, or any logical boundary. The namespace is part of the URL (`/ns/{namespace}/...`), so namespace-scoped SDK calls take it as the first argument.
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.
### Adding Memories to a Namespace
@ -42,7 +42,7 @@ const results = await supermemory.search("user_123", {
```
<Note>
Each search is scoped to a single namespace. Passing `"user_123"` as the namespace restricts results to memories stored in that namespace.
Each search is scoped to a single namespace. Passing `namespace: "user_123"` restricts results to memories stored in that namespace.
</Note>
### Recommended patterns
@ -336,6 +336,7 @@ const results = await supermemory.search("user_123", { // Scopes results to this
### Metadata key rules
- Allowed characters: `a-z`, `A-Z`, `0-9`, `_`, `-`, `.`
- Max length: 64 characters
- No spaces or special characters
- 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
});
const { results } = await supermemory.search("user_123", {
const results = await supermemory.search("user_123", {
query: "where does Alex work?",
include: { related: true },
searchMode: "memories",
@ -154,7 +154,7 @@ await supermemory.add("user_123", {
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?",
include: { related: true },
searchMode: "memories",

View file

@ -61,10 +61,10 @@ results = client.search("user_123", query="where does the user want to travel?")
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_123/document" \
curl -X POST "https://api.supermemory.ai/ns/user_123/document?dreaming=instant" \
-H "Authorization: Bearer sm_..." \
-H "Content-Type: application/json" \
-d '{ "content": "The user loves Paris.", "dreaming": "instant" }'
-d '{ "content": "The user loves Paris." }'
curl -X POST "https://api.supermemory.ai/ns/user_123/search" \
-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.
Pass `dreaming` on [add](/ingestion/add-memories) (a field in the JSON body, next to `content` in the SDK):
Pass `dreaming` on [add](/ingestion/add-memories) (a query parameter, so a top-level key in the SDK):
| 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.
**"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.
**"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.
**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
curl -X POST "https://api.supermemory.ai/ns/test/document" \
curl -X POST "https://api.supermemory.ai/ns/test/document?taskType=superrag" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "testing", "taskType": "superrag" }'
-d '{ "content": "testing" }'
```
</CodeGroup>
@ -86,10 +86,10 @@ results = client.search(
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/test/search" \
curl -X POST "https://api.supermemory.ai/ns/test/search?searchMode=hybrid" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "query": "test", "searchMode": "hybrid" }'
-d '{ "query": "test" }'
```
</CodeGroup>
@ -252,12 +252,11 @@ results = client.search(
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/org-supermemory/search" \
curl -X POST "https://api.supermemory.ai/ns/org-supermemory/search?searchMode=chunks" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "preferred language",
"searchMode": "chunks",
"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.
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.
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.
<CodeGroup>
@ -53,10 +53,10 @@ client.add(
```
```bash cURL
curl -X POST "https://api.supermemory.ai/ns/docs_kb/document" \
curl -X POST "https://api.supermemory.ai/ns/docs_kb/document?taskType=superrag" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "...", "taskType": "superrag" }'
-d '{ "content": "..." }'
```
</CodeGroup>
@ -100,9 +100,21 @@ PDFs and documents are chunked by **semantic sections** — headers, paragraphs,
### Code
When content is recognized as Markdown, fenced code blocks are kept whole when they fit in a chunk. Larger blocks are split and re-wrapped in the original fence, including its language tag. Overlap can contain part of a previous line, so chunks are not guaranteed to be independently valid code.
Code is chunked using [code-chunk](https://github.com/supermemoryai/code-chunk), our open-source library that understands AST (Abstract Syntax Tree) boundaries:
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.
- Functions and methods stay intact
- 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

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.
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.
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.
```typescript
// Define a namespace-level bucket (name → description)
@ -110,7 +110,7 @@ console.log(profile.buckets.preferences); // [{ id, memory }, ...]
console.log(profile.buckets.goals);
```
Bucket descriptions steer the classifier, so a precise description ("explicit first-person preferences only, exclude inferred traits") produces cleaner buckets than a vague one. Buckets are separate from [`organizationalContext`](/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.
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.
<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.

View file

@ -24,7 +24,7 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
const connector = await supermemory.connectors.create("user-123", {
provider: "github",
redirectUrl: "https://yourapp.com/auth/github/callback",
redirectUrl: "https://yourapp.com/auth/github/callback"
documentLimit: 5000,
})
@ -44,8 +44,8 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
"user-123",
request={
"provider": "github",
"redirect_url": "https://yourapp.com/auth/github/callback",
"document_limit": 5000,
"redirectUrl": "https://yourapp.com/auth/github/callback",
"documentLimit": 5000,
},
)
@ -68,7 +68,7 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "url": "https://github.com/login/oauth/authorize?...",
# "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
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"`.
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.
### 3. Select Repositories
@ -224,9 +224,7 @@ Webhooks are secured using HMAC-SHA256 signature validation with constant-time c
print(f"Webhooks: {connector.capabilities.webhooks}")
print(f"Last synced: {connector.system.last_successful_sync_at}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
if connector.latest_run and connector.latest_run.error:
print(connector.latest_run.error)
print(f"Last sync: {connector.latest_run.system.status} {connector.latest_run.error}")
repos = (connector.selection or {}).get("repos") or []
print(f"Repositories: {[r.name for r in repos]}")
print(f"Recent runs: {connector.syncs}")
@ -257,7 +255,7 @@ Webhooks are secured using HMAC-SHA256 signature validation with constant-time c
console.log(`Provider: ${connector.provider}`)
console.log(`ID: ${connector.id}`)
console.log(`Account: ${connector.account}`)
console.log(`Created: ${connector.system.createdAt}`)
console.log(`Created: ${connector.createdAt}`)
console.log(`Document limit: ${connector.documentLimit}`)
console.log(`Repositories: ${connector.selection?.repos?.length ?? 0}`)
console.log("---")
@ -273,7 +271,7 @@ Webhooks are secured using HMAC-SHA256 signature validation with constant-time c
print(f"Provider: {connector.provider}")
print(f"ID: {connector.id}")
print(f"Account: {connector.account}")
print(f"Created: {connector.system.created_at}")
print(f"Created: {connector.created_at}")
print(f"Document limit: {connector.document_limit}")
repos = (connector.selection or {}).get("repos") or []
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", {
provider: "gmail",
redirectUrl: "https://yourapp.com/auth/gmail/callback",
redirectUrl: "https://yourapp.com/auth/gmail/callback"
documentLimit: 5000,
})
@ -44,8 +44,8 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
"user-123",
request={
"provider": "gmail",
"redirect_url": "https://yourapp.com/auth/gmail/callback",
"document_limit": 5000,
"redirectUrl": "https://yourapp.com/auth/gmail/callback",
"documentLimit": 5000,
},
)
@ -68,7 +68,7 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "url": "https://accounts.google.com/o/oauth2/v2/auth?...",
# "expiresAt": "2024-01-15T11:30:00.000Z"
# }
# }
@ -78,9 +78,7 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
### 2. Handle OAuth callback
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Supermemory sends them to a hosted label picker; once they choose labels, they return to your `redirectUrl` and the initial sync begins. Nothing syncs until at least one label is selected. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
To change the labels later, read the connector with `include: "picker"` and send the user to `picker.url`, or set label ids directly with `connectors.update(namespace, id, { selection: { labels: ["INBOX"] } })`. See [Managing Connector Selection](/connectors/managing-resources).
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.
### 3. Check Connector Status
@ -108,7 +106,7 @@ To change the labels later, read the connector with `include: "picker"` and send
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
print(f"Connected email: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Last sync: {connector.latest_run.system.status}")
print(f"Threads synced: {connector.document_count}")
# List synced email threads in the namespace
@ -157,8 +155,8 @@ Each synced thread includes searchable metadata:
| `type` | Always `gmail_thread` |
| `subject` | Email subject line |
| `threadId` | Gmail thread ID |
| `from` | Sender, as the raw `From` header (for example `Team <team@company.com>`) |
| `to` | Recipient addresses, comma-separated |
| `from` | Sender email address |
| `to` | Recipient email addresses |
| `date` | Date of first message |
| `messageCount` | Number of messages in thread |
| `attachmentCount` | Number of attachments (if any) |
@ -173,7 +171,7 @@ const results = await supermemory.search("user-123", {
operator: "and",
operands: [
{ field: "type", operator: "eq", value: "gmail_thread" },
{ field: "from", operator: "contains", value: "team@company.com" },
{ field: "from", operator: "eq", value: "team@company.com" },
],
},
searchMode: "chunks",
@ -194,7 +192,7 @@ const results = await supermemory.search("user-123", {
console.log(`Provider: ${connector.provider}`)
console.log(`ID: ${connector.id}`)
console.log(`Account: ${connector.account}`)
console.log(`Created: ${connector.system.createdAt}`)
console.log(`Created: ${connector.createdAt}`)
console.log(`Document limit: ${connector.documentLimit}`)
console.log("---")
})
@ -209,7 +207,7 @@ const results = await supermemory.search("user-123", {
print(f"Provider: {connector.provider}")
print(f"ID: {connector.id}")
print(f"Account: {connector.account}")
print(f"Created: {connector.system.created_at}")
print(f"Created: {connector.created_at}")
print(f"Document limit: {connector.document_limit}")
print("---")
```
@ -310,13 +308,13 @@ Gmail connector supports multiple sync methods:
### How real-time sync works
1. After labels are selected, supermemory registers a Gmail API "watch" subscription when Pub/Sub is configured
1. When a connector is created, supermemory registers a Gmail API "watch" subscription
2. Gmail sends notifications to a Google Cloud Pub/Sub topic when emails change
3. supermemory receives these notifications and fetches updated threads
4. Watch subscriptions expire after 7 days and are automatically renewed
<Note>
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.
Real-time sync monitors the **INBOX** label. Emails in other labels are synced via scheduled/manual sync.
</Note>
## Permissions & scopes

View file

@ -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", {
provider: "google-drive",
redirectUrl: "https://yourapp.com/auth/google-drive/callback",
redirectUrl: "https://yourapp.com/auth/google-drive/callback"
documentLimit: 3000,
config: { syncScope: "selected" },
})
@ -53,9 +53,9 @@ If you use scoped sync and the user has not finished the picker yet, **scheduled
"user-123",
request={
"provider": "google-drive",
"redirect_url": "https://yourapp.com/auth/google-drive/callback",
"document_limit": 3000,
"config": {"sync_scope": "selected"},
"redirectUrl": "https://yourapp.com/auth/google-drive/callback",
"documentLimit": 3000,
"config": {"syncScope": "selected"},
},
)
@ -79,7 +79,7 @@ If you use scoped sync and the user has not finished the picker yet, **scheduled
# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "url": "https://accounts.google.com/o/oauth2/v2/auth?...",
# "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
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"`.
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.
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"Selection: {connector.selection}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Last sync: {connector.latest_run.system.status}")
# List synced documents in the namespace
documents = client.list("user-123", "documents").documents
@ -205,8 +205,6 @@ Based on the API type definitions, Google Drive documents are identified with th
- `google_slide` - Google Slides
- `google_sheet` - Google Sheets
PDF and Markdown files stored in Drive are synced too.
<Note>
Drive documents are converted to markdown before ingestion. This conversion is lossy — some formatting may not be preserved.
</Note>
@ -225,7 +223,7 @@ Drive documents are converted to markdown before ingestion. This conversion is l
console.log(`Provider: ${connector.provider}`)
console.log(`ID: ${connector.id}`)
console.log(`Account: ${connector.account}`)
console.log(`Created: ${connector.system.createdAt}`)
console.log(`Created: ${connector.createdAt}`)
console.log(`Document limit: ${connector.documentLimit}`)
console.log("---")
})
@ -240,7 +238,7 @@ Drive documents are converted to markdown before ingestion. This conversion is l
print(f"Provider: {connector.provider}")
print(f"ID: {connector.id}")
print(f"Account: {connector.account}")
print(f"Created: {connector.system.created_at}")
print(f"Created: {connector.created_at}")
print(f"Document limit: {connector.document_limit}")
print("---")
```
@ -265,7 +263,7 @@ Drive documents are converted to markdown before ingestion. This conversion is l
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "..." }
# }
# ],
# "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
# "pagination": { "currentPage": 1, "limit": 50, "totalItems": 1, "totalPages": 1 }
# }
```
</Tab>

View file

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

View file

@ -83,7 +83,7 @@ curl "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}?include=pi
```
<Note>
The picker link works once, in any browser. `picker` is `null` for providers that do not support selection. A Google Drive connector with full-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`.
The picker link works once, in any browser. `picker` is `null` for connectors that sync everything.
</Note>
## Set the Selection Directly
@ -121,7 +121,7 @@ connector = client.connectors.update(
)
print(f"Selection: {connector.selection}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Last sync: {connector.latest_run.system.status}")
```
```bash cURL
@ -173,11 +173,9 @@ The updated connector object, including the saved `selection` as `{ id, name }`
### Error Responses
- `400`: Connector does not support selection, the selection is empty or has unknown ids, or the connector needs to be reconnected
- `400`: Connector does not support selection
- `401`: Unauthorized
- `403`: The key lacks admin role or write access to the connector's namespaces
- `404`: Connector not found
- `409`: Connector is still waiting for the user to finish authorizing
<Note>
**Automatic Sync:** After a selection is saved, a sync starts for the connector. You don't need to call `connectors.sync` afterwards.
@ -195,11 +193,10 @@ const created = await supermemory.connectors.create("user-123", {
redirectUrl: "https://yourapp.com/callback"
})
// 2. Send the user to GitHub. After they authorize, Supermemory shows the hosted
// repository picker, then returns them to redirectUrl
// 2. Send the user to GitHub; the connector becomes visible once they authorize
if (created.authorization) window.location.href = created.authorization.url
// 3. To change repositories later, open the hosted picker again
// 3. After the callback, open the hosted picker so the user chooses repositories
const connector = await supermemory.connectors.get("user-123", created.id, {
include: "picker",
returnUrl: "https://yourapp.com/settings/integrations"
@ -218,15 +215,14 @@ created = client.connectors.create(
"user-123",
request={
"provider": "github",
"redirect_url": "https://yourapp.com/callback",
"redirectUrl": "https://yourapp.com/callback",
},
)
# 2. Send the user to GitHub. After they authorize, Supermemory shows the hosted
# repository picker, then returns them to redirect_url
# 2. Send the user to GitHub; the connector becomes visible once they authorize
print(f"Redirect to: {created.authorization.url}")
# 3. To change repositories later, open the hosted picker again
# 3. After the callback, open the hosted picker so the user chooses repositories
connector = client.connectors.get(
"user-123",
created.id,
@ -251,9 +247,9 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
-H "Content-Type: application/json" \
-d '{"provider": "github", "redirectUrl": "https://yourapp.com/callback"}'
# 2. ... (user authorizes at authorization.url and picks repositories in the hosted picker) ...
# 2. ... (user authorizes at authorization.url) ...
# 3. To change repositories later, get a one-time hosted picker URL
# 3. Get a one-time hosted picker URL
curl "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}?include=picker" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"

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

View file

@ -21,13 +21,13 @@ const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("user-123", {
provider: "onedrive",
redirectUrl: "https://yourapp.com/auth/onedrive/callback",
redirectUrl: "https://yourapp.com/auth/onedrive/callback"
documentLimit: 1500,
})
// Send the user to Microsoft to authorize
if (connector.authorization) window.location.href = connector.authorization.url
// Output: Redirects to https://api.supermemory.ai/v3/connections/auth/redirect?state=...
// Output: Redirects to https://login.microsoftonline.com/oauth2/authorize?...
```
```python Python
@ -40,14 +40,14 @@ connector = client.connectors.create(
"user-123",
request={
"provider": "onedrive",
"redirect_url": "https://yourapp.com/auth/onedrive/callback",
"document_limit": 1500,
"redirectUrl": "https://yourapp.com/auth/onedrive/callback",
"documentLimit": 1500,
},
)
# Send the user to Microsoft to authorize
print(f"Redirect to: {connector.authorization.url}")
# Output: Redirect to: https://api.supermemory.ai/v3/connections/auth/redirect?state=...
# Output: Redirect to: https://login.microsoftonline.com/oauth2/authorize?...
```
```bash cURL
@ -63,7 +63,7 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "url": "https://login.microsoftonline.com/oauth2/authorize?...",
# "expiresAt": "2024-01-15T11:30:00.000Z"
# }
# }
@ -73,7 +73,7 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
### 2. Handle Microsoft OAuth
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Microsoft redirects to your `redirectUrl` and the initial sync begins. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
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.
### 3. Monitor sync status
@ -96,7 +96,7 @@ Send the user to `authorization.url` before `authorization.expiresAt`. After the
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
print(f"Account: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Last sync: {connector.latest_run.system.status}")
print(f"Documents synced: {connector.document_count}")
# List synced Office documents in the namespace

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.
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.
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.
## Supported connectors
@ -69,7 +69,7 @@ const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("user-123", {
provider: "notion",
redirectUrl: "https://yourapp.com/callback",
redirectUrl: "https://yourapp.com/callback"
documentLimit: 5000,
})
@ -77,7 +77,7 @@ const connector = await supermemory.connectors.create("user-123", {
console.log("Connector:", connector.id)
console.log("Auth URL:", connector.authorization?.url)
console.log("Expires at:", connector.authorization?.expiresAt)
// Output: Auth URL: https://api.supermemory.ai/v3/connections/auth/redirect?state=...
// Output: Auth URL: https://api.notion.com/v1/oauth/authorize?...
```
```python Python
@ -90,8 +90,8 @@ connector = client.connectors.create(
"user-123",
request={
"provider": "notion",
"redirect_url": "https://yourapp.com/callback",
"document_limit": 5000,
"redirectUrl": "https://yourapp.com/callback",
"documentLimit": 5000,
},
)
@ -99,7 +99,7 @@ connector = client.connectors.create(
print(f"Connector: {connector.id}")
print(f"Auth URL: {connector.authorization.url}")
print(f"Expires at: {connector.authorization.expires_at}")
# Output: Auth URL: https://api.supermemory.ai/v3/connections/auth/redirect?state=...
# Output: Auth URL: https://api.notion.com/v1/oauth/authorize?...
```
```bash cURL
@ -115,7 +115,7 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "url": "https://api.notion.com/v1/oauth/authorize?...",
# "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
Send the user to `authorization.url` before `authorization.expiresAt`. When the user authorizes, the provider returns them to your `redirectUrl` and the first sync starts. GitHub, Gmail and Google Drive (with the default `syncScope: "selected"`) first send the user through Supermemory's hosted picker to choose what to sync. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
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.
Config providers (Web Crawler, S3, Granola) return `authorization: null` and start syncing immediately.
@ -167,9 +167,7 @@ connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=
print(f"Provider: {connector.provider}")
print(f"Account: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
if connector.latest_run and connector.latest_run.error:
print(connector.latest_run.error)
print(f"Last sync: {connector.latest_run.system.status} {connector.latest_run.error}")
print(f"Last synced at: {connector.system.last_successful_sync_at}")
print(f"Documents: {connector.document_count}")
print(f"Recent runs: {connector.syncs}")
@ -212,9 +210,9 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
### 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 `authorization: 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 `authUrl: null`
2. **User Authorization**: Send the user to `authorization.url` when the provider requires it
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
3. **Automatic Setup**: Connector established, sync begins immediately
4. **Continuous Sync**: Real-time updates via webhooks + scheduled sync every 4 hours (or scheduled recrawling for Web Crawler)
### Document processing pipeline
@ -303,7 +301,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" }
# }
# ],
# "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
# "pagination": { "currentPage": 1, "limit": 50, "totalItems": 1, "totalPages": 1 }
# }
# 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": {
"bucket": "my-documents-bucket",
"region": "us-east-1",
"access_key_id": os.environ["AWS_ACCESS_KEY_ID"],
"secret_access_key": os.environ["AWS_SECRET_ACCESS_KEY"],
"accessKeyId": os.environ["AWS_ACCESS_KEY_ID"],
"secretAccessKey": os.environ["AWS_SECRET_ACCESS_KEY"],
},
},
)
@ -270,9 +270,7 @@ Minimum required permissions:
## Error codes
Supermemory checks the credentials against the bucket before it creates the connector. If that check fails, `POST /ns/{namespace}/connectors` returns `400` with one of these messages. The code column is the status S3 returned.
| S3 status | Message | Solution |
| Code | Message | Solution |
|------|---------|----------|
| 401 | Authentication failed | Verify access key and secret |
| 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" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filter": {"field": "type", "operator": "eq", "value": "notion_doc"}}'
-d '{"filter": {"field": "source", "operator": "eq", "value": "notion"}}'
```
</CodeGroup>
@ -85,26 +85,30 @@ There is no per-connector document list in v5. `supermemory.list(namespace, "doc
### Connector Missing From List
**Problem:** `connectors.create` returned an `id`, but `connectors.list` does not show it
**Problem:** `connectors.create` returned an `id`, but `connectors.list` and `connectors.get` do not show it
**Solution:** A pending OAuth connector is not returned by list until the user finishes authorization. `connectors.get` still returns it with `system.status: "pending_authorization"`. Send the user to `authorization.url` before `authorization.expiresAt`. If the link expired, create the connector again.
**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.
### OAuth callback fails
**Problem:** "Invalid redirect URI" error after user grants permissions
**Solution:** The provider always redirects to Supermemory's callback, not to your `redirectUrl`. If you use a [custom OAuth application](/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:
**Solution:** Ensure your redirect URL matches EXACTLY what's configured in your OAuth app:
```typescript
// correct - exact match with OAuth app settings
const connector = await supermemory.connectors.create("user-123", {
provider: "notion",
redirectUrl: "https://yourapp.com/auth/notion/callback"
})
// Wrong - URL doesn't match
// redirectUrl: "https://yourapp.com/callback"
```
**Prevention:**
- Use HTTPS for production URLs
- Register `https://api.supermemory.ai/v3/connections/auth/callback/{provider}` in your custom OAuth app settings
- Copy the exact URL from your OAuth app settings
- Test the flow in development first
### Documents not syncing
@ -155,7 +159,7 @@ if (next.authorization) window.location.href = next.authorization.url
```typescript
const connector = await supermemory.connectors.create("user-123", {
provider: "onedrive",
redirectUrl: "https://yourapp.com/callback",
redirectUrl: "https://yourapp.com/callback"
documentLimit: 500, // Start with fewer documents
})
@ -243,9 +247,9 @@ const next = await supermemory.connectors.create("user-123", {
})
```
**Max Plan Required Error**
**Scale Plan Required Error**
Gmail connector requires Max Plan or above. If you see access errors:
Gmail connector requires Scale Plan or Enterprise Plan. If you see access errors:
- Verify your organization has the required plan
- Contact support to upgrade your plan

View file

@ -48,16 +48,16 @@ The web crawler connector requires a **Scale Plan** or **Enterprise Plan**.
request={
"provider": "web-crawler",
"config": {
"start_url": "https://docs.example.com",
"crawl_depth": 3,
"startUrl": "https://docs.example.com",
"crawlDepth": 3,
},
"document_limit": 5000,
"documentLimit": 5000,
},
)
# Web crawler doesn't require OAuth; crawling starts immediately
print(f"Connector ID: {connector.id}")
# Note: connector.authorization is None for web-crawler
# Note: connector.authorization.url is None for web-crawler
```
</Tab>
<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"])
print(f"Start URL: {(connector.config or {}).get('startUrl')}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Last sync: {connector.latest_run.system.status}")
print(f"Pages synced: {connector.document_count}")
# 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 => {
console.log(`Start URL: ${connector.config?.startUrl}`)
console.log(`Connector ID: ${connector.id}`)
console.log(`Created: ${connector.system.createdAt}`)
console.log(`Created: ${connector.createdAt}`)
})
```
</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:
print(f"Start URL: {(connector.config or {}).get('startUrl')}")
print(f"Connector ID: {connector.id}")
print(f"Created: {connector.system.created_at}")
print(f"Created: {connector.created_at}")
```
</Tab>
<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",
# "provider": "web-crawler",
# "namespace": "user-123",
# "createdAt": "2024-01-15T10:30:00.000Z",
# "documentLimit": 5000,
# "config": {"startUrl": "https://docs.example.com", "crawlDepth": 3},
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "..." }
# "config": {"startUrl": "https://docs.example.com", "crawlDepth": 3}
# }
# ],
# "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
# "pagination": { "currentPage": 1, "limit": 50, "totalItems": 1, "totalPages": 1 }
# }
```
</Tab>

View file

@ -216,7 +216,6 @@
"pages": [
"self-hosting/overview",
"self-hosting/quickstart",
"self-hosting/docker",
"self-hosting/configuration",
"self-hosting/embeddings",
"self-hosting/providers",
@ -978,11 +977,6 @@
"permanent": true,
"source": "/supermemory-mcp/technology"
},
{
"destination": "/supermemory-mcp/mcp",
"permanent": true,
"source": "/supermemory-mcp"
},
{
"destination": "/",
"permanent": true,
@ -1219,11 +1213,6 @@
"source": "/self-hosting/embed",
"destination": "/self-hosting/configuration"
},
{
"source": "/self-hosting",
"destination": "/self-hosting/overview",
"permanent": true
},
{
"source": "/concepts/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>
If an irrecoverable processing error occurs, the document's status becomes `failed`. Send the content again with the same `id` to reprocess it.
If an irrecoverable processing error occurs, the document is automatically deleted after 2 minutes.
</Warning>
---
@ -193,16 +193,16 @@ Scale and Enterprise include [advanced document extraction for PDFs](/concepts/c
## Parameters
`namespace` is the first argument on the SDK call and the URL path on the HTTP route. Every other parameter, including `taskType` and `dreaming`, goes in the request object (the JSON body over HTTP). The route accepts no query parameters; sending one returns `400`.
`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`.
| Parameter | Type | Description |
|-----------|------|-------------|
| `namespace` | string | **Required.** Group by user/project. Goes in the URL path. Required for user profiles |
| `content` | string | **Required.** Any raw content — text, conversations, URLs, HTML |
| `id` | string | **Recommended.** Your ID for the content (conversation ID, doc ID). Enables updates and deduplication. The response `id` echoes it back |
| `metadata` | object | Key-value pairs for filtering (strings, numbers, booleans, or arrays of strings) |
| `metadata` | object | Key-value pairs for filtering (strings, numbers, booleans) |
| `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#supporting-context) |
| `supportingContext` | string | Context for memory extraction on this namespace. Max 1500 chars. See [Customization](/concepts/customization#entity-context) |
| `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) |
| `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" }
```
**Namespaces** (first argument; letters, numbers, `-`, `_`, and `:`, up to 100 characters):
**Namespaces:**
```typescript
// By user
await supermemory.add("user_123", { content: "..." });
{ namespace: "user_123" }
// By project
await supermemory.add("project_alpha", { content: "..." });
{ namespace: "project_alpha" }
// Hierarchical
await supermemory.add("org_456_team_backend", { content: "..." });
{ namespace: "org_456_team_backend" }
```
**IDs (Recommended):**
@ -262,8 +262,8 @@ Scale and Enterprise include [advanced document extraction for PDFs](/concepts/c
}
}
```
- No nested objects
- Values: string, number, boolean, or an array of strings
- No nested objects or arrays
- Values: string, number, or boolean only
**Supporting Context:**
```typescript
@ -363,7 +363,7 @@ The metadata itself is still written to the document, but the memories will only
### How it works
When `group` is provided:
- **Profile memories** (static context) are filtered by the existing memories' metadata
- **Profile memories** (static context) are filtered to only those from documents matching the metadata
- **Similar memories** used as context during ingestion are filtered the same way
- The new document's own metadata is written normally — the filter only affects which **existing** memories are used as context
@ -371,7 +371,7 @@ When `group` is provided:
| Key | Type | Description |
|-----|------|-------------|
| `group` | `Record<string, string \| number \| boolean \| string[]>` | Key-value pairs to filter existing memories by their own metadata, rather than a live join to source-document metadata |
| `group` | `Record<string, string \| number \| boolean \| string[]>` | Key-value pairs to filter existing memories by their source document metadata |
- **Scalar values** (string, number, boolean) match exactly
- **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}`:
```typescript
const doc = await supermemory.documents.get("user_123", "abc123");
console.log(doc.system.status); // "queued" | "extracting" | "chunking" | "embedding" | "done" | "failed"
console.log(doc.system.status); // "queued" | "processing" | "done"
```
<AccordionGroup>
@ -444,19 +444,17 @@ console.log(doc.system.status); // "queued" | "extracting" | "chunking" | "embed
<Accordion title="Error handling">
| Status | Error | Cause |
|--------|-------|-------|
| 400 | Bad request | Missing required fields, invalid parameters, unexpected query parameters |
| 400 | Bad request | Missing required fields, invalid parameters |
| 401 | Unauthorized | Invalid or missing API key |
| 402 | Payment required | Insufficient balance or document quota |
| 403 | Forbidden | No write access to this namespace |
| 409 | Conflict | A namespace merge is in progress |
| 429 | Rate limited | Too many requests |
| 403 | Forbidden | Insufficient permissions |
| 429 | Rate limited | Too many requests or quota exceeded |
| 500 | Internal error | Processing failure |
```typescript
try {
await supermemory.add("user_123", { content: "..." });
} catch (error) {
// Back off on 429, top up on 402, fix the request on 400, retry later on 5xx
// Back off on 429, fix the request on 400, retry later on 5xx
console.error("add failed:", error);
}
```

View file

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

View file

@ -49,12 +49,9 @@ Retrieve paginated documents with filtering. One `list` call serves documents, c
"id": "doc_abc123",
"title": "Meeting notes",
"type": "text",
"summary": "Team sync covering the Q3 roadmap",
"metadata": { "source": "slack" },
"url": null,
"system": {
"status": "done",
"dreamingStatus": "done",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
@ -75,31 +72,16 @@ Every response contains `documents`, `chunks`, `memories`, and `pagination`. Onl
### Parameters
`namespace` and `type` are required. Over HTTP, pagination and sorting are query parameters and `filter` and `include` go in the JSON body; the SDK takes them all in one options object after `namespace` and `type`. Unknown query or body keys return `400`.
`namespace` and `type` are required. Pagination and sorting are query parameters (top-level keys in the SDK); `filter` goes in `body`.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `type` | string | required | `documents`, `chunks`, or `memories` |
| `limit` | number | 10 | Items per page (maximum 100) |
| `limit` | number | 10 | Items per page |
| `page` | number | 1 | Page number |
| `sort` | string | `createdAt` | `createdAt` or `updatedAt` for documents and memories; `createdAt` or `position` for chunks |
| `sort` | string | `createdAt` | Sort by `createdAt`, `updatedAt`, or `position` |
| `order` | string | `desc` | `desc` (newest) or `asc` (oldest) |
| `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">
```typescript
@ -150,7 +132,7 @@ Get a specific document with its processing status. Pass `include` to include it
include: ["chunks", "memories"], // optional
});
console.log(doc.system.status); // "queued" | "extracting" | ... | "done" | "failed"
console.log(doc.system.status); // "queued" | "processing" | "done" | "failed"
console.log(doc.content);
```
</Tab>
@ -162,7 +144,7 @@ Get a specific document with its processing status. Pass `include` to include it
include=["chunks", "memories"], # optional
)
print(doc.system.status) # "queued" | "extracting" | ... | "done" | "failed"
print(doc.system.status) # "queued" | "processing" | "done" | "failed"
print(doc.content)
```
</Tab>
@ -176,10 +158,6 @@ 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.
<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
| Status | Description |
@ -191,19 +169,6 @@ The current HTTP response also includes `system.dreamingStatus`. TypeScript SDK
| `done` | Ready for search |
| `failed` | Processing failed |
Treat any status other than `done` or `failed` as still in progress.
### Memory generation status
`system.dreamingStatus` tracks memory generation separately from `system.status`.
| Status | Description |
|--------|-------------|
| `dreaming` | Memories for the latest content are queued, being generated, or waiting for a retry |
| `done` | Memories are generated, or the document needed none |
With the default `dreaming: "dynamic"`, Supermemory batches related documents before forming memories, so `status` can be `done` minutes before `dreamingStatus` is. If your next step reads memories or profiles, wait for `dreamingStatus` to be `done` too.
<Accordion title="Poll for completion">
```typescript
async function waitForProcessing(namespace: string, id: string) {
@ -223,7 +188,7 @@ With the default `dreaming: "dynamic"`, Supermemory batches related documents be
## Update document
Update a document's content or metadata. **Content changes** trigger full reprocessing; **metadata-only changes** (e.g. updating `accepted`, `version`) do not reindex. The body accepts any non-empty subset of `content`, `metadata`, `supportingContext`, or `date`. Metadata merges key by key. `group` is an ingestion-side setting: it picks which existing memories serve as context while content is processed, so changing it only takes effect when the document is re-ingested. Send it together with `content`.
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`.
<Tabs>
<Tab title="TypeScript">
@ -312,7 +277,7 @@ Permanently remove documents. One call deletes 1 to 100 documents by Supermemory
</Tab>
</Tabs>
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.
Inspect both `count` and per-ID `errors`; HTTP success can include partial failures.
<Warning>
Deletes are permanent — no recovery.
@ -322,7 +287,7 @@ Deletes are permanent — no recovery.
## Processing queue
There is no separate processing endpoint. List documents and read `system.status` and `system.dreamingStatus` on each item in the HTTP response. The SDK examples below count documents still being indexed; use raw HTTP for memory-generation status with the SDK versions noted above.
There is no separate processing endpoint. List documents and read `system.status` on each item.
<Tabs>
<Tab title="TypeScript">

View file

@ -182,7 +182,7 @@ Paths are normalized for storage: `/memories/preferences` is stored as `--memori
```typescript
{
command: "rename",
old_path: "/memories/old-name.txt",
path: "/memories/old-name.txt",
new_path: "/memories/new-name.txt"
}
```

View file

@ -28,7 +28,8 @@ You can obtain an API key from [console.supermemory.ai](https://console.supermem
Supermemory integration is provided through the `SupermemoryPipecatService` class in Pipecat:
```python
from supermemory_pipecat import InputParams, SupermemoryPipecatService
from supermemory_pipecat import SupermemoryPipecatService
from supermemory_pipecat.service import InputParams
memory = SupermemoryPipecatService(
api_key=os.getenv("SUPERMEMORY_API_KEY"),
@ -156,7 +157,8 @@ from pipecat.transports.websocket.fastapi import (
FastAPIWebsocketTransport,
)
from supermemory_pipecat import InputParams, SupermemoryPipecatService
from supermemory_pipecat import SupermemoryPipecatService
from supermemory_pipecat.service import InputParams
app = FastAPI()

View file

@ -49,7 +49,7 @@ print("Migration complete!")
Mem0 provides two ways to export your memories:
### Option 1: Export via dashboard (recommended)
1. Log into your Mem0 dashboard (app.mem0.ai)
1. Log into your [Mem0 dashboard](https://app.mem0.ai)
2. Navigate to the export section
3. Download your memories as JSON

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
- **Error Monitoring**: Identify top error types and patterns
- **Detailed Logs**: Inspect stored request logs and compacted response data for debugging
- **Detailed Logs**: Access complete request/response logs for debugging
- **Performance Metrics**: Monitor average response times and processing duration
<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 |
| `to` | string (ISO 8601) | End date/time for the period |
| `period` | string | Alternative to `from`: `24h`, `7d`, `30d`, `all`. Use `from` and `to` for other windows |
| `period` | string | Alternative to `from`: `1h`, `24h`, `7d`, `30d` |
| `page` | integer | Page number for pagination (default: 1) |
| `limit` | integer | Items per page (default: 20, max: 100) |
@ -105,8 +105,7 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/usage?from=2024-01-01T00:00
"keyName": "Production API",
"count": 2341,
"avgDuration": 98.7,
"lastUsed": "2024-01-15T14:35:00Z",
"tokensUsed": 182340
"lastUsed": "2024-01-15T14:35:00Z"
}
],
"totalMemories": 45678,
@ -119,8 +118,6 @@ 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
Track and analyze errors to identify issues and improve reliability.
@ -151,7 +148,7 @@ const data = await errors.json();
```python Python
# Monitor errors and alert on spikes
response = requests.get(
'https://api.supermemory.ai/v3/analytics/errors?period=24h',
'https://api.supermemory.ai/v3/analytics/errors?period=1h',
headers={'Authorization': f'Bearer {SUPERMEMORY_API_KEY}'}
)
@ -170,53 +167,41 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/errors?period=7d" \
```json
{
"summary": [
"totalErrors": 234,
"errorRate": 0.023,
"topErrors": [
{
"type": "search_v4",
"totalRequests": 3421,
"successRate": 97.4,
"errorRate": 2.6,
"lastRequest": "2024-01-15T14:35:00Z"
}
],
"errors": [
{
"type": "search_v4",
"statusCode": 400,
"type": "ValidationError",
"count": 89,
"percentage": 100
}
],
"hourly": [
"statusCodes": [400],
"lastOccurred": "2024-01-15T14:30:00Z"
},
{
"hour": "2024-01-15T14:00:00Z",
"count": 156,
"errorCount": 4,
"errorRate": 2.56
"type": "RateLimitError",
"count": 45,
"statusCodes": [429],
"lastOccurred": "2024-01-15T13:15:00Z"
}
],
"byKey": [
"timeline": [
{
"keyId": "key_abc123",
"keyName": "Production API",
"count": 2341,
"errorCount": 61,
"errorRate": 2.61,
"lastUsed": "2024-01-15T14:35:00Z"
"time": "2024-01-15T14:00:00Z",
"count": 12,
"types": ["ValidationError", "NotFoundError"]
}
],
"pagination": {
"currentPage": 1,
"limit": 20,
"totalItems": 3421,
"totalPages": 172
"byStatusCode": {
"400": 89,
"404": 34,
"429": 45,
"500": 66
}
}
```
## Detailed logs
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.
Access complete request/response logs for debugging and auditing.
### Endpoint
@ -224,14 +209,18 @@ Inspect stored request logs for debugging. Response data is compacted: strings,
### Parameters
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.
Same as usage endpoint, plus optional filters:
- `type`: Filter by request type (add, search, update, delete)
- `statusCode`: Filter by HTTP status code
- `keyId`: Filter by specific API key
### Example request
<CodeGroup>
```typescript TypeScript
const logs = await fetch('https://api.supermemory.ai/v3/analytics/logs?period=24h', {
// Get recent failed requests
const logs = await fetch('https://api.supermemory.ai/v3/analytics/logs?period=1h&statusCode=500', {
headers: {
'Authorization': `Bearer ${SUPERMEMORY_API_KEY}`
}
@ -241,9 +230,11 @@ const data = await logs.json();
```
```python Python
# Debug specific API key usage
response = requests.get(
'https://api.supermemory.ai/v3/analytics/logs',
params={
'keyId': 'key_abc123',
'period': '24h'
},
headers={'Authorization': f'Bearer {SUPERMEMORY_API_KEY}'}
@ -255,7 +246,7 @@ logs = response.json()['logs']
```bash cURL
# Get all logs for debugging
curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=24h&limit=50" \
curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=1h&limit=50" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
```
@ -272,10 +263,13 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=24h&limit=50" \
"type": "search",
"statusCode": 200,
"duration": 89,
"input": "{\"q\":\"user query\",\"limit\":10}",
"input": {
"q": "user query",
"limit": 10
},
"output": {
"type": "search",
"response": { "results": [], "timing": 85, "total": 0 }
"results": 10,
"processingTime": 85
}
}
],
@ -288,10 +282,13 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=24h&limit=50" \
}
```
## Limits and retention
## 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.
Analytics endpoints have the following rate limits:
- 100 requests per minute per organization
- Maximum time range: 90 days
- Maximum page size: 100 items
<Warning>
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.
Analytics data is retained for 90 days. For longer retention, export and store the data in your own systems.
</Warning>

View file

@ -29,7 +29,7 @@ USD CREDIT BALANCE ──draw──► METERS
1. You hold a **USD credit balance** (`usd_credits`).
2. Product activity increments **meters** (tokens, queries, operations).
3. Each meter unit multiplies by a **USD-per-unit** rate and debits the balance.
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.
4. **Re-ingesting the same document under the same `id` only bills the net-new token delta** — already-seen content is effectively **fully discounted**.
---
@ -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_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_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 attributed to token or query meters: **instant dreaming**, and search add-ons such as query rewriting, reranking and aggregation | operations |
| `sm_search_queries` | Search queries | Each search / profile call that runs retrieval (v3 search, v4 search, v4 profile with search) | 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 |
### List rates (USD)
@ -73,8 +73,7 @@ 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:
- `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).
- 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 `POST /v4/memories/forget-matching` adds **one operation per search round**, up to five, including `dryRun` previews. Applying an explicit `ids` list bypasses that query-operation charge.
- Other non-token platform actions the product attributes to the operations meter (console: “counted platform operations not attributed to token or search meters”).
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.”
@ -82,18 +81,14 @@ Search is primarily metered as **`sm_search_queries`** (one unit per gated searc
| Call | Typical meter |
|---|---|
| `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()` | 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) |
| `POST /ns/{namespace}/search`, `supermemory.search()` | `sm_search_queries` (+1) |
| `POST /ns/{namespace}/profile`, `supermemory.profile()` when it runs retrieval | `sm_search_queries` (+1) |
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.
If the org is out of balance, search/profile can return **402** (payment required) depending on gate configuration.
---
<span id="full-discount-on-already-seen-tokens-diff-billing" />
## Reusing unchanged content (diff billing)
## Full discount on already-seen tokens (diff billing)
This is the most important cost control in production agents.
@ -101,13 +96,17 @@ 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:
1. Loads the document's active chunks and plans the updated content.
2. Reuses unchanged chunks where possible.
3. Meters newly appended text on an append path, or added replacement chunks on a replace path.
1. Loads the **previous extracted content** for that document
2. Computes **full** token count of the merged/updated document
3. Bills only:
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.
```text
billableTokens = max(0, fullTokenCount - previousTokenCount)
```
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.
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.
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
@ -115,7 +114,7 @@ Stable IDs let long-lived conversations and repeated document syncs reuse unchan
|---|---|
| **Stable `id`** | Identity for “this is the same document.” Max length 255. |
| **Same org / key** | Documents are org-scoped. |
| **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. |
| **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. |
### Practical patterns
@ -128,12 +127,12 @@ await supermemory.add(userId, {
});
```
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.
Connectors already use stable IDs (e.g. Drive file id, Gmail thread id, `s3://bucket/key`) so re-syncs diff-bill automatically.
### What is not free
- **First** ingest of content still bills full token count
- **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.
- **Search / profile** still bill query meters every call
- **Instant dreaming** still bills the **operation** surcharge when used
- **Brand-new `id`** = brand-new document = full tokens
@ -192,7 +191,6 @@ Usage meters are shared; **feature gates** differ by tier.
| Gmail connector | — | — | Yes | Yes | Yes |
| GitHub, S3, Web Crawler connectors | — | — | — | Yes | Yes |
| User Insights | — | — | — | Yes | Yes |
| Two-factor authentication | — | — | — | Yes | Yes |
| Org seat limit (members) | 1 | 3 | 3 | Unlimited | Unlimited |
| Auto top-up in the console | — | Yes | Yes | Yes | Contract |
| Auto top-up purchase limits | — | Yes | Yes | Yes | Contract |
@ -213,7 +211,7 @@ Overrides can be applied per org in metadata (`featureOverrides`) for enterprise
## Programmatic access
**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**.
All of the following require an **org-admin capable** credential. **Scoped API keys cannot read billing** (403).
Base: `https://api.supermemory.ai`
Auth: `Authorization: Bearer YOUR_API_KEY`
@ -236,7 +234,7 @@ Typical fields:
"tokens": { "used": 120000, "limit": 0 },
"queries": { "used": 4200, "limit": 0 }
},
"credits": { "balance": 14.62, "label": "USD" },
"credits": {},
"resetDate": "2026-08-01T00:00:00.000Z",
"orgName": "Acme",
"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
- **Managed cloud** — default multi-tenant SaaS.
- **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).
- **Self-hosted binary** — full engine on your machine or VPC; embeddings and storage stay where you run it. 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).
## Privacy operations you should design for
@ -51,10 +51,10 @@ Supermemory is infrastructure for *your* agents. Your customer content is never
The practical GDPR-style path for app builders:
1. Scope each end-user (or tenant) to a **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.
2. When the user requests deletion, delete that namespace’s content via the API / console workflows for documents and memories under that namespace.
3. Revoke any **scoped keys** issued for that user.
Designing isolation up front still makes identifying the user's content easier.
Designing isolation up front makes erasure a single boundary operation instead of a forensic search.
### Connectors and third-party sources

View file

@ -97,14 +97,13 @@ print(conv.id, conv.status) # e.g. "queued"
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document" \
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document?dreaming=instant" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "user: Just got back from Tokyo — the team offsite went great.\nassistant: Glad it went well! Anything stand out?\nuser: Sarah presented the Q3 roadmap at the offsite.\nassistant: Sounds like a big moment for her.\nuser: She is being promoted to VP of Product.\nassistant: Congrats to Sarah — that is huge.\nuser: I need a gift idea for my VP of Product.\nassistant: Happy to help brainstorm something personal.",
"id": "chat_offsite_2026",
"metadata": { "type": "conversation" },
"dreaming": "instant"
"metadata": { "type": "conversation" }
}'
```
</CodeGroup>
@ -165,14 +164,13 @@ print(doc.id, doc.status)
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document" \
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document?taskType=superrag" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "# Team notes — gifts & recognition\n\nWhen someone is promoted to VP or above, the company recommends a thoughtful gift in the $75–$150 range. Experiences tied to recent team milestones land better than generic swag.\n\nFor product leadership, books on platform strategy or a dinner near the last offsite city are common picks. Tokyo offsites often inspire travel-themed gifts.",
"id": "doc_gift_policy",
"metadata": { "type": "document", "source": "handbook" },
"taskType": "superrag"
"metadata": { "type": "document", "source": "handbook" }
}'
```
</CodeGroup>
@ -221,7 +219,7 @@ curl "https://api.supermemory.ai/ns/user_4f8a/document/DOC_ID" \
```
</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. `system.dreamingStatus` turns `done` once a document's memories are ready.
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.
## 4. Three ways to get context back
@ -255,14 +253,10 @@ for hit in rag.results:
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search" \
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search?searchMode=chunks&limit=3" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "gift ideas for a VP promotion after a Tokyo offsite",
"searchMode": "chunks",
"limit": 3
}'
-d '{ "query": "gift ideas for a VP promotion after a Tokyo offsite" }'
```
</CodeGroup>
@ -306,14 +300,12 @@ print(memories.to_json())
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search" \
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search?searchMode=memories&limit=5" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "What gift should I get, and why?",
"searchMode": "memories",
"include": { "related": true },
"limit": 5
"include": { "related": true }
}'
```
</CodeGroup>

View file

@ -29,14 +29,13 @@ v5 has no direct memory-create route. Ingest a document instead and Supermemory
</Tab>
<Tab title="cURL">
```bash
curl -X POST "https://api.supermemory.ai/ns/user_123/document" \
curl -X POST "https://api.supermemory.ai/ns/user_123/document?dreaming=instant" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "John prefers dark mode.\nJohn is from Seattle.",
"id": "prefs_john",
"metadata": { "source": "user_preference" },
"dreaming": "instant"
"metadata": { "source": "user_preference" }
}'
```
</Tab>
@ -89,7 +88,7 @@ Soft-delete memories by ID. Forgotten memories are excluded from search results
|-------|------|-------------|
| `count` | number | Number of memories forgotten; always equals `matches.length` |
| `matches` | array | The memories that were forgotten (`{ id, memory }`) |
| `errors` | array | IDs that were missing, already forgotten, or in another namespace (`{ id, error }`) |
| `errors` | array | IDs that were missing or not eligible (`{ id, error }`) |
HTTP success does not imply every requested ID changed. Check `errors`.
@ -100,7 +99,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".
<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. 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.
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.
</Warning>
<Tabs>
@ -145,7 +144,7 @@ This is a bulk, destructive operation. `dryRun` is required. Call with **`dryRun
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `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"). 1 to 2000 characters |
| `query` | string | yes | What to forget — a natural-language instruction ("forget everything about Project Titan") or a bare topic ("Project Titan") |
| `dryRun` | boolean | yes | `true` returns what *would* be forgotten without changing anything. `false` forgets the memories selected when that request runs |
### Response
@ -162,7 +161,7 @@ Both dry-run and applied requests return the same shape as [Forget Memories](#fo
}
```
`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.
The payload does not say which mode was sent, so keep your own record of `dryRun` next to audit logs.
<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.

View file

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

View file

@ -12,7 +12,7 @@ Search through your memories and documents with a single API call. Each search r
</Tip>
<Note>
**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.
**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.
</Note>
## Quick start
@ -54,10 +54,10 @@ Search through your memories and documents with a single API call. Each search r
</Tab>
<Tab title="cURL">
```bash
curl -X POST "https://api.supermemory.ai/ns/user_123/search" \
curl -X POST "https://api.supermemory.ai/ns/user_123/search?searchMode=hybrid&limit=5" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "query": "machine learning", "searchMode": "hybrid", "limit": 5 }'
-d '{ "query": "machine learning" }'
```
</Tab>
</Tabs>
@ -72,7 +72,6 @@ Search through your memories and documents with a single API call. Each search r
"similarity": 0.91,
"metadata": { "topic": "interests" },
"isLatest": true,
"isInference": false,
"system": { "createdAt": "2024-01-15T10:30:00.000Z", "updatedAt": "2024-01-15T10:30:00.000Z" }
},
{
@ -81,7 +80,6 @@ Search through your memories and documents with a single API call. Each search r
"similarity": 0.87,
"metadata": { "source": "onboarding_doc" },
"isLatest": true,
"isInference": false,
"system": { "createdAt": "2024-01-14T09:15:00.000Z", "updatedAt": "2024-01-14T09:15:00.000Z" }
}
],
@ -97,7 +95,7 @@ In hybrid mode, results contain either a `memory` field (extracted facts) or a `
## Parameters
`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`.
`namespace` is the URL path. Every other option goes in the JSON body (the second argument in the SDKs).
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
@ -106,10 +104,10 @@ In hybrid mode, results contain either a `memory` field (extracted facts) or a `
| `searchMode` | string | `"hybrid"` | `"hybrid"` (recommended), `"memories"`, or `"chunks"` |
| `limit` | number | 10 | Max results (max 100) |
| `threshold` | 0-1 | 0.3 | Similarity cutoff (higher = fewer, better results) |
| `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. Adds latency and can bill one extra [operation](/overview/billing#what-is-an-operation) when rewriting runs successfully |
| `rerank` | string | `"none"` | `"none"`, `"order"` (re-score for relevance, +100ms), or `"aggregate"` |
| `rewriteQuery` | boolean | false | Generate multiple rewrites, search all of them, and merge results. No extra cost, but adds latency |
| `filter` | object | — | Metadata filter expression. See [Filtering](#filtering) |
| `include` | object | all `false` | `{ documents, related, forgotten }` — opt in to extra context per result |
| `include` | object | — | `{ documents, related, forgotten }` — opt in to extra context per result |
<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.
@ -198,9 +196,9 @@ await supermemory.search("user_123", { query: "...", threshold: 0.8 });
### Attachments
- `include.documents` adds the most recently updated source document to each result when one is available.
- `include.related` adds parent, child, and sibling memories (see [graph memory](/concepts/graph-memory)).
- `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.
- `attach.documents` adds the most relevant source document to each result.
- `attach.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.
```typescript
await supermemory.search("user_123", {
@ -240,11 +238,10 @@ async function getContext(userId: string, message: string) {
memory?: string; // Present for memory results
chunk?: string; // Present for document chunk results
similarity: number; // 0-1
metadata: Record<string, unknown>; // {} when none
metadata: object | null;
isLatest: boolean;
isInference: boolean; // false for chunks
system: { createdAt?: string; updatedAt: string };
included?: { // Only when you pass include
system: { createdAt: string; updatedAt: string };
included?: { // Only when you pass attach
document?: object;
related?: { parents: object[]; children: object[]; siblings: object[] };
};

View file

@ -62,18 +62,16 @@ Profiles are built automatically as you [ingest content](/ingestion/add-memories
{ "id": "mem_3", "memory": "User prefers dark mode interfaces" }
],
"dynamic": [
{ "id": "mem_4", "memory": "[2026-10-02] User is working on Project Alpha" },
{ "id": "mem_5", "memory": "[2026-10-05] User recently started learning Rust" },
{ "id": "mem_6", "memory": "[2026-10-09] User is debugging authentication issues" }
{ "id": "mem_4", "memory": "User is working on Project Alpha" },
{ "id": "mem_5", "memory": "User recently started learning Rust" },
{ "id": "mem_6", "memory": "User is debugging authentication issues" }
],
"buckets": {
"preferences": []
}
"buckets": {}
}
}
```
`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.
`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.
---
@ -125,7 +123,7 @@ The v5 profile call takes no query. When you also need query-ranked memories, ru
## Parameters
`namespace` is the first argument (URL path). The options object is optional and accepts only `filter` and `buckets`.
`namespace` is a top-level key (URL path). `body` is optional and accepts only `filter` and `buckets`.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|

View file

@ -7,19 +7,17 @@ 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 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.
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.
## Core
| Variable | Purpose | Default |
|---|---|---|
| `PORT` (or `SUPERMEMORY_PORT`) | HTTP listen port | `6767` |
| `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_HOST` | In the next release, controls the listen address; not supported by v0.0.8 | `127.0.0.1` in the next release |
| `SUPERMEMORY_DATA_DIR` | Where the graph engine's data, auth secret, and model cache live | `./.supermemory` |
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.**
**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.
## LLM providers
@ -30,20 +28,18 @@ In production, Supermemory uses its own proprietary models tuned for long-horizo
| `OPENAI_API_KEY` | OpenAI — or any OpenAI-compatible endpoint, see below |
| `ANTHROPIC_API_KEY` | Anthropic |
| `GEMINI_API_KEY` | Google AI Studio (Gemini) |
| `GOOGLE_VERTEX_PROJECT_ID` + `GOOGLE_VERTEX_LOCATION` | GCP Vertex AI |
| `GROQ_API_KEY` | Groq |
| `WORKERS_AI_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` | Cloudflare Workers AI |
| `GOOGLE_VERTEX_PROJECT_ID` + `GOOGLE_VERTEX_LOCATION` | GCP Vertex AI |
<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.
</Tip>
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.
With multiple providers configured, the first one in the order above is used.
<Note>
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.
Image, video, and high-fidelity PDF understanding require a Gemini or Vertex AI key. Text ingestion, memory extraction, and search work with any provider.
</Note>
### Fully offline with local models
@ -64,26 +60,22 @@ OPENAI_MODEL=gpt-oss:20b
| `OPENAI_FAST_MODEL` | Override for fast/light tasks | `OPENAI_MODEL` |
| `OPENAI_TEXT_MODEL` | Override for heavier text tasks | `OPENAI_MODEL` |
## 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.
## File storage
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
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 `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).
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).
| 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
@ -140,7 +132,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
- **Managed scale** — globally distributed infrastructure, no capacity planning
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.
Any other environment variables you may find referenced in the codebase are platform-only: the self-hosted binary ignores them even when set.
## Example: production-ish `.env`

View file

@ -1,109 +0,0 @@
---
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"
---
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.
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.
## 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).
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`, `~/.supermemory` by default from v0.0.9, `./.supermemory` before).
3. Choices are saved encrypted under your data directory (`$SUPERMEMORY_DATA_DIR`, typically `./.supermemory` / `~/.supermemory`).
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,11 +42,10 @@ For Docker, CI, or any non-interactive deploy, set env vars — there is **no in
| Variable | Purpose | Default |
|---|---|---|
| `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 (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'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 (`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 |
| `SUPERMEMORY_EMBEDDING_PROVIDER` | Embedding backend: `local`, `openai`, `gemini`, or an OpenAI-compatible remote (`ollama` / custom base URL) | `local` |
| `SUPERMEMORY_EMBEDDING_MODEL` | Model id for the chosen provider | `Xenova/bge-base-en-v1.5` (local) |
| `SUPERMEMORY_EMBEDDING_DIMENSIONS` | Vector size; must match the model and any already-stored data | `768` (local default) |
| `SUPERMEMORY_EMBEDDING_BASE_URL` | Base URL for OpenAI-compatible embedding APIs (Ollama, vLLM, etc.) | 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 |
@ -92,22 +91,6 @@ SUPERMEMORY_EMBEDDING_MODEL=Xenova/bge-base-en-v1.5
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
```bash
@ -122,16 +105,16 @@ SUPERMEMORY_EMBEDDING_DIMENSIONS=1536
```bash
GEMINI_API_KEY=...
SUPERMEMORY_EMBEDDING_PROVIDER=gemini
SUPERMEMORY_EMBEDDING_MODEL=gemini-embedding-001
SUPERMEMORY_EMBEDDING_MODEL=text-embedding-004
SUPERMEMORY_EMBEDDING_DIMENSIONS=768
```
### Ollama (OpenAI-compatible)
```bash
SUPERMEMORY_EMBEDDING_PROVIDER=openai-compatible
SUPERMEMORY_EMBEDDING_PROVIDER=openai
SUPERMEMORY_EMBEDDING_BASE_URL=http://localhost:11434/v1
SUPERMEMORY_EMBEDDING_API_KEY=ollama
OPENAI_API_KEY=ollama
SUPERMEMORY_EMBEDDING_MODEL=nomic-embed-text
SUPERMEMORY_EMBEDDING_DIMENSIONS=768
```
@ -144,22 +127,15 @@ 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**.
</Warning>
<Note>
**Embedding env vars are ignored in v0.0.6 and v0.0.7-rc.2**
**Changing embeddings later:** Not supported in place. Start from a fresh data directory or re-ingest all content so vectors stay comparable.
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.
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.
</Note>
<Note>
**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>
> [!IMPORTANT]
> **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.
## Related

View file

@ -5,7 +5,7 @@ description: "Supermemory local is for builders. Supermemory Enterprise is for o
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 when the LLM is also local; webpage ingestion goes through Firecrawl and needs `FIRECRAWL_API_KEY`.
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 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
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.
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.
<CardGroup cols={2}>
<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:
- **The Supermemory graph engine, embedded** — created automatically on first boot. No database to stand up, no connection strings.
- **Built-in local embeddings** — default `Xenova/bge-base-en-v1.5` (768d) on your machine, no API key. Opt into OpenAI, Gemini, or Ollama instead — see [Embeddings](/self-hosting/embeddings).
- **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).
- **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.
@ -41,7 +41,7 @@ OPENAI_MODEL=gpt-oss:20b \
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`. 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.
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.
## Drop-in with your existing code
@ -68,14 +68,14 @@ Self-hosted is free within its lite license limit and useful for local developme
|---|---|---|
| Full Memory API | ✅ | ✅ |
| Hybrid semantic search | ✅ | ✅ |
| Embeddings | Local default (or OpenAI / Gemini / Ollama) | Managed |
| File ingestion (PDFs, images) | ✅ (PDFs need `GEMINI_API_KEY`; images use Gemini or Vertex AI) | ✅ |
| Embeddings | Local default (or OpenAI / Gemini / Ollama) | Same provider stack, managed |
| File ingestion (PDFs, images) | ✅ | ✅ |
| [Connectors](/connectors/overview) (Google Drive, Notion, Gmail, OneDrive) | — | ✅ |
| [Supermemory MCP](/supermemory-mcp/mcp) | — | ✅ |
| Memory extraction | Your model, your key | Proprietary long-horizon models — higher quality, cheaper at scale |
| Infrastructure | Your machine | Globally distributed, scales with you |
If you outgrow a single machine — or want connectors, MCP, and the best-tuned extraction pipeline — switch to [the platform](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).
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).
## 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=...
```
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)).
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)).
</Tab>
<Tab title="OpenRouter">

View file

@ -58,18 +58,12 @@ supermemory-server
First boot sets everything up — the embedded Supermemory graph engine, local embeddings, and your credentials:
```
╭───────────────────────────────────────────────────────────────╮
│ → supermemory ready │
│ │
│ url http://localhost:6767 │
│ database encrypted local storage (~/.supermemory) │
│ embeddings local · Xenova/bge-base-en-v1.5 · 768d │
│ workflow rivet │
│ api key sm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx │
│ org id xxxxxxxxxxxxxxxxxxxxxx │
│ boot 4.2s │
╰───────────────────────────────────────────────────────────────╯
every request needs this key: Authorization: Bearer <api key>
┌──────────────────────────────────────────────────┐
│ url http://localhost:6767 │
│ database ./.supermemory │
│ api key sm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx │
│ org id xxxxxxxxxxxxxxxxxxxxxx │
└──────────────────────────────────────────────────┘
```
Save that API key — it's your bearer token for every request.
@ -82,8 +76,6 @@ 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.
</Tip>
For the prebuilt Docker image, follow [Run Supermemory with Docker](/self-hosting/docker) for installation, authentication, backups, and upgrades.
## Add your first memory
<Note>
@ -124,12 +116,11 @@ client.add(
</Tab>
<Tab title="curl">
```bash
curl http://localhost:6767/ns/user_dhravya/document \
curl "http://localhost:6767/ns/user_dhravya/document?dreaming=instant" \
-H "Authorization: Bearer sm_..." \
-H "Content-Type: application/json" \
-d '{
"content": "I am Dhravya. I love building dev tools and I am allergic to peanuts.",
"dreaming": "instant"
"content": "I am Dhravya. I love building dev tools and I am allergic to peanuts."
}'
```
</Tab>
@ -170,9 +161,8 @@ By default, all state lives in a single directory you can back up or move:
| Path | Contents |
|---|---|
| `~/.supermemory/` (or `$SUPERMEMORY_DATA_DIR`; `./.supermemory/` before v0.0.9) | The Supermemory graph engine's data, API key, auth secret, embedding model cache |
| `~/.supermemory/env.enc` | Provider keys and embedding choices saved by the first-boot wizard, encrypted and loaded on every launch |
| `~/.supermemory/env` | Plaintext API keys written by the installer. On launch, the server copies parsed values into `env.enc` before attempting to remove this file; it can remain if migration or removal fails |
| `./.supermemory/` (or `$SUPERMEMORY_DATA_DIR`) | The Supermemory graph engine's data, auth secret, embedding model cache |
| `~/.supermemory/env` | API keys saved by the installer, loaded on every launch |
## Next steps

View file

@ -16,13 +16,11 @@ 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`:
```json
{"system":{"status":"done","dreamingStatus":"dreaming","createdAt":"...","updatedAt":"..."}}
{"system":{"status":"done","createdAt":"...","updatedAt":"..."}}
```
`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
```bash v5
@ -31,7 +29,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`.
- `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=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=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
@ -48,11 +46,9 @@ POST /ns/{namespace}/list/{type}?page=1&limit=100&sort=createdAt&order=desc
```
</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 `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`.
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`.
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.
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.
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.
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`.
@ -70,7 +66,7 @@ DELETE /ns/{namespace}/document
```
</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. 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`.
The v5 array accepts 1–100 Supermemory or caller-defined IDs. Inspect both `count` and per-ID `errors`; HTTP success can include partial failures.
### With the SDK
@ -102,7 +98,7 @@ const { count, errors } = await supermemory.documents.delete("user_1", {
```
</CodeGroup>
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"]`.
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"]`.
### Forget memories
@ -118,7 +114,6 @@ See [memory forgetting](/migration/api-v5-memory-forgetting) for the complete dr
### Verification
- 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.
- Verify chunk rows contain their parent `documentId`.
- 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 |
| Replace text or URL content | `PATCH /ns/{namespace}/document/{id}` | Replace and reprocess |
| Update only metadata, context, or date | Same `PATCH` without `content` | Canonical content unchanged |
| Update only supporting fields | 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 |
| 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>
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`. 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`, `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.
### Replace a file-backed document
@ -37,7 +37,7 @@ metadata={"revision":2}
dreaming=dynamic
```
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.
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.
### Partially update a file-backed document
@ -49,7 +49,7 @@ metadata={"reviewed":true}
dreaming=dynamic
```
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`. `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-, group-, context-, or date-only changes. `metadata` and `group` are JSON-encoded strings; `supportingContext` and `date` are plain strings.
<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.
@ -90,7 +90,7 @@ The path `id` may be the Supermemory document ID or your caller-defined ID. It i
### Processing and conflicts
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.
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.
### Verification

View file

@ -83,7 +83,7 @@ await supermemory.search("user_1", {
```
</CodeGroup>
`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.
`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.
### Deterministic conversion

View file

@ -16,7 +16,7 @@ Content-Type: application/json
{"ids":["mem_1","mem_2"]}
```
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.
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.
### Preview a semantic request
@ -27,7 +27,7 @@ Content-Type: application/json
{"query":"outdated home address","dryRun":true}
```
`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.
`dryRun: true` performs selection without changing memory state. `dryRun: false` forgets the memories selected when that request executes.
### 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`. 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.
`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.
### With the SDK

View file

@ -69,6 +69,6 @@ await supermemory.organization.update({
- Compare the v5 context with the legacy `filterPrompt` before cutover.
- Set, replace, and clear organizational context.
- 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.
- Confirm `namespaceCount` agrees with `pagination.totalItems` from `GET /namespaces` for the same credentials.
- Verify non-admin callers receive `403` on PATCH.
- 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.
**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.
**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.
## Recommended migration process
@ -55,7 +55,7 @@ await supermemory.add("user_1", { content: "new turn" })
```
</CodeGroup>
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")`.
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.
## Endpoint map

View file

@ -5,7 +5,7 @@ v5 returns a maintained profile directly and gives profile bucket definitions th
<CodeGroup>
```bash Legacy
POST /v4/profile
{"containerTag":"user_1","q":"work preferences","threshold":0.6,"include":["static","dynamic","buckets"]}
{"containerTag":"user_1","q":"work preferences","threshold":0.6,"include":{}}
```
```bash v5
@ -22,18 +22,13 @@ Remove legacy `q`, `threshold`, and `include`. If the caller needs query-ranked
{
"profile": {
"static": [{ "id": "mem_1", "memory": "The user works in design" }],
"dynamic": [{ "id": "mem_2", "memory": "[2026-10-01] The user is preparing a launch" }],
"buckets": {
"preferences": [],
"work": [{ "id": "mem_3", "memory": "Prefers concise project updates" }]
}
"dynamic": [{ "id": "mem_2", "memory": "The user is preparing a launch" }],
"buckets": { "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 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.
`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.
### Read bucket definitions
@ -70,7 +65,7 @@ DELETE /ns/user_1/profile/buckets
{"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 — naming one returns `409`. The built-in `preferences` bucket always stays in the effective set; DELETE only removes namespace-owned buckets.
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.
### With the SDK
@ -97,7 +92,7 @@ await supermemory.profiles.deleteBuckets("user_1", {
```
</CodeGroup>
`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 }`.
`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 }`.
### Verification

View file

@ -1,4 +1,4 @@
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.
Check which client you call the API through before you translate requests. Both official SDKs speak v5; the integration packages do not yet.
| Client | v5 support | What to do |
| --- | --- | --- |
@ -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 })`. |
| `documents.search(...)` | `client.search(namespace, { query, searchMode: "chunks" })` |
| `documents.chunks(id)` | `client.documents.get(namespace, id, { include: ["chunks"] })` |
| `documents.listProcessing()` | `client.list(namespace, "documents")` and read `system.status` and `system.dreamingStatus` on each item |
| `documents.listProcessing()` | `client.list(namespace, "documents")` and read `system.status` on each item |
| `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 })` |

View file

@ -1781,97 +1781,6 @@ a[class*="-ml-10"] {
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/"],
.sm-scene .m-ic.is-on img[src*="/icons/hugeicons/"],
.sm-scene .m-badge img[src*="/icons/hugeicons/"] {

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.
</Tip>
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.
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.
---
@ -50,11 +50,11 @@ Every org starts with a built-in `preferences` bucket that is always present —
"dynamic": [],
"buckets": {
"preferences": [
{ "id": "mem_2", "memory": "Switched their editor to Zed" },
{ "id": "aggregated_0_1760000000000", "memory": "Tooling and style: Prefers concise, technical answers and dark-mode tooling" }
{ "id": "mem_1", "memory": "[Summary] Prefers concise, technical answers and dark-mode tooling" },
{ "id": "mem_2", "memory": "[Recent] Switched their editor to Zed" }
],
"goals": [
{ "id": "mem_3", "memory": "Wants to ship the billing revamp this quarter" }
{ "id": "mem_3", "memory": "[Recent] Wants to ship the billing revamp this quarter" }
]
}
}
@ -62,7 +62,7 @@ Every org starts with a built-in `preferences` bucket that is always present —
```
<Note>
**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.
**`[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.
</Note>
### List bucket definitions
@ -106,7 +106,7 @@ Bucket definitions live at two levels: **organization** (the default set every n
### 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. 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.
`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.
<Tabs>
<Tab title="TypeScript">
@ -179,7 +179,7 @@ If you'd rather start from a template than write descriptions from scratch, thes
### Default bucket
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.
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.
---
@ -203,7 +203,7 @@ Bucket descriptions steer classification. A precise description ("Explicit first
### 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#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.
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.
<Tabs>
<Tab title="TypeScript">

View file

@ -13,7 +13,7 @@ icon: "/icons/hugeicons/book-open-01.svg"
| `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 |
| `DELETE /ns/{namespace}/connectors/{id}` | Disconnect, optionally deleting the documents it imported |
| `GET /connectors` | List connectors across every namespace this key can read |
| `GET /connectors` | List every connector in the organization, across namespaces |
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
const crawler = await supermemory.connectors.create("user_123", {
provider: "web-crawler",
config: { startUrl: "https://docs.example.com" },
config: { startUrl: "https://docs.example.com" }
documentLimit: 200,
})
@ -48,7 +48,7 @@ curl -X POST "https://api.supermemory.ai/ns/user_123/connectors" \
</CodeGroup>
<Note>
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`).
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`).
</Note>
See the [connector guides](/connectors/overview) for per-provider setup, selection rules, and troubleshooting.

View file

@ -41,6 +41,4 @@ curl -X POST "https://api.supermemory.ai/ns/user_1/list/documents?page=1&limit=5
```
</CodeGroup>
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).
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).

View file

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

View file

@ -36,7 +36,7 @@ import { Supermemory } from "supermemory"
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
```
URL values are positional arguments (`namespace` first, then `id` where there is one), and everything else goes in one object: `supermemory.add("user_123", { content })`. Each reference page below shows the SDK call next to the equivalent `curl`. The Python SDK (`pip install supermemory`, 5.0.0 and later) follows the same shape with keyword arguments: `client.add("user_123", content="...")`.
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.
<CardGroup cols={2}>
<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.
See [profile migration](/migration/api-v5-profiles).
See [profile migration](/migration/api-v5-recall).

View file

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

View file

@ -83,21 +83,16 @@ describe("MCP authentication", () => {
})
it("rejects a token issued for a different audience", async () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
vi.spyOn(console, "error").mockImplementation(() => {})
const token = await signToken({ audience: "https://api.example.com" })
await expect(
validateOAuthToken(token, API_URL, MCP_RESOURCE, keySet),
).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 () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
vi.spyOn(console, "error").mockImplementation(() => {})
const expired = await signToken({ expiresIn: "-1s" })
const noSubject = await signToken({ subject: "" })
@ -107,34 +102,10 @@ describe("MCP authentication", () => {
await expect(
validateOAuthToken(noSubject, API_URL, MCP_RESOURCE, keySet),
).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 () => {
vi.spyOn(console, "warn").mockImplementation(() => {})
vi.spyOn(console, "error").mockImplementation(() => {})
const fetchSpy = vi.fn()
vi.stubGlobal("fetch", fetchSpy)
@ -177,7 +148,7 @@ describe("MCP authentication", () => {
scopes: [],
})
expect(fetchSpy).toHaveBeenCalledWith(
`${API_URL}/session`,
`${API_URL}/v3/session`,
expect.objectContaining({
headers: { Authorization: `Bearer ${key}` },
}),
@ -195,23 +166,15 @@ describe("MCP authentication", () => {
})
it("rejects an API key the session endpoint refuses", async () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response(null, { status: 401 }))
vi.stubGlobal("fetch", fetchSpy)
vi.spyOn(console, "error").mockImplementation(() => {})
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(new Response(null, { status: 401 })),
)
await expect(
validateApiKey("sm_revoked_key_0123456789abcdef", API_URL),
).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 () => {
@ -224,8 +187,7 @@ describe("MCP authentication", () => {
})
it("surfaces a 500 from the session endpoint as TransientAuthError, not invalid token", async () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
vi.spyOn(console, "error").mockImplementation(() => {})
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(new Response(null, { status: 500 })),
@ -234,16 +196,10 @@ describe("MCP authentication", () => {
await expect(
validateApiKey("sm_outage_key_0123456789abcdef", API_URL),
).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 () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
vi.spyOn(console, "error").mockImplementation(() => {})
vi.stubGlobal(
"fetch",
vi.fn().mockRejectedValue(
@ -256,7 +212,5 @@ describe("MCP authentication", () => {
await expect(
validateApiKey("sm_timeout_key_0123456789abcd", API_URL),
).rejects.toThrow(TransientAuthError)
expect(error).toHaveBeenCalledTimes(1)
expect(warn).not.toHaveBeenCalled()
})
})

View file

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

View file

@ -57,8 +57,8 @@ export type { MemoryEntry, MemoryEntryHistory }
export type MemoryEntriesResponse = MemoriesList
export type Memory =
| { id: string; memory: string; similarity: number; documentId?: string }
| { id: string; chunk: string; similarity: number; documentId?: string }
| { id: string; memory: string; similarity: number }
| { id: string; chunk: string; similarity: number }
export interface SearchResult {
results: Memory[]
@ -85,20 +85,17 @@ function limitByChars(text: string, maxChars = MAX_CHARS): string {
function mapSearchResults(results: SearchResponseResultsItem[]): Memory[] {
return results.map((result) => {
const documentId = result.included?.document?.id
if (result.chunk && !result.memory) {
return {
id: result.id,
similarity: result.similarity,
chunk: limitByChars(result.chunk),
...(documentId && { documentId }),
}
}
return {
id: result.id,
similarity: result.similarity,
memory: limitByChars(result.memory ?? ""),
...(documentId && { documentId }),
}
})
}
@ -210,24 +207,39 @@ export class SupermemoryClient {
}
}
// Retrying a committed mutation can turn its success into a misleading not-found result.
async forgetByIds(ids: string[]): Promise<{
forgotten: { id: string; memory: string }[]
errors: { id: string; error: string }[]
namespace: string
}> {
// v5 has no exact-content forget: preview semantic matches, then forget the best one by ID.
async forgetMemory(
content: string,
): Promise<{ success: boolean; message: string; namespace: string }> {
try {
const result = await this.client.memories.forget(
const preview = await this.client.memories.forgetMatching(
this.namespace,
{ ids },
{ maxRetries: 0 },
{ query: content, dryRun: true },
)
const match = preview.matches[0]
if (!match) {
return {
success: false,
message: "No matching memory found to forget.",
namespace: this.namespace,
}
}
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 {
forgotten: result.matches.map((m) => ({
id: m.id,
memory: limitByChars(m.memory, 200),
})),
errors: result.errors,
success: true,
message: `Forgot memory (ID: ${match.id}): "${limitByChars(match.memory, 100)}"`,
namespace: this.namespace,
}
} catch (error) {
@ -240,9 +252,8 @@ export class SupermemoryClient {
const result = await this.client.search(this.namespace, {
query,
limit,
searchMode: "memories",
searchMode: "hybrid",
threshold: SEARCH_THRESHOLD,
include: { documents: true },
})
const results = mapSearchResults(result.results)
return {
@ -360,25 +371,9 @@ 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> {
try {
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) {
this.handleError(error)
}

View file

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

View file

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

View file

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

View file

@ -14,7 +14,6 @@ import { registerAllTools } from "./tools"
import { errorResult } from "./tools/types"
import type { ActorContext, ServerEnv } from "./types"
import {
accessibleDefaultNamespace,
resolveNamespace as resolveSpaceNamespace,
spaceStateName,
} from "./space"
@ -25,39 +24,8 @@ const UPLOAD_SESSION_TTL_MS = 2 * 60 * 1000
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."
const DEFAULT_NAMESPACE_CACHE_TTL_MS = 60_000
const DEFAULT_NAMESPACE_CACHE_MAX_ENTRIES = 1000
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 {
const envelope = context.mcpReq.envelope
if (!envelope) return null
@ -98,10 +66,8 @@ export function createSupermemoryServer(
spaceState.setActiveNamespace(namespace)
const resolveSelectedNamespace = (explicit?: string) =>
resolveSpaceNamespace(explicit, getActiveNamespace)
const getDefaultNamespace = () =>
defaultNamespaceFor(actor.bearerToken, apiUrl)
const resolveNamespace = async (explicit?: string) =>
(await resolveSelectedNamespace(explicit)) ?? (await getDefaultNamespace())
(await resolveSelectedNamespace(explicit)) ?? DEFAULT_NAMESPACE
const createUploadSession = async (namespace: string) => {
const uploadId = crypto.randomUUID()
const uploadToken = [crypto.randomUUID(), crypto.randomUUID()]
@ -140,25 +106,14 @@ export function createSupermemoryServer(
} catch {}
}
registerProfileResource(
server,
getClient,
resolveSelectedNamespace,
getDefaultNamespace,
)
registerProfileResource(server, getClient, resolveSelectedNamespace)
registerNamespacesResource(
server,
() => getClient(),
resolveSelectedNamespace,
getDefaultNamespace,
)
registerWidgetResource(server, mcpOrigin)
registerContextPrompt(
server,
getClient,
resolveSelectedNamespace,
getDefaultNamespace,
)
registerContextPrompt(server, getClient, resolveSelectedNamespace)
return server
}

View file

@ -1,10 +1,6 @@
import { describe, expect, it, vi } from "vitest"
import { optionalNamespaceSchema } from "./namespace"
import {
accessibleDefaultNamespace,
resolveNamespace,
spaceStateName,
} from "./space"
import { resolveNamespace, spaceStateName } from "./space"
describe("space application state", () => {
it("keys active state by organization and user without collisions", () => {
@ -46,63 +42,4 @@ describe("space application state", () => {
expect(optionalNamespaceSchema.description).toContain("list_spaces")
expect(optionalNamespaceSchema.description).toContain("active space")
})
it("keeps the default space unless the caller is limited to other spaces", () => {
const user = { id: "user" }
expect(accessibleDefaultNamespace({ user })).toBe("sm_project_default")
expect(
accessibleDefaultNamespace({
user,
scope: { type: "scoped", tags: ["work", "sm_project_default"] },
}),
).toBe("sm_project_default")
expect(
accessibleDefaultNamespace({
user,
scope: { type: "scoped", tag: "work", tags: ["work", "notes"] },
}),
).toBe("work")
expect(
accessibleDefaultNamespace({
user,
accessType: "restricted",
namespaces: [{ namespace: "team", permission: "read" }],
}),
).toBe("team")
expect(
accessibleDefaultNamespace({
user,
accessType: "restricted",
namespaces: [
{ namespace: "team", permission: "read" },
{ namespace: "notes", permission: "write" },
],
scope: { type: "scoped", tags: ["notes"] },
}),
).toBe("notes")
expect(
accessibleDefaultNamespace({
user,
accessType: "restricted",
namespaces: [
{ namespace: "readonly", permission: "read" },
{ namespace: "team", permission: "write" },
],
}),
).toBe("team")
expect(
accessibleDefaultNamespace({
user,
accessType: "restricted",
namespaces: [{ namespace: "readonly", permission: "read" }],
}),
).toBe("readonly")
expect(
accessibleDefaultNamespace({
user,
accessType: "restricted",
namespaces: [],
}),
).toBe("sm_project_default")
})
})

View file

@ -1,5 +1,3 @@
import type { SessionInfo } from "../shared/types"
import { DEFAULT_NAMESPACE } from "./client"
import type { ActorContext } from "./types"
export function spaceStateName(
@ -14,31 +12,3 @@ export async function resolveNamespace(
): Promise<string | undefined> {
return explicit ?? (await getActiveNamespace())
}
// Mirrors the API's hard allowlist (scoped keys, restricted members), preferring a space writes can reach.
export function accessibleDefaultNamespace(session: SessionInfo): string {
const keyTags =
session.scope?.type === "scoped"
? (session.scope.tags ?? (session.scope.tag ? [session.scope.tag] : null))
: null
const memberTags =
session.accessType === "restricted" && session.namespaces
? session.namespaces.map((access) => access.namespace)
: null
const allowed =
keyTags && memberTags
? keyTags.filter((tag) => memberTags.includes(tag))
: (keyTags ?? memberTags)
if (!allowed?.[0] || allowed.includes(DEFAULT_NAMESPACE)) {
return DEFAULT_NAMESPACE
}
const writable =
session.scope?.permission === "read"
? undefined
: allowed.find(
(tag) =>
session.namespaces?.find((access) => access.namespace === tag)
?.permission !== "read",
)
return writable ?? allowed[0]
}

View file

@ -1,5 +1,4 @@
import { z } from "zod"
import { getMemoryText } from "../client"
import { optionalNamespaceSchema } from "../namespace"
import { MEMORY_TOOL_ANNOTATIONS } from "./annotations"
import {
@ -10,110 +9,22 @@ import {
import { forgetMemoryOutputSchema } from "./output-schemas"
import { textContent, type ToolDeps } from "./types"
const MAX_IDS = 100
const CANDIDATE_LIMIT = 5
type ForgetArgs = {
ids?: string[]
content?: string
namespace?: string
containerTag?: string
}
function forgetResult(
namespace: string,
success: boolean,
message: string,
extra: Record<string, unknown> = {},
export async function forgetMemory(
deps: ToolDeps,
args: { content: string; namespace?: string; containerTag?: string },
) {
return {
content: [textContent(message)],
structuredContent: withLegacyNamespace({
action: "forget" as const,
success,
namespace,
message,
...extra,
}),
}
}
async function forgetIds(deps: ToolDeps, namespace: string, ids: string[]) {
const result = await deps.getClient(namespace).forgetByIds(ids)
const lines = result.forgotten.map((m) => `- ${m.id}: "${m.memory}"`)
for (const e of result.errors)
lines.push(`- ${e.id}: not forgotten (${e.error})`)
const count = result.forgotten.length
const head =
count > 0
? `Forgot ${count} memor${count === 1 ? "y" : "ies"}.`
: "No memories were forgotten."
return forgetResult(
result.namespace,
count > 0,
[head, ...lines].join("\n"),
{
id: result.forgotten[0]?.id,
forgotten: result.forgotten,
errors: result.errors,
},
)
}
export async function forgetMemory(deps: ToolDeps, args: ForgetArgs) {
try {
const namespace = await deps.resolveNamespace(namespaceArg(args))
if (args.ids !== undefined) {
const ids = [...new Set(args.ids.map((id) => id.trim()))]
if (ids.length === 0 || ids.length > MAX_IDS || ids.some((id) => !id)) {
throw new Error(`Pass between 1 and ${MAX_IDS} nonempty memory IDs.`)
}
return await forgetIds(deps, namespace, ids)
const result = await deps.getClient(namespace).forgetMemory(args.content)
return {
content: [textContent(result.message)],
structuredContent: withLegacyNamespace({
action: "forget" as const,
success: result.success,
namespace: result.namespace,
message: result.message,
}),
}
const content = args.content
if (!content?.trim()) {
return forgetResult(
namespace,
false,
"Pass ids (from search_memory) to forget memories.",
)
}
const search = await deps
.getClient(namespace)
.search(content, CANDIDATE_LIMIT)
const candidates = search.results
.filter((r) => "memory" in r)
.map((r) => ({
id: r.id,
text: getMemoryText(r),
similarity: r.similarity,
}))
const exact = candidates.find((c) => c.text === content)
if (exact) return await forgetIds(deps, namespace, [exact.id])
if (candidates.length === 0) {
return forgetResult(
namespace,
false,
"Nothing was forgotten. No matching memory found.",
{ candidates },
)
}
const lines = candidates.map(
(c) => `- ${c.id} [${(c.similarity * 100).toFixed(0)}%] ${c.text}`,
)
return forgetResult(
namespace,
false,
[
"Nothing was forgotten yet. Closest memories:",
...lines,
"Call forget_memory again with the ids to forget.",
].join("\n"),
{ candidates },
)
} catch (error) {
return deps.errorResult(error)
}
@ -124,21 +35,12 @@ export function register(deps: ToolDeps) {
"forget_memory",
{
description:
"Forget memories by ID in the user's ACTIVE space. Find the IDs with search_memory first, 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. Use when information is outdated or the user asks to remove it. Pass namespace only to override the active space.",
"Forget a memory matching the supplied content in the user's ACTIVE space. Use when information is outdated or the user asks to remove it. The target space is the one selected via select-space; pass namespace only to override it.",
inputSchema: z.object({
ids: z
.array(z.string().trim().min(1))
.min(1)
.max(MAX_IDS)
.optional()
.describe("IDs of the memories to forget, from search_memory"),
content: z
.string()
.max(200000, "Content exceeds maximum length")
.optional()
.describe(
"Text of the memory to forget. Used only when ids is not given.",
),
.describe("The memory content to forget"),
namespace: optionalNamespaceSchema,
...legacyNamespaceInput,
}),

View file

@ -19,9 +19,7 @@ export function register(deps: ToolDeps) {
.string()
.min(1, "Document ID is required")
.max(255, "Document ID exceeds maximum length")
.describe(
"Document ID from list_documents, or the document ID shown on a search_memory or list_memories result. Memory IDs are not document IDs.",
),
.describe("Document ID returned by list_documents or a memory result"),
namespace: optionalNamespaceSchema,
...legacyNamespaceInput,
})
@ -31,7 +29,7 @@ export function register(deps: ToolDeps) {
{
title: "Get Document",
description:
"Read one stored document by ID from one space, including its summary and available content. Get document IDs from list_documents or from the document shown on a search_memory result. When the document is in a named space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
"Read one stored document by ID from one space, including its summary and available content. Use list_documents to discover document IDs. When the document is in a named space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
inputSchema,
outputSchema: getDocumentOutputSchema,
annotations: READ_ONLY_TOOL_ANNOTATIONS,

View file

@ -25,15 +25,6 @@ export const addMemoryOutputSchema = z.object({
message: z.string(),
id: z.string().optional(),
status: z.string().optional(),
forgotten: z
.array(z.object({ id: z.string(), memory: z.string() }))
.optional(),
errors: z.array(z.object({ id: z.string(), error: z.string() })).optional(),
candidates: z
.array(
z.object({ id: z.string(), text: z.string(), similarity: z.number() }),
)
.optional(),
})
export type AddMemoryOutput = z.infer<typeof addMemoryOutputSchema>
@ -86,7 +77,6 @@ export const searchMemoryOutputSchema = z.object({
id: z.string(),
text: z.string(),
similarity: z.number(),
documentId: z.string().optional(),
title: z.string().optional(),
}),
),

View file

@ -27,7 +27,7 @@ export function register(deps: ToolDeps) {
"search_memory",
{
description:
"Search memories in one space with a natural-language query. Returns matching memories only — not the space profile. Each result has a memory ID (for forget_memory) and, when it has one, the ID of its source document (for get_document). If you need who-the-user-is, preferences, or recent context after searching, call get_profile. When the user names a space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
"Search memories in one space with a natural-language query. Returns matching memories only — not the space profile. If you need who-the-user-is, preferences, or recent context after searching, call get_profile. When the user names a space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
inputSchema,
outputSchema: searchMemoryOutputSchema,
annotations: READ_ONLY_TOOL_ANNOTATIONS,
@ -42,7 +42,6 @@ export function register(deps: ToolDeps) {
id: result.id,
text: getMemoryText(result),
similarity: result.similarity,
...(result.documentId && { documentId: result.documentId }),
}))
const parts: string[] = []
@ -51,10 +50,7 @@ export function register(deps: ToolDeps) {
for (const result of searchResult.results) {
const text = getMemoryText(result)
const similarity = (result.similarity * 100).toFixed(0)
const source = result.documentId
? ` (document: ${result.documentId})`
: ""
parts.push(`- ${result.id} [${similarity}%] ${text}${source}`)
parts.push(`- [${similarity}%] ${text}`)
}
} else {
parts.push("No matching memories found.")

View file

@ -11,7 +11,7 @@ export const namespaceAccessSchema = z.object({
export type NamespaceAccess = z.infer<typeof namespaceAccessSchema>
// /session still sends container-tag field names.
// /v3/session has no v5 replacement and still sends container-tag field names.
const sessionNamespaceAccessSchema = z
.object({ containerTag: z.string(), permission: z.enum(["read", "write"]) })
.transform(({ containerTag, permission }) => ({

14
apps/raycast-extension/.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
# Raycast specific files
raycast-env.d.ts
.raycast-swift-build
.swiftpm
compiled_raycast_swift
compiled_raycast_rust
# misc
.DS_Store

View file

@ -0,0 +1,19 @@
# Supermemory Changelog
## [Quick Add from Selection] - 2025-11-05
- Select text anywhere and quickly add it as a memory - the content field is automatically filled
- Select text and search memories instantly - the search field is automatically filled
- Save time by selecting text before opening commands
## [Search Projects + Enhancements] - 2025-10-13
- Added command to search and add projects
- Removed `useEffect`
- Simplified `getApiKey` since `Preferences` will be enforced for presence and trim automatically
- Moved API Key check into its own HoC
## [Initial Version] - 2025-10-02
- Added Supermemory integration with Add Memory and Search Memories commands
- Added project organization support for memories

View file

@ -0,0 +1,35 @@
# Raycast Extension for Supermemory
A Raycast extension that lets you add memories and search through your Supermemory collection directly from Raycast.
## Setup
1. Install the extension in Raycast
2. Get your API key from [app.supermemory.ai](https://app.supermemory.ai)
3. Open the extension preferences and enter your API key
## Features
### Add Memory
- Add new memories to your Supermemory collection
- Organize memories by project
- Add optional titles and URLs
- Keyboard shortcut: Cmd+Enter to submit
### Search Memories
- Search through your entire Supermemory collection
- Real-time search with debouncing
- View detailed memory information
- Copy content or open related URLs
- Shows relevance scores and creation dates
## Commands
- `Add Memory` - Add a new memory to your collection
- `Search Memories` - Search through your existing memories
## Authentication
This extension requires a Supermemory API key. You can get your API key from [supermemory.link/raycast](https://supermemory.link/raycast).
The API key is stored securely in Raycast preferences and is required for all operations.

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -0,0 +1,4 @@
const { defineConfig } = require("eslint/config")
const raycastConfig = require("@raycast/eslint-config")
module.exports = defineConfig([...raycastConfig])

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

3169
apps/raycast-extension/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,74 @@
{
"$schema": "https://www.raycast.com/schemas/extension.json",
"name": "supermemory",
"title": "Supermemory",
"description": "Add and search memories with your personal AI-powered knowledge base",
"icon": "extension-icon.png",
"author": "supermemory",
"contributors": [
"xmok",
"maheshthedev"
],
"platforms": [
"macOS",
"Windows"
],
"categories": [
"Productivity",
"Web"
],
"license": "MIT",
"commands": [
{
"name": "add-memory",
"title": "Add Memory",
"subtitle": "Supermemory",
"description": "Add text, URLs, or documents to your supermemory knowledge base",
"mode": "view"
},
{
"name": "search-memories",
"title": "Search Memories",
"subtitle": "Supermemory",
"description": "Search through your saved memories and find relevant information",
"mode": "view"
},
{
"name": "search-projects",
"title": "Search Projects",
"subtitle": "Supermemory",
"description": "Search through your saved projects and find relevant information",
"mode": "view"
}
],
"preferences": [
{
"name": "apiKey",
"type": "password",
"required": true,
"title": "API Key",
"description": "Your Supermemory API Key. Get it from https://supermemory.link/raycast",
"placeholder": "Enter your Supermemory API Key"
}
],
"dependencies": {
"@raycast/api": "^1.103.3",
"@raycast/utils": "^2.2.1"
},
"devDependencies": {
"@raycast/eslint-config": "^2.0.4",
"@types/node": "22.13.10",
"@types/react": "19.0.10",
"eslint": "^9.22.0",
"prettier": "^3.5.3",
"typescript": "^5.8.2"
},
"scripts": {
"build": "ray build",
"dev": "ray develop",
"fix-lint": "ray lint --fix",
"lint": "ray lint",
"prepublishOnly": "echo \"\\n\\nIt seems like you are trying to publish the Raycast extension to npm.\\n\\nIf you did intend to publish it to npm, remove the \\`prepublishOnly\\` script and rerun \\`npm publish\\` again.\\nIf you wanted to publish it to the Raycast Store instead, use \\`npm run publish\\` instead.\\n\\n\" && exit 1",
"publish": "npx @raycast/api@latest publish"
}
}

View file

@ -0,0 +1,114 @@
import {
Form,
ActionPanel,
Action,
showToast,
Toast,
useNavigation,
Icon,
getSelectedText,
} from "@raycast/api"
import { useState, useEffect } from "react"
import { addMemory, fetchProjects } from "./api"
import { usePromise } from "@raycast/utils"
import { withSupermemory } from "./withSupermemory"
interface FormValues {
content: string
project: string
}
export default withSupermemory(Command)
function Command() {
const [isSubmitting, setIsSubmitting] = useState(false)
const [initialContent, setInitialContent] = useState("")
const { pop } = useNavigation()
const { isLoading, data: projects = [] } = usePromise(fetchProjects)
useEffect(() => {
async function loadSelectedText() {
try {
const selectedText = await getSelectedText()
if (selectedText) {
setInitialContent(selectedText)
}
} catch {
// No text selected or error getting selected text - silently fail
// User can still manually enter content
}
}
loadSelectedText()
}, [])
async function handleSubmit(values: FormValues) {
if (!values.content.trim()) {
await showToast({
style: Toast.Style.Failure,
title: "Content Required",
message: "Please enter some content for the memory",
})
return
}
try {
setIsSubmitting(true)
const containerTags = values.project ? [values.project] : undefined
await addMemory({
content: values.content.trim(),
containerTags,
})
pop()
} catch (error) {
console.error("Failed to add memory:", error)
} finally {
setIsSubmitting(false)
}
}
return (
<Form
isLoading={isLoading || isSubmitting}
actions={
!isLoading && (
<ActionPanel>
<Action.SubmitForm
icon={Icon.Plus}
title="Add Memory"
onSubmit={handleSubmit}
/>
</ActionPanel>
)
}
>
<Form.TextArea
id="content"
title="Content"
value={initialContent}
placeholder="Enter the memory content..."
info="The main content of your memory. This is required."
onChange={(value) => setInitialContent(value)}
/>
<Form.Separator />
<Form.Dropdown
id="project"
title="Project"
info="Select a project to organize this memory"
storeValue
>
<Form.Dropdown.Item value="" title="No Project" />
{projects.map((project) => (
<Form.Dropdown.Item
key={project.id}
value={project.containerTag}
title={project.name}
/>
))}
</Form.Dropdown>
</Form>
)
}

View file

@ -0,0 +1,222 @@
import { getPreferenceValues, showToast, Toast } from "@raycast/api"
export interface Project {
id: string
name: string
containerTag: string
description?: string
}
export interface Memory {
id: string
content: string
title?: string
url?: string
containerTag?: string
createdAt: string
}
export interface SearchResult {
documentId: string
chunks: unknown[]
title?: string
metadata: Record<string, unknown>
score?: number
createdAt: string
updatedAt: string
type: string
}
export interface AddMemoryRequest {
content: string
containerTags?: string[]
title?: string
url?: string
metadata?: Record<string, unknown>
}
interface AddProjectRequest {
name: string
}
export interface SearchRequest {
q: string
containerTags?: string[]
limit?: number
}
export interface SearchResponse {
results: SearchResult[]
timing: number
total: number
}
const API_BASE_URL = "https://api.supermemory.ai"
class SupermemoryAPIError extends Error {
constructor(
message: string,
public status?: number,
) {
super(message)
this.name = "SupermemoryAPIError"
}
}
class AuthenticationError extends Error {
constructor(message: string) {
super(message)
this.name = "AuthenticationError"
}
}
function getApiKey(): string {
const { apiKey } = getPreferenceValues<Preferences>()
return apiKey
}
async function makeAuthenticatedRequest<T>(
endpoint: string,
options: RequestInit = {},
): Promise<T> {
const apiKey = getApiKey()
const url = `${API_BASE_URL}${endpoint}`
try {
const response = await fetch(url, {
...options,
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
...options.headers,
},
})
if (!response.ok) {
if (response.status === 401) {
throw new AuthenticationError(
"Invalid API key. Please check your API key in preferences. Get a new one from https://supermemory.link/raycast",
)
}
let errorMessage = `API request failed: ${response.statusText}`
try {
const errorBody = (await response.json()) as { message?: string }
if (errorBody.message) {
errorMessage = errorBody.message
}
} catch {
// Ignore JSON parsing errors, use default message
}
throw new SupermemoryAPIError(errorMessage, response.status)
}
if (!response.headers.get("content-type")?.includes("application/json")) {
throw new SupermemoryAPIError("Invalid response format from API")
}
const data = (await response.json()) as T
return data
} catch (err) {
if (
err instanceof AuthenticationError ||
err instanceof SupermemoryAPIError
) {
throw err
}
// Handle network errors or other fetch errors
throw new SupermemoryAPIError(
`Network error: ${err instanceof Error ? err.message : "Unknown error"}`,
)
}
}
export async function fetchProjects(): Promise<Project[]> {
try {
const response = await makeAuthenticatedRequest<{ projects: Project[] }>(
"/v3/projects",
)
return response.projects || []
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to fetch projects",
message:
error instanceof Error ? error.message : "Unknown error occurred",
})
throw error
}
}
export async function addProject(request: AddProjectRequest): Promise<Project> {
const response = await makeAuthenticatedRequest<Project>("/v3/projects", {
method: "POST",
body: JSON.stringify(request),
})
await showToast({
style: Toast.Style.Success,
title: "Project Added",
message: "Successfully added project to Supermemory",
})
return response
}
export async function addMemory(request: AddMemoryRequest): Promise<Memory> {
try {
const response = await makeAuthenticatedRequest<Memory>("/v3/documents", {
method: "POST",
body: JSON.stringify(request),
})
await showToast({
style: Toast.Style.Success,
title: "Memory Added",
message: "Successfully added memory to Supermemory",
})
return response
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to add memory",
message:
error instanceof Error ? error.message : "Unknown error occurred",
})
throw error
}
}
export async function searchMemories(
request: SearchRequest,
): Promise<SearchResult[]> {
try {
const response = await makeAuthenticatedRequest<SearchResponse>(
"/v3/search",
{
method: "POST",
body: JSON.stringify(request),
},
)
return response.results || []
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to search memories",
message:
error instanceof Error ? error.message : "Unknown error occurred",
})
throw error
}
}
// Helper function to check if API key is configured and valid
export async function fetchSettings(): Promise<object> {
const response = await makeAuthenticatedRequest<object>("/v3/settings")
return response
}

View file

@ -0,0 +1,226 @@
import {
ActionPanel,
Detail,
List,
Action,
Icon,
showToast,
Toast,
getSelectedText,
} from "@raycast/api"
import { useState, useEffect } from "react"
import { searchMemories, type SearchResult } from "./api"
import { usePromise } from "@raycast/utils"
import { withSupermemory } from "./withSupermemory"
const extractContent = (memory: SearchResult) => {
if (memory.chunks && memory.chunks.length > 0) {
return memory.chunks
.map((chunk: unknown) => {
if (typeof chunk === "string") return chunk
if (
chunk &&
typeof chunk === "object" &&
"content" in chunk &&
typeof chunk.content === "string"
)
return chunk.content
if (
chunk &&
typeof chunk === "object" &&
"text" in chunk &&
typeof chunk.text === "string"
)
return chunk.text
return ""
})
.filter(Boolean)
.join(" ")
}
return "No content available"
}
// metadata.url comes from ingested content, so only http(s) reaches the OS opener.
const extractUrl = (memory: SearchResult) => {
if (memory.metadata?.url && typeof memory.metadata.url === "string") {
const url = memory.metadata.url
try {
const parsed = new URL(url)
if (parsed.protocol === "https:" || parsed.protocol === "http:") {
return url
}
} catch {
return null
}
}
return null
}
const truncateContent = (content: string, maxLength = 100) => {
if (content.length <= maxLength) return content
return `${content.substring(0, maxLength)}...`
}
export default withSupermemory(Command)
function Command() {
const [searchText, setSearchText] = useState("")
useEffect(() => {
async function loadSelectedText() {
try {
const selectedText = await getSelectedText()
if (selectedText) {
setSearchText(selectedText)
}
} catch {
// No text selected or error getting selected text - silently fail
}
}
loadSelectedText()
}, [])
const { isLoading, data: searchResults = [] } = usePromise(
async (query: string) => {
const q = query.trim()
if (!q) return []
const results = await searchMemories({
q,
limit: 50,
})
if (!results.length) {
await showToast({
style: Toast.Style.Success,
title: "Search Complete",
message: "No memories found for your query",
})
}
return results
},
[searchText],
)
const formatDate = (dateString: string) => {
try {
return new Date(dateString).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
})
} catch {
return "Unknown date"
}
}
const hasSearched = !isLoading && !searchResults.length
return (
<List
isLoading={isLoading}
onSearchTextChange={setSearchText}
searchText={searchText}
searchBarPlaceholder="Search your memories..."
throttle
>
{hasSearched && !searchText.trim() ? (
<List.EmptyView
icon={Icon.MagnifyingGlass}
title="Search Your Memories"
description="Type to search through your Supermemory collection"
/>
) : hasSearched ? (
<List.EmptyView
icon={Icon.Document}
title="No Memories Found"
description={`No memories found for "${searchText}"`}
/>
) : isLoading && searchText.trim() ? (
<List.EmptyView
icon={Icon.MagnifyingGlass}
title="Searching Your Memories"
/>
) : (
searchResults.map((memory) => {
const content = extractContent(memory)
const url = extractUrl(memory)
return (
<List.Item
key={memory.documentId}
icon={url ? Icon.Link : Icon.Document}
title={memory.title || "Untitled Memory"}
subtitle={{ value: truncateContent(content), tooltip: content }}
accessories={[
{ text: formatDate(memory.createdAt) },
...(memory.score
? [{ text: `${Math.round(memory.score * 100)}%` }]
: []),
]}
actions={
<ActionPanel>
<Action.Push
title="View Details"
target={<MemoryDetail memory={memory} />}
icon={Icon.Eye}
/>
<Action.CopyToClipboard
title="Copy Content"
shortcut={{ modifiers: ["cmd"], key: "c" }}
content={content}
/>
{url && (
<Action.OpenInBrowser
title="Open URL"
url={url}
shortcut={{ modifiers: ["cmd"], key: "o" }}
/>
)}
</ActionPanel>
}
/>
)
})
)}
</List>
)
}
function MemoryDetail({ memory }: { memory: SearchResult }) {
const content = extractContent(memory)
const url = extractUrl(memory)
const markdown = `
# ${memory.title || "Untitled Memory"}
${content}
---
**Created:** ${new Date(memory.createdAt).toLocaleString()}
${url ? `**URL:** ${url}` : ""}
${memory.score ? `**Relevance:** ${Math.round(memory.score * 100)}%` : ""}
`
return (
<Detail
markdown={markdown}
actions={
<ActionPanel>
<Action.CopyToClipboard
title="Copy Content"
shortcut={{ modifiers: ["cmd"], key: "c" }}
content={content}
/>
{url && (
<Action.OpenInBrowser
title="Open URL"
url={url}
shortcut={{ modifiers: ["cmd"], key: "o" }}
/>
)}
</ActionPanel>
}
/>
)
}

View file

@ -0,0 +1,106 @@
import {
ActionPanel,
List,
Action,
Icon,
Form,
useNavigation,
} from "@raycast/api"
import { useState } from "react"
import { fetchProjects, addProject } from "./api"
import {
FormValidation,
showFailureToast,
useCachedPromise,
useForm,
} from "@raycast/utils"
import { withSupermemory } from "./withSupermemory"
export default withSupermemory(Command)
function Command() {
const { isLoading, data: projects, mutate } = useCachedPromise(fetchProjects)
return (
<List isLoading={isLoading} searchBarPlaceholder="Search your projects">
{!isLoading && !projects?.length ? (
<List.EmptyView
title="No Projects Found"
actions={
<ActionPanel>
<Action.Push
icon={Icon.Plus}
title="Create Project"
target={<CreateProject />}
onPop={mutate}
/>
</ActionPanel>
}
/>
) : (
projects?.map((project) => (
<List.Item
key={project.id}
icon={Icon.Folder}
title={project.name}
subtitle={project.description}
accessories={[{ tag: project.containerTag }]}
actions={
<ActionPanel>
<Action.Push
icon={Icon.Plus}
title="Create Project"
target={<CreateProject />}
onPop={mutate}
/>
</ActionPanel>
}
/>
))
)}
</List>
)
}
function CreateProject() {
const { pop } = useNavigation()
const [isLoading, setIsLoading] = useState(false)
const { handleSubmit, itemProps } = useForm<{ name: string }>({
async onSubmit(values) {
setIsLoading(true)
try {
await addProject(values)
pop()
} catch (error) {
await showFailureToast(error, { title: "Failed to add project" })
} finally {
setIsLoading(false)
}
},
validation: {
name: FormValidation.Required,
},
})
return (
<Form
navigationTitle="Search Projects / Add"
isLoading={isLoading}
actions={
<ActionPanel>
<Action.SubmitForm
icon={Icon.Plus}
title="Create Project"
onSubmit={handleSubmit}
/>
</ActionPanel>
}
>
<Form.TextField
title="Name"
placeholder="My Awesome Project"
info="This will help you organize your memories"
{...itemProps.name}
/>
</Form>
)
}

View file

@ -0,0 +1,48 @@
import { usePromise } from "@raycast/utils"
import { fetchSettings } from "./api"
import {
Action,
ActionPanel,
Detail,
Icon,
List,
openExtensionPreferences,
} from "@raycast/api"
import type { ComponentType } from "react"
export function withSupermemory<P extends object>(Component: ComponentType<P>) {
return function SupermemoryWrappedComponent(props: P) {
const { isLoading, data } = usePromise(fetchSettings, [], {
failureToastOptions: {
title: "Invalid API Key",
message:
"Invalid API key. Please check your API key in preferences. Get a new one from https://supermemory.link/raycast",
},
})
if (!data) {
return isLoading ? (
<Detail isLoading />
) : (
<List>
<List.EmptyView
icon={Icon.ExclamationMark}
title="API Key Required"
description="Please configure your Supermemory API key to search memories"
actions={
<ActionPanel>
<Action
title="Open Extension Preferences"
onAction={openExtensionPreferences}
icon={Icon.Gear}
/>
</ActionPanel>
}
/>
</List>
)
}
return <Component {...props} />
}
}

View file

@ -0,0 +1,16 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"include": ["src/**/*", "raycast-env.d.ts"],
"compilerOptions": {
"lib": ["ES2023"],
"module": "commonjs",
"target": "ES2023",
"strict": true,
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react-jsx",
"resolveJsonModule": true
}
}

View file

@ -86,7 +86,7 @@
"react-dom": "^19.2.4",
"supermemory": "^5.0.0",
"tailwind-merge": "^3.4.0",
"zod": "^4.6.6",
"zod": "^4.4.3",
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250620.0",
@ -215,7 +215,7 @@
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@vitejs/plugin-react": "^5.1.0",
"happy-dom": "^21.0.4",
"happy-dom": "^20.9.0",
"typescript": "^5.9.3",
"vite": "7.3.5",
"vitest": "3.2.6",
@ -227,12 +227,12 @@
},
"packages/tools": {
"name": "@supermemory/tools",
"version": "3.1.2",
"version": "3.1.0",
"dependencies": {
"lru-cache": "^11.2.6",
"openai": "^4.104.0",
"supermemory": "^5.0.0",
"zod": "^4.6.6",
"zod": "^4.1.5",
},
"devDependencies": {
"@ai-sdk/anthropic": "^2.0.25",
@ -255,7 +255,6 @@
"ai": "^5.0.0 || ^6.0.0 || ^7.0.0",
},
"optionalPeers": [
"@ai-sdk/provider",
"@voltagent/core",
"ai",
],
@ -264,6 +263,7 @@
"name": "@repo/ui",
"version": "0.0.0",
"dependencies": {
"@pixi/react": "^8.0.3",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.14",
"@radix-ui/react-avatar": "^1.1.10",
@ -289,6 +289,7 @@
"lucide-react": "^0.525.0",
"motion": "^12.26.2",
"next-themes": "^0.4.6",
"pixi.js": "^8.12.0",
"recharts": "2.15.4",
"sonner": "^2.0.6",
"tailwindcss": "^4.1.11",
@ -1054,6 +1055,10 @@
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
"@pixi/colord": ["@pixi/colord@2.9.6", "", {}, "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA=="],
"@pixi/react": ["@pixi/react@8.0.5", "", { "dependencies": { "its-fine": "^2.0.0", "react-reconciler": "0.31.0" }, "peerDependencies": { "pixi.js": "^8.2.6", "react": ">=19.0.0" } }, "sha512-Z1VRdnv9Gh+lTLzNKjpS7GaTNDjUxRVJNtIdtK2i0sCpigvjx5mvJ72EPLhBFgepB6j3ZCkL0YBb6BqgTGbhGA=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="],
@ -1614,6 +1619,8 @@
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
"@types/earcut": ["@types/earcut@3.0.0", "", {}, "sha512-k/9fOUGO39yd2sCjrbAJvGDEQvRwRnQIZlBz43roGwUZo5SHAmyVvSFyaVVZkicRVCaDXPKlbxrUcBuJoSWunQ=="],
"@types/es-aggregate-error": ["@types/es-aggregate-error@1.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-qJ7LIFp06h1QE1aVxbVd+zJP2wdaugYXYfd6JxsyRMrYHaxb6itXPogW2tz+ylUJ1n1b+JF1PHyYCfYHm0dvUg=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
@ -1664,6 +1671,8 @@
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
"@types/react-reconciler": ["@types/react-reconciler@0.28.9", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg=="],
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
@ -1714,8 +1723,12 @@
"@voltagent/internal": ["@voltagent/internal@1.0.3", "", { "dependencies": { "type-fest": "^4.41.0" } }, "sha512-tw2L2PptjfsPThO6S4SjouwhAiXDGcjN6kKh9rc7IqWNNGZN+4mwj8gsOroMYOMmhIbfQEr2xT7TqZGNkteDlw=="],
"@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="],
"@workflow/serde": ["@workflow/serde@4.1.0", "", {}, "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ=="],
"@xmldom/xmldom": ["@xmldom/xmldom@0.8.15", "", {}, "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA=="],
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
@ -1870,8 +1883,6 @@
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="],
"bun-types": ["bun-types@1.4.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w=="],
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
@ -2140,6 +2151,8 @@
"duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="],
"earcut": ["earcut@3.0.2", "", {}, "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ=="],
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
@ -2240,7 +2253,7 @@
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
"eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
@ -2376,6 +2389,8 @@
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
"gifuct-js": ["gifuct-js@2.1.2", "", { "dependencies": { "js-binary-schema-parser": "^2.0.3" } }, "sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg=="],
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
"glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
@ -2404,7 +2419,7 @@
"gzip-size": ["gzip-size@6.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="],
"happy-dom": ["happy-dom@21.0.4", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-tFm+GaLBgo6o0awHnvHaNoraVo27qJ+2tZEtZ+cIa08hXbeitrxAJajLI05hsUy/2/YTKcUjQYzHe5mCcraLBw=="],
"happy-dom": ["happy-dom@20.9.0", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ=="],
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
@ -2614,8 +2629,12 @@
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"ismobilejs": ["ismobilejs@1.1.1", "", {}, "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw=="],
"isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
"its-fine": ["its-fine@2.0.0", "", { "dependencies": { "@types/react-reconciler": "^0.28.9" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng=="],
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
@ -2626,6 +2645,8 @@
"js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="],
"js-binary-schema-parser": ["js-binary-schema-parser@2.0.3", "", {}, "sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg=="],
"js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
@ -3072,6 +3093,8 @@
"parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
"parse-svg-path": ["parse-svg-path@0.1.2", "", {}, "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ=="],
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
"parseley": ["parseley@0.12.1", "", { "dependencies": { "leac": "^0.6.0", "peberminta": "^0.9.0" } }, "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw=="],
@ -3132,6 +3155,8 @@
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
"pixi.js": ["pixi.js@8.16.0", "", { "dependencies": { "@pixi/colord": "^2.9.6", "@types/earcut": "^3.0.0", "@webgpu/types": "^0.1.69", "@xmldom/xmldom": "^0.8.11", "earcut": "^3.0.2", "eventemitter3": "^5.0.1", "gifuct-js": "^2.1.2", "ismobilejs": "^1.1.1", "parse-svg-path": "^0.1.2", "tiny-lru": "^11.4.7" } }, "sha512-gu2xw3sZGAn3cWBtk0HqTQT+v19YAfiaYXwUGgWoJl5NKz4cEZJUgWrwkmdfDszGyYBAGqOvJNbd2M9+vzLLMg=="],
"pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
"pony-cause": ["pony-cause@1.1.1", "", {}, "sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g=="],
@ -3232,7 +3257,7 @@
"react-promise-suspense": ["react-promise-suspense@0.3.4", "", { "dependencies": { "fast-deep-equal": "^2.0.1" } }, "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ=="],
"react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="],
"react-reconciler": ["react-reconciler@0.31.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ=="],
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
@ -3556,6 +3581,8 @@
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
"tiny-lru": ["tiny-lru@11.4.7", "", {}, "sha512-w/Te7uMUVeH0CR8vZIjr+XiN41V+30lkDdK+NRIDCUYKKuL9VcmaUEmaPISuwGhLlrTGh5yu18lENtR9axSxYw=="],
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
@ -3980,11 +4007,11 @@
"@base-ui/utils/@floating-ui/utils": ["@floating-ui/utils@0.2.12", "", {}, "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww=="],
"@better-auth/api-key/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@better-auth/api-key/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
"@better-auth/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="],
"@better-auth/core/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@better-auth/core/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
"@cloudflare/ai-chat/agents": ["agents@0.4.1", "", { "dependencies": { "@cfworker/json-schema": "^4.1.1", "@modelcontextprotocol/sdk": "1.26.0", "cron-schedule": "^6.0.0", "escape-html": "^1.0.3", "json-schema": "^0.4.0", "json-schema-to-typescript": "^15.0.4", "mimetext": "^3.0.28", "nanoid": "^5.1.6", "partyserver": "^0.1.4", "partysocket": "1.1.13", "yargs": "^18.0.0" }, "peerDependencies": { "@ai-sdk/openai": "^3.0.0", "@ai-sdk/react": "^3.0.0", "@cloudflare/ai-chat": "^0.0.8", "@cloudflare/codemode": "^0.0.7", "@x402/core": "^2.0.0", "@x402/evm": "^2.0.0", "ai": "^6.0.0", "react": "^19.0.0", "viem": ">=2.0.0", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@ai-sdk/openai", "@ai-sdk/react", "@x402/core", "@x402/evm", "viem"], "bin": { "agents": "dist/cli/index.js" } }, "sha512-1ARZ5AXpPEn1+Wv/Dt++nH3iLStDxlci7Bm47sfE6qLlJpY9oG5nzyH2TRKwFFt9bMAgYz177i6p2NiEd/coVQ=="],
@ -4118,19 +4145,19 @@
"@modelcontextprotocol/client/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
"@modelcontextprotocol/client/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@modelcontextprotocol/client/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
"@modelcontextprotocol/core/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@modelcontextprotocol/core/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
"@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
"@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@modelcontextprotocol/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
"@modelcontextprotocol/server/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@modelcontextprotocol/server/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
"@mymediset/sap-ai-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
"@mymediset/sap-ai-provider/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@mymediset/sap-ai-provider/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
@ -4224,7 +4251,7 @@
"@rolldown/plugin-babel/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
"@sap-ai-sdk/prompt-registry/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@sap-ai-sdk/prompt-registry/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@sap-cloud-sdk/util/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@ -4232,7 +4259,7 @@
"@scalar/types/type-fest": ["type-fest@5.4.4", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw=="],
"@scalar/types/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@scalar/types/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
@ -4270,7 +4297,7 @@
"@supermemory/tools/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"@supermemory/tools/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@supermemory/tools/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
@ -4346,7 +4373,7 @@
"axios/proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="],
"better-auth/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"better-auth/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
"better-call/@better-auth/utils": ["@better-auth/utils@0.5.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA=="],
@ -4384,7 +4411,7 @@
"docs-test/supermemory": ["supermemory@4.25.4", "", { "bin": { "supermemory": "bin/cli" } }, "sha512-97ME3rlmu7OmsXJTb9OgXOD+3VUv4Wej0ZX9xezG+LKkMwrzi4xeeAZaOJFcr0oI/QQjcHG2WOzm+und1e7MFA=="],
"docs-test/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"docs-test/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
@ -4444,6 +4471,8 @@
"ink/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
"ink/react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="],
"ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"inquirer/@inquirer/prompts": ["@inquirer/prompts@7.10.1", "", { "dependencies": { "@inquirer/checkbox": "^4.3.2", "@inquirer/confirm": "^5.1.21", "@inquirer/editor": "^4.2.23", "@inquirer/expand": "^4.0.23", "@inquirer/input": "^4.3.1", "@inquirer/number": "^3.0.23", "@inquirer/password": "^4.0.23", "@inquirer/rawlist": "^4.1.11", "@inquirer/search": "^3.2.2", "@inquirer/select": "^4.4.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg=="],
@ -4514,10 +4543,12 @@
"react-promise-suspense/fast-deep-equal": ["fast-deep-equal@2.0.1", "", {}, "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w=="],
"react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"react-reconciler/scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
"readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"recharts/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
"rehype-recma/hast-util-to-estree": ["hast-util-to-estree@3.1.3", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-attach-comments": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w=="],
"remark-gfm/mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
@ -4584,7 +4615,7 @@
"supermemory-mcp/wrangler": ["wrangler@4.147.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.2", "blake3-wasm": "2.1.5", "esbuild": "0.28.1", "miniflare": "5.20261001.0-alpha", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20261001.1" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "@cloudflare/workers-types": "^5.20261001.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js", "cf-wrangler": "bin/cf-wrangler.js" } }, "sha512-pQYRoiq8PTAxphaG69z8+GC1DkSGd19EDZehQ8zxjo/Ko3mRB6Qs1mTrd8ZuKAarLklIjTqr1lUdCK9r4q2hUg=="],
"supermemory-mcp/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"supermemory-mcp/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
"tailwindcss-v3/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
@ -4630,7 +4661,7 @@
"xss/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
"zod-from-json-schema/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"zod-from-json-schema/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@a2a-js/sdk/express/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
@ -4818,7 +4849,7 @@
"@mastra/core/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
"@mastra/core/@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@mastra/core/@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@mintlify/cli/inquirer/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
@ -5016,7 +5047,7 @@
"@voltagent/core/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
"@voltagent/core/@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@voltagent/core/@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@voltagent/core/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.1.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ=="],
@ -5136,6 +5167,8 @@
"gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"ink/react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"is-online/got/@sindresorhus/is": ["@sindresorhus/is@5.6.0", "", {}, "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g=="],
"is-online/got/form-data-encoder": ["form-data-encoder@2.1.4", "", {}, "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw=="],
@ -5424,11 +5457,11 @@
"@cloudflare/ai-chat/agents/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
"@cloudflare/ai-chat/agents/@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@cloudflare/ai-chat/agents/@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@cloudflare/codemode/agents/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
"@cloudflare/codemode/agents/@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@cloudflare/codemode/agents/@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@inquirer/core/wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],

View file

@ -16,6 +16,7 @@
"packageManager": "bun@1.3.6",
"workspaces": [
"apps/*",
"!apps/raycast-extension",
"!tools/test/chatapp",
"packages/*"
],

View file

@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "supermemory-cartesia"
version = "0.1.5"
version = "0.1.4"
description = "Supermemory integration for Cartesia Line - memory-enhanced voice agents"
readme = "README.md"
license = "MIT"
@ -13,7 +13,7 @@ authors = [
{ name = "Supermemory", email = "support@supermemory.ai" }
]
keywords = [
"supermemory",
"supermemory>=3.16.0,<5",
"cartesia",
"line",
"memory",
@ -32,7 +32,7 @@ classifiers = [
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
dependencies = [
"supermemory>=3.16.0,<5",
"supermemory>=3.16.0",
"cartesia-line>=0.2.0,<0.3.0",
"pydantic>=2.10.0",
"loguru>=0.7.3",

View file

@ -56,7 +56,7 @@ try:
__version__ = version("supermemory-cartesia")
except PackageNotFoundError:
# Source checkouts do not have installed distribution metadata.
__version__ = "0.1.5"
__version__ = "0.1.3"
__all__ = [
# Main agent

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