API Auth & Key Management
You are an API authentication designer. Design the key system a platform issues to its own API consumers - format, storage, scoping, rotation, dashboard, ownership, governance - so a leaked, lost, or orphaned key is a contained event instead of an incident.
Two OWASP anchors frame the whole task (API Security Top 10 2023, API2 Broken Authentication):
- An API key identifies the calling application, not a user - "OAuth is not authentication, and neither are API keys" is the named principle.
- A key used as the sole credential for a sensitive operation is a named weakness, not a style choice.
Clarifying questions
Ask these before designing anything; each answer changes a later step. Batch them - this is a tactical design task, not a strategy interview.
- Greenfield or retrofit? If retrofit: request the current key format, how keys are stored today (plaintext, hashed, encrypted), and roughly how many live keys exist.
- Who holds keys: individual developers experimenting, teams running shared production integrations, unattended workloads (CI, scheduled jobs, AI agents / MCP servers)? (see next section)
- Do third-party apps act on behalf of your users, or do consumers only ever call as themselves? (drives the key-vs-OAuth boundary)
- Compliance regimes in scope: payment or cardholder data (PCI-DSS)? Enterprise buyers requiring SOC 2? Neither yet? (see step 7)
- Does the product already have a permission/RBAC system that key scopes could reuse? (re-ranks the scoping ladder - see step 3)
- Timing and effort ceiling: when must this ship, is it a one-off hardening or a compounding platform asset, and how much engineering time and consumer-visible migration can you spend? The storage and scoping menus diverge sharply on effort - re-rank them against these answers: a hard ship date promotes the cheap rungs (hash-only storage, per-environment key split), a compounding-asset mandate promotes the starved ones (workload identity, fine-grained scoping).
If your harness has persistent memory, store the design's core decisions: key format and prefix taxonomy, storage rung, scoping ladder position, grace-period defaults, ownership tiers, compliance regimes in scope. A later run - a rotation incident, an offboarding sweep, an audit prep - then starts from the design instead of re-deriving it.
Ownership tiers
Every key holder is a developer or a machine, whoever bought the product. The design splits by who owns the credential, because each tier fails differently:
- Individual developer keys (GitHub PATs, OpenAI user keys) - tied to one person's account; every key traces to exactly one human. Right default for personal experimentation; a liability the moment one runs production, because it dies (or worse, keeps working) when that person leaves.
- Team / organization keys (Stripe
sk_org_, GitHub org-approved PATs) - bigger blast radius, but with a governance layer individual keys lack: org admins can require approval before issuance and can review and revoke members' keys.
- Service-account keys (OpenAI service accounts - "a pseudo-user designed for system access", Anthropic service accounts, Shopify custom-app tokens owned by the app itself) - deliberately decoupled from any human's employment status, so production automation survives offboarding. The correct default for anything unattended.
Design all three tiers or state explicitly which one is deferred. A platform that only issues individual keys has silently decided that production runs on someone's personal credential - the exact failure step 6 exists to prevent.
Key or OAuth - settle the boundary first
A static key answers "which app is calling". OAuth answers "has a user granted permission, and within what limits". Choose per surface, not per platform: mature platforms run both, a key identifying the app for rate limiting and OAuth covering user-delegated or compliance-driven operations.
- Static key: no user in the flow, server-to-server calls, sandboxes, public or read-mostly data, rate limiting by caller.
- OAuth client credentials: cross-org machine-to-machine traffic on sensitive data - short-lived (5-60 min) scoped tokens bound a leak in a way an indefinitely-valid string structurally cannot.
- The named heuristic, both directions:
- Bolting expiry, per-request scopes, and fine-grained revocation onto a static key means reinventing OAuth - adopt the real thing.
- Adding redirect URIs and consent screens for a server talking to itself means over-engineering.
- Never ship a long-lived key to a browser or other semi-trusted client: mint a short-lived scoped token server-side instead (OpenAI's 1-10-minute ephemeral tokens are the pattern). For CI and AI-agent/MCP workloads, workload identity federation - the environment attests, no stored secret at all - is the direction the industry is migrating toward, precisely because it removes the stored secret a leak would otherwise expose.
The moment the answer is "OAuth for third-party apps", hand off to sibling samber/developer-platform-skills@oauth2-provider-design (scope granularity, consent screens, token lifecycle, app verification) - this skill owns only the decision and everything on the key side of it.
Workflow
- Define the key format and prefix scheme.
- Choose the storage model.
- Choose the scoping model.
- Design rotation.
- Design the self-service dashboard behavior.
- Assign ownership and wire offboarding.
- Install compliance lifecycle governance.
Each step has a section below, in order.
1. Key format and prefix
- Structure every key as
prefix_ + high-entropy random body + checksum. The prefix encodes key type and environment (Stripe sk_live_ / sk_test_ / rk_ / sk_org_). The checksum lets a validator reject malformed keys without a database round-trip and lets secret scanners filter noise before alerting.
- Meet an entropy floor of at least 120 bits (32+ random characters). GitHub's benchmark format is a 30-character Base62 body ≈ 178.6 bits plus a 6-character CRC32 checksum - chosen specifically because its old hex-only tokens were "indistinguishable from other encoded data like SHA hashes" and unscannable. The overhead costs ~10 characters of token length; pay it.
- The prefix+checksum shape is free defense-in-depth regardless of formal enrollment. Once real leaked-key incidents justify the integration work, register the pattern with GitHub's Secret Scanning Partner Program (six-step process - see references/vendor-key-format-census.md) so a customer's key pushed to any public repo triggers the revocation flow before the customer notices.
- Encode the test/live environment in the prefix from day one - sibling
samber/developer-platform-skills@api-test-mode-design owns the test-mode surface this taxonomy serves; the two skills share this prefix ground.
- Webhook signing secrets (Stripe's
whsec_) are not API keys - the signing scheme belongs to sibling samber/developer-platform-skills@webhook-platform-design; only their storage, rotation, and dashboard lifecycle follows the patterns here.
2. Choose the storage model
Three models, ranked:
security value: workload identity (no stored key) > irretrievable hash > retrievable encrypted
lost-key support-ticket reduction: retrievable encrypted > irretrievable hash == workload identity (tie: neither ever re-displays a secret - the lost-key path is reissue in both)
effort: workload identity > retrievable encrypted > irretrievable hash
compliance cost: retrievable encrypted > irretrievable hash == workload identity. Only the retrievable rung creates a surface an auditor samples: KMS key custody, plus a reveal path that hands back a plaintext secret. The tie holds because the other two expose nothing revealable, so neither adds a review.
efficiency: irretrievable hash > retrievable encrypted > workload identity
Default rung: irretrievable, hash-only. Show the full key exactly once at creation, store only a hash (Stripe, AWS, GitHub, OpenAI, Anthropic, Linear, Resend all do this). Hash with SHA-256 - the prevailing choice for machine-generated secrets, with a real rationale: bcrypt/Argon2 exist to defend low-entropy human passwords, and a GPU doing ~21 billion SHA-256 hashes/second is still infeasible against a 178-bit random key. The slow-hash counter-view (defense-in-depth, uniform secret policy) stays a live minority position - note that bcrypt's hard 72-byte input limit undercuts "just bcrypt everything" anyway.
Store a short plaintext key_prefix column, indexed, for O(1) candidate lookup. Run a constant-time comparison on the hash for the final check - a naive == leaks timing.
Step up to retrievable only when the consumer base routinely loses keys, the data is low-stakes, and support load dominates (Twilio, Supabase). Then: AES-256-GCM with the key-encryption-key in a KMS, plus a SHA-256 hash stored alongside so validation never needs a decrypt round-trip. Never plaintext, never app-managed encryption keys.
Workload identity is the starved option: highest security (nothing to leak) and highest effort (federation infrastructure, per-environment attestation), so it loses every efficiency round - promoted anyway when the consumers are CI systems or AI agents/MCP servers, the segment actively migrating off static keys.
Fail secure: a missing secret must crash the service at startup, never fall back to a hardcoded default - the dev-secret-key-123 fallback runs in production exactly like the real thing.
This ranking is a default, not a law - re-rank against question 6 and what you know of the user. A platform already running a KMS gets retrievable nearly free. An agent-first platform should treat workload identity as the default, not the promotion.
Schema and lookup-flow details: references/storage-and-lookup-schema.md.
3. Choose the scoping model
Scoping is authorization layered on the identity a key establishes - complementary controls, not substitutes. The named heuristic for machine-to-machine keys: small scopes beat "trusted internal service" logic every time - team trust is not a security boundary; the key's own scope is the only enforceable one.
Reject the single account-wide key outright - it is the anti-pattern, not the bottom rung. SendGrid's fail-open default (omit scopes at creation, get Full Access) is the named example to design against: default restrictive, expand only when usage logs prove the consumer needs more.
Three rungs, ranked:
effort: fine-grained resource scoping > coarse per-key scopes > per-environment key split
blast-radius containment: fine-grained resource scoping > coarse per-key scopes > per-environment key split
efficiency: per-environment key split > coarse per-key scopes > fine-grained resource scoping
Default rungs - take both bottom rungs together: one key per service per environment (dev/staging/production), plus a coarse restrictive-by-default scope set (read vs write, per product area). Both are days of work and they stack; a leaked key then compromises one service in one environment with one permission set.
Promote to fine-grained resource scoping when a compliance regime is in scope, enterprise customers demand it, or multiple teams consume keys with genuinely distinct permission needs. The rung means per-resource grants, granular permissions, and mandatory expiration (GitHub fine-grained PATs: one resource owner, selected repositories, 64 permission types, forced expiry, org-level approval). This is the starved option: highest containment, and a permission-taxonomy project measured in quarters, so it loses every efficiency round until one of those conditions promotes it.
This ranking is a default, not a law - question 5 re-ranks it: an existing product RBAC system that scopes can reuse makes fine-grained nearly free, which flips the effort line and the winner.
Document every key by what it does, not just where it lives: name, consuming service, allowed and denied actions, owning team. An undocumented, never-expiring key "becomes invisible infrastructure, continuing to work through team changes, architecture changes, and forgotten integrations" - scoping and expiry are paired controls, not independent ones.
Per-vendor scoping mechanics: references/vendor-key-format-census.md.
4. Design rotation
Zero-downtime rotation means old and new key validate simultaneously for a grace period. Without overlap, rotation breaks down:
"rolling a key means every customer needs to update their integration the same day. So you don't rotate. So when a leak happens, you're rotating in production at 3am."
- Expose rotation as one atomic "roll" operation: caller picks the old key's expiry, the system creates the new key and schedules the old one in a single call - never two manual steps where the expiry half gets forgotten.
- Copy the named mechanics:
- Stripe's roll keeps both keys valid for up to 7 days (pick "Now" to kill immediately) behind two-factor verification.
- AWS IAM hard-caps each user at two active access keys, which forces old/new overlap instead of N-key sprawl, and gates deletion on
get-access-key-last-used showing zero traffic.
- Grace-period defaults by scenario:
- Routine rotation: 24 hours to 7 days, depending on deploy cadence.
- Confirmed compromise or offboarding: zero grace, revoke immediately and accept the downtime.
- Agent fleets and queued workloads: longer than the human default.
- These are practitioner conventions to adjust in the interview, not standards - no universal rotation frequency exists, and PCI-DSS 4.0 explicitly refuses to name one (see step 7).
- Two operational invariants: never delete an old key before last-used telemetry confirms zero traffic on it, and never create a new key without scheduling the old one's revocation - the second without the first doubles the attack surface instead of rotating anything.
- Rotation matters absent any breach: GitGuardian found 64% of valid secrets leaked in 2022 were still valid and exploitable years later.
Full worked runbook, per-vendor mechanics, and the cadence table: references/rotation-runbook.md.
5. Design the self-service dashboard behavior
Self-service means four actions completable without a support ticket:
- Sign up.
- Create a key.
- Rotate a key.
- Revoke a compromised key.
A dashboard missing any one of them is self-service-with-an-escape-hatch-to-support for that action. Sibling samber/developer-platform-skills@developer-portal-design owns where this dashboard sits in the portal. This skill owns what it does.
- List view per key: human-readable label (distinct from the key value - "CI pipeline, staging"), creation date, last-used timestamp, status badge (active/inactive/expiring), and direct rotate/revoke actions.
- Mask everywhere after creation: first and last few characters only (Anthropic returns just a
partial_key_hint); the full key appears once, in the creation modal, and never again on the irretrievable default rung.
- Update
last_used_at asynchronously, fire-and-forget, after a successful auth check - never block the request path on telemetry. A stale last-used is the signal that makes "revoke this key" and "expand scope only when logs prove need" actionable instead of aspirational.
- Show per-key usage (and spend, where metered) as a separate surface from last-used: one answers "is this key alive", the other "what is this key doing/costing" - a mature dashboard needs both.
- Auth failures the key system emits (expired, revoked, insufficient scope) go through the platform's error surface: 401 for a key that fails authentication, 403 for a valid key lacking the scope - sibling
samber/developer-platform-skills@api-error-design owns those semantics.
6. Assign ownership and wire offboarding
Ownership is the lifecycle decision the dashboard must surface, because user-owned and org-owned keys fail in opposite directions (see Ownership tiers).
- Default production and automation to service-account or org-owned keys. Reserve individual keys for personal experimentation. The design reason service accounts exist: decouple credential validity from employment status, so automation neither silently breaks nor keeps running on a departed employee's still-valid personal key.
- Give org admins a filterable view of every key a specific user created or can see - without it, the offboarding runbook below is unexecutable.
- Offboarding runbook, run on every departure: enumerate the leaver's user-owned keys, revoke each (zero grace - this is the compromise scenario in step 4), rotate any shared credential they had access to, and log each action for the audit trail (step 7).
- Treat a production integration discovered running on a personal key as a standing incident: migrate it to a service account on a scheduled rotation, not during the next departure.
7. Install compliance lifecycle governance
Auditors check policy plus evidence, not tools. Ask question 4 first - cardholder data pulls in PCI-DSS directly. Enterprise buyers pull in SOC 2 through procurement even when the platform's own risk profile wouldn't.
- SOC 2 (CC7.2, C1.2): a documented policy covering the full key lifecycle - provisioning, rotation, revocation, destruction - plus evidence it actually operates: key-event logs, periodic access reviews, approval records. Type II requires those controls operating consistently across a 3-12-month observation window, so a policy adopted the week before the audit fails by construction; automate evidence generation from day one.
- PCI-DSS 4.0/4.0.1, Requirement 8.6.3 (mandatory since April 1, 2025): correct the myth - PCI does not mandate a fixed rotation interval. It requires the cadence to be justified by a documented targeted risk analysis (Requirement 12.3.1), plus immediate rotation on suspicion or confirmation of compromise regardless of schedule. Separately: tamper-resistant logging of API transactions, retained 12 months with the latest 3 immediately accessible.
- Compliance pressure is why mature platforms ship these as product features, not policies: mandatory expiration, expiry warnings and forced-rotation reminders, audit logging of every create/use/rotate/revoke with who/when/why, and service accounts themselves - each one generates the evidence an auditor asks for.
Full framework-by-framework matrix: references/compliance-lifecycle-matrix.md.
Failure modes
Anti-pattern checklist, grounded in OWASP API2 - each is a direct audit finding:
- An API key as the sole credential for a sensitive operation, or key identification conflated with user authentication.
- Plaintext keys at rest, or reversible encryption without a KMS-held key.
- A missing secret that falls back to a hardcoded default instead of crashing at startup.
- A fail-open scope default - omitting scopes at creation grants full access.
- No rate limiting or brute-force protection on key validation and key-creation endpoints (treat them as login endpoints).
- Non-constant-time hash comparison on the auth path.
- A key with no owner, no scope documentation, and no expiry - invisible infrastructure.
- An old key deleted before last-used telemetry hit zero (rotation-caused outage), or a new key created with no revocation scheduled on the old one (doubled attack surface).
- A production integration running on a departed employee's personal key.
- A webhook signing secret managed as if it were an API key, or vice versa.
Measurement
Gates - iterate the design until every one passes:
- Storage: 100% of stored keys hashed (irretrievable rung) or KMS-encrypted (retrievable rung); zero plaintext keys at rest, verified by schema audit, not by policy assertion.
- Inventory: every live key carries an owner, a scope, and an expiry or a documented exemption.
- Rotation: the runbook exists and has been exercised end-to-end at least once - a real key rolled with zero downtime and the old key confirmed at zero traffic before deletion.
- Self-service: all four actions (sign up, create, rotate, revoke) completable without a support ticket.
Trends to baseline from the first month and improve against (no industry thresholds exist): time from leak detection to revocation, share of live keys with last-used older than 90 days, share of production traffic on service-account keys vs personal keys.
Invocation examples
- "Design the API key system for our public API - today we issue one UUID per account and store it plaintext in Postgres."
- "Define a key format and prefix scheme so leaked keys are detectable by secret scanners."
- "Our enterprise customers are asking how we rotate API keys - design a zero-downtime rotation flow and the dashboard UX for it."
- "An engineer just left and half our integrations run on keys they created - fix the ownership model."
References
samber/developer-platform-skills@bulk-data-sharing-design - per-recipient storage credentials for bulk file/lake delivery follow a separate lifecycle from the API keys this skill covers.
- references/vendor-key-format-census.md - prefix taxonomy across seven vendors, GitHub's entropy math, the six-step Secret Scanning Partner Program, per-vendor scoping mechanics and ownership models.
- references/storage-and-lookup-schema.md - the key table schema, prefix-based lookup flow, hashing decision detail, retrievable-model storage.
- references/rotation-runbook.md - worked zero-downtime rotation runbook, Stripe/AWS/Azure mechanics, grace-period cadence table, offboarding sequence.
- references/compliance-lifecycle-matrix.md - SOC 2 and PCI-DSS 4.0 expectations mapped to key-lifecycle controls and the product features they force.
1---2name: api-auth-key-management3description: Design the API-key authentication surface a platform issues to its own API consumers - key format with prefix+checksum conventions, hashed (irretrievable) vs encrypted (retrievable) storage, zero-downtime rotation with dual-key overlap, least-privilege scoping, individual vs org vs service-account ownership, self-service key dashboard behavior, and SOC 2 / PCI-DSS 4.0 lifecycle governance. Use whenever the user mentions API keys, secret keys, key prefixes, key rotation, key scoping, or revoking keys after an employee departure - even if they never say "API key management". Provider side only, not consuming another vendor's API. Do NOT use for OAuth authorization-server design - use samber/developer-platform-skills@oauth2-provider-design instead.4license: MIT5---67# API Auth & Key Management89You are an API authentication designer. Design the key system a platform issues to its own API consumers - format, storage, scoping, rotation, dashboard, ownership, governance - so a leaked, lost, or orphaned key is a contained event instead of an incident.1011Two OWASP anchors frame the whole task (API Security Top 10 2023, API2 Broken Authentication):1213- An API key identifies the calling application, not a user - "OAuth is not authentication, and neither are API keys" is the named principle.14- A key used as the sole credential for a sensitive operation is a named weakness, not a style choice.1516## Clarifying questions1718Ask these before designing anything; each answer changes a later step. Batch them - this is a tactical design task, not a strategy interview.19201. Greenfield or retrofit? If retrofit: request the current key format, how keys are stored today (plaintext, hashed, encrypted), and roughly how many live keys exist.212. Who holds keys: individual developers experimenting, teams running shared production integrations, unattended workloads (CI, scheduled jobs, AI agents / MCP servers)? (see next section)223. Do third-party apps act on behalf of your users, or do consumers only ever call as themselves? (drives the key-vs-OAuth boundary)234. Compliance regimes in scope: payment or cardholder data (PCI-DSS)? Enterprise buyers requiring SOC 2? Neither yet? (see step 7)245. Does the product already have a permission/RBAC system that key scopes could reuse? (re-ranks the scoping ladder - see step 3)256. Timing and effort ceiling: when must this ship, is it a one-off hardening or a compounding platform asset, and how much engineering time and consumer-visible migration can you spend? The storage and scoping menus diverge sharply on effort - re-rank them against these answers: a hard ship date promotes the cheap rungs (hash-only storage, per-environment key split), a compounding-asset mandate promotes the starved ones (workload identity, fine-grained scoping).2627If your harness has persistent memory, store the design's core decisions: key format and prefix taxonomy, storage rung, scoping ladder position, grace-period defaults, ownership tiers, compliance regimes in scope. A later run - a rotation incident, an offboarding sweep, an audit prep - then starts from the design instead of re-deriving it.2829## Ownership tiers3031Every key holder is a developer or a machine, whoever bought the product. The design splits by who owns the credential, because each tier fails differently:3233- **Individual developer keys** (GitHub PATs, OpenAI user keys) - tied to one person's account; every key traces to exactly one human. Right default for personal experimentation; a liability the moment one runs production, because it dies (or worse, keeps working) when that person leaves.34- **Team / organization keys** (Stripe `sk_org_`, GitHub org-approved PATs) - bigger blast radius, but with a governance layer individual keys lack: org admins can require approval before issuance and can review and revoke members' keys.35- **Service-account keys** (OpenAI service accounts - "a pseudo-user designed for system access", Anthropic service accounts, Shopify custom-app tokens owned by the app itself) - deliberately decoupled from any human's employment status, so production automation survives offboarding. The correct default for anything unattended.3637Design all three tiers or state explicitly which one is deferred. A platform that only issues individual keys has silently decided that production runs on someone's personal credential - the exact failure step 6 exists to prevent.3839## Key or OAuth - settle the boundary first4041A static key answers "which app is calling". OAuth answers "has a user granted permission, and within what limits". Choose per surface, not per platform: mature platforms run both, a key identifying the app for rate limiting and OAuth covering user-delegated or compliance-driven operations.4243- Static key: no user in the flow, server-to-server calls, sandboxes, public or read-mostly data, rate limiting by caller.44- OAuth client credentials: cross-org machine-to-machine traffic on sensitive data - short-lived (5-60 min) scoped tokens bound a leak in a way an indefinitely-valid string structurally cannot.45- The named heuristic, both directions:46 - Bolting expiry, per-request scopes, and fine-grained revocation onto a static key means reinventing OAuth - adopt the real thing.47 - Adding redirect URIs and consent screens for a server talking to itself means over-engineering.48- Never ship a long-lived key to a browser or other semi-trusted client: mint a short-lived scoped token server-side instead (OpenAI's 1-10-minute ephemeral tokens are the pattern). For CI and AI-agent/MCP workloads, workload identity federation - the environment attests, no stored secret at all - is the direction the industry is migrating toward, precisely because it removes the stored secret a leak would otherwise expose.4950The moment the answer is "OAuth for third-party apps", hand off to sibling `samber/developer-platform-skills@oauth2-provider-design` (scope granularity, consent screens, token lifecycle, app verification) - this skill owns only the decision and everything on the key side of it.5152## Workflow53541. Define the key format and prefix scheme.552. Choose the storage model.563. Choose the scoping model.574. Design rotation.585. Design the self-service dashboard behavior.596. Assign ownership and wire offboarding.607. Install compliance lifecycle governance.6162Each step has a section below, in order.6364## 1. Key format and prefix6566- Structure every key as `prefix_` + high-entropy random body + checksum. The prefix encodes key type and environment (Stripe `sk_live_` / `sk_test_` / `rk_` / `sk_org_`). The checksum lets a validator reject malformed keys without a database round-trip and lets secret scanners filter noise before alerting.67- Meet an entropy floor of at least 120 bits (32+ random characters). GitHub's benchmark format is a 30-character Base62 body ≈ 178.6 bits plus a 6-character CRC32 checksum - chosen specifically because its old hex-only tokens were "indistinguishable from other encoded data like SHA hashes" and unscannable. The overhead costs ~10 characters of token length; pay it.68- The prefix+checksum shape is free defense-in-depth regardless of formal enrollment. Once real leaked-key incidents justify the integration work, register the pattern with GitHub's Secret Scanning Partner Program (six-step process - see [references/vendor-key-format-census.md](references/vendor-key-format-census.md)) so a customer's key pushed to any public repo triggers the revocation flow before the customer notices.69- Encode the test/live environment in the prefix from day one - sibling `samber/developer-platform-skills@api-test-mode-design` owns the test-mode surface this taxonomy serves; the two skills share this prefix ground.70- Webhook signing secrets (Stripe's `whsec_`) are not API keys - the signing scheme belongs to sibling `samber/developer-platform-skills@webhook-platform-design`; only their storage, rotation, and dashboard lifecycle follows the patterns here.7172## 2. Choose the storage model7374Three models, ranked:7576- security value: `workload identity (no stored key) > irretrievable hash > retrievable encrypted`77- lost-key support-ticket reduction: `retrievable encrypted > irretrievable hash == workload identity` (tie: neither ever re-displays a secret - the lost-key path is reissue in both)78- effort: `workload identity > retrievable encrypted > irretrievable hash`79- compliance cost: `retrievable encrypted > irretrievable hash == workload identity`. Only the retrievable rung creates a surface an auditor samples: KMS key custody, plus a reveal path that hands back a plaintext secret. The tie holds because the other two expose nothing revealable, so neither adds a review.80- efficiency: `irretrievable hash > retrievable encrypted > workload identity`8182- **Default rung: irretrievable, hash-only.** Show the full key exactly once at creation, store only a hash (Stripe, AWS, GitHub, OpenAI, Anthropic, Linear, Resend all do this). Hash with SHA-256 - the prevailing choice for machine-generated secrets, with a real rationale: bcrypt/Argon2 exist to defend low-entropy human passwords, and a GPU doing ~21 billion SHA-256 hashes/second is still infeasible against a 178-bit random key. The slow-hash counter-view (defense-in-depth, uniform secret policy) stays a live minority position - note that bcrypt's hard 72-byte input limit undercuts "just bcrypt everything" anyway.83- Store a short plaintext `key_prefix` column, indexed, for O(1) candidate lookup. Run a constant-time comparison on the hash for the final check - a naive `==` leaks timing.84- **Step up to retrievable** only when the consumer base routinely loses keys, the data is low-stakes, and support load dominates (Twilio, Supabase). Then: AES-256-GCM with the key-encryption-key in a KMS, plus a SHA-256 hash stored alongside so validation never needs a decrypt round-trip. Never plaintext, never app-managed encryption keys.85- **Workload identity is the starved option**: highest security (nothing to leak) and highest effort (federation infrastructure, per-environment attestation), so it loses every efficiency round - promoted anyway when the consumers are CI systems or AI agents/MCP servers, the segment actively migrating off static keys.86- Fail secure: a missing secret must crash the service at startup, never fall back to a hardcoded default - the `dev-secret-key-123` fallback runs in production exactly like the real thing.87- This ranking is a default, not a law - re-rank against question 6 and what you know of the user. A platform already running a KMS gets retrievable nearly free. An agent-first platform should treat workload identity as the default, not the promotion.8889Schema and lookup-flow details: [references/storage-and-lookup-schema.md](references/storage-and-lookup-schema.md).9091## 3. Choose the scoping model9293Scoping is authorization layered on the identity a key establishes - complementary controls, not substitutes. The named heuristic for machine-to-machine keys: small scopes beat "trusted internal service" logic every time - team trust is not a security boundary; the key's own scope is the only enforceable one.9495Reject the single account-wide key outright - it is the anti-pattern, not the bottom rung. SendGrid's fail-open default (omit scopes at creation, get Full Access) is the named example to design against: default restrictive, expand only when usage logs prove the consumer needs more.9697Three rungs, ranked:9899- effort: `fine-grained resource scoping > coarse per-key scopes > per-environment key split`100- blast-radius containment: `fine-grained resource scoping > coarse per-key scopes > per-environment key split`101- efficiency: `per-environment key split > coarse per-key scopes > fine-grained resource scoping`102103- **Default rungs - take both bottom rungs together**: one key per service per environment (dev/staging/production), plus a coarse restrictive-by-default scope set (read vs write, per product area). Both are days of work and they stack; a leaked key then compromises one service in one environment with one permission set.104- **Promote to fine-grained resource scoping** when a compliance regime is in scope, enterprise customers demand it, or multiple teams consume keys with genuinely distinct permission needs. The rung means per-resource grants, granular permissions, and mandatory expiration (GitHub fine-grained PATs: one resource owner, selected repositories, 64 permission types, forced expiry, org-level approval). This is the starved option: highest containment, and a permission-taxonomy project measured in quarters, so it loses every efficiency round until one of those conditions promotes it.105- This ranking is a default, not a law - question 5 re-ranks it: an existing product RBAC system that scopes can reuse makes fine-grained nearly free, which flips the effort line and the winner.106- Document every key by what it does, not just where it lives: name, consuming service, allowed and denied actions, owning team. An undocumented, never-expiring key "becomes invisible infrastructure, continuing to work through team changes, architecture changes, and forgotten integrations" - scoping and expiry are paired controls, not independent ones.107108Per-vendor scoping mechanics: [references/vendor-key-format-census.md](references/vendor-key-format-census.md).109110## 4. Design rotation111112Zero-downtime rotation means old and new key validate simultaneously for a grace period. Without overlap, rotation breaks down:113114> "rolling a key means every customer needs to update their integration the same day. So you don't rotate. So when a leak happens, you're rotating in production at 3am."115116- Expose rotation as one atomic "roll" operation: caller picks the old key's expiry, the system creates the new key and schedules the old one in a single call - never two manual steps where the expiry half gets forgotten.117- Copy the named mechanics:118 - Stripe's roll keeps both keys valid for up to 7 days (pick "Now" to kill immediately) behind two-factor verification.119 - AWS IAM hard-caps each user at two active access keys, which forces old/new overlap instead of N-key sprawl, and gates deletion on `get-access-key-last-used` showing zero traffic.120- Grace-period defaults by scenario:121 - Routine rotation: 24 hours to 7 days, depending on deploy cadence.122 - Confirmed compromise or offboarding: zero grace, revoke immediately and accept the downtime.123 - Agent fleets and queued workloads: longer than the human default.124- These are practitioner conventions to adjust in the interview, not standards - no universal rotation frequency exists, and PCI-DSS 4.0 explicitly refuses to name one (see step 7).125- Two operational invariants: never delete an old key before last-used telemetry confirms zero traffic on it, and never create a new key without scheduling the old one's revocation - the second without the first doubles the attack surface instead of rotating anything.126- Rotation matters absent any breach: GitGuardian found 64% of valid secrets leaked in 2022 were still valid and exploitable years later.127128Full worked runbook, per-vendor mechanics, and the cadence table: [references/rotation-runbook.md](references/rotation-runbook.md).129130## 5. Design the self-service dashboard behavior131132Self-service means four actions completable without a support ticket:133134- Sign up.135- Create a key.136- Rotate a key.137- Revoke a compromised key.138139A dashboard missing any one of them is self-service-with-an-escape-hatch-to-support for that action. Sibling `samber/developer-platform-skills@developer-portal-design` owns where this dashboard sits in the portal. This skill owns what it does.140141- List view per key: human-readable label (distinct from the key value - "CI pipeline, staging"), creation date, last-used timestamp, status badge (active/inactive/expiring), and direct rotate/revoke actions.142- Mask everywhere after creation: first and last few characters only (Anthropic returns just a `partial_key_hint`); the full key appears once, in the creation modal, and never again on the irretrievable default rung.143- Update `last_used_at` asynchronously, fire-and-forget, after a successful auth check - never block the request path on telemetry. A stale last-used is the signal that makes "revoke this key" and "expand scope only when logs prove need" actionable instead of aspirational.144- Show per-key usage (and spend, where metered) as a separate surface from last-used: one answers "is this key alive", the other "what is this key doing/costing" - a mature dashboard needs both.145- Auth failures the key system emits (expired, revoked, insufficient scope) go through the platform's error surface: 401 for a key that fails authentication, 403 for a valid key lacking the scope - sibling `samber/developer-platform-skills@api-error-design` owns those semantics.146147## 6. Assign ownership and wire offboarding148149Ownership is the lifecycle decision the dashboard must surface, because user-owned and org-owned keys fail in opposite directions (see Ownership tiers).150151- Default production and automation to service-account or org-owned keys. Reserve individual keys for personal experimentation. The design reason service accounts exist: decouple credential validity from employment status, so automation neither silently breaks nor keeps running on a departed employee's still-valid personal key.152- Give org admins a filterable view of every key a specific user created or can see - without it, the offboarding runbook below is unexecutable.153- Offboarding runbook, run on every departure: enumerate the leaver's user-owned keys, revoke each (zero grace - this is the compromise scenario in step 4), rotate any shared credential they had access to, and log each action for the audit trail (step 7).154- Treat a production integration discovered running on a personal key as a standing incident: migrate it to a service account on a scheduled rotation, not during the next departure.155156## 7. Install compliance lifecycle governance157158Auditors check policy plus evidence, not tools. Ask question 4 first - cardholder data pulls in PCI-DSS directly. Enterprise buyers pull in SOC 2 through procurement even when the platform's own risk profile wouldn't.159160- SOC 2 (CC7.2, C1.2): a documented policy covering the full key lifecycle - provisioning, rotation, revocation, destruction - plus evidence it actually operates: key-event logs, periodic access reviews, approval records. Type II requires those controls operating consistently across a 3-12-month observation window, so a policy adopted the week before the audit fails by construction; automate evidence generation from day one.161- PCI-DSS 4.0/4.0.1, Requirement 8.6.3 (mandatory since April 1, 2025): correct the myth - PCI does not mandate a fixed rotation interval. It requires the cadence to be justified by a documented targeted risk analysis (Requirement 12.3.1), plus immediate rotation on suspicion or confirmation of compromise regardless of schedule. Separately: tamper-resistant logging of API transactions, retained 12 months with the latest 3 immediately accessible.162- Compliance pressure is why mature platforms ship these as product features, not policies: mandatory expiration, expiry warnings and forced-rotation reminders, audit logging of every create/use/rotate/revoke with who/when/why, and service accounts themselves - each one generates the evidence an auditor asks for.163164Full framework-by-framework matrix: [references/compliance-lifecycle-matrix.md](references/compliance-lifecycle-matrix.md).165166## Failure modes167168Anti-pattern checklist, grounded in OWASP API2 - each is a direct audit finding:169170- An API key as the sole credential for a sensitive operation, or key identification conflated with user authentication.171- Plaintext keys at rest, or reversible encryption without a KMS-held key.172- A missing secret that falls back to a hardcoded default instead of crashing at startup.173- A fail-open scope default - omitting scopes at creation grants full access.174- No rate limiting or brute-force protection on key validation and key-creation endpoints (treat them as login endpoints).175- Non-constant-time hash comparison on the auth path.176- A key with no owner, no scope documentation, and no expiry - invisible infrastructure.177- An old key deleted before last-used telemetry hit zero (rotation-caused outage), or a new key created with no revocation scheduled on the old one (doubled attack surface).178- A production integration running on a departed employee's personal key.179- A webhook signing secret managed as if it were an API key, or vice versa.180181## Measurement182183Gates - iterate the design until every one passes:184185- Storage: 100% of stored keys hashed (irretrievable rung) or KMS-encrypted (retrievable rung); zero plaintext keys at rest, verified by schema audit, not by policy assertion.186- Inventory: every live key carries an owner, a scope, and an expiry or a documented exemption.187- Rotation: the runbook exists and has been exercised end-to-end at least once - a real key rolled with zero downtime and the old key confirmed at zero traffic before deletion.188- Self-service: all four actions (sign up, create, rotate, revoke) completable without a support ticket.189190Trends to baseline from the first month and improve against (no industry thresholds exist): time from leak detection to revocation, share of live keys with last-used older than 90 days, share of production traffic on service-account keys vs personal keys.191192## Invocation examples193194- "Design the API key system for our public API - today we issue one UUID per account and store it plaintext in Postgres."195- "Define a key format and prefix scheme so leaked keys are detectable by secret scanners."196- "Our enterprise customers are asking how we rotate API keys - design a zero-downtime rotation flow and the dashboard UX for it."197- "An engineer just left and half our integrations run on keys they created - fix the ownership model."198199## References200201- `samber/developer-platform-skills@bulk-data-sharing-design` - per-recipient storage credentials for bulk file/lake delivery follow a separate lifecycle from the API keys this skill covers.202- [references/vendor-key-format-census.md](references/vendor-key-format-census.md) - prefix taxonomy across seven vendors, GitHub's entropy math, the six-step Secret Scanning Partner Program, per-vendor scoping mechanics and ownership models.203- [references/storage-and-lookup-schema.md](references/storage-and-lookup-schema.md) - the key table schema, prefix-based lookup flow, hashing decision detail, retrievable-model storage.204- [references/rotation-runbook.md](references/rotation-runbook.md) - worked zero-downtime rotation runbook, Stripe/AWS/Azure mechanics, grace-period cadence table, offboarding sequence.205- [references/compliance-lifecycle-matrix.md](references/compliance-lifecycle-matrix.md) - SOC 2 and PCI-DSS 4.0 expectations mapped to key-lifecycle controls and the product features they force.