Skip to main content

Context

An API key is the first thing an integrator touches, and its validation sits on the Runtime hot path — every track call pays for it. It has to be fast and it has to be safe, and those pull against each other. DEC-D5 removes the usual complication: a key is a subject with role assignments, not a thing with its own scope language. The same resolution and the same check serve a person and a machine, so there is no second authorization path that can drift. DEC-D6 removes another: third parties are never required to hand over IP ranges. A key plus HMAC plus rate limits is the security model; an IP allowlist exists as opt-in hardening, off by default, because demanding IPs from an integrator running on someone else’s cloud is asking for something they cannot give.

Scope (normative)

  • identity.api_keys with hashed secret and a clear-text prefix for identification.
  • Role assignment to a key, through the same mechanism as a user.
  • Rotation with an overlap window so no request fails during a rollover.
  • last_used_at and last-used IP for detection.
  • Optional expiry.
  • Public write keys for browser track ingestion (F1b, with the widget).

Non-scope (normative)

  • OAuth2 clients — FS-IDN-0007. A key is issued by the tenant to themselves; an OAuth client is granted by the tenant to somebody else.
  • The permission model itself — FS-IDN-0002.
  • Per-tenant IP allowlisting, which is a later security-policy feature (opt-in, default off).

Behaviour (normative)

  1. A key is shown exactly once, at creation. It is stored hashed and cannot be retrieved afterwards — losing it means rotating it, and that is the correct trade.
  2. Every key carries a clear-text prefix (sk_live_a1b2…). A key found in a public repository can be identified and revoked from the prefix alone, without anyone ever holding the secret.
  3. A key is a subject with role assignments. There is no key-specific scope language, and the permission check is the same code path as for a user.
  4. Rotation issues a new secret and keeps the old one valid for a configurable overlap window, default 24 hours. A rotation that breaks production is a rotation nobody performs, and a key nobody rotates is the real risk.
  5. Validation is cached and performs zero database queries on the hot path once warm. Revocation invalidates the cache immediately, not on expiry.
  6. last_used_at and the last-used IP are recorded asynchronously, never in the request path.
  7. Keys may carry an optional expiry. An expiring key notifies its creator ahead of time.
  8. Creating or rotating a key requires re-authentication (FS-IDN-0004) and writes an audit row.
  9. A key belongs to one organization and can never act on another, whatever roles it carries.
  10. Public write keys (F1b) are a distinct kind: track-ingestion only, safe to ship in a browser bundle, rate-limited harder and bound to configured origins. They are never granted any other permission.
  11. FORBIDDEN: a key in a log, an event payload, an error message or a URL query parameter.

Data (normative)

API (normative)

Listing never returns a secret, only prefixes and metadata.

Events (normative)

identity.api_key.created, .rotated and .revoked, carrying the prefix and the actor — never the secret.

Acceptance criteria (normative)

  1. A key is returned exactly once; every subsequent read returns only its prefix.
  2. During the overlap window both the old and the new secret authenticate; after it, only the new.
  3. Validation performs zero database queries once the cache is warm, verified by a query counter.
  4. Revocation is effective on the very next request.
  5. A key from organization A cannot act on organization B under any role.
  6. A public write key can only reach track ingestion — every other endpoint rejects it.
  7. last_used_at updates without adding measurable latency to the request.
  8. Creating a key without re-authentication is rejected.
  9. Negative: no secret appears in any log, event, error or URL — verified by a scan over a seeded request set.

Execution

Single slice, synchronous command. Validation lives in the API’s auth middleware with an Upstash cache; the roles resolution is shared with FS-IDN-0002.

Open questions

Changelog

Delivery record

Not implemented yet.