API Key and Token Service  ·  View 11 of 22  ·  Act 4 · Data

Data Model

Nine entities, and the column that is conspicuously missing from the middle of them.

Editable source SVG draw.io All views
tenant tenant_id PK status revocation_epoch credential_quota leak_policy principal principal_id PK tenant_id FK kind human | workload status held_scopes credential kid PK tenant_id FK created_by FK prefix class + env digest HMAC(secret, pepper_v) pepper_version status expires_at label credential_version kid FK version PK scope_set constraints cidr | resource | env effective_from superseded_by audit_record record_id PK prev_hash chain tenant_id FK kid FK nullable action actor before / after at usage_observation kid FK window last_used_at ≤ 5 min lag call_count lossy last_region revocation seq PK monotonic kid FK (or tenant_id) reason actor accepted_at propagated_p99_ms scope scope PK resource:action vocabulary_version grade read | write | admin deprecated_at leak_incident incident_id PK kid FK source partner | internal confirmed_at action_taken trigger_trail 1 : N 1 : N 1 : N N : M 1 : 0..1 1 : N 1 : N 0..1 : 1 Data Model — what a credential actually is There is no secret column. The digest and its pepper version are the whole of what is stored, and the pepper is not here. v 1.0 · owner Security Platform Architecture · date 2026-09

Decisions

  • `credential` holds a digest and a pepper version, never a secret. The pepper version is what makes rotation possible without re-issuing every customer credential.
  • Scopes live on `credential_version`, not on `credential`, so a scope change is a new version with the old one retained in audit rather than an update that erases what the key used to be able to do.
  • `revocation` is keyed by a monotonic sequence, and a bulk revocation is a tenant epoch bump rather than N rows — one message revokes a hundred thousand keys.

Observations, not inputs

  • `usage_observation` is explicitly lossy and lagging. Nothing reads it to make an authorization decision, which is what allows it to be cheap.
  • Promoting it to an authorization input — auto-expiring idle keys — would make the hot path a durable write at 250,000/s. That trade is named in the requirement and deferred to Phase 3.

Assumptions

  • last_used_at freshness within 5 minutes; call counts approximate.
  • Audit records are hash-chained per tenant, so deletion is detectable without a separate ledger.