Commands
Endpoint: /api/events/commands/
Run commands on servers and read their results. A command runs in one of two ways:
- Through a service token, under the token’s server and command access-control rules. This is the way for automation. See Service tokens.
- Inside a work session, for a person signed in to the console or the CLI. Name the session in
work_session; without one the request is refused withwork_session_required. See Work sessions.
Personal API tokens cannot be issued command execution scopes or file-transfer scopes. Use a service token for automation and CI.
The examples on this page use a service token.
List
List commands
Retrieve a paginated list of commands.
Request
GET /api/events/commands/Query parameters
| Parameter | Type | Description |
|---|---|---|
server | UUID | Filter by server ID |
work_session | UUID | Filter by work session ID |
id__in | string | Filter by command IDs (comma-separated UUIDs, up to 100) |
shell | string | Filter by shell |
requested_by | integer | Filter by requesting user ID |
requested_by_isnull | boolean | true for commands with no requesting user |
service_token | UUID | Filter by the service token that sent the command |
application | UUID | Filter by the application whose service token sent the command |
api_token | UUID | Filter by the personal API token that sent the command |
request_channel | string | How the command was sent: web, cli, mcp, api_token, service_token, slack, unknown_client or unknown. Comma-separate several to match any |
declared_agent | string | Commands whose client declared this agent name |
agent_command | boolean | true for commands sent by an AI agent or a token, false for the rest |
is_scheduled | boolean | true for commands scheduled in the future, false for the rest |
verification_status | string | Filter by AI verification status: pending, awaiting_purpose, awaiting_approval, approved, rejected, skipped, monitor_executed or advisory |
risk_level | string | Filter by risk level: INFO, LOW, MEDIUM, HIGH or CRITICAL. Case-insensitive; comma-separate several (HIGH,CRITICAL). An unknown value returns 400 with invalid_input |
refused | boolean | true for commands that were refused: rejected, or denied by a command access-control rule |
denial_cause | string | Refused commands by group of rejection_cause: not_allowed (command_acl_denied, command_acl_unavailable), risk, approval or integrity_system |
override_reason | string | Use session_less_service_token to find commands that ran after a rejecting verdict under the session-less service-token policy. Other values return 400 with invalid_choice |
credential_deleted | boolean | true for token commands whose token has since been deleted |
credential_exposure_acknowledged | boolean | null | Commands sent with credential_exposure_acknowledged |
added_at__gte | datetime | Commands created at or after this time (ISO 8601) |
added_at__lt | datetime | Commands created before this time (ISO 8601). With added_at__gte, a half-open range, so back-to-back ranges never count a command twice |
search | string | Search by server name, command line, or requester name |
ordering | string | Sort field (default: -scheduled_at). Options: added_at, scheduled_at, delivered_at, acked_at, handled_at |
page | integer | Page number |
page_size | integer | Results per page (default: 15, max: 100) |
There is no filter on status; filter by verification_status, or read status from the results.
Response
{
"count": 85,
"current": 1,
"next": 2,
"previous": null,
"last": 6,
"results": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"shell": "system",
"line": "df -h",
"success": true,
"exit_code": 0,
"result": "Filesystem Size Used Avail Use% Mounted on\n/dev/xvda1 20G 8.5G 11G 46% /",
"status": "completed",
"cancellable": false,
"response_delay": 0.125,
"elapsed_time": 0.023,
"added_at": "2026-10-20T10:30:44Z",
"scheduled_at": "2026-10-20T10:30:44Z",
"server": {
"id": "7e3984de-49ab-4cc6-bcdf-21fbd35858b8",
"name": "web-server-01",
"os": "Ubuntu 22.04.4 LTS",
"platform": "debian",
"is_connected": true,
"is_deleted": false
},
"username": "deploy",
"groupname": "alpacon",
"requested_by": null,
"request_channel": "service_token",
"work_session": null,
"service_token": "3f1c2a9e-7b6d-4e1f-9a2b-5c8d7e6f4a31",
"service_token_name": "ci-deploy",
"application": "9d2e4b71-0c3a-4f5e-8b6d-1a2c3e4f5a6b",
"application_name": "CI pipeline",
"risk_level": "LOW",
"verification_status": "approved",
"override_reason": null,
"purpose": "Check disk space before deploying"
}
]
}The list returns more fields than shown here, such as declared_agent and rejection_cause. override_reason is a read-only string | null in list and detail responses and in work-session timeline command items. Its only current non-null value is session_less_service_token: a command sent with a service token outside a work session ran even though AI verification rejected it. Such a command has verification_status: "monitor_executed". Other commands have override_reason: null, including commands that ran under signing monitor mode with the same verification status.
Example
curl "https://your-workspace.us1.alpacon.io/api/events/commands/?ordering=-scheduled_at&page_size=20" \
-H "Authorization: token=\"alpst-xxxxxxxxxxxxxxxxxx\""Bulk status lookup
When tracking many commands at once (for example, a dashboard or automation polling for results), pass a comma-separated list of command IDs to id__in instead of issuing one GET /api/events/commands/{command_id}/ per command. Set page_size=100 as well: the default page size is 15, so without it a request for more than 15 IDs returns only the first page, with next set to 2.
curl "https://your-workspace.us1.alpacon.io/api/events/commands/?id__in=550e8400-e29b-41d4-a716-446655440000,9b52a7c4-13de-48f6-a01b-7f3e5d982c44&page_size=100" \
-H "Authorization: token=\"alpst-xxxxxxxxxxxxxxxxxx\""Up to 100 IDs can be requested at once; exceeding this limit returns 400 with event_command_bulk_limit_exceeded, and any value that is not a valid UUID returns 400. An empty id__in value is ignored: the request behaves as if the parameter were omitted and returns the full accessible list. Results follow the ordering parameter (default: -scheduled_at), not the order of the IDs in the request, so match each result to your list by its id field. IDs for commands you cannot access or that no longer exist are silently excluded; compare the response count with the number of unique IDs you requested to detect exclusions.
Command status
status is a string that Alpacon works out each time you read the command:
| Status | Meaning |
|---|---|
scheduled | Waiting for its scheduled_at time |
queued | Due, and waiting to be delivered, for example until the server reconnects or a run_after command finishes |
verifying | Alpacon AI is judging it |
awaiting_purpose | Waiting for the requester to say what the command is for |
awaiting_approval | Held for an approver |
delivered | Sent to the server |
running | The server started it |
stuck | Delivered or started, with no progress for a long time |
completed | Finished. Read success and exit_code for the outcome |
rejected | Refused by a verdict before it ran: the AI, an approver, a workspace rule, or the token’s command rules. rejection_cause says which, for example risk_denied, approval_rejected, approval_policy_denied or command_acl_denied |
denied | Stopped before it ran without a verdict: the token’s access-control rules refused it when it was sent, the agent session’s approved account doesn’t cover it, or its work session ended first. denial_code says why; the list response also has denied: true. Not the same as rejected. A token command its command rules refuse is rejected on Alpacon Cloud and denied on a self-hosted deployment; see Refused by the token’s command rules |
error | The command’s state couldn’t be determined |
On the detail route, read rejection_cause, rejection_gate and rejected_at for a rejected command, and denial_code for a denied one. For a completed command, read success first, then exit_code, result, elapsed_time and handled_at; when success is false, error_phase can say where it failed. A stuck command that times out ends as completed with success: false, exit_code: null and a timeout message in result.
cancellable is a separate boolean: true while the command can still be cancelled.
Each command also records its progress in timestamps:
| Field | Description |
|---|---|
added_at | Command created |
scheduled_at | Scheduled run time |
delivered_at | Sent to the server |
acked_at | The server acknowledged it and started running it |
handled_at | Finished |
Rejection causes
rejection_cause uses the values below. rejection_gate is null for the two signature causes and for account_bound_to_other_person.
rejection_cause | rejection_gate | Meaning |
|---|---|---|
command_acl_denied | command_acl | The command is not pre-approved by the token’s command rules |
command_acl_unavailable | command_acl | The command pre-approval decision is unavailable |
risk_denied | risk | Risk control refused the command |
risk_unavailable | risk | The risk judgment is unavailable |
approval_policy_denied | approval | The approval policy refused it or no eligible approver other than the requester is available |
approval_rejected | approval | An approver rejected the request |
approval_expired | approval | The pending approval expired |
approval_cancelled | approval | The requester cancelled the approval request |
work_session_terminated | work_session | The work session ended while the command was held |
account_bound_to_other_person | null | The run-as account became linked to another person before delivery |
unsigned_file | null | The file command has no signature |
signature_invalid | null | The agent reported an invalid signature |
use_file_execution | approval | An access rule requires the script to run through file execution |
Denial codes
denial_code is null unless the command was denied. A client can branch on these fixed values:
denial_code | Meaning |
|---|---|
command_acl_no_match | No command rule on the token allows the line |
server_acl_denied | The token’s server rules leave out the server |
acl_not_configured | The token has no server or command rules |
command_rejected_by_parser | The line uses shell syntax no command rule can allow |
work_session_terminated | The work session ended before the command was delivered |
account_bound_to_other_person | The run-as account became linked to another person before delivery |
work_session_account_not_approved | The agent’s work session doesn’t approve the run-as account |
account_not_resolved | The command names no account or group to run as |
account_group_not_permitted | The requester may not run as that group |
denied | Denied for another reason |
For a work session you can’t view, or one that no longer exists, work_session_terminated reads as denied.
Error phases
error_phase says where a command went wrong when success is false or the command is stuck. It is null for a command that succeeded, and for a command that ran and exited non-zero for its own reasons, such as exit 1.
error_phase | When |
|---|---|
agent_disconnected | stuck: the server never acknowledged the command, likely because it disconnected |
agent_timeout | stuck: the server acknowledged the command but hasn’t reported a result |
remote_command_exceeded_timeout | The command ran past its time limit on the server (exit code 124) |
remote_command_not_executable | The command was found but couldn’t be run (exit code 126) |
remote_command_not_found | The command wasn’t found on the server (exit code 127) |
remote_command_killed | The command was killed by a signal |
dependency_cancelled | The command never reached the server because a run_after command it waited on failed or was refused |
file_hash_mismatch | Script: the file changed after it was checked, so the server refused to run it. Send it again so the current file is checked |
file_payload_invalid | Script: the server couldn’t read the request. Send it again; if it keeps failing, upgrade the agent |
file_open_failed | Script: the server couldn’t open the file. Check the path exists and is a readable regular file |
file_exec_unsupported | Script: this server’s platform can’t run scripts. Send a command line instead |
file_too_large | Script: the file is larger than the server will run |
alpacon exec prints the phase on stderr, with a description for most phases and a next step for the script phases. The MCP command tools add a similar next step as error_phase_hint.
Sudo approval
A command that runs sudo can wait for an approver. Two detail fields follow that wait:
sudo_grant_statusisnullwhen the command has nosudorequest. Otherwise it ispending_mfa(waiting for the requester’s MFA check),pending_approval(waiting for an approver),authorized(approved),used(the approvedsudoran),rejectedorexpired.sudo_approval_request_idnames the approval request whilesudo_grant_statusispending_approval, and isnullotherwise. It turnsnullboth when the request is approved and when it is rejected, so readsudo_grant_statusto tell the two apart.
alpacon exec waits on these fields when a sudo command needs approval. Both are detail-only; poll Get command while you wait.
Script fields
approved_file_execution is set when a standing approval let a script run without a new approval, and stays set after that approval is revoked or expires. With the approved_file_execution:read permission it carries id, expires_at, approved_by (id, username), approval_request (id), either of which can be null, revoked_at and revoke_reason (manual, approver_removed, approver_deactivated, expired, or empty). Without it, only id.
file_execution_suggestion is set while a command line that runs a script file is held (awaiting_approval or awaiting_purpose), and the line could be sent again as a script request. It carries path, args, interpreter (an absolute path, or null), interpreter_name (a bare name such as bash, or null; supply the absolute path yourself before sending) and repeatedly_approved, which is true when people approved this exact line often enough recently to make it a candidate for a standing approval. To send it again, add the script’s content and copy the server, username and groupname from the held command. It is null when masking changed the line, or when the script request would be refused anyway.
Error responses
| Status | Error code | Description |
|---|---|---|
| 400 | api_insufficient_data | No target server, or no line or file |
| 400 | does_not_exist | On server: the server is outside the service token’s server access rules, or does not exist |
| 400 | server_not_connected | The single server is not connected |
| 400 | server_not_commissioned | The single server is not commissioned |
| 400 | command_inline_credential | line contains an inline credential. Move it to env, or set credential_exposure_acknowledged: true to proceed |
| 400 | work_session_required | A person signed in to the console or the CLI sent a command without work_session |
| 400 | work_session_not_active, work_session_expired, work_session_scope_not_allowed, work_session_server_not_allowed | The work session isn’t active, has ended, doesn’t include commands, or doesn’t include this server |
| 400 | event_command_bulk_limit_exceeded | More than 100 command IDs passed to id__in |
| 400 | event_command_run_after_too_many | More than 100 commands in run_after |
| 400 | file_exec_content_too_large, file_exec_empty_content, file_exec_invalid_path, file_exec_line_not_allowed, file_exec_data_not_allowed, file_exec_env_not_allowed | The file request is invalid: the script is over 64 KB or empty, a path isn’t absolute, or it carries line, data or env |
| 400 | event_command_already_sent | Cancel: the command was already delivered to the server |
| 400 | event_command_not_cancellable | Cancel: a firewall command can’t be cancelled |
| 401 | auth_mfa_required | The command runs as an account that isn’t yours, and needs a recent MFA check. The body also carries "source": "command" |
| 403 | api_token_scope_missing | The token lacks command:create (or command:read to read). missing names the scope |
| 403 | api_token_acl_not_allowed | On a self-hosted deployment, the token’s command rules don’t allow the line. Retrying does not help—widen the token’s rules |
| 403 | event_command_not_requester | Cancel: only the requester or a Superuser can cancel a firewall command |
| 404 | api_not_found | Command not found |
Every error body is coded; see Error messages.
Refused by the token’s command rules
A command the token’s command rules don’t allow never runs. How it is refused depends on the deployment:
- Alpacon Cloud: the request is accepted with
201, and the command is then refused before it runs: itsstatusbecomesrejectedwithrejection_causecommand_acl_denied. If the rules couldn’t be checked at that moment,rejection_causeiscommand_acl_unavailableinstead, and sending the command again can succeed. - Self-hosted: the request is refused with
403andapi_token_acl_not_allowed, and the command is recorded withstatusdenied. A self-hosted deployment whose command rules have been moved to Alpacon AI behaves like Alpacon Cloud.
For command_acl_denied or api_token_acl_not_allowed, retrying does not help—widen the rule. See Command patterns and sudo.