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.

MethodSuffixSuccessToken scope or access
GET(none)200, paginated sessionswork_session:read
POST(none)201, sessionwork_session:create; no service tokens
GET{id}/200, sessionwork_session:read
PUT, PATCH{id}/200 or 202, sessionwork_session:update; manage permission
POST{id}/activate/200, sessionwork_session:activate; manage permission
POST{id}/complete/200, sessionwork_session:complete; manage permission
POST{id}/revoke/200, sessionwork_session:revoke; Superuser
POST{id}/cancel/200, sessionCreator or Superuser signed in; tokens with granular scopes are refused
POST{id}/approve/200, session or pending voteWeb console approver
POST{id}/reject/200, sessionWeb console approver
POST{id}/extend/200 or 202, sessionwork_session:extend; manage permission
GET{id}/timeline/200, timelinework_session:timeline
GET{id}/records/200, recording chunkswork_session:timeline; paid plan
GET{id}/live_snapshot/200, snapshotSigned-in session with view permission; tokens with granular scopes are refused
POST{id}/analyze/200 or 202work_session:analyze; manage permission and paid plan
POST{id}/acknowledge-recommendations/200, acknowledgment timeAssigned person signed in; tokens with granular scopes are refused
POSTsuggest-scopes/200, suggestionswork_session:suggest_scopes
POSTresolve-target-account/200, account previewwork_session:resolve_target_account and proc:read; no service tokens
POSTcandidate-target-accounts/200, candidateswork_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

TransitionTrigger and caller
Create → activeNo approval needed and no future start time
Create → approvedNo approval needed, with a future starts_at
Create → pendingApproval needed
pending → active or approvedAn approver settles approval in the web console; future sessions become approved
pending → rejectedAn approver rejects in the web console
pending → cancelledCreator or Superuser cancels, or the request reaches its approval deadline or expiry
approved → activeStart time arrives, or a caller with manage permission calls activate
active → completedA caller with manage permission calls complete
approved or active → revokedA Superuser revokes
approved or active → expiredExpiry 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

http
POST /api/work-sessions/sessions/
FieldTypeRequiredDescription
titlestringNoUp to 128 characters; default empty
descriptionstringYesPurpose, up to 2,048 characters
requester_typestringYesuser or agent; machine channels force agent
scopesarray[string]YesNon-empty presets: websh, tunnel, editor, webftp, command, sudo, kube, kube_exec
serversarray[UUID]One target kindUp to 100 visible server IDs; objects containing id are also accepted
clustersarray[UUID]One target kindAt most one cluster; requires kube and only Kubernetes presets, where Kubernetes is available
target_account_usernamestringNoUp 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_useruser IDNoDefaults to the caller
assigned_tokenUUIDNoAssign to a personal API token instead of a user
assigned_service_tokenUUIDNoAssign to a service token instead of a user
durationdurationOne time limitPositive duration, such as 01:00:00, within the workspace’s maximum session duration
expires_atdatetimeOne time limitFuture ISO 8601 expiry within the maximum duration
starts_atdatetimeNoFuture start time before expiry; default null means activate on approval
sudo_policiesarray[object]NoUp 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.

bash
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.

bash
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

http
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:

