Client Keys (service-to-service auth)
Long-lived API keys with scoped permissions and full usage logs — the right tool for backend integrations.
Client Keys are how a backend service authenticates against Febasi Auth without sitting on a user JWT. They are scoped, revocable, audited, and rate-limited the same way user requests are.
Use them for:
- Service-to-service requests between Febasi products.
- Third-party integrations (CI runners, exporters, billing reconciliation jobs).
- Background workers that don't have a human session to ride on.
Do not use them for:
- A user-facing login. Users go through
/login. - Anything that requires the hierarchy check — Client Keys carry their own scope set, not a level.
Anatomy of a key
A Client Key is a high-entropy random string. Only its SHA-256 hash is stored; the plaintext value is shown once at creation and never again.
| Field | Purpose |
|---|---|
id | UUID. The internal identifier. |
tenant_id | Owner tenant. NULL for platform-admin keys (rare). |
name | Human label, e.g. "Stripe webhook receiver". |
scopes | JSONB array of allowed scopes (e.g. users:read). |
key_hash | SHA-256 of the secret. |
revoked_at | When the key was revoked. NULL while active. |
last_used_at | Updated on every successful authentication. |
Creating a key
POST /api/v1/client-keys
Authorization: Bearer <jwt-with-client-keys:create>{
"name": "Stripe webhook receiver",
"description": "Verifies webhook signatures for billing events",
"scopes": ["auth:logs"]
}Response (the only time you'll ever see the plaintext key):
{
"success": true,
"data": {
"id": "01HZ8...",
"name": "Stripe webhook receiver",
"scopes": ["auth:logs"],
"key": "fbk_live_R9eZ...",
"createdAt": "2026-05-01T12:00:00Z"
}
}Store the key now — you won't see it again
The Auth does not store the plaintext, so it cannot show it back to you. Pass it straight into your secret manager (1Password, Doppler, AWS Secrets Manager, …). If you lose it, revoke and create a new one.
Using a key
Send the key in the Authorization header alongside the tenant code:
GET /api/v1/users
Authorization: ApiKey fbk_live_R9eZ...
X-Tenant-Code: febasiThe Auth:
- Hashes the key, looks it up.
- Confirms it isn't revoked.
- Resolves the tenant from the
X-Tenant-Codeheader (or from the key'stenant_idwhen present). - Checks the requested route against the key's
scopes. - Updates
last_used_atand writes a row toclient_key_logs.
If any step fails, the request is rejected with UNAUTHORIZED or
FORBIDDEN — never with a body that hints at the failure reason.
Available scopes
GET /api/v1/client-keys/scopes…returns the full list. Common ones:
| Scope | What it unlocks |
|---|---|
users:read | List/read users. |
users:update | Modify user fields (status, email…). |
roles:read | Read role definitions. |
auth:logs | Read audit logs. |
tenants:read | Read tenant config and metrics. |
Scopes are scope:action pairs — same shape as user permissions.
Listing & inspecting keys
GET /api/v1/client-keys/tenantLists every key for the current tenant. To inspect a single key, including its usage stats:
GET /api/v1/client-keys/{id}
GET /api/v1/client-keys/{id}/stats
GET /api/v1/client-keys/{id}/logs?limit=50&offset=0/stats aggregates total calls, success rate, and the last call timestamp.
/logs returns paginated usage entries: method, path, IP, status, and
response time.
Revoking
Revocation is immediate — the key stops working before the response returns to the caller:
POST /api/v1/client-keys/{id}/revoke{ "reason": "Suspected leak in CI logs" }The row is updated with revoked_at and revoke_reason. The same hash
cannot be used again; even if you somehow re-create a key with the same
plaintext, the old hash row stays revoked.
Rate limiting
Client Keys share the same rate limit budget as user JWTs — 100
requests/minute by default, customizable per route. Hitting the limit
returns RATE_LIMIT_EXCEEDED (HTTP 429). For high-traffic integrations,
ask the platform team to lift your specific tenant's budget.
Operational tips
One key per integration
Don't share keys across services. Revoking a leaked one is painless if its blast radius is one workload.
Minimum scope
Grant only the scopes the integration actually needs. The audit log will thank you the day something looks off.
Rotate on cadence
For high-value keys, set a rotation cadence (90/180 days). Create the new key first, deploy, then revoke the old one.
Monitor /stats
A key whose call volume changes shape is worth a look. The /stats
endpoint is cheap; wire it into a small dashboard.