Skip to main content
Sessions are the right shape for users — short-lived, refresh-able, revocable per device. For server-to-server calls (a webhook from Stripe, a cron job in another service, a CI script), you want long-lived, independently-revocable API keys, not 30-day session tokens. The framework ships this in the box.

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 your PYLON_SESSION_DB SQLite file, in-memory if unset) and mounts the management endpoints under /api/auth/api-keys. What you get:
  • /api/auth/api-keys endpoints 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):
The 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>:
The runtime resolves the key via constant-time hash comparison and produces an 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(...) is false for key-authenticated requests, so a key can’t exercise role-gated policies its owner otherwise could.
If you need finer per-key restriction, the reliable levers are minting the key under a narrowly-privileged service user, and checking expires_at. Don’t rely on the scopes string to enforce anything on its own.

List keys

Response:
The full key is not returned. Only the prefix and metadata.

Rotate

There’s no dedicated rotate endpoint — rotate by creating a fresh key and revoking the old one:
The old key stops working the instant it’s revoked.

Revoke

Ownership is checked first — you can only revoke your own keys (404 NOT_FOUND otherwise). Returns {"revoked": true}, and any subsequent request with that key gets 401 INVALID_API_KEY.

Expiry

Set expires_at (unix seconds) to make a key auto-expire:
Once past 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 with expires_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.

Webhook signature backup

Even with HMAC-signed webhooks, requiring an API key on top means a leaked HMAC secret alone isn’t enough. Defense in depth.

Differences from sessions

When to use a session instead

You can do server-to-server auth with a long-lived session token — but you lose per-integration tracking, explicit expiry, and clean revocation. Prefer keys for any non-user caller.