Work sessions
Endpoint: /api/work-sessions/sessions/
Create a bounded session for a person or a token, follow its approval status, and read its activity. A service token cannot create a work session: a person creates one assigned to that token with assigned_service_token. Service-token automation does not require a work session.
Access
A Superuser can view and manage every session. Otherwise, the assigned user, the owner of the assigned personal token, or the owner of the assigned service token’s application can manage it. A server or cluster owner or manager can view sessions involving their servers or clusters; their timeline, recordings and live snapshot contain only activity on those targets.
Personal API tokens need the corresponding work_session scope and the owner’s permission on the session. Creating through a personal API token sets requester_type to agent. An agent cannot use the websh or kube_exec presets. Service-token reads are limited by session visibility, token scopes and the audit window.
Approval and rejection work only from the web console. Personal tokens, service tokens, CLI and MCP requests receive 403 permission_denied. Revocation is Superuser-only; a personal token can revoke only when its owner is a Superuser. Cancellation is limited to the creator or a Superuser, not the assignee merely because they are assigned. Tokens with granular scopes cannot call cancel, live snapshot or recommendation acknowledgment.
Routes
All routes below begin with /api/work-sessions/sessions/. {id} is the work session UUID.
| Method | Suffix | Success | Token scope or access |
|---|---|---|---|
GET | (none) | 200, paginated sessions | work_session:read |
POST | (none) | 201, session | work_session:create; no service tokens |
GET | {id}/ | 200, session | work_session:read |
PUT, PATCH | {id}/ | 200 or 202, session | work_session:update; manage permission |
POST | {id}/activate/ | 200, session | work_session:activate; manage permission |
POST | {id}/complete/ | 200, session | work_session:complete; manage permission |
POST | {id}/revoke/ | 200, session | work_session:revoke; Superuser |
POST | {id}/cancel/ | 200, session | Creator or Superuser signed in; tokens with granular scopes are refused |
POST | {id}/approve/ | 200, session or pending vote | Web console approver |
POST | {id}/reject/ | 200, session | Web console approver |
POST | {id}/extend/ | 200 or 202, session | work_session:extend; manage permission |
GET | {id}/timeline/ | 200, timeline | work_session:timeline |
GET | {id}/records/ | 200, recording chunks | work_session:timeline; paid plan |
GET | {id}/live_snapshot/ | 200, snapshot | Signed-in session with view permission; tokens with granular scopes are refused |
POST | {id}/analyze/ | 200 or 202 | work_session:analyze; manage permission and paid plan |
POST | {id}/acknowledge-recommendations/ | 200, acknowledgment time | Assigned person signed in; tokens with granular scopes are refused |
POST | suggest-scopes/ | 200, suggestions | work_session:suggest_scopes |
POST | resolve-target-account/ | 200, account preview | work_session:resolve_target_account and proc:read; no service tokens |
POST | candidate-target-accounts/ | 200, candidates | work_session:candidate_target_accounts and proc:read; no service tokens |
DELETE is unsupported and returns 405. Scheduled sessions activate on their own; activate is only needed to start an approved session early.
Status flow
| Transition | Trigger and caller |
|---|---|
Create → active | No approval needed and no future start time |
Create → approved | No approval needed, with a future starts_at |
Create → pending | Approval needed |
pending → active or approved | An approver settles approval in the web console; future sessions become approved |
pending → rejected | An approver rejects in the web console |
pending → cancelled | Creator or Superuser cancels, or the request reaches its approval deadline or expiry |
approved → active | Start time arrives, or a caller with manage permission calls activate |
active → completed | A caller with manage permission calls complete |
approved or active → revoked | A Superuser revokes |
approved or active → expired | Expiry arrives |
Completed, expired, revoked, rejected and cancelled sessions cannot be modified. Ending a session also cancels pending modifications and closes its live connections. With a duration, expires_at stays null until activation, then becomes started_at + duration.
Create a session
POST /api/work-sessions/sessions/| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Up to 128 characters; default empty |
description | string | Yes | Purpose, up to 2,048 characters |
requester_type | string | Yes | user or agent; machine channels force agent |
scopes | array[string] | Yes | Non-empty presets: websh, tunnel, editor, webftp, command, sudo, kube, kube_exec |
servers | array[UUID] | One target kind | Up to 100 visible server IDs; objects containing id are also accepted |
clusters | array[UUID] | One target kind | At most one cluster; requires kube and only Kubernetes presets, where Kubernetes is available |
target_account_username | string | No | Up to 128 printable ASCII characters, with no whitespace, : or /; defaults to the assigned person’s username. Not accepted on token-held or cluster sessions |
assigned_user | user ID | No | Defaults to the caller |
assigned_token | UUID | No | Assign to a personal API token instead of a user |
assigned_service_token | UUID | No | Assign to a service token instead of a user |
duration | duration | One time limit | Positive duration, such as 01:00:00, within the workspace’s maximum session duration |
expires_at | datetime | One time limit | Future ISO 8601 expiry within the maximum duration |
starts_at | datetime | No | Future start time before expiry; default null means activate on approval |
sudo_policies | array[object] | No | Up to 10 policies; requires the sudo preset |
Choose either servers or clusters, exactly one assignee, and either duration or expires_at. A non-Superuser may assign only to themselves, their own personal token, or a service token of an application they own. The default maximum session duration is four hours. request_channel and declared_agent are read-only.
Each sudo policy has non-empty commands (each up to 512 characters), optional users, allow_bypass_mfa (default false, allowing the session approval to take the place of MFA for sudo), reason (up to 1,024 characters) and optional valid_until. A non-Superuser may name only the assigned user in users. Changes to sudo policies where the session approval takes the place of MFA can require approval or be refused with the errors below.
For example, use a personal token with work_session:create to request an agent command session for its owner to use from a signed-in CLI or MCP session. The personal token cannot run commands in it: personal tokens cannot be issued command execution scopes.
curl -X POST "https://your-workspace.us1.alpacon.io/api/work-sessions/sessions/" \
-H "Authorization: token=\"alpat-xxxxxxxxxxxxxxxxxx\"" \
-H "Content-Type: application/json" \
-d '{"description":"Check disk space before deployment","requester_type":"agent","scopes":["command"],"servers":["7e3984de-49ab-4cc6-bcdf-21fbd35858b8"],"duration":"01:00:00"}'Every successful create returns 201, including when approval is required. In that case the session has status: "pending" and an approval_request_id. Poll the detail route until its status changes; a successful HTTP response does not mean the session is ready to use.
curl "https://your-workspace.us1.alpacon.io/api/work-sessions/sessions/550e8400-e29b-41d4-a716-446655440000/" \
-H "Authorization: token=\"alpat-xxxxxxxxxxxxxxxxxx\""The polling token needs work_session:read. On the Free plan, delegation that would prevent the creator from reading the resulting session returns 402 workspace_plan_update_required.
Update or extend
PATCH /api/work-sessions/sessions/{id}/
PUT /api/work-sessions/sessions/{id}/
POST /api/work-sessions/sessions/{id}/extend/For a pending session, update title, description, scopes, servers, clusters, target_account_username, starts_at or expires_at. The change applies directly and the approval request is reassessed. The assignee cannot change, and a declared account cannot be cleared.
For an approved or active session, update title, description, scopes, servers, target_account_username or sudo_policies. Send the complete desired sudo policy list; existing entries can include their id. Removals and restrictions apply immediately, as does a title change from the web console. Changes requiring approval include description changes, added scopes or servers, wider sudo policies and account changes. Only one modification request can be pending.
An update returns 200 when applied directly, or 202 with the full session when a modification is pending. Its pending_modification_request has this shape:
{
"id": "9b52a7c4-13de-48f6-a01b-7f3e5d982c44",
"changes": {},
"added_at": "2026-10-24T10:00:00Z",
"can_cancel": true
}To extend a session, send a later expires_at and a non-blank reason of up to 2,048 characters. It must be approved or active, its start time must have arrived, and it must not have expired. The new expiry must remain within the workspace’s maximum duration measured from now.
{
"expires_at": "2026-10-24T12:00:00Z",
"reason": "Finish deployment verification"
}An extension returns 200 when applied directly or 202 with the full session when approval is needed. Only one extension request can be pending. Its pending_extension_request is:
{
"id": "9b52a7c4-13de-48f6-a01b-7f3e5d982c44",
"requested_expires_at": "2026-10-24T12:00:00Z",
"expires_at": "2026-10-24T11:00:00Z",
"reason": "Finish deployment verification",
"added_at": "2026-10-24T10:00:00Z"
}Here expires_at is the request deadline, bounded by the session’s current expiry. Read the detail response’s can_extend, extend_blocked_reason, max_extendable_to, extension_request_deadline and extension_ends_with_session before requesting an extension. These hints are null in list responses.
For approval-related changes on this page, only updates and extensions return 202; creation returns 201 even when pending. Analysis uses 202 separately to acknowledge asynchronous work.
Approval and other actions
Approve and reject through the web console as an eligible approver. A policy can prevent the requester from approving their own request. If fresh MFA presence is required, these routes return 400 auth_mfa_required without a source field.
Approval accepts optional adjusted_scopes (non-empty valid presets), adjusted_servers (up to 100 UUIDs, only narrowing), adjusted_target_account_username (non-blank, only narrowing), and either adjusted_duration (positive whole seconds) or adjusted_expires_at (future). Optional recommendations is an array of objects containing id, text, severity (low, medium, high), source (ai_suggested, ai_edited, admin_added) and auto_checkable (boolean). sudo_auto_approve: true is refused; sudo_risk_ceiling is accepted but has no effect.
An approver without visibility of the session receives 404 here and must use POST /api/approvals/approvals/{id}/approve/ for the approval request instead.
Approval returns the session when settled. If more votes are needed, it returns 200 with:
{"status":"pending","approvals_received":1,"approvals_required":2}Reject and cancel return the session. Cancel requires a pending session. Complete requires an active session and starts analysis. Revoke requires an approved or active session. Activate requires an approved, unexpired session and also succeeds if it is already active.
POST {id}/acknowledge-recommendations/ is idempotent and returns {"recommendations_acknowledged_at":"2026-10-24T10:00:00Z"}. Only the assigned person can acknowledge.
POST {id}/analyze/ requires completed, expired or revoked. It returns 202 {"status":"accepted","work_session":"SESSION_ID"}, or 200 with status: "already_exists". ?force=true retries a stuck pending or processing analysis once it is old enough to retry. This route requires a paid plan.
List and read sessions
GET /api/work-sessions/sessions/
GET /api/work-sessions/sessions/{id}/| Parameter | Description |
|---|---|
status | Comma-separated session statuses |
request_channel | Comma-separated cli, mcp, api_token, service_token, unknown, unknown_client, web |
scopes | Comma-separated presets; matches any overlap |
requester_type | Exact requester type |
server, cluster, assigned_user | Filter by UUID |
start, end | Inclusive ISO 8601 bounds on creation time |
has_attention | Boolean; also enables live summaries |
analysis_status | pending, processing, completed or failed |
analysis_ended_at__gte, analysis_ended_at__lt | Inclusive lower and exclusive upper bounds on analysis completion |
search | Search title, description, server and cluster names |
ordering | added_at, expires_at, starts_at, started_at, status, attention; default -added_at. attention must come first and enables live summaries |
include | Set to live_summary to include it per row |
page, page_size | Integer pagination; default size 15, maximum 100, larger values clamped |
Unknown statuses, channels or presets return 400 invalid_input.
{"count":0,"current":1,"next":null,"previous":null,"last":1,"results":[]}results contains session objects. next and previous are integer page numbers or null.
Session fields
| Fields | Description |
|---|---|
id, title, description, status | Identity, purpose and current state |
requester_type, request_channel, requester_type_verified, declared_agent | Requester and channel information |
scopes, servers, clusters | Allowed activities and targets. Each server contains id, name, os, platform, is_connected, is_deleted |
target_account_username, target_account_is_self, account_flag | Target account information |
created_by, assigned_user, assigned_token, assigned_service_token, assigned_credential | Creator and assignee; assigned_credential is null unless you can manage the session |
starts_at, expires_at, duration, started_at, completed_at | Requested and actual time window |
approval_request_id, admission_decision, adjustments | Approval and adjustments |
pending_modification_request, pending_extension_request | Pending changes in the shapes above |
sudo_policies, proposed_sudo_policies, sudo_auto_approve, sudo_risk_ceiling, sudo_posture | Sudo configuration. Bound policies contain id, commands, allow_bypass_mfa, reason, valid_from, valid_until |
ai_suggestion, recommendations, recommendations_acknowledged_at | Suggestions and acknowledgment |
analysis, can_analyze, can_manage | Analysis is null or an object with id, status, risk_score; available actions |
can_extend, extend_blocked_reason, max_extendable_to, extension_request_deadline, extension_ends_with_session | Extension hints, populated on detail and live snapshot |
live_summary | Included when requested |
added_at, updated_at | Creation and update timestamps |
Timeline and recordings
These routes use their own cursor formats, not the integer pagination above.
Timeline
GET /api/work-sessions/sessions/{id}/timeline/
GET /api/work-sessions/sessions/{id}/timeline/?cursor=&page_size=100Without cursor, the response is {"results":[...]} for the whole session. include_records defaults to true; 0, false or no turns recording inclusion off. Embedded recordings are limited to 2 MiB per response and 128 KiB per Websh session. The Free plan returns only the opening of each recording. page_size has no effect in this form.
Include cursor, even empty, to select forward cursor pagination:
{"next":"OPAQUE_CURSOR","results":[]}Pass the returned value as cursor for the next request; stop when next is null. There is no count or previous. page_size defaults to 100 and must be an integer from 1 to 500; invalid or out-of-range values return 400 invalid_input. A cursor from a different workspace, session or access scope returns 400 api_invalid_cursor.
Items share type, timestamp, id and server_id. Types are command, websh_session, tunnel_session, websh_record, ftp_session, file_upload, file_download, sudo_grant and kube_request. They are chronological; ties follow that type order. Commands contain masked_output_summary (up to 200 characters) and output_truncated. Cursor pages contain no recording bytes; websh_session items include record_chunks_total so you can fetch chunks separately.
Recording chunks
GET /api/work-sessions/sessions/{id}/records/?session={websh_session_id}session is a required Websh session UUID belonging to this work session. Tunnel sessions are not accepted. A paid plan is required.
{
"count": 1,
"next": null,
"previous": null,
"results": [{"added_at":"2026-10-24T10:00:00Z","record":"masked terminal output"}]
}next and previous are opaque cursors or null. Send the chosen cursor in cursor; cursor=last starts at the last page. page_size defaults to 8 and is capped at 16. Invalid values use the default, and larger values are clamped. Each response also stops at 2 MiB, so a page may contain fewer chunks; continue with next. Expired cursors return 400 api_cursor_expired; invalid cursors return 400 api_invalid_cursor. If history search is unavailable, the route returns 503 api_search_unavailable.
Live snapshot
GET /api/work-sessions/sessions/{id}/live_snapshot/Use a signed-in session with view permission. since accepts an ISO 8601 datetime with an offset. limit defaults to 200 and is capped at 200; a non-integer returns 400 invalid_input. The response contains work_session, last_seq, snapshot_taken_at, status_history, commands, websh_sessions, sudo_grants and ftp_events.
Planning helpers
| Route | Request | Response |
|---|---|---|
POST /api/work-sessions/sessions/suggest-scopes/ | Required description (up to 2,048 characters), non-empty servers; optional requester_type (default user), proposed_sudo_policies, include_improved_description (default false) | suggested_scopes, risk_level, reasoning, improved_description, sudo_policy_assessments |
POST /api/work-sessions/sessions/resolve-target-account/ | Required servers; optional assigned_user, target_account_username (defaults to the assignee’s username) | Account preview: version, account, is_self, is_own_username, lane, reason, role_reach, servers |
POST /api/work-sessions/sessions/candidate-target-accounts/ | Required servers; optional assigned_user | server_count, accounts |
The account helpers refuse service tokens. A non-Superuser can name only themselves in assigned_user. Personal tokens need both the route’s work_session scope and proc:read. Scope suggestions return 400 work_session_ai_unavailable when AI is unavailable.
Audit window and plans
The audit window is 7 days on Free, 120 days on Essentials and 365 days on Enterprise. List results omit older sessions. Free also limits the list to your own sessions. On Essentials and Enterprise, you can read organization-wide history within your normal visibility; service tokens have organization audit scope within their own visibility and the plan’s window.
For detail, timeline, recordings and live snapshot, a Free-plan request for another person’s session returns 404 api_not_found. An otherwise accessible session outside the audit window returns 402 workspace_plan_update_required. Recordings and analysis require a paid plan even inside the window.
Errors
| Status | Code | Meaning |
|---|---|---|
400 | work_session_principal_not_supported | Service token used for create or either account helper |
400 | work_session_invalid_scope, work_session_scope_not_allowed | Invalid preset or preset unavailable to agents |
400 | work_session_no_servers, work_session_mixed_targets, work_session_too_many_clusters, cluster_not_found | Invalid targets |
400 | server_invalid, server_not_identified, server_not_found, server_too_many, server_access_denied | Invalid, excessive or inaccessible servers |
400 | work_session_multiple_assignees, work_session_assignee_not_allowed | Conflicting assignees or assignment not permitted |
400 | work_session_duration_or_expires_required, work_session_duration_and_expires_exclusive, work_session_duration_not_positive | Invalid duration or expiry choice |
400 | work_session_expired, work_session_starts_at_in_past, work_session_starts_at_after_expires, work_session_expires_exceeds_max_ttl | Invalid time window |
400 | work_session_sudo_policy_without_scope, work_session_sudo_policy_user_not_assigned, work_session_sudo_policy_too_many, work_session_sudo_policy_id_not_bound | Invalid sudo policy request |
400 | work_session_target_account_invalid, work_session_target_account_not_applicable, work_session_target_account_clear_forbidden, work_session_target_account_absent, work_session_target_account_unavailable, work_session_target_account_not_reachable, target_account_other_person | Target account invalid, unavailable, inaccessible or belongs to another person |
400 | work_session_admission_denied | Session refused by policy |
400 | work_session_not_pending, work_session_not_active, work_session_not_approved, work_session_not_started, work_session_not_terminal, work_session_not_modifiable | Action requires a different state |
400 | work_session_mod_no_changes, work_session_mod_already_pending | No modification or another modification pending |
400 | work_session_extend_not_later, work_session_extension_reason_required, work_session_extension_already_pending | Invalid extension or another extension pending |
400 | work_session_invalid_recommendation_item, work_session_sudo_loosen_forbidden | Invalid approval adjustments |
400 | approval_request_already_reviewed_or_cancelled | Approval already decided or cancelled |
400 | auth_mfa_required | Approve or reject requires fresh MFA presence; no source field |
400 | work_session_not_assignee | Only the assigned person can acknowledge recommendations |
400 | work_session_ai_unavailable | Scope suggestions unavailable |
400 | invalid_input, api_invalid_cursor, api_cursor_expired | Invalid query, recording session or cursor |
400 | user_not_found | Requester of a pending edit cannot be found |
401 | auth_token_missing | Authentication missing |
402 | workspace_plan_update_required | Paid-plan feature, audit window or Free-plan delegation limit |
403 | api_token_scope_missing | Missing token scope; carries gate: "token_scope" |
403 | permission_denied | Insufficient session permission or wrong approval channel |
403 | approval_request_not_approver, approval_requester_may_not_approve, approval_no_other_approver | Approval permission or available approver requirement |
403 | work_session_agent_not_allowed | Declared agent refused |
403 | work_session_sudo_bypass_console_only, sudo_policy_bypass_agent_session_needs_presence | Request for a sudo policy where the session approval takes the place of MFA requires the console or human presence |
403 | workspace_suspended | Write in a suspended workspace; approve and reject are exempt |
404 | api_not_found | Session or recording not visible |
405 | Unsupported method | Session deletion is unsupported |
503 | api_search_unavailable | Recording search unavailable |
See the API reference for authentication and error envelopes.