Skip to main content
Amends 0207 decision 2: API keys for programmatic access are issued by Stackra and mapped to a ZITADEL machine identity. Restates and extends the token rules of 0087 decision 5 (cross-reference only; 0087 stays superseded by 0206). Amends 0217 (API keys used on the REST and MCP surfaces).

Context

0207 decision 2 sends every machine identity through ZITADEL, and 0087 decision 5 said tokens are shown once, stored hashed, scoped and revocable. Neither says who issues an API key, how it is rotated, recognised when leaked, or seen in use. The owner’s answers of 2026-10-21 (screenshot gap review, item 9, and the issuer decision that followed the PR review) set the issuer and the whole lifecycle.

Decision

  1. Stackra issues API keys. @figentra/service-identity issues API keys for programmatic access by developers and integrations.
  2. Each key maps to a ZITADEL machine identity. The gateway (0237) validates the key and maps it to a ZITADEL machine identity. The machine identity is the principal; its permissions are its IAM grants (0208), decided by the policy decision point (0209). ZITADEL stays the issuer for sign-in, OIDC and machine-to-machine JWTs.
  3. Shown once, stored hashed. The secret is shown once at creation and stored only as a hash.
  4. Prefix registered with GitHub secret scanning. Every key starts with a recognisable prefix registered with GitHub secret scanning.
  5. Checksum suffix. The key ends with a CRC32 checksum (as GitHub’s suffix does). It catches typos and malformed keys, so a scanner and the gateway reject them without a database lookup. It does not prove a key is authentic; only the hash lookup does.
  6. Tenant tokens are bound to one tenant. A tenant service token works only in the tenant it was issued for.
  7. Scopes. A key carries scopes, and the gateway checks each call against them.
  8. Last used. The key list shows each key’s last-used time and IP address.
  9. Rotation with overlap. Rotating a key issues a new one; old and new both work during an overlap window, then the old one stops.
  10. Expiry. A key has an expiry.
  11. Revoke, and auto-revoke on a scanning report. A key can be revoked at once. A key reported by GitHub secret scanning is revoked automatically and its owner is notified.
  12. Example usage. Each key’s page shows an example usage snippet for that key’s scopes.

Consequences

  • A leaked key is revoked when GitHub reports it, not found by the customer weeks later.
  • The key is a credential, not a second permission model: the machine identity is the principal, its permissions are its IAM grants (0208), and the policy decision point (0209) decides each call.
  • service-identity owns key issuance, hashing, the checksum format and the secret-scanning endpoint.
  • Rotation does not need a deploy at the exact moment the old key dies, because of the overlap window.
  • Last-used data is written on calls, so its write path must stay cheap (sampled or batched).

Enforcement

planned — a storage test asserts no plaintext key is persisted; a gateway test rejects a bad checksum before lookup, refuses a tenant token in another tenant, accepts both keys inside the overlap window and only the new one after it, and refuses expired and revoked keys; a test of the secret-scanning endpoint asserts a reported key is revoked.

Open details

The owner did not give these; the conservative default below applies until an ADR changes it.
  1. Prefix. <product>_<env>_ followed by the secret and the checksum, for example stk_live_ and stk_test_; the exact strings are fixed when the GitHub secret-scanning partnership is filed.
  2. Checksum. CRC32 of the secret, base62-encoded, as the last 6 characters.
  3. Overlap window. 24 hours by default, settable up to 7 days.
  4. Expiry. Required at creation, 90 days by default, at most 1 year.
  5. Last-used IP retention. Follows the access-log retention of 0212.

Supersedes and sources

Supersedes: none. Amends: 0207 decision 2, 0217. Cross-references: 0087 decision 5. Sources: owner answers in chat, 2026-10-21 (screenshot gap review, item 9; issuer decision after the PR review).