Skip to content

[finding] The ApiKey reference table documents better-auth's apiKey-plugin schema — a plugin this platform does not load and a shape sys_api_key does not have #8715

Description

@qq9340100

Found while screening docs drift for #8287 (PR #8709). Pre-existing, unrelated to that change, and left unfixed there deliberately — correcting it is a docs-only job, not a rider on a code PR.

1. content/docs/references/identity/identity.mdx — the whole ApiKey table is the wrong schema

Its property list is better-auth's apiKey plugin schema:

documented reality on sys_api_key
start, lastRefetchAt do not exist
rateLimitEnabled, rateLimitTimeWindow, rateLimitMax, remaining do not exist — there is no rate-limit surface on this object at all
permissions (Record<string, boolean>), metadata do not exist
enabled (boolean, required) the column is revoked, with the opposite polarity
organizationId — "Scoped organization ID" did not exist until #8287, which adds active_organization_id (deliberately snake_case and deliberately not organization_id — see that PR)

better-auth's apiKey plugin is not loaded by this platform. plugin-auth/src/managed-extension-fields.ts states it outright: the table is hand-rolled ObjectStack, minted by runtime/src/domains/keys.ts and verified by core/src/security/api-key.ts. The real column set is name, prefix, user_id, active_organization_id, scopes, expires_at, last_used_at, revoked, key, id, created_at, updated_at.

This is the shape AGENTS.md PD #10 names — "never advertise or demo a capability the runtime doesn't actually deliver" — and the rate-limit rows are the sharpest instance: a reader can reasonably conclude API keys support per-key rate limiting, which nothing implements.

⚠️ Note for whoever fixes it: do not just add a row for the new column. The table's frame is wrong, so patching one line would make it more convincing and no more true.

2. content/docs/protocol/kernel/http-protocol.mdx — wrong key prefix in the example

GET /api/v1/data/task
X-API-Key: sk_live_abc123...

ObjectStack keys are osk_-prefixed (API_KEY_PREFIX in core/src/security/api-key.ts), and the prefix is load-bearing, not cosmetic: extractApiKey accepts Authorization: Bearer <token> only when the token starts with osk_, which is what stops a session Bearer being read as an API key. sk_live_… is a Stripe-shaped placeholder that would not authenticate and cannot be sent as a Bearer.

Screened and NOT affected (recorded so the next person need not re-check)

  • content/docs/automation/connectors.mdx, content/docs/references/integration/connector.mdx — a different subsystem: OUTBOUND connector auth (auth: { type: 'api-key', headerName: 'X-API-Key' }) for calling third-party APIs. Nothing to do with ObjectStack's inbound key surface.
  • content/docs/api/index.mdx, content/docs/api/client-sdk.mdx, content/docs/ai/agents.mdx, content/docs/ai/connect-mcp.mdx, content/docs/getting-started/build-with-claude-code.mdx, content/docs/deployment/environment-variables.mdx — all accurate on the mint endpoint, the osk_ prefix and the header forms.
  • content/docs/permissions/authentication.mdx, content/docs/permissions/system-context.mdx — screened, zero API-key vocabulary.
  • content/docs/permissions/authorization.mdx — accurate, including its statement that sys_api_key stays public-posture with row scoping as its guard.

Metadata

Metadata

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions