API keys
Service-account API keys are the credential type for server-to-server integrations. Each service account (SA) carries a fixed role + scope and owns exactly one API key at a time (D83). Issuing, rotating, and disabling SAs is an admin- or owner-only operation — operators cannot manage key material. If you only need to mint a key, the admin UI flow is documented at Rotate API keys (frontend route: /settings/service-accounts per Phase 6); this page covers what you need to know as the developer consuming the key.
What the SA key looks like
When an SA is created (POST /api/v1/service-accounts), the response contains the full key exactly once. After that response, only the key_prefix is readable; the full key is irretrievable.
{ "service_account": { "id": "a1b2c3d4-1234-5678-9abc-def012345678", "name": "Slack SOC bridge", "role": "viewer", "created_at": "2026-05-22T10:00:00Z" }, "api_key": { "full_key": "sa_<one-time secret>", "key_prefix": "sa_...", "warning": "Store this key immediately. It will never be shown again." }}Use the full key in Authorization: Bearer sa_... on every API call. The sa_ prefix is what the cloud’s middleware matches to route the bearer token through APIKeyAuth (one-by-one bcrypt verify) instead of the standard JWT path; without sa_ the gateway treats the token as a JWT.
List service accounts and inspect prefixes
GET /api/v1/service-accounts (admin+) returns the org’s SAs with their key prefix only:
curl -H "Authorization: Bearer sa_<one-time secret>" \ https://novavms.novalien.com/api/v1/service-accountsExpected response:
{ "data": [ { "id": "a1b2c3d4-1234-5678-9abc-def012345678", "name": "Slack SOC bridge", "role": "viewer", "key_prefix": "sa_...", "created_at": "2026-05-22T10:00:00Z", "last_used_at": "2026-05-22T13:59:42Z", "disabled": false } ]}The single-SA shape is at GET /api/v1/service-accounts/{id}.
Rotate a key (admin+)
POST /api/v1/service-accounts/{id}/rotate-key invalidates the current key and returns a new one in the response body (again, shown exactly once). Use the rolling-cutover pattern: the old key keeps working until the rotate call commits — there is no overlap window beyond the moment of rotation, so push the new key to the integration in the same deploy that consumes the rotated value, or stage a brief blue/green.
curl -X POST -H "Authorization: Bearer sa_<one-time secret>" \ https://novavms.novalien.com/api/v1/service-accounts/a1b2c3d4-1234-5678-9abc-def012345678/rotate-keyResponse:
{ "service_account": { "id": "a1b2c3d4-...", "name": "Slack SOC bridge" }, "api_key": { "full_key": "sa_<new one-time secret>", "key_prefix": "sa_...", "warning": "Store this key immediately." }}Disable / re-enable a service account (admin+)
DELETE /api/v1/service-accounts/{id} soft-disables the SA. The key returns 401 UNAUTHORIZED on every subsequent request; the row remains in audit_log for compliance.
curl -X DELETE -H "Authorization: Bearer sa_<one-time secret>" \ https://novavms.novalien.com/api/v1/service-accounts/a1b2c3d4-1234-5678-9abc-def012345678To re-enable an SA, use the admin UI at /settings/service-accounts; the API does not expose an explicit enable endpoint in v1.
Site access (site-scoped SAs)
If the SA’s role is viewer, you can constrain it to a subset of sites:
GET /api/v1/service-accounts/{id}/site-accessreturns the list.PUT /api/v1/service-accounts/{id}/site-access(admin+) sets the list. Empty array means “no sites” (the SA can authenticate but every site-scoped read returns 404).
curl -X PUT -H "Authorization: Bearer sa_<one-time secret>" \ -H "Content-Type: application/json" \ -d '{"site_ids":["b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b"]}' \ https://novavms.novalien.com/api/v1/service-accounts/a1b2c3d4-1234-5678-9abc-def012345678/site-accessFor non-viewer roles the site-access list is ignored at evaluation time (admin, operator, owner are not site-scoped per D78 / D81).
Per-SA rate limit
SAs share an org-wide bucket of 600 requests per minute (set in cloud/cmd/server/main.go via authmw.SARateLimit(600)). Human (JWT) callers are not subject to this bucket. There is no per-key override in v1 — see Rate limits for the full quota story and how to handle 429 RATE_LIMITED.
Related
- Rotate API keys — admin-side UI flow (frontend route:
/settings/service-accounts) - Roles and permissions — SA roles and what each one can do
- Rate limits — bucket sharing and 429 handling