api_keys plugin ships this in the box.
Enable the plugin
Inpylon.manifest.json:
- An
ApiKeyentity to your schema /api/keysadmin endpoints to create/list/rotate/revoke- Bearer-token resolution: requests with
Authorization: Bearer pk_*resolve to the API key’s owner instead of looking up a session
Schema
The plugin auto-provisions:keyPrefix (first 8 chars after the pk_ prefix) is shown in lists — the full key is shown once, at creation time, and never again.
Create a key
key field is shown exactly once — copy it now or rotate the key. The server stores only the Argon2id hash.
Use a key
Just like a session —Authorization: Bearer <key>:
AuthContext with the key owner’s userId and the scopes intersected with the owner’s roles.
Scopes
Scopes are strings the plugin interprets as access predicates. The built-in vocabulary:| Scope | Allows |
|---|---|
fn:<name> | Calling that specific function |
fn:* | Calling any function |
entity:<Name>:read | Listing/reading rows of that entity |
entity:<Name>:write | Creating/updating rows |
entity:<Name>:delete | Deleting rows |
entity:<Name>:* | All ops on that entity |
entity:* | All ops on all entities |
* | Unrestricted (use sparingly) |
scope: "*".
List keys
Rotate
name and scopes, revokes the old key. Update your downstream system, then the old key stops working immediately.
Revoke
INVALID_API_KEY.
Expiry
SetexpiresAt to an ISO 8601 datetime to make a key auto-expire:
default_lifetime_days to apply a default to every newly-minted key:
API_KEY_EXPIRED. The lastUsedAt field updates on every successful request — use it to find dormant keys you can safely revoke:
Security
- Keys are CSPRNG-generated with 256 bits of entropy, prefixed
pk_. - Hashed with Argon2id server-side — same algorithm as passwords.
- Constant-time lookup — no timing leak on key resolution.
- Shown once — the plaintext key never touches the database; only the hash is stored.
- Scoped to the owner’s role intersection — a key can’t grant access the owner doesn’t have.
- Auto-rotation candidates surfaced via
lastUsedAtfor dormant detection.
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 read access for two weeks? Mint a key withexpiresAt 14 days out and scopes: ["entity:*:read"]. 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
| Sessions | API keys |
|---|---|
| 30-day default lifetime | Indefinite by default; explicit expiresAt |
| Refresh-able | Rotate-able |
| Per-device tracking | Per-integration tracking |
| Cookie or bearer | Bearer only |
| User-bound | User-bound, but typically used for service-to-service |
Token prefix pylon_ | Token prefix pk_ |
Shown only via /api/auth/sessions (prefix only) | Shown once at creation, then prefix only |
Without the plugin
If you don’t enable theapi_keys plugin, you can still do server-to-server auth via long-lived sessions — but you lose scoping, expiry, rotation, and the per-key audit trail. Always prefer keys for non-user callers.