API keys
What a key contains, how it is checked, and what you can change after issuing one.
Anatomy
apikee_<base62 payload>.<base62 signature>The payload holds the claims; the signature is HMAC-SHA256 over them using
APIKEE_SECRET. Editing any claim invalidates the signature, so a tampered key is
rejected before any database work happens.
{
"id": "k_2f9c…",
"tenant": "acme",
"scopes": ["read:users", "write:orders"],
"environment": "production",
"expiresAt": "2027-01-01T00:00:00.000Z",
"notBefore": null,
"meta": { "plan": "partner" }
}Issuing
key = apikee.create("acme", scopes=["read:users"], expires_in="90d")expiresIn accepts a duration string ("90d", "24h", "3600s"), a timestamp, or
milliseconds. Omit it for a key that never expires.
Through the platform — dashboard or REST — keys are created on a client, optionally several at once.
The raw key is returned once, at creation, and never again. The database stores only a SHA-256 hash and a 12-character display prefix. If it is lost, issue a new one.
Verification order
Server mode runs eight checks and stops at the first failure, cheapest first. Every outcome — accepted or rejected — is written to the request log with credentials redacted.
| # | Check | Failure |
|---|---|---|
| 1 | Timestamp is sane (not in the future) | 401 STALE_REQUEST |
| 2 | Format, HMAC signature, embedded expiry | 401 (400 if malformed) |
| 3 | Key exists, is enabled, not expired in the database | 401 |
| 4 | Signed claims match the stored row; key belongs to this client | 401 |
| 5 | Owning client is active | 403 |
| 6 | Caller IP is in the client's allow-list | 403 |
| 7 | Request signature, when the environment has a public key | 401 |
| 8 | Template rate limits | 429 + Retry-After |
Steps 1–2 are what your SDK does locally. Everything from step 3 needs the platform, which is why local mode cannot revoke.
What you can change later
| Secret, scopes, environment | Immutable. They are signed. Issue a new key instead. |
| Expiry | Can only be shortened. |
| Name, metadata | Freely editable. |
| Enabled / revoked | Toggle any time (server mode). |
This is a deliberate constraint, not a limitation to work around: a key that could be widened after issuance would make the signed claims meaningless.
Limits
| Active keys per client | 25, enforced under a row lock |
| Keys per request | 10 |
| Clients per bulk request | 100 |
| Name | 100 characters, no control characters |
| Metadata | 8 KiB, 5 levels deep |
| Expiry window | At least 1 minute out, at most 5 years |
| IP rules per client | 100 |
Rotating the signing secret
The SDKs accept an array of secrets. Verification tries each, so you can rotate without downtime:
- Add the new secret first in the list, keep the old one after it.
- Deploy. New keys sign with the new secret; old keys still verify.
- Re-issue outstanding keys.
- Remove the old secret.
new Apikee({ secrets: [process.env.APIKEE_SECRET_NEW!, process.env.APIKEE_SECRET_OLD!] })Request signing
For environments that need more than a bearer credential, set a PEM public key on
the environment. The SDK then signs {clientId}|{method_path}|{timestamp} with the
matching private key and sends it as x-apikee-signature (base64url). Signed
requests are additionally held to a five-minute replay window.
See setPublicKey for configuring it.

