You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 3c2b415
Browse filesBrowse the repository at this point in the historyBrowse files
docs(api,ai): the API-key lifecycle codes reach every face that accepts a key
The runtime dispatcher (MCP HTTP among its routes), the settings routes and
the storage download routes now relay the refusal, so the changeset, the
error catalog and the MCP connection guide list every face instead of
naming three as answering the generic 401.
Claude-Session: https://claude.ai/code/session_0121ooBYRmS8ziFXm9ai5YHm
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: .changeset/23367-api-key-lifecycle.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -28,7 +28,7 @@ Ruling A on #23367: the key store stays `sys_api_key`, and the policy is auth co
28
28
-**The key-admission seam (`resolveApiKeyAdmission`, `@objectstack/core`).** A real key its own state refuses is a new outcome, `lifecycle`, with one of three distinct codes: `API_KEY_REVOKED`, `API_KEY_EXPIRED` (past `expires_at`, or past the grace of a key created with none) or `API_KEY_INACTIVE` (no recorded use for longer than the window). `resolveAuthzContext` raises it as the branded `ApiKeyLifecycleRefusalError` (`403`, a located message naming the key's prefix, the date and the remedy). An unknown key is still `none`.
29
29
-**`last_used_at` is written.** Its description promised "Automatically updated on each API call", and nothing wrote it. The seam now stamps it on an admitted (or held) call, at most once an hour per key, and the inactivity judgment reads the value as it was BEFORE that call's stamp, so a key's own use cannot revive it. A stamp that fails never fails the request; it is said once, at `error`.
30
30
-**Keys created before the policy.** A key with no expiry gets one year from `api_key_policy_started_at`; the date is stamped onto its `expires_at` the first time it is admitted, so the keys page shows it, and the refusal after it names it. A key with no recorded use counts its inactivity from the same anchor (or from its own creation, when later) — never from an earlier `created_at`, since no use was ever recorded before the policy.
31
-
-**Every face that admits keys** passes the policy source (`apiKeyLifecyclePolicySourceOf(authService)`, a required input beside `userAuthGates`). The REST data API, the datasource admin routes, the share-link routes, the marketplace install-local routes and the MCP stdio transport answer the refusal's `403` and code. The runtime dispatcher (the MCP HTTP endpoint among its routes), the settings routes and the storage routes refuse the same key with their generic `401` — fail-closed, never an admission — and the server log names the code.
31
+
-**Every face that admits keys** passes the policy source (`apiKeyLifecyclePolicySourceOf(authService)`, a required input beside `userAuthGates`). Every one of them answers the refusal's `403` and code: the runtime dispatcher (the MCP HTTP endpoint among its routes), the REST data API, the settings routes, the storage download routes, the datasource admin routes, the share-link routes, the marketplace install-local routes and the MCP stdio transport. Each relays the refusal by its brand, so every other identity fault at those doors keeps its existing answer.
32
32
-**`OS_CLOUD_API_KEY` is untouched.** It is the runtime's outbound credential to the control plane, never admitted by this seam.
33
33
34
34
What changes for you:
@@ -37,5 +37,5 @@ What changes for you:
37
37
-**A signing asking for more than a year is refused**`400 API_KEY_EXPIRY_TOO_LONG`, where it used to be accepted. Ask for the date the message names, or an earlier one.
38
38
-**A key unused for 90 days is refused**`403 API_KEY_INACTIVE`, where it used to be served. It is not revoked; the remedy is a new key.
39
39
-**A key that already existed with no expiry keeps working for one year** from the date the policy took effect on your deployment (`auth.api_key_policy_started_at`), then answers `403 API_KEY_EXPIRED` naming that date. Replace such keys within the year.
40
-
-**A revoked or expired key answers its own code** (`403 API_KEY_REVOKED` / `API_KEY_EXPIRED`) on the faces listed above, where it used to answer the generic `401`; and a session cookie sent beside such a key no longer answers for it.
40
+
-**A revoked or expired key answers its own code** (`403 API_KEY_REVOKED` / `API_KEY_EXPIRED`) on every face that accepts a key, where it used to answer the generic `401`; and a session cookie sent beside such a key no longer answers for it.
41
41
-**Hosts calling `resolveAuthzContext`, `resolveApiKeyAdmission` or `resolveApiKeyPrincipal` directly** pass `apiKeyLifecycle: apiKeyLifecyclePolicySourceOf(authService)` (or the sixth argument), or an explicit `undefined` where no auth service exists. A caller of `resolveAuthzContext` that wants the located answer re-raises `ApiKeyLifecycleRefusalError` (`isApiKeyLifecycleRefusalError`) the way it re-raises the store outage. Code switching exhaustively over `ApiKeyAdmission['outcome']` handles the new `lifecycle` outcome.
Copy file name to clipboardExpand all lines: content/docs/api/error-catalog.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -449,7 +449,7 @@ result set — a response indistinguishable from a successful query.
449
449
**Fix:** Create a new key and replace this one wherever it is configured. A revoked key never works again.
450
450
**Retry:**`no_retry`
451
451
452
-
These three are answered by the REST data API, the datasource admin and share-link routes, the marketplace install routes and the MCP stdio transport. The other endpoints the runtime serves (the MCP HTTP endpoint among them) and the settingsand file-storage routes refuse the same key with the generic `401 UNAUTHENTICATED`. An unknown key is always the generic 401.
452
+
These three are answered on every endpoint that accepts an API key: the endpoints the runtime serves (the MCP HTTP endpoint among them), the REST data API, the settings, file-download, datasource admin and share-link routes, the marketplace install routes and the MCP stdio transport. An unknown key is always the generic 401.
0 commit comments