8. Service Accounts
Non-interactive identities with their own roles and API keys, for automation that should not authenticate as a specific human tenant user.
Endpoint Summary
| Path | Summary | Method |
|---|---|---|
/api/v1/service-accounts |
List service accounts | GET |
/api/v1/service-accounts |
Create a service account | POST |
/api/v1/service-accounts/{service_account_id} |
Get a service account | GET |
/api/v1/service-accounts/{service_account_id} |
Update a service account | PATCH |
/api/v1/service-accounts/{service_account_id} |
Delete a service account | DELETE |
/api/v1/service-accounts/{service_account_id}/roles |
List service account roles | GET |
/api/v1/service-accounts/{service_account_id}/roles |
Set service account roles | PATCH |
/api/v1/service-accounts/{service_account_id}/tokens |
List service account keys | GET |
/api/v1/service-accounts/{service_account_id}/tokens |
Issue a service account key | POST |
/api/v1/service-accounts/{service_account_id}/tokens/{token_id} |
Revoke a service account key | DELETE |
GET /api/v1/service-accounts
List service accounts
Return the service accounts of the caller’s own tenant, newest first.
Authentication: required (PasswordLogin or BearerJWT).
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | The service accounts. | ServiceAccountListResponse |
| 401 | Unauthorized. Authentication required. | - |
| 403 | Forbidden. Insufficient permissions. | - |
| 500 | Internal server error. | - |
Response Body (200, ServiceAccountListResponse)
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
service_accounts |
array of ServiceAccountResponse object |
yes | n/a | The accounts. | |
total |
integer | yes | n/a | How many accounts are listed. |
POST /api/v1/service-accounts
Create a service account
Create a non-interactive principal in your own tenant. It holds no password and cannot sign in; it acts only through the keys you issue it, and only within the roles you bind to it. A new account starts with no roles, so it can do nothing until you grant some.
Authentication: required (PasswordLogin or BearerJWT).
Request Body (application/json, required)
Schema: ServiceAccountCreateRequest
Request to create a service account.
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
description |
string, nullable | no | null |
What this account is for. Newlines and tabs are allowed; other control characters are not. | |
display_name |
string, nullable | no | null |
Human-readable label. Control and invisible characters are not allowed. | |
name |
string | yes | n/a | Unique name within the tenant, 4-30 characters of lowercase letters, digits and hyphens, starting with a letter. Identifies the account wherever it appears; it cannot be changed afterwards. |
Example
{
"display_name" : "Nightly CI",
"name" : "ci-nightly"
}Responses
| Status | Description | Schema |
|---|---|---|
| 201 | Service account created. | ServiceAccountResponse |
| 400 | Bad request. | - |
| 401 | Unauthorized. Authentication required. | - |
| 403 | Forbidden. Insufficient permissions. | - |
| 409 | Conflict. The request conflicts with the current state of the resource. | - |
| 422 | Validation error. | - |
Response Body (201, ServiceAccountResponse)
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
created_at |
string (date-time) | yes | n/a | When the account was created. | |
created_by |
string (uuid) | yes | n/a | The user that created it. | |
description |
string, nullable | no | null |
What this account is for. | |
display_name |
string, nullable | no | null |
Human-readable label. | |
id |
string (uuid) | yes | n/a | Identifier of the service account. | |
name |
string | yes | n/a | Unique name within the tenant, 4-30 characters of lowercase letters, digits and hyphens, starting with a letter. Identifies the account wherever it appears; it cannot be changed afterwards. | |
updated_at |
string (date-time) | yes | n/a | When the account was last changed. |
GET /api/v1/service-accounts/{service_account_id}
Get a service account
Return one service account of the caller’s own tenant.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
service_account_id |
path | string (uuid) | yes | Identifier of the service account. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | The service account. | ServiceAccountResponse |
| 401 | Unauthorized. Authentication required. | - |
| 403 | Forbidden. Insufficient permissions. | - |
| 404 | Not found. The requested resource does not exist. | - |
| 422 | Validation Error | HTTPValidationError |
Response Body (200, ServiceAccountResponse)
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
created_at |
string (date-time) | yes | n/a | When the account was created. | |
created_by |
string (uuid) | yes | n/a | The user that created it. | |
description |
string, nullable | no | null |
What this account is for. | |
display_name |
string, nullable | no | null |
Human-readable label. | |
id |
string (uuid) | yes | n/a | Identifier of the service account. | |
name |
string | yes | n/a | Unique name within the tenant, 4-30 characters of lowercase letters, digits and hyphens, starting with a letter. Identifies the account wherever it appears; it cannot be changed afterwards. | |
updated_at |
string (date-time) | yes | n/a | When the account was last changed. |
PATCH /api/v1/service-accounts/{service_account_id}
Update a service account
Apply a partial update. The name is fixed at creation: it is how the account is recognised wherever it appears, and a rename would leave every earlier record naming something that no longer exists.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
service_account_id |
path | string (uuid) | yes | Identifier of the service account. |
Request Body (application/json, required)
Schema: ServiceAccountUpdateRequest
Request to update a service account. Only the fields present are applied.
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
description |
string, nullable | no | null |
What this account is for. Newlines and tabs are allowed; other control characters are not. | |
display_name |
string, nullable | no | null |
Human-readable label. Control and invisible characters are not allowed. |
Example
{
"description" : "Nightly backup runner"
}Responses
| Status | Description | Schema |
|---|---|---|
| 200 | The updated service account. | ServiceAccountResponse |
| 401 | Unauthorized. Authentication required. | - |
| 403 | Forbidden. Insufficient permissions. | - |
| 404 | Not found. The requested resource does not exist. | - |
| 422 | Validation error. | - |
Response Body (200, ServiceAccountResponse)
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
created_at |
string (date-time) | yes | n/a | When the account was created. | |
created_by |
string (uuid) | yes | n/a | The user that created it. | |
description |
string, nullable | no | null |
What this account is for. | |
display_name |
string, nullable | no | null |
Human-readable label. | |
id |
string (uuid) | yes | n/a | Identifier of the service account. | |
name |
string | yes | n/a | Unique name within the tenant, 4-30 characters of lowercase letters, digits and hyphens, starting with a letter. Identifies the account wherever it appears; it cannot be changed afterwards. | |
updated_at |
string (date-time) | yes | n/a | When the account was last changed. |
DELETE /api/v1/service-accounts/{service_account_id}
Delete a service account
Delete the account, every key it holds and every role bound to it. The keys stop authenticating on the next request. Repeating the call finishes an interrupted delete.
A protected account cannot be deleted through the API, and the request is refused with 409.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
service_account_id |
path | string (uuid) | yes | Identifier of the service account. |
Responses
| Status | Description | Schema |
|---|---|---|
| 204 | Deleted. | - |
| 401 | Unauthorized. Authentication required. | - |
| 403 | Forbidden. Insufficient permissions. | - |
| 404 | Not found. The requested resource does not exist. | - |
| 409 | Conflict. The request conflicts with the current state of the resource. | - |
| 422 | Validation Error | HTTPValidationError |
GET /api/v1/service-accounts/{service_account_id}/roles
List service account roles
Return the roles bound to the account. Every key it holds resolves exactly these.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
service_account_id |
path | string (uuid) | yes | Identifier of the service account. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | The roles. | RolesResponse |
| 401 | Unauthorized. Authentication required. | - |
| 403 | Forbidden. Insufficient permissions. | - |
| 404 | Not found. The requested resource does not exist. | - |
| 422 | Validation Error | HTTPValidationError |
Response Body (200, RolesResponse)
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
roles |
array of string | yes | n/a | ["admin"] |
Roles currently bound to the principal. |
PATCH /api/v1/service-accounts/{service_account_id}/roles
Set service account roles
Replace the roles bound to the account. Every key it holds resolves the new set from the next request onwards, whenever it was issued. An empty list leaves the account with no permissions at all, which is how it is switched off without destroying anything.
The roles of a protected account cannot be changed through the API, and the request is refused with 409.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
service_account_id |
path | string (uuid) | yes | Identifier of the service account. |
Request Body (application/json, required)
Schema: SetRolesRequest
Request payload for replacing the full role set of a Tenant User.
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
roles |
array of string | yes | n/a | ["admin"] |
Roles to assign to the user; replaces the current set. A role the target tenant does not define, or a reserved one, is rejected with HTTP 400. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | The roles now bound. | RolesResponse |
| 400 | Bad request. A requested role is not defined by the tenant’s RBAC policy. | - |
| 401 | Unauthorized. Authentication required. | - |
| 403 | Forbidden. Insufficient permissions. | - |
| 404 | Not found. The requested resource does not exist. | - |
| 409 | Conflict. The request conflicts with the current state of the resource. | - |
| 422 | Validation error. | - |
Response Body (200, RolesResponse)
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
roles |
array of string | yes | n/a | ["admin"] |
Roles currently bound to the principal. |
GET /api/v1/service-accounts/{service_account_id}/tokens
List service account keys
Return the keys the account holds, newest first. Carries no secret - a key is unrecoverable after issuance. Revoked and expired keys stay listed.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
service_account_id |
path | string (uuid) | yes | Identifier of the service account. |
active_only |
query | boolean | no | Return only records whose state is active. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | The keys. | ApiTokenListResponse |
| 401 | Unauthorized. Authentication required. | - |
| 403 | Forbidden. Insufficient permissions. | - |
| 404 | Not found. The requested resource does not exist. | - |
| 422 | Validation Error | HTTPValidationError |
Response Body (200, ApiTokenListResponse)
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
tokens |
array of ApiTokenResponse object |
yes | n/a | Token metadata, newest first. | |
total |
integer | yes | n/a | Number of tokens returned. |
POST /api/v1/service-accounts/{service_account_id}/tokens
Issue a service account key
Issue a long-lived credential that authenticates as the service account. It resolves whatever roles the account holds at the time of each request - every key of one account carries the same permissions, whenever it was issued.
The key is returned once, in this response, and cannot be retrieved again.
Requires an interactive session: a key cannot issue another, and a tenant switch cannot leave one behind.
Refused with 409 for an account the platform provisions (the S3 gateway, the node programs): its credentials are issued by its own key-possession exchange.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
service_account_id |
path | string (uuid) | yes | Identifier of the service account. |
Request Body (application/json, required)
Schema: ApiTokenCreateRequest
Request to issue a long-lived API token for the caller.
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
description |
string, nullable | no | null |
What this token is for. Optional; name remains the handle an operator identifies it by. |
|
expires_in_days |
integer | yes | n/a | 30, 90, 180, 365 |
Lifetime in days. Required, and must name one of the offered values (30, 90, 180, 365), so a long lifetime is always a deliberate choice. |
name |
string | yes | n/a | Name for this token, unique among the caller’s usable tokens. An operator identifies a token by it. A revoked or expired token releases its name, so a rotated credential can keep the name it had. |
Example
{
"description" : "Nightly backup runner",
"expires_in_days" : 90,
"name" : "nightly-backup@ci-runner-04"
}Responses
| Status | Description | Schema |
|---|---|---|
| 201 | Key issued. The only response that contains the secret. | ApiTokenCreatedResponse |
| 400 | Bad request. | - |
| 401 | Unauthorized. Authentication required. | - |
| 403 | Forbidden. Insufficient permissions. | - |
| 404 | Not found. The requested resource does not exist. | - |
| 409 | Conflict. The request conflicts with the current state of the resource. | - |
| 422 | Validation error. | - |
Response Body (201, ApiTokenCreatedResponse)
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
created_at |
string (date-time) | yes | n/a | When the secret was created. Never changes. | |
created_by |
Principal object |
yes | n/a | Who issued the token. | |
description |
string, nullable | no | null |
What this token is for, if a description was given. | |
expires_at |
string (date-time) | yes | n/a | Absolute expiry. | |
id |
string | yes | n/a | Public token identifier. Appears in audit events; not a secret. | |
name |
string | yes | n/a | The handle given at issuance, unique among the owner’s usable tokens. | |
revoked_at |
string (date-time), nullable | no | null |
When the token was revoked, if it was. The only lifecycle fact the record stores. | |
revoked_by |
Principal object, nullable |
no | null |
Who revoked the token, if it was. | |
roles |
array of string, nullable | no | null |
The role ceiling held in the record: the owner’s own roles as at issuance. Not chosen - a token carries what its owner held, and never more than they hold now. Null for a service account key, which carries no ceiling: it resolves whatever the account holds at the time of each request. | |
state |
ApiTokenState (active, disabled) |
yes | n/a | Whether the token still authenticates. Computed per response, never stored. | |
state_reason |
ApiTokenStateReason (expired, revoked), nullable |
no | null |
Why the token is disabled. Null while it is active. Revocation outranks expiry. | |
token |
string | yes | n/a | vdt1_6XuU1pyDS0abcDEF12345w_9fK3mZq7Xw2Lb1cD_hT8n...7bQ2xM |
The token. Returned once and never retrievable again; a lost token must be replaced. |
warning |
string | no | Store this token now. It cannot be retrieved again. |
Reminder that the secret is shown exactly once. |
DELETE /api/v1/service-accounts/{service_account_id}/tokens/{token_id}
Revoke a service account key
Revoke one key. It stops authenticating on the next request. The record is kept as a tombstone, so a client still presenting it is told the key was revoked rather than that it never existed.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
service_account_id |
path | string (uuid) | yes | Identifier of the service account. |
token_id |
path | string | yes | Public identifier of the key. |
Responses
| Status | Description | Schema |
|---|---|---|
| 204 | Revoked. | - |
| 401 | Unauthorized. Authentication required. | - |
| 403 | Forbidden. Insufficient permissions. | - |
| 404 | Not found. The requested resource does not exist. | - |
| 422 | Validation Error | HTTPValidationError |