Built in — nothing to enable
API keys are first-class and always on. There’s no plugin to add and no manifest entity to declare — the framework owns a dedicated key store (persisted to yourPYLON_SESSION_DB SQLite file, in-memory if unset) and
mounts the management endpoints under /api/auth/api-keys.
What you get:
/api/auth/api-keysendpoints to create, list, and revoke keys- Bearer-token resolution: a request with
Authorization: Bearer pk.<...>resolves to the key’s owner instead of looking up a session
What’s stored
The key store keeps an internal record per key (not a manifest entity):
Only the
prefix is shown in lists — the full key is shown once, at creation time, and never again.
Create a key
scopes is an optional free-form string and expires_at is an optional unix timestamp (seconds). API-key auth can’t create keys — a real session is required (403 API_KEY_AUTH_FORBIDDEN otherwise), so a leaked key can’t mint more keys.
Response (200):
key field is shown exactly once — copy it now or revoke and re-create. The server stores only the hash.
Use a key
Just like a session —Authorization: Bearer <key>:
AuthContext with the key owner’s userId. The key’s scopes are not interpreted or enforced by pylon — see below.
Scopes
Scopes are an opaque, application-defined label. Pylon stores whatever string you pass and shows it back when you list keys, but it does not parse or enforce it — there is no built-in scope vocabulary, no automatic “read vs write” gating, and the scope string is not surfaced to your function or policy code. Treat it as metadata for humans auditing the key list, not as an access-control mechanism. Two things actually constrain what a key can do:- It resolves to the key’s owner
userId. A key can do whatever that user can do, subject to your entity policies. - It does not load the owner’s roles.
auth.hasRole(...)isfalsefor key-authenticated requests, so a key can’t exercise role-gated policies its owner otherwise could.
expires_at.
Don’t rely on the scopes string to enforce anything on its own.
List keys
Rotate
There’s no dedicated rotate endpoint — rotate by creating a fresh key and revoking the old one:Revoke
404 NOT_FOUND otherwise). Returns {"revoked": true}, and any subsequent request with that key gets 401 INVALID_API_KEY.
Expiry
Setexpires_at (unix seconds) to make a key auto-expire:
expires_at, the key is rejected at resolution as 401 INVALID_API_KEY. The last_used_at field updates on every successful request — list your keys and diff it against now to find dormant keys you can safely revoke.
Security
- Keys are CSPRNG-generated — a 256-bit random secret, wire format
pk.key_<id>.<secret>. - Hashed with HMAC-SHA256 server-side. Unlike passwords (Argon2id), API-key secrets are full-entropy random, so a fast keyed hash is safe here and adds no per-request latency; a slow KDF would buy nothing.
- Constant-time comparison — no timing leak on key resolution.
- Shown once — the plaintext key never touches the database; only the hash is stored.
- Owner-scoped management — creating, listing, and revoking keys all require a real session, never an API key.
Common patterns
Per-integration keys
One key per third-party integration. Stripe, SendGrid, your CI pipeline, your monitoring agent — each gets a key scoped to exactly the functions it calls.Per-environment keys
CI key for staging, separate key for prod. Rotate independently. If a CI build leaks the staging key, prod isn’t affected.Time-limited keys for handoffs
A consultant needs access for two weeks? Mint a key withexpires_at 14 days out (and enforce read-only in your functions if you tag it with a read scope). Auto-expires; no one needs to remember to revoke it.