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

# Authentication

> Bearer API keys, key handling, and the errors you get when authentication fails.

Every Anona Memory **data-plane** request — record, retrieve, reason, spaces,
documents and the proxies — authenticates with a Bearer API key. A handful of
control-plane endpoints do not take one at all: [API keys](https://docs.memory.anonalabs.com/api-reference/api-keys.md),
[organization](https://docs.memory.anonalabs.com/api-reference/organization.md), [billing](https://docs.memory.anonalabs.com/api-reference/billing.md),
space membership and `GET /v1/usage` authenticate with a **dashboard session**
instead, and answer `401 session_required` to an API key. A few of them also
accept the token `anona login --cli` stores; see
[which credential each route accepts](#which-credential-each-route-accepts).

```bash cURL theme={null}
curl -X POST https://api.anonalabs.com/v1/record \
  -H "Authorization: Bearer anona_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"space_id": "customer-support-bot", "content": "User data"}'
```

```python Python theme={null}
from anona import AnonaClient

client = AnonaClient(api_key="anona_live_YOUR_KEY")

client.record(space_id="customer-support-bot", content="User data")
```

```typescript TypeScript theme={null}
import { Anona } from "@anona-labs/memory";

const anona = new Anona({ apiKey: "anona_live_YOUR_KEY" });

await anona.record({ spaceId: "customer-support-bot", content: "User data" });
```

The SDKs set the header for you. Both take the key as a constructor argument and
neither reads an environment variable on its own.

## Getting a key

**1. Sign up and verify**

Create an account at the [dashboard](https://memory.anonalabs.com) and verify your
email. Sign-in is blocked until you do.

**2. Create the key**

Go to **API keys → New key**, choose an environment, and give the key a name.
Signing up does not create one for you.

**3. Copy it immediately**

The full key is shown once. Afterwards only its prefix is visible, in the dashboard
and in the API.

| Environment | Format | Intended for |
| - | - | - |
| `live` | `anona_live_<random>` | Production traffic |
| `test` | `anona_test_<random>` | Local development and CI |

Keys can also be created and revoked programmatically. See
[API Keys](https://docs.memory.anonalabs.com/api-reference/api-keys.md).

## Handling keys safely

| Practice | Why |
| - | - |
| Treat the key like a password | It carries your organization's full data-plane access. |
| Never commit it to version control | Load it from an environment variable or a secret manager instead. |
| Never call the API from a browser | A client-side key is public. Proxy through your own backend. |
| Use `test` keys outside production | Keeps development traffic off your production space and quota. |
| Rotate on a schedule, or immediately on exposure | Create the new key, cut traffic over, then revoke the old one. |

```bash Shell theme={null}
export ANONA_API_KEY="anona_live_YOUR_KEY"
```

```python Python theme={null}
import os
from anona import AnonaClient

client = AnonaClient(api_key=os.environ["ANONA_API_KEY"])
```

```typescript TypeScript theme={null}
import { Anona } from "@anona-labs/memory";

const anona = new Anona({ apiKey: process.env.ANONA_API_KEY! });
```

## Organization and space context

Every request runs in the context of the organization behind the API key, plus the
`space_id` you supply. The boundary is enforced at the gateway and again at the
database layer, so another organization's space is not reachable by naming it.

The one way across that boundary is a **share**: another organization can invite you
to one of its spaces, and from then on it is addressable with your own key like any
other. An API key acts as the member who created it, so it reaches, and lists, the
spaces shared with that person. Removing a member from the organization revokes
the keys they created. A shared space comes back from `GET /v1/spaces` carrying `shared_by` (the
owning organization's name) and `qualified_id`. When a shared name collides with one
of your own — a space's id is its name, so both can be `default` — the bare name is
refused with `409 space_ambiguous` and you address it by the qualified
`{owner_slug}:{space_name}` form instead. See
[Space members](https://docs.memory.anonalabs.com/api-reference/spaces.md#space-members).

## Which credential each route accepts

| Credential | Data plane | Control plane |
| - | - | - |
| API key (`anona_live_` / `anona_test_`) | Yes | No |
| Temporary profile (`anona start`) | Yes | No |
| `anona login --cli` | Yes | The routes listed below only |
| Dashboard session | Yes | Yes |

A token from `anona login --cli` is accepted on this fixed set of control-plane
routes, and answers with the same body a dashboard session gets:

| Method | Route |
| - | - |
| `GET` | `/v1/orgs/me`, `/v1/orgs` |
| `GET` | `/v1/projects` |
| `GET` | `/v1/api-keys` (lists keys; never returns a secret) |
| `GET` | `/v1/usage`, `/v1/usage/breakdown` |
| `GET` | `/v1/billing`, `/v1/billing/topups` |
| `GET` | `/v1/members`, `/v1/members/me`, `/v1/members/invitations` |
| `GET` | `/v1/invitations/pending` |
| `GET` | `/v1/spaces/{space_id}/members` (owner or admin) |
| `POST` | `/v1/invitations/accept`, `/v1/invitations/decline` |
| `GET`, `POST` | `/v1/spaces/{space_id}/exports` |
| `GET` | `/v1/spaces/{space_id}/exports/{job_id}/download` |

Everything else on the control plane, including creating or revoking a key,
changing plan, renaming or deleting an organization and inviting or removing
members (of the organization or of a space), stays dashboard-only. A CLI token's role and organization are read from
your membership on every request, not from the token, so a removal or a change of
role applies to it within 30 seconds. `X-Anona-Org` selects one of your other
organizations, as it does on the data plane.

An API key or a temporary-profile token sent to one of the routes above gets
`401 session_required` with the message: This needs a dashboard session or
`anona login --cli`; an API key or temporary profile cannot use it.

## Rate limiting

Requests to **metered endpoints** — the ones that spend credits — are rate-limited per
organization according to your plan. Current limits are returned on every response from
one of those endpoints:

```
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Window
X-Credits-Remaining
```

Exceeding the limit returns `429` with code `rate_limited`. Retry with exponential
backoff. See [Usage](https://docs.memory.anonalabs.com/api-reference/usage.md) for reading your limits directly.

## Authentication errors

```json theme={null}
{
  "error": {
    "code": "invalid_api_key",
    "message": "Invalid API key",
    "request_id": "req_..."
  }
}
```

Each cause has its own `code`, and that is what to branch on — never the generic
`unauthorized` / `forbidden`, which no endpoint returns. The
[error reference](https://docs.memory.anonalabs.com/api-reference/errors.md) is the canonical list.

| Cause | Code | Fix |
| - | - | - |
| Header missing | `401 missing_auth` | Send `Authorization: Bearer <key>` on every request. |
| Wrong header format | `401 invalid_token` | The `Bearer` scheme is required: a bare `Authorization: <key>` is rejected. `x-api-key: <key>` **is** accepted on every data-plane endpoint, which is how the Anthropic SDK sends it. |
| Key revoked or invalid | `401 invalid_api_key` | Create a new key in the dashboard. |
| Key past its expiry date | `401 key_expired` | Rotate it. Expiry cannot be extended. |
| Key belongs to another organization | `403 space_access_denied`, or `404 space_not_found` | Each organization's data is its own schema, so another organization's space is not merely forbidden, it is not there. Use a key issued by the organization that owns the space, or have them share it with you. |
| A bare space name matches both your own space and a shared one | `409 space_ambiguous` | Address it by the `qualified_id` (`{owner_slug}:{space_name}`) returned by `GET /v1/spaces`. |
| An API key or temporary-profile token sent to a dashboard-only endpoint | `401 session_required` | The key may be perfectly valid — it is the wrong kind of credential there. Sign in to the dashboard, or, on the [routes the CLI may use](#which-credential-each-route-accepts), run `anona login --cli`. |

## Next steps

> **[Memories API](https://docs.memory.anonalabs.com/api-reference/memories.md)**
>
> Store and manage memories.

> **[Retrieve API](https://docs.memory.anonalabs.com/api-reference/retrieve.md)**
>
> Query memories semantically.

> **[API Keys](https://docs.memory.anonalabs.com/api-reference/api-keys.md)**
>
> Create and revoke keys over the API.

> **[Errors](https://docs.memory.anonalabs.com/api-reference/errors.md)**
>
> The full error code reference.


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