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 status
and exitCode to learn how the process finished.
Run a command
POST /v1/sandboxes/{sandboxId}/commands — requires scope
command:run, plus artifact:create if you pass collect. Accepts an
Idempotency-Key header.
The endpoint runs the command and waits for it to exit by default. Set
background to true to return immediately and follow the command through
the get, logs, or SSE endpoints.
| Field | Type | Required | Default | Constraints |
|---|---|---|---|---|
command | string | Yes | — | Non-empty shell command line |
background | boolean | No | false | collect and collectOn aren't valid when true |
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; foreground commands only |
collect | array | No | — | Artifact paths and names; foreground commands only |
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 \
-H "Authorization: Bearer $CROWNEST_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9d2e4f1b-run-01" \
-d '{
"command": "python3 train.py",
"timeoutMs": 120000,
"collect": [{ "path": "/workspace/model.bin", "name": "model" }]
}'With background omitted or false, the endpoint returns 200 with the
finished command:
{
"command": {
"id": "cmd_abc123",
"sandboxId": "sbx_abc123",
"command": "python3 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.
To start the command in the background, use the same endpoint with
background: true:
curl -X POST https://api.crownest.dev/v1/sandboxes/sbx_abc123/commands \
-H "Authorization: Bearer $CROWNEST_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9d2e4f1b-background-01" \
-d '{ "command": "npm run dev", "background": true, "timeoutMs": 600000 }'Returns 202 with the command in queued or starting status. Follow it
with get, logs, or the SSE stream.
Deprecated aliases
The previous foreground and background paths remain available until July 10, 2027. Their responses include Deprecation, Sunset, and Link headers that
identify the canonical endpoint.
POST /v1/sandboxes/{sandboxId}/commands/runruns a foreground command.POST /v1/sandboxes/{sandboxId}/commands/startruns a background command.
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 |
|---|---|---|---|
afterSeq | integer | — | Return chunks after this sequence |
limit | integer | 100 | Max 100 |
curl "https://api.crownest.dev/v1/commands/cmd_abc123/logs?afterSeq=12" \
-H "Authorization: Bearer $CROWNEST_API_KEY"{
"data": [
{
"commandId": "cmd_abc123",
"seq": 13,
"createdAt": "2026-06-11T13:00:05.000Z",
"data": "warning: ignored flag\n",
"stream": "stderr"
}
],
"hasMore": false,
"nextSeq": 13
}Each chunk has commandId, seq, createdAt, 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, createdAt, 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.
Not yet available
Log download URLs require log-archive storage, which isn't enabled yet. This endpoint
currently returns feature_not_enabled (403). Page through the logs endpoint or
follow the SSE stream instead.