# Run commands in a sandbox
URL: https://docs.munito.ai/sandboxes/commands

A sandbox runs commands in two ways. Pick the one that matches how long the work takes.

|                                         | ends when                                                       | read its output               |
| --------------------------------------- | --------------------------------------------------------------- | ----------------------------- |
| `POST /sandboxes/{sandbox_id}/exec`     | the request ends                                                | in the answer, or as a stream |
| `POST /sandboxes/{sandbox_id}/commands` | it exits, its timeout ends, you signal it, or its sandbox stops | poll the command              |

## Wait for the result [#wait-for-the-result]

`exec` runs one command and answers its exit code and its output. It is the right call for a build step, a test run
or a script that ends in seconds or minutes.

```bash
curl -sS --fail-with-body "https://api.munito.ai/sandboxes/v1/sandboxes/$SANDBOX/exec" \
  -H "Authorization: Bearer $MUNITO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command": "pytest -q", "timeout": 600}'
```

- A string runs in `bash -lc`. A list is the argument vector itself.
- `"stream": true` answers NDJSON events as the command writes, instead of one result at its end.
- A command runs for at most 1,800 seconds.
- **The command ends with its request.** A client that leaves kills the command.

## Start a background command [#start-a-background-command]

A background command answers at once with an id, and keeps running. Use it for a dev server, a watcher, a long
training step, or any work that outlives one HTTP request.

```bash
CMD=$(curl -sS --fail-with-body "https://api.munito.ai/sandboxes/v1/sandboxes/$SANDBOX/commands" \
  -H "Authorization: Bearer $MUNITO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command": "npm run dev"}' | jq -r .id)
```

Then read it, poll it, and stop it:

```bash
# Its status and its whole output so far.
curl -sS --fail-with-body "https://api.munito.ai/sandboxes/v1/sandboxes/$SANDBOX/commands/$CMD" \
  -H "Authorization: Bearer $MUNITO_API_KEY"

# Only what the command wrote after the first 4,096 bytes of each stream.
curl -sS --fail-with-body \
  "https://api.munito.ai/sandboxes/v1/sandboxes/$SANDBOX/commands/$CMD?stdout=4096&stderr=4096" \
  -H "Authorization: Bearer $MUNITO_API_KEY"

# Ask it to stop with SIGTERM, or send 9 for SIGKILL.
curl -sS --fail-with-body "https://api.munito.ai/sandboxes/v1/sandboxes/$SANDBOX/commands/$CMD/signal" \
  -H "Authorization: Bearer $MUNITO_API_KEY" \
  -H "Content-Type: application/json" -d '{"signal": 15}'
```

- `GET /sandboxes/{sandbox_id}/commands` lists them, the newest first, with no output.
- `status` is `running`, then `exited` with an `exit_code`, or `failed` when the sandbox lost the command.
- A command that a signal stopped has the `exit_code` 128 plus the signal.
- A `DELETE` kills a command that runs and drops what the sandbox keeps of it.

### Read the output as it grows [#read-the-output-as-it-grows]

Each answer carries `stdout_bytes` and `stderr_bytes`: the bytes of each stream so far. Pass them back as `stdout`
and `stderr`, and the next answer holds only what follows. A poll loop then reads each byte once.

### What a sandbox holds [#what-a-sandbox-holds]

- A sandbox runs 16 background commands at once. Start one more and it answers `too_many_commands`.
- The sandbox keeps 1 MiB of each stream. A command that writes more sets `truncated`, and the rest is left out.
  Write the output to a file for more, and read the file.
- The commands live with their sandbox. A pause, a stop and a restart of the sandbox all end them, and the sandbox
  then holds no record of them.
- A background command runs for at most 24 hours, and the timeout of its sandbox still ends it first.