json
{
  "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.

json
{
  "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:

json
{
  "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:

json
{"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

http
GET /api/work-sessions/sessions/
GET /api/work-sessions/sessions/{id}/
ParameterDescription
statusComma-separated session statuses
request_channelComma-separated cli, mcp, api_token, service_token, unknown, unknown_client, web
scopesComma-separated presets; matches any overlap
requester_typeExact requester type
server, cluster, assigned_userFilter by UUID
start, endInclusive ISO 8601 bounds on creation time
has_attentionBoolean; also enables live summaries
analysis_statuspending, processing, completed or failed
analysis_ended_at__gte, analysis_ended_at__ltInclusive lower and exclusive upper bounds on analysis completion
searchSearch title, description, server and cluster names
orderingadded_at, expires_at, starts_at, started_at, status, attention; default -added_at. attention must come first and enables live summaries
includeSet to live_summary to include it per row
page, page_sizeInteger pagination; default size 15, maximum 100, larger values clamped

Unknown statuses, channels or presets return 400 invalid_input.

json
{"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

FieldsDescription
id, title, description, statusIdentity, purpose and current state
requester_type, request_channel, requester_type_verified, declared_agentRequester and channel information
scopes, servers, clustersAllowed activities and targets. Each server contains id, name, os, platform, is_connected, is_deleted
target_account_username, target_account_is_self, account_flagTarget account information
created_by, assigned_user, assigned_token, assigned_service_token, assigned_credentialCreator and assignee; assigned_credential is null unless you can manage the session
starts_at, expires_at, duration, started_at, completed_atRequested and actual time window
approval_request_id, admission_decision, adjustmentsApproval and adjustments
pending_modification_request, pending_extension_requestPending changes in the shapes above
sudo_policies, proposed_sudo_policies, sudo_auto_approve, sudo_risk_ceiling, sudo_postureSudo configuration. Bound policies contain id, commands, allow_bypass_mfa, reason, valid_from, valid_until
ai_suggestion, recommendations, recommendations_acknowledged_atSuggestions and acknowledgment
analysis, can_analyze, can_manageAnalysis 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_sessionExtension hints, populated on detail and live snapshot
live_summaryIncluded when requested
added_at, updated_atCreation and update timestamps

Timeline and recordings

These routes use their own cursor formats, not the integer pagination above.

Timeline

http
GET /api/work-sessions/sessions/{id}/timeline/
GET /api/work-sessions/sessions/{id}/timeline/?cursor=&page_size=100

Without 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:

json
{"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

http
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.

json
{
  "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

http
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

RouteRequestResponse
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_userserver_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

StatusCodeMeaning
400work_session_principal_not_supportedService token used for create or either account helper
400work_session_invalid_scope, work_session_scope_not_allowedInvalid preset or preset unavailable to agents
400work_session_no_servers, work_session_mixed_targets, work_session_too_many_clusters, cluster_not_foundInvalid targets
400server_invalid, server_not_identified, server_not_found, server_too_many, server_access_deniedInvalid, excessive or inaccessible servers
400work_session_multiple_assignees, work_session_assignee_not_allowedConflicting assignees or assignment not permitted
400work_session_duration_or_expires_required, work_session_duration_and_expires_exclusive, work_session_duration_not_positiveInvalid duration or expiry choice
400work_session_expired, work_session_starts_at_in_past, work_session_starts_at_after_expires, work_session_expires_exceeds_max_ttlInvalid time window
400work_session_sudo_policy_without_scope, work_session_sudo_policy_user_not_assigned, work_session_sudo_policy_too_many, work_session_sudo_policy_id_not_boundInvalid sudo policy request
400work_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_personTarget account invalid, unavailable, inaccessible or belongs to another person
400work_session_admission_deniedSession refused by policy
400work_session_not_pending, work_session_not_active, work_session_not_approved, work_session_not_started, work_session_not_terminal, work_session_not_modifiableAction requires a different state
400work_session_mod_no_changes, work_session_mod_already_pendingNo modification or another modification pending
400work_session_extend_not_later, work_session_extension_reason_required, work_session_extension_already_pendingInvalid extension or another extension pending
400work_session_invalid_recommendation_item, work_session_sudo_loosen_forbiddenInvalid approval adjustments
400approval_request_already_reviewed_or_cancelledApproval already decided or cancelled
400auth_mfa_requiredApprove or reject requires fresh MFA presence; no source field
400work_session_not_assigneeOnly the assigned person can acknowledge recommendations
400work_session_ai_unavailableScope suggestions unavailable
400invalid_input, api_invalid_cursor, api_cursor_expiredInvalid query, recording session or cursor
400user_not_foundRequester of a pending edit cannot be found
401auth_token_missingAuthentication missing
402workspace_plan_update_requiredPaid-plan feature, audit window or Free-plan delegation limit
403api_token_scope_missingMissing token scope; carries gate: "token_scope"
403permission_deniedInsufficient session permission or wrong approval channel
403approval_request_not_approver, approval_requester_may_not_approve, approval_no_other_approverApproval permission or available approver requirement
403work_session_agent_not_allowedDeclared agent refused
403work_session_sudo_bypass_console_only, sudo_policy_bypass_agent_session_needs_presenceRequest for a sudo policy where the session approval takes the place of MFA requires the console or human presence
403workspace_suspendedWrite in a suspended workspace; approve and reject are exempt
404api_not_foundSession or recording not visible
405Unsupported methodSession deletion is unsupported
503api_search_unavailableRecording search unavailable

See the API reference for authentication and error envelopes.

Last updated: