> ## Documentation Index
> Fetch the complete documentation index at: https://docs.memory.anonalabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# User Profiles

> Everything a space has learned about one end user, and a scoped question-and-answer over just their memories.

If your app [scopes writes and reads by `user_id`](https://docs.memory.anonalabs.com/guides/scoping-multi-tenant), a space
accumulates a per-user history without anywhere to address it directly; you'd otherwise
reconstruct it yourself with `retrieve` and a wide-open query. These two endpoints are that
address: one reads back what a space knows about a user, the other asks a question that is
answered from that user's memories only.

> [!NOTE]
>
> Both endpoints require `user_id` to already be a scope your app uses. There is nothing to
> create or register up front. See [An unknown user is not a 404](#an-unknown-user-is-not-a-404)
> below.

## Get a user's profile

```http theme={null}
GET /v1/spaces/{space_id}/users/{user_id}/profile
Authorization: Bearer anona_live_YOUR_KEY
```

| Query parameter | Type | Default | Description |
| - | - | - | - |
| `limit` | integer | `50` | Maximum memories to return, 1 to 200. |
| `offset` | integer | `0` | Pagination offset. |
| `memory_type` | string | none | Restrict to one layer, e.g. `note` for only the synthesized view. Omit for everything known about this user, collapsed so each piece of knowledge appears once. See [below](#the-default-view-is-one-layer). |
| `format` | string | `results` | `results` returns only `memories`. `block` also renders a prompt-ready `context` string, the same renderer [Retrieve](https://docs.memory.anonalabs.com/api-reference/retrieve.md#prompt-ready-context) uses. |
| `context_max_tokens` | integer | none | Approximate token ceiling for that block. Only applies when `format` is `block`. |

**Response** `200 OK`

```json theme={null}
{
  "space_id": "customer-support-bot",
  "user_id": "alice_123",
  "memory_count": 14,
  "first_seen": "2026-05-02T09:12:00Z",
  "last_active": "2026-08-15T18:40:11Z",
  "memories": [
    {
      "id": "mem_abc123def456",
      "content": "Alice prefers email over phone for support follow-ups.",
      "text": "Alice prefers email over phone for support follow-ups.",
      "context": "support call",
      "date": "2026-08-15T18:40:11Z",
      "type": "note",
      "entities": "Alice",
      "metadata": { "source": "crm" },
      "source_ids": ["mem_9f21e2", "mem_9f2c04"],
      "user_id": "alice_123",
      "agent_id": null,
      "session_id": null,
      "member_id": null
    }
  ]
}
```

| Field | Type | Description |
| - | - | - |
| `space_id` | string | The space this profile was read from. |
| `user_id` | string | The end user, as passed in the path. |
| `memory_count` | integer | How many memories carry this user's scope, under whatever `memory_type` filter was sent. `0` for a user who has never been written to. See below. **This number can go down as well as up**. See [The count is not monotonic](#the-count-is-not-monotonic). |
| `first_seen` | string or null | When this user's earliest memory was learned, taken from record time, not the event it describes. `null` if `memory_count` is `0`, or if the oldest memory has no recorded timestamp. |
| `last_active` | string or null | When this user's most recent memory was learned. `null` if `memory_count` is `0`. |
| `memories` | array | The same item shape [`GET /v1/spaces/{space_id}/memories`](https://docs.memory.anonalabs.com/api-reference/memories#list-memories-in-a-space) returns. |
| `context` | string or null | Present only when `format` is `block`. |
| `token_estimate` | integer or null | Present only when `format` is `block`. Derived from a characters-per-token approximation, not a tokenizer, so treat it as a guide, not an exact count. |

```bash cURL theme={null}
curl "https://api.anonalabs.com/v1/spaces/customer-support-bot/users/alice_123/profile?limit=20" \
  -H "Authorization: Bearer anona_live_YOUR_KEY"
```

```python Python theme={null}
profile = client.get_user_profile(
    space_id="customer-support-bot",
    user_id="alice_123",
    limit=20,
)
print(profile["memory_count"], profile["last_active"])
```

```typescript TypeScript theme={null}
const profile = await anona.getUserProfile({
  spaceId: "customer-support-bot",
  userId: "alice_123",
  limit: 20,
});
console.log(profile.memory_count, profile.last_active);
```

### Prompt-ready profile

Pass `format=block` to also get a joined, token-budgeted string, ready to drop into a
system prompt, the same rendering `retrieve` uses.

```bash cURL theme={null}
curl "https://api.anonalabs.com/v1/spaces/customer-support-bot/users/alice_123/profile?format=block&context_max_tokens=500" \
  -H "Authorization: Bearer anona_live_YOUR_KEY"
```

```python Python theme={null}
data = client.get_user_profile(
    space_id="customer-support-bot",
    user_id="alice_123",
    format="block",
    context_max_tokens=500,
)

messages = [
    {"role": "system", "content": f"What we know about this user:\n{data['context']}"},
    {"role": "user", "content": user_message},
]
```

```typescript TypeScript theme={null}
const data = await anona.getUserProfile({
  spaceId: "customer-support-bot",
  userId: "alice_123",
  format: "block",
  contextMaxTokens: 500,
});

const messages = [
  { role: "system", content: `What we know about this user:\n${data.context}` },
  { role: "user", content: userMessage },
];
```

```json theme={null}
{
  "context": "1. Alice prefers email over phone for support follow-ups.\n2. Alice is on the Scale plan.",
  "token_estimate": 24,
  "memory_count": 14,
  "memories": [ "…unchanged…" ]
}
```

## Ask about a user

Runs a synthesis pass over one user's memories only, and returns a single answer instead
of a list. Scope is not a filter you can relax here: it is forced to `all_strict`, so the
answer only ever comes from memories written under this `user_id`.

```http theme={null}
POST /v1/spaces/{space_id}/users/{user_id}/ask
Authorization: Bearer anona_live_YOUR_KEY
Content-Type: application/json
```

```json theme={null}
{
  "query": "What does Alice care about most in support interactions?",
  "model": "balanced"
}
```

| Parameter | Type | Required | Description |
| - | - | - | - |
| `query` | string | Yes | What you want to learn about this user. |
| `model` | string | No | Which LLM answers. A tier name (`fast`, `balanced`) or a model id, from the same catalog [Reason](https://docs.memory.anonalabs.com/api-reference/reason.md) uses, and credits are charged at that model's rate. See [model selection](https://docs.memory.anonalabs.com/api-reference/model-selection.md). Omit it to use the space's reason default, then the platform default. |
| `depth` | string | No | `fast` (default) or `thorough`. How hard to look before answering, the same field [Reason](https://docs.memory.anonalabs.com/api-reference/reason.md#depth) takes. Omit it to use the space's reason default, then `fast`. |

**Response** `200 OK`

```json theme={null}
{
  "space_id": "customer-support-bot",
  "user_id": "alice_123",
  "insights": "Alice values fast, low-friction resolutions and prefers email confirmation over phone calls.",
  "usage": {
    "input_tokens": 210,
    "output_tokens": 42
  },
  "model": "openai.gpt-oss-120b-1:0"
}
```

| Field | Type | Description |
| - | - | - |
| `space_id` | string | The space this answer was synthesized from. |
| `user_id` | string | The end user this answer is about. |
| `insights` | string or null | The synthesized answer, or `null` when nothing was found for this user. |
| `usage` | object or null | Token usage for the synthesis call. |
| `model` | string or null | The model that actually answered, not necessarily the one requested, since omitting `model` resolves a default. Reconcile the credits on this call against this field, not against what you asked for. |

> [!NOTE]
>
> `model` and `depth` both resolve **request → the space's reason default → the platform
> default**, the same ladder [`/v1/reason`](https://docs.memory.anonalabs.com/api-reference/reason) uses and from the same
> stored settings. So configuring a space once covers both surfaces, and a per-question
> value here overrides it without changing what is stored.
>
> `depth: "thorough"` is worth more on this endpoint than on the space-wide one: a single
> user's [memory models](https://docs.memory.anonalabs.com/api-reference/models.md) are refreshed less often than the space's,
> so a standing answer about one person is the more likely one to have gone stale.

```bash cURL theme={null}
curl -X POST https://api.anonalabs.com/v1/spaces/customer-support-bot/users/alice_123/ask \
  -H "Authorization: Bearer anona_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "What does Alice care about most in support interactions?"}'
```

```python Python theme={null}
answer = client.ask_about_user(
    space_id="customer-support-bot",
    user_id="alice_123",
    query="What does Alice care about most in support interactions?",
)
print(answer["insights"])
```

```typescript TypeScript theme={null}
const answer = await anona.askAboutUser({
  spaceId: "customer-support-bot",
  userId: "alice_123",
  query: "What does Alice care about most in support interactions?",
});
console.log(answer.insights);
```

## Two things worth knowing

### An unknown user is not a 404

A `user_id` is not a resource you create; it's a scope tag stamped onto memories the
first time you record with that `user_id`. There's no user registry, so there's no valid
set for a typo to fall outside of. A `user_id` nobody has ever recorded under still
returns `200`:

```json theme={null}
{
  "space_id": "customer-support-bot",
  "user_id": "does-not-exist-yet",
  "memory_count": 0,
  "first_seen": null,
  "last_active": null,
  "memories": []
}
```

A `404 space_not_found` is still returned for an unknown **space**, because a space is a real
object your account owns, so that stays a real error.

### The default view is one layer

Omitting `memory_type` returns each piece of knowledge about the user **once**: the
consolidated `note` where one has been synthesized, the raw `fact` where it hasn't yet.
Consolidation runs on a background loop, so a user active in the last few minutes may
show up as raw facts with no note above them yet. That's expected, not a gap.

Pass `memory_type=note` to see only the synthesized layer. `memory_type=fact` is **not**
the full evidence set: this endpoint always collapses a fact into its consolidation once
one exists, so `memory_type=fact` here returns only facts that have not been consolidated
yet, a set that shrinks toward empty as consolidation catches up. To read every raw fact
regardless of consolidation state, use
[`GET /v1/spaces/{space_id}/memories`](https://docs.memory.anonalabs.com/api-reference/memories#list-memories-in-a-space)
with `type=fact&prefer_observations=false`. See
[Memory types](https://docs.memory.anonalabs.com/api-reference/retrieve.md#memory-types) for the full set of layers.

### The count is not monotonic

Because the default view collapses layers, **`memory_count` can decrease between
two reads even though nothing was deleted and more was written in between.**

Consolidation folds several raw facts into a single note. Before it runs, those
facts are counted individually; afterwards they are hidden behind the one note
that replaced them, and the count drops. A profile observed during a bulk import
can genuinely read 115, then 67 a few minutes later, while the underlying corpus
grew the whole time.

Nothing is lost. To see the full corpus including every raw fact regardless of
consolidation state, use
[`GET /v1/spaces/{space_id}/memories`](https://docs.memory.anonalabs.com/api-reference/memories#list-memories-in-a-space)
with `prefer_observations=false`, and that total only ever grows.

Treat `memory_count` as "how many distinct things we currently know about this
user", not as an ingestion counter. If you need a stable number to show users or
alert on, take it from the uncollapsed list instead.

## Scope must match exactly

`user_id` here has to be the **same value** your app passes when recording. See
[Scoping](https://docs.memory.anonalabs.com/guides/scoping-multi-tenant.md). A profile only sees memories written **with**
that scope, so:

* A typo'd `user_id` on either side silently looks like an empty profile, not an error.
* If your space has history that predates adopting scoping, that older, unscoped history
  will not appear here. Backfill the scope onto it if these endpoints need to see it too.

## Pricing

| Operation | Credits | Notes |
| - | - | - |
| Get profile | 1 | Same base cost as [Search](https://docs.memory.anonalabs.com/api-reference/retrieve.md). |
| Ask about a user | 5, plus token usage | Priced the same as [Reason](https://docs.memory.anonalabs.com/api-reference/reason.md), since a synthesis pass runs behind it. |

## Error responses

| Status | Code | Cause |
| - | - | - |
| 401 | `missing_auth` / `invalid_api_key` / `key_expired` | No `Authorization` header, a key Anona did not issue or has revoked, or a key past its expiry date. |
| 403 | `space_access_denied` | You are not a member of this space. |
| 403 | `space_unavailable` | The space is shared, but the owner can't be reached: the owning organization has revoked its API keys and holds no live one, or was deleted. A share is served while the owner holds a live key, and also when it has never created a key or holds only expired ones. |
| 404 | `space_not_found` | No space with that id. |
| 409 | `space_ambiguous` | This name exists both as a space you own and as one shared with you. Address it with the qualified form, `{owner}:{space_name}`. |
| 422 | `invalid_scope` | `user_id` is empty, or is not 1-128 characters of letters, digits, or `._@=+-` (no spaces or colons). |
| 429 | `rate_limited` / `credits_exhausted` | Rate limit or credit quota exceeded. |

See the full [error reference](https://docs.memory.anonalabs.com/api-reference/errors.md).

## Next steps

> **[Scoping guide](https://docs.memory.anonalabs.com/guides/scoping-multi-tenant.md)**
>
> How `user_id` isolates memories inside one space.

> **[Reason API](https://docs.memory.anonalabs.com/api-reference/reason.md)**
>
> Synthesize an answer across an entire space, not just one user.

> **[Retrieve API](https://docs.memory.anonalabs.com/api-reference/retrieve.md)**
>
> Rank a space's memories against a query.

> **[MCP Integration](https://docs.memory.anonalabs.com/mcp-integration.md)**
>
> Read a user's profile from an MCP-connected assistant.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.