Skip to content

docs(reference): correct the X-API-Key auth claim — it is not general-purpose (#3247) - #3251

Closed
bdunncompany wants to merge 1 commit into
LanternOps:mainfrom
bdunncompany:fix/3247-api-key-auth-docs
Closed

docs(reference): correct the X-API-Key auth claim — it is not general-purpose (#3247)#3251
bdunncompany wants to merge 1 commit into
LanternOps:mainfrom
bdunncompany:fix/3247-api-key-auth-docs

Conversation

@bdunncompany

Copy link
Copy Markdown
Collaborator

Fixes #3247 — the top item in your #3249 ordering, and the one that costs hours rather than days.

What was wrong

reference/api.mdx said an API key authenticates the REST API and used GET /api/v1/devices as the example. That call returns 401 Missing or invalid authorization header.

Confirmed against origin/main rather than taken from the issue: authMiddleware (middleware/auth.ts:448) reads the Authorization header and nothing else, throwing at :451 when it is absent or not Bearer-prefixed. devices/core.ts:276 puts the entire devices surface behind it, so the documented example is specifically one of the calls that cannot work.

Where keys actually work

Three surfaces, all dual-auth — key when X-API-Key is present, user JWT when it is absent:

Endpoint Scope Mount
/api/v1/mcp/* ai:* index.ts:1114, mcpServer.ts:221
POST /api/v1/dev/push devices:execute index.ts:1115, devPush.ts:79
GET /api/v1/devices/:id/custom-fields devices:read devices/customFieldValues.ts:87
PATCH /api/v1/devices/:id/custom-fields devices:write same

Both reference pages now carry that table, and the broken /devices example is replaced by one that works. I added the distinction that made this trap easy to fall into: scopes gate what a key may do on those routes, not where it is accepted. A devices:read key still cannot call GET /api/v1/devices, which is exactly the wrong inference a scope list invites.

Also covered your third point — the requireMfa() gate on POST /orgs/partners (orgs.ts:394), POST /orgs/organizations (:1279) and POST /orgs/sites (:1855), so nobody discovers mid-migration that an MFA-less service account cannot create tenancy.

Two line-number corrections

Not substantive, but so the issue isn't a false trail later: apiKeyAuthMiddleware is mounted at mcpServer.ts:221 (not 219) and the devices authMiddleware is at devices/core.ts:276 (not 268). Everything else in the issue checked out exactly, including that the three mount sites are the only non-test ones — servicePrincipals.ts:20 even carries a comment asserting it never imports it.

On the cross-link I did not add

Your issue points at migration/toolkit.mdx as the source of truth for the accurate behaviour, and it is — but that page only exists in #3250, which is still open. Linking to it from here would ship a dead link until that merges, so I kept the MFA guidance self-contained. Worth adding the cross-link when #3250 lands; happy to do it in that PR or a follow-up, whichever you prefer.

Verification

  • astro build — exit 0, 150 pages, both pages render
  • the new #where-api-keys-work heading generates its anchor and the in-page link resolves to it (checked in the built HTML, not assumed)
  • /features/mcp-server/ link target exists in dist
  • pnpm test:docs-automation — 5/5 pass

Diff is two .mdx files; no lockfile or manifest churn.

…-purpose (LanternOps#3247)

`reference/api.mdx` told readers an API key authenticates the REST API and gave
`GET /api/v1/devices` as the example. That call returns 401: `authMiddleware`
(middleware/auth.ts:448) reads the `Authorization` header and nothing else, and
devices/core.ts:276 puts the whole devices surface behind it, so the key is never
consulted. It was the first page anyone scripting Breeze reads, and it pointed at
a wall of 401s.

`X-API-Key` is honoured on three surfaces only, each of them dual-auth (key when
the header is present, user JWT when it is absent):

- `/api/v1/mcp/*` — the MCP server (index.ts:1114, mcpServer.ts:221)
- `POST /api/v1/dev/push` — scope `devices:execute` (index.ts:1115, devPush.ts:79)
- `GET`/`PATCH /api/v1/devices/:id/custom-fields` — scopes `devices:read` /
  `devices:write` (devices/customFieldValues.ts:87, mounted at devices/index.ts:39)

Both reference pages now say so, with a table of the accepting routes and their
required scopes, and the broken `/devices` example is replaced by one that works.
Added the point that scopes gate what a key may DO on those routes but do not
widen WHERE it is accepted — a `devices:read` key still cannot call
`GET /api/v1/devices`.

Also documented the other thing that silently breaks a scripted integration: the
`requireMfa()` gate on `POST /orgs/partners` (orgs.ts:394),
`POST /orgs/organizations` (:1279) and `POST /orgs/sites` (:1855), so an account
without MFA enrolled cannot create tenancy unattended.

Verified: `astro build` clean, 150 pages, both pages render, the new
`#where-api-keys-work` anchor is generated and the link resolves to it, and the
`/features/mcp-server/` target exists. `pnpm test:docs-automation` passes 5/5.
@bdunncompany
bdunncompany force-pushed the fix/3247-api-key-auth-docs branch from c52febc to 99506cb Compare August 8, 2026 03:41
@bdunncompany

Copy link
Copy Markdown
Collaborator Author

Superseded by #3286 — closing this rather than leaving a duplicate.

Yours is the better version and should land instead: it covers the same two files, adds openapi.ts, and documents the brz_sp_ partner service-principal class, which did not exist when I wrote this (it arrived with #3274 earlier today). Your MFA framing is also stronger than mine — I stated the gate; you explain why it makes user JWTs a poor fit for automation and point at the credential class that isn't gated.

One nuance from mine that I did not find in #3286, offered only in case you want it — no need to act on it:

Scopes gate what a key may do on those routes, not where it is accepted. A devices:read key still cannot call GET /api/v1/devices.

That was the specific wrong inference I expected a reader to draw: a key page listing devices:read as a scope reads as though it grants the devices API. A sentence ruling it out closes the loop, but the surface table in #3286 already conveys it structurally, so this is a nicety rather than a gap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Docs] API reference wrongly documents X-API-Key as general-purpose auth — the /devices example returns 401

1 participant