Context
An API key is the first thing an integrator touches, and its validation sits on the Runtime hot path — everytrack 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_keyswith 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_atand 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)
- 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.
- 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. - 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.
- 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.
- Validation is cached and performs zero database queries on the hot path once warm. Revocation invalidates the cache immediately, not on expiry.
last_used_atand the last-used IP are recorded asynchronously, never in the request path.- Keys may carry an optional expiry. An expiring key notifies its creator ahead of time.
- Creating or rotating a key requires re-authentication (FS-IDN-0004) and writes an audit row.
- A key belongs to one organization and can never act on another, whatever roles it carries.
- 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.
- 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)
- A key is returned exactly once; every subsequent read returns only its prefix.
- During the overlap window both the old and the new secret authenticate; after it, only the new.
- Validation performs zero database queries once the cache is warm, verified by a query counter.
- Revocation is effective on the very next request.
- A key from organization A cannot act on organization B under any role.
- A public write key can only reach track ingestion — every other endpoint rejects it.
last_used_atupdates without adding measurable latency to the request.- Creating a key without re-authentication is rejected.
- Negative: no secret appears in any log, event, error or URL — verified by a scan over a seeded request set.