Service tokens
Endpoint: /api/auth/service-tokens/
Issue a service token for an application to run automation within that application’s roles and the token’s access-control rules. Its key starts with alpst-. A service token cannot call the users API or the applications API.
Authentication and permissions
Token management and history require a person signed in to the console or CLI. Create also accepts the caller’s own personal API token with service_token:create. Other management actions refuse personal API tokens; all management actions refuse service tokens with 403 permission_denied.
The examples with Authorization: Bearer <access_token> use a signed-in session. These routes require that session except create, which also accepts your own personal API token with service_token:create using the token authorization format. No service token can call them. Follow the setup guide to create and approve a token. A service token created from the CLI always waits for approval in the web console. See API authentication.
| Action | Required permission |
|---|---|
| List, get and history | service_token:read globally or on the application |
| Create | service_token:create globally or on the application |
| Update | service_token:update, plus permission to create tokens for that application |
| Rotate | service_token:rotate, plus permission to create tokens for that application |
| Disable | service_token:disable, plus permission to create tokens for that application |
| Delete | service_token:delete |
| Activate or deactivate break-glass | Superuser account, signed in |
An admin can read, create, update, rotate, disable and delete tokens. The application’s creator receives the application:maintainer role for it: they can read, create and update its tokens, but need an additional grant to rotate, disable or delete them. The service_token:manager role also grants read, create and update, globally or for an application. Break-glass is Superuser-only, even for an admin.
Writes are refused with 403 workspace_suspended while the workspace is suspended, except for activating or deactivating break-glass.
List tokens
Request
GET /api/auth/service-tokens/tokens/Only tokens for applications on which you have service_token:read appear.
Query parameters
| Parameter | Type | Description |
|---|---|---|
name | string | Filter by name |
enabled | boolean | Filter by enabled state; false includes pending tokens |
activation_state | string | pending or active |
application | UUID | Filter by application |
expiring | boolean | Live tokens expiring within 14 days |
stale | boolean | Live tokens whose key was issued more than 180 days ago |
search | string | Search name and description |
ordering | string | added_at, updated_at, last_used_at, expires_at or last_issued_at; prefix with - for descending order. Default: -updated_at |
page | integer | Page number, starting at 1 |
page_size | integer | Results per page; default 15, maximum 100 |
Use activation_state=active&enabled=false to find switched-off tokens, and activation_state=pending for tokens awaiting approval.
Response
200 OK, using integer pagination:
{
"count": 1,
"current": 1,
"next": null,
"previous": null,
"last": 1,
"results": [
{
"id": "3f1c2a9e-7b6d-4e1f-9a2b-5c8d7e6f4a31",
"name": "ci-deploy",
"activation_state": "active",
"enabled": true,
"scopes": ["command:create", "command:read"],
"expires_at": "2027-01-01T00:00:00Z"
}
]
}The example shows selected token fields. The next section lists the full set.
Example
curl "https://your-workspace.us1.alpacon.io/api/auth/service-tokens/tokens/?activation_state=pending" \
-H "Authorization: Bearer <access_token>"Token fields
| Field | Type | Description |
|---|---|---|
id | UUID | Read-only token ID |
name | string | Required on create; at most 128 characters. Unique among enabled and pending tokens; a disabled token’s name is available |
description | string | Optional; default "" |
application | UUID / object | Required UUID on create; read-only on update. Responses contain {id, name, service_type, username} |
enabled | boolean | Read-only on create; changing it requires approval |
activation_state | string | Read-only: pending or active |
scopes | array[string] | Each scope has at most 64 characters and names a catalog scope or wildcard. Supply scopes or presets on create |
presets | array[string] | Create-only, write-only preset keys; default [] |
expires_at | datetime | Required future ISO 8601 time on create; changing it requires approval |
rotation_policy | string | 30d, 90d, 180d or 365d; default 90d. Sets the new lifetime when you rotate; does not rotate automatically |
ip_whitelist | array[string] | IPv4/IPv6 addresses or CIDRs, each at most 64 characters; [] means unrestricted |
rate_limit | string | "" uses the fixed default of 1000/hour. An override is N/period, with a positive integer N and second, minute, hour, day, s, m, h or d; at most 32 characters |
allowed_time_start, allowed_time_end | time / null | UTC HH:MM[:SS]; set both or neither. Null means unrestricted. A start after the end crosses midnight; an end on a whole minute includes that minute |
allowed_days | array[string] | mon, tue, wed, thu, fri, sat, sun, case-insensitive; [] permits every day |
justification | string | Write-only, at most 1,024 characters; required for medium/high-risk creation and sensitive changes to tokens with medium/high-risk resulting scopes. Whitespace alone is empty |
last_used_at, usage_count | datetime / integer | Read-only usage; null unless you are an admin or can create tokens for the application |
last_issued_at | datetime | Read-only; creation or latest rotation time |
break_glass_active, break_glass_expires_at | boolean / datetime | Read-only break-glass state |
added_at, updated_at | datetime | Read-only timestamps |
permissions | object | Read-only {update, delete, rotate, disable} permission hints |
key | string | Returned only on create, including pending creation, and rotation |
scope_usage | array / null | Detail response only; null unless you are an admin or can create tokens for the application |
Create a token
Request
POST /api/auth/service-tokens/tokens/The scopes must fit the application’s global roles, including when a Superuser creates the token. Presets are merged with explicit scopes; unavailable scopes are removed from presets. The resulting set cannot be empty. Explicitly requesting an unmintable scope returns 400 service_token_scope_not_mintable.
Service tokens cannot receive ftp_session:create, session:create, session:invite, session:share, tunnel_session:create, tunnel_session:reconnect, userchannel:create or userchannel:update. Cluster scopes are also refused when the workspace does not offer Kubernetes access.
Request body
{
"name": "ci-deploy",
"application": "9d2e4b71-0c3a-4f5e-8b6d-1a2c3e4f5a6b",
"scopes": ["command:create", "command:read"],
"expires_at": "2027-01-01T00:00:00Z",
"rotation_policy": "90d",
"justification": "Run deployment checks from CI"
}Response
The token and its key are created in both cases:
| Status | When | Token state |
|---|---|---|
201 Created | A signed-in admin creates a low-risk token from the web console | activation_state: "active", enabled: true |
202 Accepted | Every other creation, including CLI requests, personal API tokens, non-admin callers and medium/high-risk scopes | activation_state: "pending", enabled: false |
Any wildcard is high risk. Command, upload, download and session scopes with an access-control requirement are medium risk. Other scopes are low risk. A token with command scopes always returns 202 and requires justification, including when a signed-in admin creates it in the web console.
A 202 returns the full token, including key, and adds status, risk_level, risk_details and approval_request_id. Selected fields:
{
"id": "3f1c2a9e-7b6d-4e1f-9a2b-5c8d7e6f4a31",
"key": "alpst-xxxxxxxxxxxxxxxxxx",
"activation_state": "pending",
"enabled": false,
"status": "pending_approval",
"risk_level": "medium",
"risk_details": [],
"approval_request_id": "5cf4106b-b6c5-4ae3-b33c-e90e7a872155"
}risk_details contains entries shaped as {scope, reason, level}; the array above omits those entries for brevity. Save the key from this response: the approver does not receive it. A pending token authenticates nowhere until approval. The approval request expires after 7 days.
A Superuser or an approver assigned to the request approves it in the web console; CLI and token calls cannot approve. The requester may approve their own request unless an approval-policy rule forbids it. Approval rechecks the application’s roles and the expiry time. Rejection, cancellation or expiry invalidates the pending key.
After approval, GET /api/auth/service-tokens/tokens/{id}/ returns 200 with activation_state: "active" and enabled: true. Only then can you add ACLs: ACL writes require an enabled, approved service token.
Example
curl -X POST "https://your-workspace.us1.alpacon.io/api/auth/service-tokens/tokens/" \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"name":"ci-deploy","application":"9d2e4b71-0c3a-4f5e-8b6d-1a2c3e4f5a6b","scopes":["command:create","command:read"],"expires_at":"2027-01-01T00:00:00Z","justification":"Run deployment checks from CI"}'Get a token
GET /api/auth/service-tokens/tokens/{id}/id is the token UUID. Returns 200 with the token fields and scope_usage, but no key. Read activation_state and enabled to check whether a pending token has been activated.
Update a token
Request
PATCH /api/auth/service-tokens/tokens/{id}/
PUT /api/auth/service-tokens/tokens/{id}/name and description changes apply immediately. A request touching any of these sensitive fields returns 202 and waits for approval, for every caller:
scopes,enabled,expires_at,rotation_policyip_whitelist,rate_limitallowed_time_start,allowed_time_end,allowed_days
This includes PATCH {"enabled": false}. Use Disable a token to stop use immediately. There is no enable route: request PATCH {"enabled": true} and wait for approval. The application’s roles are checked again when re-enabling a token.
Only one modification may be pending per token. A token still awaiting its initial approval cannot be updated. application is read-only: create a new token to use a different application. presets are refused on update.
PUT also supplies defaults for ip_whitelist, rate_limit and allowed_days, so it returns 202. Prefer PATCH when changing selected fields.
Response
An immediate update returns 200 with the token and no key. A sensitive update returns:
{
"status": "pending_approval",
"risk_level": "medium",
"risk_details": [],
"approval_request_id": "5cf4106b-b6c5-4ae3-b33c-e90e7a872155"
}This body contains no key. risk_details uses {scope, reason, level} entries, omitted above for brevity. Scope changes must fit the application’s roles both when requested and when approved.
Example
curl -X PATCH "https://your-workspace.us1.alpacon.io/api/auth/service-tokens/tokens/3f1c2a9e-7b6d-4e1f-9a2b-5c8d7e6f4a31/" \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"rate_limit":"2000/hour","justification":"Support deployment checks across more servers"}'Rotate a key
POST /api/auth/service-tokens/tokens/{id}/rotate/Send an optional reason string of at most 1,024 characters. Rotation needs no approval, but your own global roles must cover the token’s stored scopes. Pending and disabled tokens cannot be rotated.
Returns 200 with the token and its new key. The old key stops authenticating immediately, with no overlap. Expiry resets to now plus rotation_policy, not the old expiry plus that duration; this can make the lifetime shorter. Update the secret in the automation that uses it in the same step.
curl -X POST "https://your-workspace.us1.alpacon.io/api/auth/service-tokens/tokens/3f1c2a9e-7b6d-4e1f-9a2b-5c8d7e6f4a31/rotate/" \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"reason":"Scheduled key rotation"}'Disable a token
POST /api/auth/service-tokens/tokens/{id}/disable/Send an optional reason string of at most 1,024 characters. Returns 204 No Content and stops the token immediately without approval. It also ends all active break-glass access for the token. Pending tokens cannot be disabled.
A token is also disabled if an application role or permission is removed and its stored scopes exceed the application’s remaining permissions.
Delete a token
DELETE /api/auth/service-tokens/tokens/{id}/Send an optional reason string of at most 1,024 characters. Returns 204 No Content; the token stops authenticating. Deleting a pending token also cancels its issuance request and invalidates its key. Its history remains available while the application exists.
Break-glass
Break-glass temporarily lifts only the IP whitelist, allowed hours and allowed days. The token must still be approved and enabled, with a valid, unexpired key. It cannot authenticate if its application has been deleted. Rate limits, scopes, application roles and ACLs still apply. See Access-control lists.
Activate
POST /api/auth/service-tokens/tokens/{id}/break-glass/Only a signed-in Superuser can activate break-glass, including while the workspace is suspended.
| Field | Type | Required | Description |
|---|---|---|---|
justification | string | Yes | Nonblank reason, at most 2,048 characters |
duration_hours | integer | No | 1–8 hours; default 4 |
{
"justification": "Restore deployment access during the incident",
"duration_hours": 2
}Returns 201 with {id, service_token, activated_by, justification, activated_at, expires_at, deactivated_at, is_active}. A pending token is refused. Activating break-glass does not make a disabled or expired token usable. The exception remains effective only while the activating user is active and a Superuser; deactivation or demotion ends it.
Deactivate
DELETE /api/auth/service-tokens/tokens/{id}/break-glass/Only a signed-in Superuser can deactivate it. Returns 204 and ends all active break-glass access for this token. If none is active, returns 400 service_token_break_glass_not_active. Activation and deactivation are recorded in history and send webhooks.
Token history
Request
GET /api/auth/service-tokens/history/?service_token={token_id}
GET /api/auth/service-tokens/history/{history_id}/Both routes require service_token:read on the application. The list also requires the service_token query parameter: a missing parameter returns 400 service_token_history_scope_required, and an invalid or inaccessible token ID returns 404 api_not_found. Detail uses the history entry UUID without requiring that query parameter.
Query parameters
| Parameter | Type | Description |
|---|---|---|
service_token | UUID | Required token ID for the list |
change_type | string | One change type |
change_type__in | string | Comma-separated change types |
changed_by | UUID | User who made the change |
added_at__gte, added_at__lte | datetime | Inclusive timestamp bounds |
ordering | string | added_at or -added_at; default -added_at |
page, page_size | integer | List pagination; default 15 results, maximum 100 |
Change types are created, activated, updated, rotated, disabled, deleted, break_glass_activated and break_glass_deactivated.
Response
The list returns 200 with count, current, next, previous, last and results. Detail returns one history entry:
{
"id": "660e8400-e29b-41d4-a716-446655440000",
"service_token_id": "3f1c2a9e-7b6d-4e1f-9a2b-5c8d7e6f4a31",
"service_token_name": "ci-deploy",
"change_type": "updated",
"changed_by": null,
"changes": {"description": {"old": "", "new": "Deployment checks"}},
"reason": "Clarify the token's purpose",
"added_at": "2026-10-24T10:00:00Z"
}Keys are masked as ***. History remains readable after token deletion, but not after application deletion.
Read-only helpers
These routes help you choose scopes, presets and servers before creating a token or its ACLs. They require authentication. The scope and server helpers also require permission to create tokens for the requested application; a personal token reading the server helper needs server:read.
| Request | Parameters | 200 response |
|---|---|---|
GET /api/auth/service-tokens/scopes/ | Required application UUID | {resources: [{name, actions, acl, allowed_actions}], wildcards}. actions lists the workspace catalog; allowed_actions lists what the application can delegate. wildcards includes * only if the application holds it |
GET /api/auth/service-tokens/presets/ | None | Object keyed by preset ID; each value has localized name and description (en, ko) and a permissions array. Presets exclude unavailable extensions and scopes service tokens cannot receive |
GET /api/auth/service-tokens/servers/ | Required application UUID; optional exclude_service_token UUID to omit servers the token already reaches, search by name, ordering (name or platform, default name), page, page_size | Paginated server objects {id, name, os, platform, is_connected, is_deleted} for servers both you and the application can reach. Default 15 results, maximum 100 |
Missing application returns 400 service_token_application_required. An invalid, unknown or deleted application returns 404 api_not_found; lacking permission to create tokens for it returns 403 service_token_application_forbidden.
Error responses
| Status | Error code | Description |
|---|---|---|
| 400 | unique, service_token_name_exists | Name conflicts with an enabled or pending token |
| 400 | service_token_scopes_required | No scopes remain after preset expansion |
| 400 | service_token_scope_invalid, service_token_invalid_preset | Invalid scope or unknown/unavailable preset |
| 400 | service_token_scope_not_mintable | A requested scope cannot be issued to a service token |
| 400 | service_token_scope_exceeds_role | Scopes exceed the application’s roles |
| 400 | service_token_scope_exceeds_rbac | The person rotating the token does not hold its scopes |
| 400 | api_token_invalid_expiration | Expiry is not in the future |
| 400 | service_token_invalid_ip_whitelist, service_token_invalid_rate_limit, service_token_invalid_time_window | Invalid IP range, request rate, hours or days |
| 400 | service_token_justification_required | Missing justification for medium/high-risk issuance or a sensitive change |
| 400 | service_token_pending_activation | The token is still awaiting its initial approval |
| 400 | service_token_update_pending | A modification is already awaiting approval |
| 400 | service_token_presets_not_allowed_on_update | Presets supplied on update |
| 400 | service_token_disabled | A disabled token cannot be rotated |
| 400 | service_token_request_superseded | An issuance request can no longer activate its token |
| 400 | required, blank | Required field missing or empty, including break-glass justification |
| 400 | service_token_break_glass_not_active | No active break-glass access to deactivate |
| 400 | service_token_history_scope_required, service_token_application_required | Required history or helper parameter missing |
| 401 | auth_token_missing | Authentication missing |
| 403 | permission_denied | Credential type cannot call this management route |
| 403 | service_token_application_forbidden | You cannot create tokens for this application |
| 403 | service_token_break_glass_unauthorized | Break-glass caller is not a Superuser |
| 403 | rbac_permission_required, rbac_object_permission_required, api_token_scope_missing | Missing role permission or personal-token scope |
| 403 | workspace_suspended | Writes refused while the workspace is suspended |
| 404 | api_not_found | Token, history entry or application not found or not visible |
When using the issued alpst- key, invalid, disabled, pending, expired or deleted tokens, including tokens on deleted applications, return 401 auth_authentication_failed. IP and time restrictions return 401 auth_ip_not_allowed or auth_access_outside_allowed_time. Exceeding the token’s rate limit returns 429 api_rate_limited with Retry-After; inability to verify a credential returns 503 auth_verification_unavailable. See API authentication.