Skip to content

Commit a28b47c

Browse files
Merge pull request #96 from mnemom/docs/api-key-capability-scopes
docs(api-keys): capability-based scope vocabulary (ADR-049)
2 parents 3ab64ae + bc30d67 commit a28b47c

3 files changed

Lines changed: 150 additions & 21 deletions

File tree

api-reference/openapi.json

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3419,6 +3419,7 @@
34193419
"post": {
34203420
"operationId": "createApiKey",
34213421
"summary": "Create a personal API key",
3422+
"description": "Mints a new personal API key. The full secret is returned in the `key` field — capture it now, it is not retrievable again. Scopes use the capability-based vocabulary (ADR-049); the default scope set is `[\"gateway\", \"api:read\", \"api:write\"]`. Admin scopes (`admin:org`, `admin:platform`) are opt-in and rejected at mint time if the requester does not hold the corresponding role.",
34223423
"tags": [
34233424
"Billing"
34243425
],
@@ -3436,7 +3437,20 @@
34363437
"name": {
34373438
"type": "string",
34383439
"maxLength": 100,
3439-
"default": "Default"
3440+
"default": "Default",
3441+
"description": "Friendly name for the key (e.g., \"ci-prod\", \"local-dev\"). Capped at 100 characters; longer names are truncated server-side."
3442+
},
3443+
"scopes": {
3444+
"type": "array",
3445+
"items": {
3446+
"$ref": "#/components/schemas/ApiKeyScope"
3447+
},
3448+
"default": [
3449+
"gateway",
3450+
"api:read",
3451+
"api:write"
3452+
],
3453+
"description": "Capability set for this key. If omitted, the API substitutes the default. Admin scopes (`admin:org`, `admin:platform`) are gated by the requester's current role and return HTTP 403 at mint time if the requester is not eligible."
34403454
}
34413455
}
34423456
}
@@ -3454,8 +3468,14 @@
34543468
}
34553469
}
34563470
},
3471+
"400": {
3472+
"description": "Validation error — typically an unknown scope name. The error message lists the valid scopes."
3473+
},
34573474
"401": {
34583475
"$ref": "#/components/responses/Unauthorized"
3476+
},
3477+
"403": {
3478+
"description": "Mint-time ceiling violation — `admin:platform` requested by a non-staff user, or `admin:org` requested by a user who is not owner/admin of any organization. The error body identifies which scope was rejected."
34593479
}
34603480
}
34613481
},
@@ -4893,7 +4913,20 @@
48934913
"properties": {
48944914
"name": {
48954915
"type": "string",
4896-
"maxLength": 100
4916+
"maxLength": 100,
4917+
"description": "Friendly name for the key (e.g., \"ci-prod\")."
4918+
},
4919+
"scopes": {
4920+
"type": "array",
4921+
"items": {
4922+
"$ref": "#/components/schemas/ApiKeyScope"
4923+
},
4924+
"default": [
4925+
"gateway",
4926+
"api:read",
4927+
"api:write"
4928+
],
4929+
"description": "Capability set for this org key (ADR-049). If omitted, the API substitutes the default. The caller is already verified as owner/admin of this org by the route's RBAC gate, so `admin:org` is implicit-eligible here; `admin:platform` still requires Mnemom-staff role."
48974930
}
48984931
}
48994932
}
@@ -4917,6 +4950,9 @@
49174950
"401": {
49184951
"$ref": "#/components/responses/Unauthorized"
49194952
},
4953+
"403": {
4954+
"description": "Mint-time ceiling violation — typically `admin:platform` requested by a non-staff user."
4955+
},
49204956
"404": {
49214957
"$ref": "#/components/responses/NotFound"
49224958
}
@@ -20570,6 +20606,18 @@
2057020606
}
2057120607
}
2057220608
},
20609+
"ApiKeyScope": {
20610+
"type": "string",
20611+
"enum": [
20612+
"gateway",
20613+
"api:read",
20614+
"api:write",
20615+
"admin:org",
20616+
"admin:platform",
20617+
"api"
20618+
],
20619+
"description": "Capability-based scope (ADR-049). `gateway` permits gateway-worker traffic; `api:read` and `api:write` permit identity-scoped GET and write endpoints respectively; `admin:org` permits org-admin operations on orgs the bearer owns/admins (per-request membership re-check); `admin:platform` permits `/v1/admin/*` Mnemom-staff operations (per-request staff-role re-check). The legacy `api` scope is accepted for backward compatibility and aliased to `api:read` + `api:write` at the auth gate; new keys should use the canonical vocabulary."
20620+
},
2057320621
"ApiKey": {
2057420622
"type": "object",
2057520623
"properties": {
@@ -20595,8 +20643,9 @@
2059520643
"scopes": {
2059620644
"type": "array",
2059720645
"items": {
20598-
"type": "string"
20599-
}
20646+
"$ref": "#/components/schemas/ApiKeyScope"
20647+
},
20648+
"description": "Capabilities granted to this key. Default for new keys is `[\"gateway\", \"api:read\", \"api:write\"]`. Admin scopes (`admin:org`, `admin:platform`) are opt-in and gated by the requester's role at mint time. See [API Keys → Scope vocabulary](https://docs.mnemom.ai/guides/api-keys#scope-vocabulary)."
2060020649
},
2060120650
"created_at": {
2060220651
"type": "string",

guides/api-keys.mdx

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
---
22
title: "API Keys"
3-
description: "Personal vs. organization keys, when to use which, and how to rotate."
3+
description: "Personal vs. organization keys, capability scopes, and how to rotate them safely."
44
sidebarTitle: "API Keys"
55
icon: "key"
66
---
77

8-
Mnemom has **two API key surfaces** with different ownership, billing, and lifecycle. This guide covers when to use each and how to rotate them safely.
8+
Mnemom has **two API key surfaces** — personal keys and organization keys — sharing one capability-based scope vocabulary. This guide covers when to use each surface, how scopes work, and how to rotate them safely.
99

1010
If you came here from the [Org Admin guide](/guides/org-admin), this is the deep dive on key management.
1111

@@ -16,19 +16,53 @@ If you came here from the [Org Admin guide](/guides/org-admin), this is the deep
1616
| **Endpoint** | `POST /v1/api-keys` | `POST /v1/orgs/{org_id}/api-keys` |
1717
| **Who can create** | Any authenticated user | Owner or admin of the org |
1818
| **Billed to** | The user's billing account | The org's billing account |
19-
| **Acts as** | The user's identity + permissions | The user's identity, scoped to the org |
19+
| **Acts as** | The user's identity | The user's identity, scoped to the org |
2020
| **Visible in** | Settings → Personal → API keys | Settings → Organization → API keys |
2121
| **Survives departure?** | No — when the user leaves, key revokes | Yes — admin must explicitly revoke |
2222
| **Revoke** | The user (or admin via support) | Owner, admin, or original creator |
2323

2424
Both surfaces produce keys with the `mnm_` prefix; they're identical at the wire level. The difference is **billing attribution** and **lifecycle ownership**.
2525

26-
## When to use which
26+
## Scope vocabulary
27+
28+
Mnemom keys use a **capability-based** scope vocabulary. Each scope names what the key is *allowed to do*. The default is least-privilege; admin scopes are opt-in and gated by your role at mint time and re-checked on every request.
29+
30+
| Scope | Grants | Mintable by |
31+
|---|---|---|
32+
| `gateway` | Send traffic through the Mnemom gateway worker (proxy LLM requests, capture traces). | Any authenticated user |
33+
| `api:read` | `GET` endpoints scoped to the bearer's identity (your agents, traces, cards, policies). | Any authenticated user |
34+
| `api:write` | `POST` / `PUT` / `PATCH` / `DELETE` endpoints scoped to the bearer's identity (mutate your agents, cards, policies). | Any authenticated user |
35+
| `admin:org` | `/v1/orgs/{org_id}/...` admin operations on orgs you own or admin (members, settings, billing, org-level keys). Re-checked per request against your current org membership. | Owner or admin of at least one org |
36+
| `admin:platform` | Mnemom-staff platform operations across tenants. Re-checked per request against your current staff role. | Mnemom-staff users only |
37+
38+
**Default for new keys:** `["gateway", "api:read", "api:write"]`. Admin scopes are not added unless you explicitly request them.
39+
40+
### Scope semantics — what to know
41+
42+
- **Capabilities are independent.** `admin:platform` does NOT imply `api:read`. A key with only `admin:platform` cannot call `/v1/agents`. If your automation needs to call non-admin endpoints alongside admin ones, request both.
43+
- **Mint-time ceiling is enforced server-side.** Requesting a scope you're not eligible for returns HTTP 403 at creation time. Examples:
44+
```
45+
POST /v1/api-keys
46+
{ "name": "doomed", "scopes": ["admin:platform"] }
47+
← 403 admin:platform scope requires Mnemom-staff role on your account
48+
```
49+
- **Request-time role is re-checked** for admin scopes. If a staff member is demoted after minting an `admin:platform` key, that key's admin power stops working immediately on the next request — without rotation. The key still authenticates for non-admin endpoints if those scopes are also present.
50+
- **Demotion is silent.** Mnemom does not auto-revoke admin-scoped keys when a user's role changes. Use `DELETE /v1/api-keys/{key_id}` (or rotate) when offboarding admin-role users.
51+
52+
### Picking scopes for common patterns
53+
54+
- **Local development on your laptop:** `gateway`, `api:read`, `api:write` (the default).
55+
- **CI runner that publishes traces but doesn't mutate state:** `gateway`, `api:read`.
56+
- **Read-only analytics ingest into Snowflake/BQ:** `api:read`.
57+
- **Service account that manages org members from a script:** `admin:org` (plus `api:read` if it needs to read non-admin endpoints).
58+
- **Mnemom-internal automation (deploy gates, on-call tooling):** `admin:platform` (plus `api:read`/`api:write` if it touches non-admin endpoints too).
59+
60+
## When to use which surface
2761

2862
**Personal key, every time***unless* one of the things below applies.
2963

3064
- **Local development.** A developer on the team using Mnemom from their laptop. Personal key.
31-
- **Per-engineer CI accounts.** Each engineer has their own CI runner credentialed against their personal account. Personal key. (You probably want option 2 below for actual CI.)
65+
- **Per-engineer CI accounts.** Each engineer has their own CI runner credentialed against their personal account. Personal key.
3266
- **Single-purpose script you're running yourself.** Personal key.
3367

3468
**Organization key when:**
@@ -47,7 +81,10 @@ If a member leaves the org, their **personal keys remain valid** (they're still
4781
curl -X POST https://api.mnemom.ai/v1/api-keys \
4882
-H "Authorization: Bearer $MNEMOM_JWT" \
4983
-H "Content-Type: application/json" \
50-
-d '{"name": "local dev"}'
84+
-d '{
85+
"name": "local dev",
86+
"scopes": ["gateway", "api:read", "api:write"]
87+
}'
5188
```
5289

5390
### Organization
@@ -56,7 +93,10 @@ curl -X POST https://api.mnemom.ai/v1/api-keys \
5693
curl -X POST https://api.mnemom.ai/v1/orgs/$ORG_ID/api-keys \
5794
-H "Authorization: Bearer $MNEMOM_JWT" \
5895
-H "Content-Type: application/json" \
59-
-d '{"name": "ci-prod"}'
96+
-d '{
97+
"name": "ci-prod",
98+
"scopes": ["gateway", "api:read", "api:write"]
99+
}'
60100
```
61101

62102
In both cases the response carries the **full secret** in the `key` field. You see it once. Mnemom stores only its SHA-256 hash; we cannot recover the secret if you lose it.
@@ -67,11 +107,33 @@ In both cases the response carries the **full secret** in the `key` field. You s
67107
"key": "mnm_8f3e7c2b91a4d6f8...",
68108
"key_prefix": "mnm_8f3e",
69109
"name": "ci-prod",
70-
"scopes": ["gateway", "api"],
110+
"scopes": ["gateway", "api:read", "api:write"],
71111
"created_at": "2026-04-25T12:34:56Z"
72112
}
73113
```
74114

115+
If you omit `scopes` from the request body, the API substitutes the default set (`["gateway", "api:read", "api:write"]`).
116+
117+
## Using a key
118+
119+
```bash
120+
curl https://api.mnemom.ai/v1/agents \
121+
-H "X-Mnemom-Api-Key: mnm_8f3e7c2b91a4d6f8..."
122+
```
123+
124+
The `X-Mnemom-Api-Key` header is the canonical auth path for `mnm_*` keys. (Some legacy SDKs send the key as `Authorization: Bearer mnm_...` — that path is deprecated; migrate to `X-Mnemom-Api-Key`.)
125+
126+
## Legacy keys (`["gateway", "api"]`)
127+
128+
Keys minted before May 2026 carry the legacy two-scope set `["gateway", "api"]`. They continue to work — the auth gate aliases them to `["gateway", "api:read", "api:write"]` at request time, so existing integrations need no changes.
129+
130+
The dashboard renders these keys with a "legacy" badge. To upgrade a legacy key to the canonical vocabulary:
131+
132+
1. Rotate the key (`POST /v1/api-keys/{key_id}/rotate`) — the rotation preserves the legacy scope set on the new key.
133+
2. Revoke the rotated key and create a fresh one (`POST /v1/api-keys`) without specifying `scopes`. The new key inherits the post-ADR-049 default `["gateway", "api:read", "api:write"]`.
134+
135+
(You don't have to upgrade — legacy keys remain valid indefinitely. But if you want the dashboard to stop annotating them, this is the migration path.)
136+
75137
## Rotation
76138

77139
Two endpoints, identical contract:
@@ -123,8 +185,10 @@ For org keys, the same shape applies under `/v1/orgs/{org_id}/api-keys/{key_id}`
123185

124186
## Key hygiene checklist
125187

126-
- **Rotate annually**, or whenever a member with access leaves the org. The audit log shows last_used_at per key — rotate dormant keys aggressively.
127-
- **Name your keys**. `"name": "ci-prod"` is much more useful than `"Default"` six months later when you're triaging an alert.
188+
- **Rotate annually**, or whenever a member with access leaves the org. The audit log shows `last_used_at` per key — rotate dormant keys aggressively.
189+
- **Name your keys.** `"name": "ci-prod"` is much more useful than `"Default"` six months later when you're triaging an alert.
190+
- **Use the smallest scope set that works.** Default to `gateway` + `api:read`/`api:write` and only add admin scopes when the integration genuinely needs them. Smaller blast radius if leaked.
191+
- **Prefer `api:read` for analytics integrations.** Read-only keys are safer and the audit trail is cleaner.
128192
- **Never commit secrets.** The `mnm_` prefix is a string git secret-scanners recognize; we recommend GitHub Secret Scanning + push protection to catch accidents.
129-
- **Use scopes.** Keys default to `["gateway", "api"]`; if you only need one, request just one. Smaller blast radius if leaked.
130193
- **Include a renewal reminder** in your deploy / IaC if the key has a TTL constraint from your security policy. Mnemom keys themselves don't expire by default.
194+
- **Treat admin-scope keys with extra care.** Use them only for the specific automation that needs them; rotate them on any suspected leak; audit which keys carry `admin:org` or `admin:platform` quarterly.

guides/authentication.mdx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,19 +86,33 @@ Server-side, sessions are revalidated on every protected request against the `au
8686

8787
## API keys
8888

89-
API keys authenticate server-to-server calls against `api.mnemom.ai`. They are scoped to an organization and inherit the role of the creator.
89+
API keys authenticate server-to-server calls against `api.mnemom.ai` and `gateway.mnemom.ai`. They use a **capability-based scope vocabulary** (ADR-049): each scope names what the key is allowed to do, defaults are least-privilege, and admin scopes are opt-in and gated by your role.
90+
91+
The full deep dive on key management — including the side-by-side comparison of personal vs. organization keys, the per-scope reference table, and rotation/revocation flows — lives at [API Keys](/guides/api-keys). This section is the auth-flow summary.
92+
93+
### Scope vocabulary at a glance
94+
95+
| Scope | Grants | Mintable by |
96+
|---|---|---|
97+
| `gateway` | Send LLM traffic through Mnemom's gateway worker. | Anyone |
98+
| `api:read` | `GET` endpoints scoped to your identity. | Anyone |
99+
| `api:write` | `POST`/`PUT`/`PATCH`/`DELETE` endpoints scoped to your identity. | Anyone |
100+
| `admin:org` | Org-admin operations on orgs you own/admin. | Org owner / org admin |
101+
| `admin:platform` | Mnemom-staff platform operations. | Mnemom staff only |
102+
103+
**Default for new keys:** `["gateway", "api:read", "api:write"]`. Admin scopes are explicit opt-in; the API enforces a mint-time ceiling against your current role and re-checks on every request.
90104

91105
### Creating an API key
92106

93107
<Steps>
94108

95109
### Sign in and open API keys
96110

97-
**Settings → API Keys** (user-scoped) or **Org Settings → API Keys** (org-scoped).
111+
**Settings → API Keys** (personal) or **Org Settings → API Keys** (organization).
98112

99-
### Create the key
113+
### Pick capabilities
100114

101-
Click **Create API key**, name it, and pick a scope (Owner, Admin, Member — must not exceed the creator's role). AAL2 step-up is required.
115+
The dialog has a Capabilities section (default scopes) and an Admin permissions section (opt-in, role-gated). Quick-pick presets ("Default", "Read-only", "Gateway-only") cover most cases. AAL2 step-up is required.
102116

103117
### Copy the secret
104118

@@ -115,6 +129,10 @@ curl https://api.mnemom.ai/v1/agents \
115129

116130
API keys are rate-limited per plan tier (see [API reference overview](/api-reference/overview)).
117131

132+
### Legacy keys
133+
134+
Keys minted before May 2026 carry the legacy two-scope set `["gateway", "api"]`. The auth gate aliases them to `["gateway", "api:read", "api:write"]` at request time, so existing integrations continue to work unchanged. The dashboard renders them with a "legacy" annotation; rotation upgrades them to the canonical vocabulary. See [API Keys → Legacy keys](/guides/api-keys#legacy-keys-gateway-api) for the migration path.
135+
118136
### Rotating an API key
119137

120138
Rotation is **create-new-then-revoke-old**, not an in-place swap. This gives you a window to roll callers over before the old key dies.
@@ -126,8 +144,6 @@ Rotation is **create-new-then-revoke-old**, not an in-place swap. This gives you
126144

127145
Revocation is immediate. Any in-flight request using the revoked key receives HTTP 401 on its next call.
128146

129-
{/* TODO: Alex — confirm whether we surface a "last used at" timestamp in the dashboard today, and whether there's an automated rotation reminder (e.g., suggest rotation after 90 days). */}
130-
131147
### Rotating a provider API key bound to an agent
132148

133149
If you are rotating an **agent's provider key** (Anthropic / OpenAI / Gemini) — not a Mnemom API key — see [Rotating your agent's API key](/guides/agent-key-rotation). The flow preserves the agent's ID, traces, and reputation.

0 commit comments

Comments
 (0)