On this page
  1. Install
  2. Compose and issue
  3. Service configuration
  4. Issue and verification properties
  5. Persistence bridge
  6. Rate-limit and Nest composition
  7. Rotate with a bounded grace period
  8. Boundary
  9. Live authorization policy
  10. API entry points and requirements
  11. Peer dependencies

@playstack/api-keys

Opaque customer API-key issuance, hashing, verification, scopes, usage, and revocation.

Free. MIT licensed. Check preview availability before installing. See package access.

@playstack/api-keys authenticates customer and integration callers with opaque credentials that are shown once and stored only as indexed SHA-256 digests.

Install

sh
npm install @playstack/api-keys @playstack/core @playstack/events

Compose and issue

ts
import { createApiKeys } from '@playstack/api-keys'

export const apiKeys = createApiKeys({
  persistence,
  events,
  crypto: globalThis.crypto,
  clock,
  ids,
  product: 'bm',
  scopes: ['spans:read', 'spans:write'],
  maxKeyRateLimitCapacity: 1_000,
})

const { secret, key } = await apiKeys.issue({
  owner: { type: 'account', id: accountId },
  name: 'Production ingest',
  environment: 'live',
  scopes: ['spans:write'],
  createdBy: userId,
  rateLimitOverride: { capacity: 500 },
})

Return secret once. The stored record contains only its product/environment prefix, hash, and last four hexadecimal characters.

Service configuration

ApiKeysOptions propertyRequiredPurpose
persistenceYesTransactional lookup, locking, listing, and retained revocation state.
eventsYesEmits typed issue, use, and revocation events.
cryptoYesWeb Crypto subtle and getRandomValues.
clock, idsYesApplication-owned time and identifiers.
productYesLowercase 2–16 character key prefix.
scopesYesComplete allowlist of canonical product scopes.
maxKeyRateLimitCapacityYesCeiling for any per-key capacity override.
lastUsedWriteIntervalMsNoUsage-write throttle; defaults to one hour.
formatNoSecret byte length and complete owner/environment prefix table.

By default, secrets have the form <product>_<live|test>_<32 hex characters> and contain 128 bits of CSPRNG entropy. For a product requiring 32-byte secrets, set format.secretBytes: 32; supported integer lengths are 16–64 bytes.

ts
// Supply this as ApiKeysOptions.format alongside the required ports.
const format = {
  secretBytes: 32,
  prefixes: {
    live: { user: 'pmk_u', account: 'pmk_a', accountMember: 'pmk_m' },
    test: { user: 'pmk_test_u', account: 'pmk_test_a', accountMember: 'pmk_test_m' },
  },
}

Prefixes omit the separator before the random suffix and use 1–64 lowercase alphanumeric characters with single internal underscores, beginning with a letter. product remains the canonical namespace, independent of the displayed prefix. Issue and rotate share the snapshotted format; verification checks exact length, prefix and stored owner/environment metadata. A prefix never grants authority. Changing formats does not automatically accept older credentials: use explicit legacyVerifiers only for application-owned imports, retaining live policy checks.

Issue and verification properties

PropertyMeaning
owner{ type: account | accountMember | user, id }.
nameHuman-readable key label.
environmentlive or test, also encoded in the prefix.
scopesNon-empty subset of the configured scope allowlist.
createdByOpaque actor ID.
expiresAtOptional absolute millisecond timestamp.
rateLimitOverride.capacityOptional per-key ceiling that cannot exceed the service maximum.
requiredScopesOptional verification requirement; every requested scope must be present.

Persistence bridge

ApiKeysPersistence exposes a transaction boundary, locked lookup by hash, lookup by ID, owner-scoped listing, and save. Verification and revocation must lock the same record so a last-used update cannot race past revocation.

ts
import { prismaApiKeysPersistence } from '@playstack/api-keys/prisma'

const persistence = prismaApiKeysPersistence(prisma)

The managed Prisma fragment is security-critical and non-ejectable. The application owns migrations.

Rate-limit and Nest composition

The key’s optional override is only an inner ceiling. Compose @playstack/rate-limit so an account-plan bucket remains authoritative, then add @playstack/nest-api-keys at the HTTP boundary to verify scopes and consume both buckets atomically.

Revocation retains the record and emits a transactional audit event. Manual rotation can still issue a second key, deploy it, verify traffic, and revoke the first.

Rotate with a bounded grace period

ts
const rotation = await apiKeys.rotate({
  id: currentKeyId,
  rotatedBy: userId,
  overlapEndsAt: Date.now() + 60 * 60_000,
})

Atomic rotation links both generations and keeps the previous key valid only through the configured overlap. maxRotationOverlapMs defaults to seven days. Retrying a completed rotation returns already_rotated rather than creating another key, but cannot recover the copy-once replacement secret.

revokeFamily() traverses both sides of the lineage and revokes every generation in one transaction. Missing links and cycles fail closed. Named legacy verifiers can validate an application-owned key format and optionally rehash the managed record during a gradual migration.

Boundary

API keys are not JWTs, OAuth tokens, third-party credentials, offline licences, or a complete authorization policy. Application code still authorizes the verified owner and scopes against the requested resource.

Live authorization policy

Optional authorization.assertAllowed(input, transaction) runs for issue, verify and rotate, including already-rotated retries. Verification checks policy on every use, not only last-used writes. Use the transaction to recheck membership, issuer ceilings and resource ownership; requested resource context is not trusted tenancy.

List/revoke management routes need separate authorization. Rotation preserves the original creator while recording the rotator; map replacement credentials to an application-owned stable grant actor. No generic machine-principal or vault-policy package is implied.

API entry points and requirements

Reference snapshot: @playstack/api-keys@0.1.0-beta.1. Import only the entry point your runtime needs. Paths below are relative to the installed package; use Go to Definition in your editor to inspect exact parameters, return types and overloads. Do not import the declaration-file paths directly.

Public entry pointDeclaration file
@playstack/api-keys./dist/index.d.ts
@playstack/api-keys/errors./dist/errors.d.ts
@playstack/api-keys/prisma./dist/prisma.d.ts
@playstack/api-keys/testing./dist/testing.d.ts
@playstack/api-keys/playstack.artifacts.jsonNo TypeScript declaration (asset or metadata export).
@playstack/api-keys/package.jsonNo TypeScript declaration (asset or metadata export).

Node.js engine requirement: >=20. This is not a claim that every entry point works in browsers or Workers.

Peer dependencies

This package declares no peer dependencies. Its ordinary dependencies are resolved by the package manager.

For a complete first program, start with Getting started. For API lookup and partial-example conventions, see Reading the reference. Provider failures, lifecycle requirements and application responsibilities remain described in the guide above; types alone do not establish production safety.

Go

Playstack Pro tag
OriginsPricingBlogNewsletterChangelogStatusRoadmap
ContributorsCommunityIn Use ShowcaseCase StudiesPartnersSponsors
FAQsSupportContact

© 2026 Playstack. All rights reserved.

With OSS
Terms of ServicePrivacy PolicyCookie PolicyImprint

By

Commune Software