Managing API keys
Creating, distributing, and revoking Flux API keys — the hashing scheme, the lifecycle, and how to rotate.
API keys are Flux’s long-lived bearer credential for service-account principals — CI runners, scheduled-job dispatchers, anything that calls the REST API without an OIDC browser flow. Every key belongs to one service_account principal; its permissions come from that principal’s role assignments. Enable the provider first:
[flux.security.auth.api_keys]
enabled = true
Issuing a key
A key needs a service_account principal to attach to. Create one if it does not exist, then mint the key under the principals group (there is no top-level api-key group):
flux principals create ci-runner --type service_account --role operator
flux principals create-key ci-runner --key-name production --expires 90d
Output:
API key created: flux_sk_5b2c8e1a9f4d6e2c8b5a3f7e1d9c4b6a2f8e5d3c1b9a7f5e
Store this key securely — it will not be shown again.
The token is flux_sk_<48 hex chars> (secrets.token_hex(24)). The server stores a SHA-256 hash of the full token plus a 12-character prefix for display; the plaintext is returned exactly once and is not recoverable afterward. --expires is optional — omit it for a non-expiring key. The flag accepts <n>d (days only).
REST equivalent: POST /admin/principals/{subject}/keys with body {"name": "<key-name>", "expires_in_days": 90}, requiring admin:principals:manage. The response is {"key": "<plaintext>"} — no key id or prefix is returned at creation, so list keys to see them.
Hashing at rest
The hash is hashlib.sha256(token).hexdigest() — no salt, no bcrypt, no argon2. SHA-256 alone is sufficient because the token already carries 192 bits of entropy from secrets.token_hex(24); there is nothing to brute-force. A database dump cannot be used to log in, but it can confirm whether a leaked token matches a known key. The key_prefix column stores the first 12 characters of the plaintext; listings show only that prefix.
Listing and revoking
flux principals list-keys ci-runner
# production (flux_sk_5b2c...) expires: 2026-08-12T14:22:17+00:00
# ci-2026-q2 (flux_sk_9a1f...) expires: never
flux principals revoke-key ci-runner --key-name production
REST equivalents:
| Operation | Method | Path | Permission |
|---|---|---|---|
| Create | POST | /admin/principals/{subject}/keys | admin:principals:manage |
| List | GET | /admin/principals/{subject}/keys | admin:principals:read |
| Revoke | DELETE | /admin/principals/{subject}/keys/{key_name} | admin:principals:manage |
Revocation is immediate: the row is deleted from the api_keys table, so the next request carrying that token gets 401. There is no soft-delete or grace period.
Permissions and scoping
A key has no permissions of its own. The provider authenticates the token, looks up the attached principal, and builds the permission set from that principal’s role assignments. flux principals create-key takes no --role flag; scope is a property of the principal. Two keys on the same ci-runner principal are interchangeable. For narrower access, mint a key against a separate principal with tighter roles:
flux principals create readonly-dashboard --type service_account --role viewer
flux principals create-key readonly-dashboard --key-name grafana
Built-in roles are admin, operator, viewer, and worker; custom roles live under flux roles. Adjust assignments later with flux principals grant / revoke.
Rotating a key
Flux has no built-in rotation primitive. Rotation is three explicit steps:
- Create the new key against the same principal, with a new name. Both keys are now valid.
- Distribute the new key to whatever consumes it (secrets manager, CI variable, container env).
- Revoke the old key by name once the new one is live.
The old key keeps working until it is deleted, so both run in parallel for as long as your distribution pipeline needs. There is no per-key “last used” timestamp — only the principal’s last_seen_at updates on each request — so confirm cut-over by watching traffic before revoking. Cadence is your org’s policy; set --expires at creation if you want the server to enforce a maximum age.
Using the key
Send it as a standard bearer token:
curl -H "Authorization: Bearer flux_sk_..." https://flux.example.com/workflows
API keys share the Authorization: Bearer header with OIDC tokens. The auth service walks the provider chain in order — execution-token → OIDC → API key — and the first one to recognise the token wins; the client does not declare which kind it is sending.
Failure modes
Lost key. The plaintext is irrecoverable. Revoke by name and issue a new one.
Leaked key. Revoke immediately. The principal’s last_seen_at shows recent use, but Flux does not record per-key timestamps or per-request audit events — capture those in a reverse proxy or OpenTelemetry traces from [flux.observability] if you need them. Rotate other keys on the same principal if you cannot tell which one leaked.
Wrong permissions. Permissions are on the principal. Adjust roles with flux principals grant / revoke, or — if the principal is shared and you only want to narrow one workload — reissue the key against a tighter principal.