> For the complete documentation index, see [llms.txt](https://docs.gitloom.cloud/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.gitloom.cloud/documentation/content/namespaces.md).

# Namespaces and multi-user patterns

A namespace is one memory: its own git repository, its own index, its own vectors. Two namespaces are two repositories under two different storage prefixes, so isolation is a storage boundary rather than a `WHERE` clause somebody can forget to write.

An account has many namespaces. A credential resolves to an account, never to a namespace — **which namespace a request touches is decided by your server, per request.**

### The rules

* **Names are `[a-z0-9-]`, 1–64 characters.** Anything else is `400 invalid_namespace`. Uppercase is not normalised, it is rejected. Neither is `_` or `.` accepted.
* **They are created explicitly.** Reading or writing an uncreated namespace is `404 namespace_not_found`, not an implicit create. A typo in a user id must not mint an empty memory and report success.
* **Creation is idempotent.** `201` the first time, `200` after; both are success.
* **`default` is an ordinary name.** Omitting `namespace` means `default`, and `default` must be created like any other.
* **Writes to one namespace serialise; different namespaces run in parallel.**
* **There is no delete.** Nothing in the API removes a namespace or a memory today.

### Creating and listing

```bash
curl -s -X POST "$GITLOOM_API/v1/namespaces" \
  -H "Authorization: Bearer $GITLOOM_KEY" \
  -H "content-type: application/json" \
  -d '{"namespace":"user-8213"}'
```

```json
{"namespace":"user-8213","created":true}
```

A second call:

```json
{"namespace":"user-8213","created":false}
```

:::note\[Send a body, always] A `POST /v1/namespaces` with no body at all is `400 invalid_body` — "a JSON body is required". To get `default`, send `{}` or `{"namespace":""}` explicitly. :::

```bash
curl -s "$GITLOOM_API/v1/namespaces" -H "Authorization: Bearer $GITLOOM_KEY"
```

```json
{"namespaces":["default","user-8213"]}
```

The listing returns the **first 200 namespaces and truncates silently**. There is no pagination and no count. If you expect more than 200, do not use this endpoint as your source of truth — your own user table already knows which namespaces exist.

### Pattern 1: one namespace per end user

The default, and the shape the isolation model was built for.

```ts
const ns = `user-${user.id}`

await gl("/v1/namespaces", { method: "POST", body: JSON.stringify({ namespace: ns }) })
```

Why per-user rather than one shared memory with a user tag:

* **Isolation is structural.** A retrieval cannot return another user's memory, because the other user's memories are not in the repository being searched.
* **Semantic search scans linearly** in the number of stored vectors. One namespace per user keeps each index small — this is a large part of why the model is shaped this way.
* **Write serialisation is per namespace.** One shared memory would serialise every write in your product behind one queue group.
* **Erasure is tractable.** When a delete endpoint exists, "forget this user" is one object prefix, not a filtered rewrite.

Deriving the name deterministically from your own user id is the important part. Do not store the namespace name as a separate mutable field, and do not use anything the user controls — an email address is not `[a-z0-9-]`.

```ts
// Safe: your own opaque id, prefixed so it can never collide with an internal name.
const ns = `user-${user.id}`

// Unsafe: user-supplied, wrong charset, and a 400 you will discover in production.
const ns = user.email
```

If your ids are UUIDs, they already fit the charset once lowercased. If they are integers, prefix them — a bare `8213` is legal but tells you nothing in a log line.

#### Creating it at the right moment

Create the namespace when you create the user, not on the first write. The write path checks existence at the front door and returns `404` while you are still listening, so the failure is loud — but recovering from it mid-conversation means the turn the user just had is the one you lose.

Creation is idempotent and cheap, so the belt-and-braces version is fine too:

```ts
try {
  await memory.remember(turns, { namespace: ns })
} catch (e) {
  if (e instanceof GitloomError && e.isNamespaceNotFound) {
    await memory.createNamespace(ns)
    await memory.remember(turns, { namespace: ns })
  } else throw e
}
```

### Pattern 2: one namespace per project or workspace

When the memory belongs to a team rather than a person — a shared assistant in a Slack channel, a per-repository code agent — name the namespace after the workspace:

```
account "acme"
├── workspace-42     ← shared by everyone in that workspace
├── workspace-91
└── default
```

The tradeoff is that everything one member tells the assistant is retrievable by every other member. That is usually the intent for a shared bot; it is never the intent for a consumer product.

### Pattern 3: one namespace, single-user

A personal tool, a single-tenant internal agent. Create `default`, never send a `namespace` parameter, and stop thinking about it.

```bash
curl -s -X POST "$GITLOOM_API/v1/namespaces" \
  -H "Authorization: Bearer $GITLOOM_KEY" \
  -H "content-type: application/json" -d '{}'
```

### Anti-pattern: a namespace per conversation

Do not do this. A namespace per session throws away the thing GitLoom is for — a memory is valuable precisely because it spans sessions. Use `session_id` on the write to tag which conversation a memory came from; the namespace is the *person*, not the chat.

### What isolation does and does not give you

**Does:** one namespace's retrieval cannot see another's content, at the storage layer. A container caches up to four warm namespaces at once and keys that cache by the exact account-and-namespace pair, so a shared Lambda container cannot cross the line either.

**Does not:** protect you from your own routing bug. An API key is account-wide. If your server passes the wrong namespace for the request it is handling, GitLoom will answer faithfully with the wrong user's memories. The namespace should be derived from the authenticated session on every request, in one place, and never accepted from a request parameter your client controls.

```ts
// Good: derived server-side from the session.
const ns = `user-${session.userId}`

// Catastrophic: the client chooses whose memory to read.
const ns = req.query.namespace
```

### Quotas are per account, not per namespace

The free plan's 200 writes / 5 000 reads / 2 000 memories per month are counted across **every namespace in the account**. Adding users does not add allowance. Check where you stand:

```bash
curl -s "$GITLOOM_API/v1/usage" -H "Authorization: Bearer $GITLOOM_KEY"
```

```json
{
  "period": "2026-07",
  "plan": "free",
  "used": {"writes": 12, "reads": 340, "memories": 87},
  "limits": {"writes": 200, "reads": 5000, "memories": 2000},
  "exceeded": false
}
```

Counters are month-keyed, so a new month starts from zero with no reset to wait for. Exceeding a meter returns `429 quota_exceeded` on the affected route.

### Multi-tenant SaaS: one account or many?

If you are building a product whose customers each have many end users, the natural shape is **your GitLoom account : your customers : their users = 1 : n : m**, with one namespace per end user and a naming convention that encodes both levels:

```
c42-u8213
c42-u9007
c91-u1004
```

A single account means one credential to manage and one quota to watch. It also means one blast radius: a leaked key exposes every customer. Separate GitLoom accounts per customer give a real credential boundary, at the cost of managing signups and quotas per customer — and there is no API for provisioning accounts today, so that path is manual. For most launches, one account with a strict server-side namespace derivation is the right call.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.gitloom.cloud/documentation/content/namespaces.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
