Skip to content

Service Keys

Service keys are admin-managed API keys used for server-to-server and privileged access. Unlike client keys (per-user, browser-safe) and JWTs (per-session), a service key grants elevated, often tenant-wide access and is meant to stay secret on your backend.

  • A backend service or cron job calling the Fluxbase API as a privileged caller
  • A CI/CD pipeline running migrations or syncing functions
  • Any context where you need to bypass Row-Level Security or act on behalf of a tenant rather than a user

Service keys (service / global_service) bypass RLS. Treat them like database credentials — store in a secret manager, never ship them to a browser, and rotate them regularly.

TypePrefixPrivilegeRLS
anonpk_live_ / fb_anon_Anonymous accessEnforced (anon role)
publishablefb_pk_Browser-safe publishable keyEnforced (authenticated)
servicesk_live_Elevated, legacy admin-API typeBypassed (service_role)
tenant_servicefb_tsk_Elevated, tenant-scopedEnforced for the tenant (tenant_service)
global_servicefb_gsk_Elevated, cross-tenantBypassed (service_role)

The admin create endpoint only emits anon and service; the wider set (publishable / tenant_service / global_service) is produced by tenant bootstrap and config. See the SDK’s ServiceKey type for the full union.

Send the key in any of these forms:

Terminal window
# Preferred
curl -H "X-Service-Key: sk_live_..." https://api.example.com/api/v1/...
# Or
curl -H "Authorization: ServiceKey sk_live_..." https://api.example.com/api/v1/...
# A service-role JWT also works
curl -H "Authorization: Bearer eyJ..." https://api.example.com/api/v1/...

Expired, disabled, or revoked keys are rejected at auth time. See Client Keys for the client-vs-service-vs-JWT comparison.

All endpoints are under /api/v1/admin/service-keys and require the admin, instance_admin, or tenant_admin role (tenant_admin is tenant-scoped via RLS).

MethodEndpointDescription
GET/List service keys
POST/Create a key (full plaintext returned once)
GET/{id}Get a key
PUT/{id}Update name/description/scopes/namespaces/enabled/limits
DELETE/{id}Hard-delete the key
POST/{id}/disableSoft-disable (reversible)
POST/{id}/enableRe-enable
POST/{id}/revokeEmergency revoke (immediate, permanent)
POST/{id}/deprecateMark deprecated with a grace period
POST/{id}/rotateCreate a replacement and deprecate the old key
GET/{id}/revocationsRevocation audit history

Create returns the full key once — store it immediately.

Terminal window
curl -X POST https://api.example.com/api/v1/admin/service-keys \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{"name":"CI pipeline","key_type":"service","scopes":["*"]}'
const { data, error } = await client.admin.serviceKeys.create({
name: "CI pipeline",
key_type: "service",
scopes: ["*"],
});
// data.key is the full key — save it now, it's not retrievable later
// Rotate: returns a new full key; the old key is deprecated
const { data: rotated } = await client.admin.serviceKeys.rotate(id);
// Revoke immediately
await client.admin.serviceKeys.revoke(id, { reason: "compromised" });
// Audit history (multi-event)
const { data: history } = await client.admin.serviceKeys.getRevocationHistory(id);

Requires an admin token: client.admin.setToken(...). See the SDK admin reference for all methods.

Lifecycle: what actually invalidates a key

Section titled “Lifecycle: what actually invalidates a key”

This is the important part — rotate and deprecate do not stop a key from working on their own.

OperationDB effectStops authenticating?Reversible
disableenabled=falseImmediatelyYes (enable)
enableenabled=true
revokerevoked_at=now, enabled=false + audit row (emergency)Immediately, permanentlyNo
deprecatedeprecated_at=now, grace_period_ends_at setNo — the key keeps working past the grace period until you disable/revoke/delete itClear the fields via SQL
rotateNew key created; old key deprecated_at + replaced_by set + audit row (rotation)No — the old key keeps working until you disable/revoke/delete it
deleteRow removed (audit rows cascade-delete)ImmediatelyNo

Every revoke and rotate writes a row to the auth.service_key_revocations audit table (revocation_type = emergency / rotation). Query it via GET /{id}/revocations or client.admin.serviceKeys.getRevocationHistory(id). Note: deleting a key cascade-deletes its audit rows.

Terminal window
fluxbase servicekeys list
fluxbase servicekeys create --name "CI" --type service
fluxbase servicekeys rotate <id>
fluxbase servicekeys revoke <id> --reason "compromised"
fluxbase servicekeys revocations <id>

See CLI: Service Keys.