Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions apps/docs/concepts/container-tags.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
---
title: "Container Tags"
sidebarTitle: "Container Tags"
description: "The isolation boundary that groups and partitions memories by user, project, or any logical scope"
icon: "folder"
---

A **container tag** is the primary way you organize and isolate memories in Supermemory. It's a simple string identifier you attach to content when you add it — and that you pass back when you search, list, or update it.

Think of a container tag as a **namespace**: every memory tagged with `user_alex` lives in its own isolated space, completely separate from memories tagged `user_jordan`. This is what makes Supermemory safe to use in multi-tenant applications — one user can never see another user's memories unless you explicitly query across both tags.

<CardGroup cols={2}>
<Card title="Group" icon="layers">
Bucket memories by user, project, agent, workspace, or any boundary that makes sense for your app.
</Card>
<Card title="Isolate" icon="shield">
Each container tag maps to its own vector namespace, so search and retrieval never leak across boundaries.
</Card>
</CardGroup>

---

## How it works

When you add a memory with a container tag, Supermemory automatically creates a **space** for that tag (scoped to your organization) if one doesn't already exist. You don't need to provision anything ahead of time — the first write with a new tag creates the container, and subsequent writes reuse it.

```typescript
// First call auto-creates the "user_alex" container
await client.add({
content: "Alex prefers dark mode and concise answers",
containerTag: "user_alex",
});

// Later, retrieve only Alex's memories
const results = await client.search.memories({
q: "what are the user's UI preferences?",
containerTag: "user_alex",
});
```

Under the hood, each container tag is hashed into a dedicated vector namespace. Embeddings, chunks, and memory entries for one tag are stored and searched independently of every other tag — there is no shared index to filter through, which is why isolation is strict rather than best-effort.

<Note>
A container tag is an **opaque identifier you choose**. Supermemory does not parse meaning out of it — `user_123`, `project_mobile`, and `org:acme:team:growth` are all equally valid. Pick a convention that mirrors the access boundaries in your own application.
</Note>

---

## Naming rules

Container tags are validated on every request. A tag must:

- Be **100 characters or less**
- Contain only **alphanumeric characters, hyphens (`-`), underscores (`_`), and colons (`:`)**

Matching pattern: `^[a-zA-Z0-9_:-]+$`

```typescript
// ✅ Valid
"user_123"
"project-mobile-app"
"org:acme:user:john"
"tenant_42_workspace_7"

// ❌ Invalid — spaces, slashes, and other symbols are rejected
"user 123"
"project/mobile"
"team@acme"
```

The colon is intentionally allowed so you can build **hierarchical** tags (for example `org:acme:user:john`) that encode several levels of structure in a single identifier.

---

## `containerTag` vs `containerTags`

Supermemory's current API uses a **single** `containerTag` string per request.

<Warning>
The plural `containerTags` array field is **deprecated**. It still works for backward compatibility on older (`/v3`) endpoints, but new integrations should use the singular `containerTag` string. The `/v4` API only accepts `containerTag`.
</Warning>

| API field | Type | Status |
|-----------|------|--------|
| `containerTag` | `string` | ✅ Current — use this |
| `containerTags` | `string[]` | ⚠️ Deprecated |

---

## Where container tags are used

The same tag flows through the entire lifecycle of a memory. Pass it consistently and your data stays neatly partitioned.

| Operation | Behavior |
|-----------|----------|
| **Add** | Writes the memory into the tag's container (auto-creating the space). |
| **Search** | Restricts retrieval to the given tag's namespace. |
| **List** | Returns only memories belonging to the tag(s). |
| **Update / Delete** | Targets the memory inside the specified tag's container. |

```typescript
// Add
await client.add({ content: "Q1 planning notes", containerTag: "project_q1" });

// Search within the same container
await client.search.memories({ q: "planning", containerTag: "project_q1" });

// List everything in the container
await client.documents.list({ containerTags: ["project_q1"] });
```

---

## Access control

Container tags are also an **authorization boundary**, not just an organizational one. Two mechanisms can restrict which tags a given caller may touch:

- **API key scopes** — an API key can be limited to a specific set of container tags, with read or write permission per tag.
- **Member restrictions** — an organization member can be granted access to only certain container tags.

When a request is restricted, Supermemory validates the requested tag against the caller's allowed set:

- Requesting a tag outside the allowed set returns `403 Forbidden`.
- A write (add/update/delete) to a read-only tag returns `403 Forbidden`.
- If no tag is supplied by a restricted caller, the request is automatically scoped to their allowed tag(s).

This means you can hand out an API key that is physically incapable of reading or writing another tenant's data, enforced at the data layer rather than in your application code.

---

## Per-container settings

Each container tag can carry its own configuration, independent of other tags in the same organization:

| Setting | Purpose |
|---------|---------|
| `name` | A human-friendly display name for the container. |
| `entityContext` | A custom context prompt applied when processing documents in this container — useful for steering extraction and summarization per project or tenant. |

```typescript
await client.containerTags.update("project_research", {
entityContext: "This project contains research papers about machine learning.",
});
```

Container tags can also be **merged** when you need to consolidate two buckets of memories into one.

---

## Choosing a convention

Pick a tagging scheme that maps onto the isolation boundaries your application actually needs.

| Pattern | Example | Use case |
|---------|---------|----------|
| User isolation | `user_{userId}` | Per-user memory in a consumer app |
| Project grouping | `project_{projectId}` | Project- or workspace-scoped content |
| Agent scoping | `agent_{agentId}` | Separate long-term memory per AI agent |
| Hierarchical | `org:{orgId}:user:{userId}` | Multi-level, multi-tenant SaaS |

<Tip>
Keep tags **deterministic** — derive them directly from IDs you already have (a user ID, a tenant ID) so you can always reconstruct the right tag at query time without a lookup.
</Tip>

---

## Next steps

<CardGroup cols={2}>
<Card title="Organizing & Filtering" icon="filter" href="/concepts/filtering">
Combine container tags with metadata filters for precise retrieval.
</Card>
<Card title="Adding Memories" icon="plus" href="/add-memories">
See container tags in action across the add API.
</Card>
</CardGroup>
1 change: 1 addition & 0 deletions apps/docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"concepts/content-types",
"concepts/super-rag",
"concepts/memory-vs-rag",
"concepts/container-tags",
"concepts/filtering",
"concepts/user-profiles",
"concepts/customization",
Expand Down
Loading