Commands
Run processes in a sandbox synchronously or in the background, cancel them, and read or stream their logs.
A command is a top-level process invocation record. You can run a command and wait for it to exit, or start it in the background and follow its logs by polling or over a server-sent events (SSE) stream.
The Command object
| Field | Type | Description |
|---|---|---|
id | string | Command ID, prefixed cmd_ |
sandboxId | string | Sandbox the command ran in, prefixed sbx_ |
command | string | The shell command line |
cwd | string | Working directory |
env | object | Extra environment variables |
status | string | queued, starting, running, exited, failed, canceled, timed_out, or killed |
cancelMode | string | graceful or force, when canceled |
canceledAt | string | ISO 8601 cancellation timestamp |
exitCode | integer | Process exit code, once exited |
stdout | string | Captured stdout |
stderr | string | Captured stderr |
stdoutTruncated | boolean | Whether stdout was truncated |
stderrTruncated | boolean | Whether stderr was truncated |
startedAt | string | ISO 8601 start timestamp |
finishedAt | string | ISO 8601 finish timestamp |
durationMs | integer | Wall-clock duration |
collectStatus | string | failed, not_requested, partial, pending, skipped, or succeeded |
collectErrors | array | Per-path errors from artifact collection |
killedReason | string | sandbox_destroyed, when the sandbox died under the command |
terminationSignal | string | SIGKILL or SIGTERM, when terminated |
[!IMPORTANT] A non-zero exit code is not an API error. The HTTP request succeeds — check
statusandexitCodeto learn how the process finished.
Run a command
POST /v1/sandboxes/{sandboxId}/commands/run — requires scope
command:run, plus artifact:create if you pass collect. Accepts an
Idempotency-Key header.
Runs the command and waits for it to exit before responding.
| Field | Type | Required | Default | Constraints |
|---|---|---|---|---|
command | string | Yes | — | Non-empty shell command line |
cwd | string | No | — | Working directory inside the workspace |
env | object | No | — | String map; keys with the CROWNEST prefix are reserved |
timeoutMs | integer | No | 60000 | Max 600000 |
collectOn | string | No | "success" | success or always |
collect | array | No | — | Items of shape path (required) and name (optional) |
collect exports the listed workspace paths as
artifacts after the command finishes; collectOn
controls whether that happens only on a zero exit code (success) or
regardless of outcome (always).
curl -X POST https://api.crownest.dev/v1/sandboxes/sbx_abc123/commands/run \
-H "Authorization: Bearer $CROWNEST_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9d2e4f1b-run-01" \
-d '{
"command": "python train.py",
"timeoutMs": 120000,
"collect": [{ "path": "/workspace/model.bin", "name": "model" }]
}'Returns 200 with the finished command:
{
"command": {
"id": "cmd_abc123",
"sandboxId": "sbx_abc123",
"command": "python train.py",
"status": "exited",
"exitCode": 0,
"stdout": "epoch 1 complete\n",
"stderr": "",
"stdoutTruncated": false,
"stderrTruncated": false,
"durationMs": 8421,
"collectStatus": "succeeded"
}
}Errors: invalid_request, reserved_env_key, forbidden, not_found,
sandbox_destroyed, quota_exceeded, command_timed_out.
Start a command
POST /v1/sandboxes/{sandboxId}/commands/start — requires scope
command:run. Accepts an
Idempotency-Key header.
Starts the command in the background and returns immediately. The body is
the same as run, without collect and collectOn.
curl -X POST https://api.crownest.dev/v1/sandboxes/sbx_abc123/commands/start \
-H "Authorization: Bearer $CROWNEST_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "command": "npm run dev", "timeoutMs": 600000 }'Returns 202 with the command in queued or starting status. Follow it
with get, logs, or the SSE stream.
Get a command
GET /v1/commands/{commandId} — requires scope command:read.
Returns the command record, including its current status, exit code, and captured output.
curl https://api.crownest.dev/v1/commands/cmd_abc123 \
-H "Authorization: Bearer $CROWNEST_API_KEY"Returns 200 with { "command": { ... } }. Errors: forbidden,
not_found.
Cancel a command
POST /v1/commands/{commandId}/cancel — requires scope command:cancel.
Stops a running command. The operation is idempotent by state — canceling an already-finished command returns the current record.
| Field | Type | Required | Default | Constraints |
|---|---|---|---|---|
mode | string | No | "graceful" | graceful or force |
Graceful cancellation sends SIGTERM and lets the process clean up; force
sends SIGKILL.
curl -X POST https://api.crownest.dev/v1/commands/cmd_abc123/cancel \
-H "Authorization: Bearer $CROWNEST_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "mode": "force" }'Returns 200 with { "command": { ... } } showing status canceled.
Errors: forbidden, not_found.
Read logs
GET /v1/commands/{commandId}/logs — requires scope command:read.
Returns log chunks in sequence order. Use afterSeq to poll for new
output incrementally.
| Parameter | Type | Default | Constraints |
|---|---|---|---|
stream | string | combined | stdout, stderr, or combined |
afterSeq | integer | — | Return chunks after this sequence |
limit | integer | 50 | Max 100 |
curl "https://api.crownest.dev/v1/commands/cmd_abc123/logs?stream=stderr&afterSeq=12" \
-H "Authorization: Bearer $CROWNEST_API_KEY"{
"data": [
{
"seq": 13,
"type": "log",
"timestamp": "2026-06-11T13:00:05.000Z",
"data": "warning: deprecated flag\n",
"stream": "stderr"
}
],
"hasMore": false,
"nextSeq": 13
}Each chunk has seq, type, timestamp, data, and stream. Pass the
returned nextSeq as afterSeq on your next poll.
Stream logs over SSE
GET /v1/commands/{commandId}/stream — requires scope command:read.
Streams log output as server-sent events, ending when the command finishes. Event types:
| Event | Meaning |
|---|---|
log | A log chunk with seq, type, timestamp, data, and stream |
heartbeat | Keep-alive signal while the command is quiet |
terminal | The command reached a terminal status |
error | The stream failed; reconnect or fall back to polling |
To resume after a disconnect, send the last seq you saw, either as the
afterSeq query parameter or as the standard Last-Event-ID header —
SSE clients send Last-Event-ID automatically on reconnect. If the
requested sequence has fallen outside the retention window, the stream
returns a stream_gap error; fetch the full logs again instead.
curl -N "https://api.crownest.dev/v1/commands/cmd_abc123/stream" \
-H "Authorization: Bearer $CROWNEST_API_KEY" \
-H "Last-Event-ID: 13"Get a logs download URL
POST /v1/commands/{commandId}/logs/download-url — requires scope
command:read.
Returns a short-lived URL for downloading a full log stream, which is the right tool when output is too large to page through.
| Field | Type | Required | Constraints |
|---|---|---|---|
stream | string | Yes | stdout, stderr, or combined |
curl -X POST https://api.crownest.dev/v1/commands/cmd_abc123/logs/download-url \
-H "Authorization: Bearer $CROWNEST_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "stream": "combined" }'{
"url": "https://api.crownest.dev/v1/...",
"method": "GET",
"expiresAt": "2026-06-11T13:15:00.000Z"
}