---
title: "User Profiles"
sidebarTitle: "User Profiles"
description: "What a profile is, how supermemory derives it from a container's memories, and how to inject it into a prompt without wrecking your cache"
icon: "circle-user"
---
Every container tag has a profile: supermemory's current understanding of the entity that container is about, derived from everything you've ingested there. You don't write it, update it, or manage it. You fetch it:
```typescript TypeScript
import Supermemory from "supermemory";
const client = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY });
const { profile } = await client.profile({
containerTag: "user_4f8a",
});
```
```python Python
from supermemory import Supermemory
client = Supermemory()
result = client.profile(container_tag="user_4f8a")
```
```bash cURL
curl -X POST "https://api.supermemory.ai/v4/profile" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"containerTag": "user_4f8a"}'
```
And you get back two arrays of plain sentences:
```json
{
"profile": {
"static": [
"Sarah is a senior engineer at Meridian, working on the payments platform",
"Sarah prefers short, technical answers with code over prose",
"Sarah works from Lisbon, usually async"
],
"dynamic": [
"[Recent] Sarah's being promoted to VP of Product",
"[Recent] Sarah is migrating the billing service off the legacy queue",
"[2026-07-14] Sarah hit a deadlock in the payout worker and is still debugging it"
]
}
}
```
That's the whole interface. The interesting part is where those sentences come from, and what to do with them.
## The profile samples the container
A profile isn't a record you populate — it's derived. When you [add content](/add-memories), the ingestion pipeline extracts memories, connects them into the [graph](/concepts/graph-memory), and continuously distills the container's memories into a compact summary of the entity behind them. The profile *samples the container*: it's the engine's answer to "given everything in here, what should an assistant know about this entity right now?"
```mermaid
graph LR
A[documents you ingest] --> B[derived memories]
B --> C[knowledge graph]
B --> D["profile (per container tag)"]
C --> D
```
Two consequences fall out of that:
**One container tag, one profile.** The profile is scoped to exactly one container. It never reads across container tags — a profile call for `user_4f8a` can't surface anything derived from another user's container, full stop. That's the same isolation boundary that governs [search and writes](/concepts/permissioning), and it's why the standard pattern is one container tag per user.
**Everything in the container is fair game.** If you put ten users' conversations in one container, the profile blends all ten — because as far as the engine can tell, that's one entity. It can bite you in a subtler way too: ingest a user's email and the profile can pick up facts about the people they *correspond with*, not just the user. The fix is `entityContext` — tell the engine who the container is about ("This container is about Sarah Chen, a Meridian employee; other people mentioned are her contacts, not the subject") and extraction prioritizes accordingly. See [Customization](/concepts/customization).
## Static vs dynamic
The two arrays split by how long-lived a fact is, not by topic:
- **`static`** — durable facts: who they are, what they do, standing preferences. This is the stuff that's true next month.
- **`dynamic`** — recent episodes and current state: what they're working on, what happened lately. Entries carry `[Recent]` or `[YYYY-MM-DD]` prefixes so your model can weigh recency; strip them if you only want the text.
Static populates as the engine sees the same durable facts hold up across ingestions — a brand-new container won't have a settled static section after one message, and early on you'll mostly see dynamic entries. {/* CONFIRM: exact static-population timing/threshold */} Older dynamic context doesn't pile up forever either: it gets periodically consolidated into denser summaries, so the profile stays compact instead of growing with the container.
Beyond the lifespan axis, you can define **buckets** — topical categories like `preferences` or `goals` that a classifier assigns memories to at ingestion time. Every org starts with a built-in `preferences` bucket; you define more in the console, and request them with `include: ["buckets"]` on the profile call. Buckets and `filterPrompt` (your rules for what's worth remembering at all) are the two levers that shape what lands in a profile — bucket descriptions steer where facts get filed, `filterPrompt` steers what gets extracted in the first place. Mechanics for both are in the [profile API reference](/user-profiles) and [Customization](/concepts/customization).
## Combine profile with search
Pass `q` and the same call also runs a search in that container, returned alongside the profile:
```typescript TypeScript
const result = await client.profile({
containerTag: "user_4f8a",
q: "billing migration",
});
result.profile.static; // who Sarah is
result.profile.dynamic; // what she's up to
result.searchResults?.results; // memories matching "billing migration"
```
```python Python
result = client.profile(
container_tag="user_4f8a",
q="billing migration",
)
result.profile.static
result.profile.dynamic
result.search_results.results if result.search_results else []
```
```bash cURL
curl -X POST "https://api.supermemory.ai/v4/profile" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"containerTag": "user_4f8a", "q": "billing migration"}'
```
`searchResults` only appears when you pass `q`. This is the one-round-trip context call: broad understanding plus query-specific memories, instead of a profile fetch and a separate [search](/search).
## Inject it without wrecking your prompt cache
The profile is built to sit in a prompt: plain sentences, and compact — it stays within roughly a 1k-token budget rather than growing with the container. But *where* you put each piece matters, because prompt caching works on stable prefixes.
The pattern that works:
- **Static profile goes in the system prompt.** It changes rarely, so the prefix stays byte-identical across turns and your provider's prompt cache keeps hitting.
- **Dynamic context (and `q` results) get appended to the user message.** They change turn to turn — put them in the system prompt and you bust the cache on every message.
To wire that up:
```typescript
async function buildMessages(userId: string, userMessage: string) {
const result = await client.profile({ containerTag: userId, q: userMessage });
const system = `You are a personal assistant.
About this user:
${result.profile.static?.join("\n") ?? "No profile yet."}`;
const memories = result.searchResults?.results
?.map((r) => r.memory)
.join("\n");
// dynamic + search context ride with the message, not the cached prefix
const user = `${userMessage}
${result.profile.dynamic?.join("\n") ?? ""}
${memories ?? ""}
`;
return [
{ role: "system", content: system },
{ role: "user", content: user },
];
}
```
If you're on the Vercel AI SDK, `withSupermemory` does this injection for you — see the [AI SDK integration](/integrations/ai-sdk).
The profile call is a read — it doesn't count as ingestion, so calling it on every message costs you latency, not quota. It's fast enough to sit in the hot path. {/* CONFIRM: profile latency ~100ms publishable */}
## What profiles are not
**Not a key-value store.** There's no API to set a profile field, and that's deliberate — the profile is derived understanding, so it stays consistent with the memories underneath it. If you need to assert a fact, ingest it ("Sarah's preferred language is Portuguese") and the profile absorbs it.
**Not cross-user data.** A profile can't aggregate across users, and you shouldn't try to make it — a shared "everyone" container gives you a mushy profile of no one. For team-wide knowledge, use a shared container for the *content* and per-user containers for the *people*; the [multi-tenant pattern](/patterns/multi-tenant-saas) shows the split.
**Not a replacement for search.** The profile answers "who is this entity" in ~1k tokens. Specific recall — "what did Sarah say about the payout worker" — is a [search](/concepts/hybrid-search) question. Use both: profile as the standing context, search (or the `q` param) for the details a given message needs.
That's it — your assistant opens every conversation already knowing who it's talking to.
## Where next
- [Profile API reference](/user-profiles) — every parameter, buckets, response schema
- [Permissioning](/concepts/permissioning) — container tags, isolation, scoped keys
- [Customization](/concepts/customization) — `entityContext`, `filterPrompt`, and shaping extraction
- [AI SDK integration](/integrations/ai-sdk) — automatic profile injection with `withSupermemory`