Velocity • Durability

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