> ## Documentation Index
> Fetch the complete documentation index at: https://docs.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`](/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.
</Note>

## 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](/api-reference/retrieve#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",
      "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`](/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 — treat it as a guide, not an exact count.                                                                                                          |

<CodeGroup>
  ```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}
  import requests

  resp = requests.get(
      "https://api.anonalabs.com/v1/spaces/customer-support-bot/users/alice_123/profile",
      headers={"Authorization": "Bearer anona_live_YOUR_KEY"},
      params={"limit": 20},
  )
  profile = resp.json()
  print(profile["memory_count"], profile["last_active"])
  ```
</CodeGroup>

### 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.

<CodeGroup>
  ```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}
  resp = requests.get(
      "https://api.anonalabs.com/v1/spaces/customer-support-bot/users/alice_123/profile",
      headers={"Authorization": "Bearer anona_live_YOUR_KEY"},
      params={"format": "block", "context_max_tokens": 500},
  )
  data = resp.json()

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

```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?"
}
```

| Parameter | Type   | Required | Description                             |
| --------- | ------ | -------- | --------------------------------------- |
| `query`   | string | Yes      | What you want to learn about this user. |

**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
  }
}
```

| 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.                                     |

<CodeGroup>
  ```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}
  resp = requests.post(
      "https://api.anonalabs.com/v1/spaces/customer-support-bot/users/alice_123/ask",
      headers={"Authorization": "Bearer anona_live_YOUR_KEY"},
      json={"query": "What does Alice care about most in support interactions?"},
  )
  print(resp.json()["insights"])
  ```
</CodeGroup>

## 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** — 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`](/api-reference/memories#list-memories-in-a-space)
with `type=fact&prefer_observations=false`. See
[Memory types](/api-reference/retrieve#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`](/api-reference/memories#list-memories-in-a-space)
with `prefer_observations=false` — 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](/guides/scoping-multi-tenant). 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](/api-reference/retrieve).                                  |
| Ask about a user | 5, plus token usage | Priced the same as [Reason](/api-reference/reason) — a synthesis pass runs behind it. |

## Error responses

| Status | Code                                 | Cause                                                                                                                            |
| ------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| 401    | `unauthorized`                       | Missing or invalid API key.                                                                                                      |
| 403    | `forbidden`                          | The space is not owned by your organization.                                                                                     |
| 403    | `space_unavailable`                  | The space is shared, but the owning organization has no active API key, so the grant can't be honored.                           |
| 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](/api-reference/errors).

## Next steps

<CardGroup cols={2}>
  <Card title="Scoping guide" icon="user-group" href="/guides/scoping-multi-tenant">
    How `user_id` isolates memories inside one space.
  </Card>

  <Card title="Reason API" icon="lightbulb" href="/api-reference/reason">
    Synthesize an answer across an entire space, not just one user.
  </Card>

  <Card title="Retrieve API" icon="magnifying-glass" href="/api-reference/retrieve">
    Rank a space's memories against a query.
  </Card>

  <Card title="MCP Integration" icon="plug" href="/mcp-integration">
    Read a user's profile from an MCP-connected assistant.
  </Card>
</CardGroup>
