Febasidocs
Guides

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.

FieldPurpose
idUUID. The internal identifier.
tenant_idOwner tenant. NULL for platform-admin keys (rare).
nameHuman label, e.g. "Stripe webhook receiver".
scopesJSONB array of allowed scopes (e.g. users:read).
key_hashSHA-256 of the secret.
revoked_atWhen the key was revoked. NULL while active.
last_used_atUpdated 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: febasi

The Auth:

  1. Hashes the key, looks it up.
  2. Confirms it isn't revoked.
  3. Resolves the tenant from the X-Tenant-Code header (or from the key's tenant_id when present).
  4. Checks the requested route against the key's scopes.
  5. Updates last_used_at and writes a row to client_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:

ScopeWhat it unlocks
users:readList/read users.
users:updateModify user fields (status, email…).
roles:readRead role definitions.
auth:logsRead audit logs.
tenants:readRead tenant config and metrics.

Scopes are scope:action pairs — same shape as user permissions.

Listing & inspecting keys

GET /api/v1/client-keys/tenant

Lists 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.

On this page