---
title: "Run an allowlisted, sandboxed, non-root command via a service token"
method: POST
path: "/api/v1/secure-exec"
---

# Run an allowlisted, sandboxed, non-root command via a service token

`POST /api/v1/secure-exec`

## Request

- Base URL: `https://aicommander.dev`
- URL: `https://aicommander.dev/api/v1/secure-exec`
- Auth: HTTP bearer

Runs `argv` (an exec-style argument vector, NOT a shell string) on the machine the presenting SERVICE TOKEN is pinned to, as a SANDBOXED NON-ROOT user. The token also carries an allowlist of command basenames; `argv[0]`'s basename must be on it or the call is refused. This is the deliberate OPPOSITE of /api/v1/exec: that one runs an arbitrary shell string as root on a machine named by `code`; this one runs only a fixed set of non-root commands on a single pinned machine, with no shell. Auth is a SERVICE TOKEN ONLY (Bearer `aics_…`) — an account API key, an admin token, or anonymous are all rejected 401. A service token bypasses the 24h dormancy gate (it's a server-to-server credential) and is revoke-only. Output is buffered and capped at 8 MiB. Create service tokens manually in the dashboard; list/revoke/audit them via /api/v1/secure-tokens and /api/v1/secure-exec/audit.

## Request body

- object
  - `argv` string[], required: Non-empty argument vector with at most 256 elements and at most 128 KiB of total string content (argv[0] is the program; its basename must be on the token's allowlist). No shell is involved — arguments are passed verbatim.
  - `input` string: Optional standard input, base64-encoded. Capped at the secure-exec input limit.
  - `cwd` string: Optional working directory (absolute path, maximum 4,096 characters).
  - `timeout_ms` number: Requested timeout in milliseconds; the server normalizes it rather than rejecting out-of-range values. An omitted, non-number, or non-finite value uses the 300000 ms (5 min) default. Any finite number below 1000 is raised to 1000 ms; any finite number above 3600000 is capped at 3600000 ms (1 hr); a finite value inside that range is kept as supplied.

## Response `200`

Command completed (any exit code).

- object
  - `ok` true, required
  - `exitCode` integer, required
  - `durationMs` integer, required
  - `stdout` string, required
  - `stderr` string, required
  - `truncated` boolean, required: True when output hit the 8 MiB cap and was truncated.

## Other responses

- `400`: Missing/invalid fields. On the job endpoints this is also how the MACHINE reports a request it can never run (`reason: "invalid_request"` — an empty command, a relative `cwd`, a `gpu_index` that names no card): the request itself is at fault, so correct the field the message names rather than retrying it.
- `401`: A token was supplied but is not a valid API key/admin/OAuth token.
- `413`: The encoded request body exceeds this endpoint's byte limit and was rejected before JSON parsing.
- `415`: A JSON request body was sent without an explicit JSON Content-Type. Use `application/json` (parameters such as `charset=utf-8` are allowed); `application/*+json` structured-suffix media types are also accepted.
- `429`: Per-IP or global tool-call rate limit exceeded.
- `502`: The session exists but the agent is offline or disconnected mid-command.
- `503`: The session exists but the agent is offline or disconnected mid-command.

---

[API](https://skmtc.dev/aicommander/apis/ai-commander-remote-shell-and-long-running-jobs-api.md) · [All operations](https://skmtc.dev/aicommander/apis/ai-commander-remote-shell-and-long-running-jobs-api/llms.txt) · [OpenAPI document](https://skmtc.dev/aicommander/apis/ai-commander-remote-shell-and-long-running-jobs-api/revisions/a6bb89f779a1?raw)
