Authentication and API keys
Presenting the key
Every call carries the key in the Authorization header:
Authorization: Bearer fpk_9al41uPRzeglnYvHa3YfvsK86OfQwx6BmC2EKmI0Vhw
Beareris case-insensitive, and whitespace around the secret is ignored.- No login call, no session, no cookie, no signature. A dashboard session cookie is never accepted on
/v1.
| Situation | Answer |
|---|---|
| Header absent or empty | 401 API_KEY_MISSING, with WWW-Authenticate: Bearer realm="freshperf-api" |
| Secret not recognised | 401 API_KEY_INVALID |
Key passed as a query parameter (?api_key=, ?apikey=, ?key=, ?token=, ?access_token=) | 400 API_KEY_IN_QUERY |
X-Acting-Account header present | 400 API_HEADER_NOT_ALLOWED |
What a key is
| The key | Detail |
|---|---|
| Secret | fpk_ + 43 characters, 47 in total |
| Storage | only a digest is kept. The secret is shown once, at creation or rotation |
keyPrefix | the first 12 characters (fpk_ + 8), shown in the dashboard, the logs and the emails |
| Acts as | the account holder, on their own account |
| Ceiling | 25 usable keys per account (active and unexpired) |
A key cannot act on an account someone shared with you, and cannot create, list or revoke other keys.
Status, expiry, address allowlist and the owning account are re-read on every call. Nothing is cached: a revocation or a tightened restriction lands on the next call.
Life cycle
All of it under Account > API keys.
| Action | Effect | Identity confirmation |
|---|---|---|
| Create | picks name, permissions, restrictions, expiry, cap. The secret is shown once | yes |
| Narrow | fewer permissions, fewer services, more address rules, earlier expiry, lower cap | no |
| Widen | the reverse of the line above | yes |
| Rotate | new secret, same settings, old secret killed at the same instant | yes |
| Revoke | the key dies. Later calls: 403 API_KEY_REVOKED | no |
| Expire | at the chosen date. Later calls: 403 API_KEY_EXPIRED | - |
A key may live without an expiry; the dashboard flags those. Deleting the account deletes all its keys.
Emails sent to the holder: creation, rotation, expiry in 7 days, revocation by an administrator, and a call refused from outside the address allowlist (at most one a day per key).
Confirming it is you
Creating, rotating or widening a key asks for one proof, the strongest the account has.
| Factor | Condition | Possible refusals |
|---|---|---|
| Two-factor code (or a recovery code) | 2FA enabled | 400 TOTP_REQUIRED if absent, 403 INVALID_2FA_CODE if wrong |
| Password | 2FA off, password set | 403 CURRENT_PASSWORD_INCORRECT |
| Six-digit code sent by email | account with neither password nor 2FA (Google sign-in or equivalent) | 400 EMAIL_CODE_REQUIRED, 403 EMAIL_CODE_INVALID, 410 EMAIL_CODE_EXPIRED, 429 EMAIL_CODE_LOCKED after five tries |
The email code lasts 15 minutes. Five wrong proofs in fifteen minutes, all factors counted together, lock the step: 429 STEP_UP_LOCKED.
Watching a key
The dashboard keeps, per key:
| Trail | Retention | Contents |
|---|---|---|
| Request log | 30 days | route, HTTP status, calling address, duration, X-Request-Id, and the reason for refusals |
| Event history | 1 year | creation, edit, rotation, revocation, expiry, and by whom |
The logged route is the template (/v1/services/{code}/power), never the concrete path or the query string.
A call whose bearer matches no key is not logged. A burst of 403 API_KEY_IP_NOT_ALLOWED in the log means the secret is being used somewhere other than where you installed it.