> 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/authentication.md).

# Authentication

Two credentials reach the same API. An **API key** is what your server uses. A **dashboard session** is a Cognito ID token, held by the browser app at `https://app.gitloom.cloud`. Both are presented the same way:

```
Authorization: Bearer <credential>
```

Authorization happens at the gateway, in a Lambda authorizer, before any handler runs. It accepts either credential and resolves both to exactly one account. A missing, malformed, expired or revoked credential is `401` with no detail about which — telling you which half of a guess was right is a gift to whoever is guessing.

`GET /health` is the only unauthenticated route.

### Which credential am I holding?

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

```json
{"account":"acme","auth":"api_key","env":"live"}
```

| field     | values                                                       |
| --------- | ------------------------------------------------------------ |
| `account` | your account id — the owner of every namespace you can reach |
| `auth`    | `api_key` or `jwt`                                           |
| `env`     | `live` or `test` for a key; empty for a dashboard session    |

### API keys

A key looks like this:

```
gl_live_3f9a2b7c1d4e6f80_kQ8vN2xR7mZpLd3Wc0Ty5Ub9Aj1Hs4Ge
└┬─┘└─┬┘ └──────┬───────┘ └──────────────┬──────────────┘
 │    │         │                        └─ secret: 24 random bytes, base64url
 │    │         └─ key id: 16 hex chars, appears in logs, used to revoke
 │    └─ environment: live | test
 └─ prefix, so secret scanners can match it
```

Only the SHA-256 of the secret is stored. The full key exists exactly once, in the `201` response that created it. There is no endpoint that returns it again, and no operator can recover it. Lost key: revoke it, mint another.

#### Creating a key

Key management requires a **dashboard session**. An API key calling any `/v1/keys` route gets:

```json
{"error":{"code":"dashboard_only","message":"API keys cannot manage API keys; sign in to the dashboard"}}
```

`403`. That is the whole point: a leaked key that can mint its own replacements survives the revocation meant to contain it.

With a dashboard token:

```bash
curl -s -X POST "$GITLOOM_API/v1/keys" \
  -H "Authorization: Bearer $ID_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"production worker","env":"live"}'
```

```json
{
  "id": "3f9a2b7c1d4e6f80",
  "env": "live",
  "name": "production worker",
  "key": "gl_live_3f9a2b7c1d4e6f80_kQ8vN2xR7mZpLd3Wc0Ty5Ub9Aj1Hs4Ge",
  "note": "store this now; it cannot be retrieved again"
}
```

`env` defaults to `live` when omitted. Anything other than `live` or `test` is `400 invalid_env`.

#### Listing and revoking

```bash
curl -s "$GITLOOM_API/v1/keys" -H "Authorization: Bearer $ID_TOKEN"
```

```json
{"keys":[{"id":"3f9a2b7c1d4e6f80","env":"live","name":"production worker","revoked":false,"created_at":"2026-07-31T09:14:02Z"}]}
```

Revoke by key id — the middle segment, never the secret:

```bash
curl -s -X DELETE "$GITLOOM_API/v1/keys/3f9a2b7c1d4e6f80" \
  -H "Authorization: Bearer $ID_TOKEN"
```

```json
{"id":"3f9a2b7c1d4e6f80","revoked":true}
```

An unknown id is `404 not_found`.

:::caution\[Revocation takes up to 30 seconds] Authorization results are cached at the gateway for 30 seconds so an agent calling with the same key every turn does not pay a lookup each time. A revoked key therefore keeps working for up to half a minute. Treat revocation as "stops within a minute". :::

#### What a key can do

Everything except key management: create and list namespaces, write memories, retrieve, read usage. There is no scoping — **a key is account-wide read and write over every namespace**. Two consequences:

* Keys belong on a server. A key in a browser bundle, a mobile app, or a public repo is every one of your users' memories.
* Per-user isolation is a property of *which namespace your server asks for*, not of the credential. See [Namespaces and multi-user patterns](https://github.com/MelloB1989/gitloom/tree/main/namespaces/README.md).

#### `live` and `test` are not a data boundary

The environment is in the prefix and reported by `whoami`, and that is all it does today. A `gl_test_` key reads and writes exactly the same namespaces as a `gl_live_` key on the same account. It is useful for spotting a production credential pasted into a test config, and for secret scanners. It is not a sandbox. If you need one, use a separate namespace — that *is* a hard boundary — or a separate account.

### Dashboard sessions

The dashboard signs in through the Cognito hosted UI and calls the same API with the resulting **ID token** (not the access token). The account comes from the `custom:tenant_id` claim, which a Pre-Token-Generation trigger injects from DynamoDB at sign-in — the user cannot set it, and the app client's writable attributes are restricted so a signup cannot claim someone else's tenant.

Tokens expire. A `401` in the dashboard means refresh the session, not that anything is wrong with the account.

### Browsers and CORS

CORS on the API allows **`GET` and `OPTIONS` only**, from the dashboard's own origins, with `authorization` and `content-type` headers. So from a browser:

* `GET /v1/retrieve`, `GET /v1/whoami`, `GET /v1/usage`, `GET /v1/namespaces` work.
* `POST /v1/memories`, `POST /v1/namespaces` and every write to `/v1/keys` do **not** — the preflight fails.

This is a real limitation of the current stack rather than a security model. The advice would be identical without it: call GitLoom from your backend and let your backend decide what a browser may ask for.

### Handling failures

| status | code                  | what to do                                                                                                                                      |
| ------ | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | `unauthorized`        | check the key is set, unrevoked, and not a stale token                                                                                          |
| `403`  | `dashboard_only`      | you used a key on `/v1/keys`; sign in instead                                                                                                   |
| `404`  | `namespace_not_found` | create the namespace before reading or writing                                                                                                  |
| `429`  | `quota_exceeded`      | monthly limit; see [Limits](https://github.com/MelloB1989/gitloom/tree/main/limits/README.md). Do **not** retry — it will not clear in a second |
| `5xx`  | `internal`            | retry with exponential backoff and jitter                                                                                                       |

Every route returns the same envelope:

```json
{"error":{"code":"namespace_not_found","message":"create namespace \"user-8213\" before writing to it"}}
```

Branch on `code`. `message` is prose and may be reworded.


---

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