Execute
Run a foreground shell command without SSH, a PTY, or a local CLI. Select an owned, running container and its exact generation.
POST /containers/exec?id=<id>&createdAt=<URL-encoded-createdAt>
Authorization: Bearer mb_<your-key>
Content-Type: application/json
{"command":"echo hello from mainbrella","timeoutMs":30000}
Read the result
{
"stdout": "hello from mainbrella\n",
"stderr": "",
"exitCode": 0,
"timedOut": false,
"outputTruncated": false
}
HTTP 200 means the execution request completed; check exitCode and both flags to decide whether the command succeeded. A nonzero exit is a command failure. A timeout or output limit returns partial output with exitCode: null.
Execution limits
Commands run through /bin/sh -lc with closed stdin. Timeout defaults to 30 seconds and accepts 1–60,000 milliseconds, including startup, bounded by the container deadline. Command text is limited to 16 KiB of UTF-8; the JSON body to 32 KiB; combined stdout and stderr to 1 MiB.
Foreground commands, managed jobs, and file operations share four concurrent operations per container. They have a separate pool from interactive terminals. For retained jobs, streaming and reconnect, use managed execution below; use managed stdin/PTY controls below or SSH and the browser terminal for interactive sessions.
Failures and retries
A lost HTTP response can hide a command that already ran. Reconcile its effects before retrying commands that write, send, charge, or otherwise change state. Results are not retained. Stop or generation replacement revokes active commands.
Managed execution
First read public GET /capabilities and check execution.background, streaming, reconnect and cancellation. These flags describe deployment support; account allowances and images come from authenticated GET /containers.
POST /containers/executions?id=<id>&createdAt=<URL-encoded-createdAt>
Authorization: Bearer mb_<your-key>
Idempotency-Key: <unique-operation-key>
Content-Type: application/json
{"command":"npm test","timeoutMs":300000}
The 202 response contains an execution ID. Preserve it and the creation key. Matching key and all creation-option retries resolve the original job within its one-hour retention window. Never retry an old key beyond that window.
GET /containers/executions/<execution-id>?id=<id>&createdAt=<generation>reads state and retained stdout/stderr.DELETEat the same URL requests cancellation; poll until terminal.GET /containers/executions/<execution-id>/events?id=<id>&createdAt=<generation>&cursor=0streams SSE. Resume with the last output sequence as cursor. Streams rotate after 30 seconds; reconnect after nonterminal status.
Stop streaming on succeeded, failed, canceled, timed_out, output_limit or interrupted status. Disconnecting only detaches; it does not cancel. Managed timeouts default to 30 seconds and allow up to 15 minutes within the container deadline. Each container retains up to 32 records for one hour; output is bounded to 1 MiB and a limited event count. Up to eight streams attach per container.
Input and process controls
Check execution.argv, stdin, signals, managedProcessListing, programmaticPty and ptyResize before using these controls. A command string runs through /bin/sh -lc; an argv array starts literal executable arguments. Optional cwd and env go into the guest. Environment values are not protected secrets.
const job = await sandbox.commands.start(['cat'], { stdin: true });
await job.stdin.write(new TextEncoder().encode('hello\n'));
await job.stdin.close();
const result = await job.wait();
const attached = sandbox.commands.attach(job.id);
const { executions } = await sandbox.commands.list();
Input defaults to EOF. With stdin:true, writes accept raw bytes, up to 64 KiB per request, 1 MiB accepted per job and 256 KiB pending. Writes stay ordered and respect pipe backpressure. Ambiguous writes remain counted and are never retried automatically. Close input to send pipe EOF; inspect application state before repeating a lost write.
job.signal('SIGINT'), SIGTERM and SIGKILL signal the managed operation's process group. Delivery does not prove exit; inspect retained state. SIGKILL requests cancellation. Deliberately detached processes remain bounded by the machine lease. Listing returns retained managed jobs; it is not a guest-wide process table.
const terminal = await sandbox.commands.start(['/bin/sh'], {
stdin: true, pty: { cols: 80, rows: 24 }
});
await terminal.resize(132, 40);
await terminal.stdin.write(new TextEncoder().encode('exit\n'));
await terminal.wait();
PTY dimensions are 1–1000. Terminal output combines stdout and stderr, including line endings and possible input echo. Closing PTY input may hang up the terminal; send application-appropriate bytes or an explicit exit command. Disconnecting output only detaches. Input and resize require the exact live paid generation; cancellation and retained results remain available during billing outages. See the JavaScript and Python references.
A runtime restart interrupts unfinished managed jobs and stops their matching container generation to revoke orphan processes. This can interrupt other work in that generation; use a dedicated generation when that matters. Commands are never replayed automatically. See the full contract.