# Background commands

> Start long-running commands that outlive the request, page through their logs, and kill them.

A background command runs detached: the request returns as soon as it starts, there is no timeout, and its combined output goes to a log you can page through. Use it for servers, watchers, and jobs longer than the 10-minute [exec](/docs/guides/commands) limit. Each start is also recorded in the machine's [command history](/docs/guides/command-history).

## Start one

```ts tab="TypeScript"
const command = await machine.commands.start("npm run dev");
console.log(command.command_id);
```

```python tab="Python"
command = machine.commands.start("npm run dev")
print(command["command_id"])
```

```sh tab="curl"
curl -X POST "$VMYARD_API_URL/v1/machines/$MACHINE_ID/commands" \
  -H "Authorization: Bearer $VMYARD_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"command": "npm run dev"}'
```

The body takes exactly one of `argv` or `command` (a `/bin/sh -c` string of at most 8 KiB). The answer is `202` with a `command_id`, which is also the ID of the operation that starts it, and a `command_url`. The CLI has no background-command subcommand yet; the console's **Terminal** tab can start and watch them.

## Status and logs

```ts tab="TypeScript"
let offset = 0;
const status = await machine.commands.status(command.command_id, { logOffset: offset });
if (status.log) {
  process.stdout.write(Buffer.from(status.log.data_base64, "base64"));
  offset = status.log.next_offset;
}
```

```python tab="Python"
import base64

status = machine.commands.status(command["command_id"], log_offset=0)
if status.get("log"):
    print(base64.b64decode(status["log"]["data_base64"]).decode(), end="")
    next_offset = status["log"]["next_offset"]
```

```sh tab="curl"
curl "$VMYARD_API_URL/v1/machines/$MACHINE_ID/commands/$COMMAND_ID?log_offset=0&log_limit=65536" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

`GET /v1/machines/{id}/commands/{command_id}` returns the status and a window of the log starting at `log_offset` (default 0), at most `log_limit` bytes (1 to 1,048,576; default 65,536). Pass the returned `next_offset` as the next `log_offset`. If the read is still queued, the answer is `202` with an operation to follow; the SDKs do that for you.

| `status` | Meaning |
| --- | --- |
| `starting` | Being started. |
| `failed_to_start` | It could not start; see `error_code`. |
| `running` | Running. |
| `exited` | Finished; `exit_code` holds its status. |
| `killed` | Killed; `signal` holds the signal when known. |
| `unknown` | The machine has no record of it, for example after a reboot. Stop polling. |

VMYard asks for the status before reading the log, so once a command reports `exited`, its log is complete. Logs are bytes: decode them as a stream so a character split across two pages stays whole.

`machine.commands.wait(id)` in the SDKs polls until the command is `exited`, `killed`, or `failed_to_start`.

## Kill one

```ts tab="TypeScript"
await machine.commands.kill(command.command_id);
```

```python tab="Python"
machine.commands.kill(command["command_id"])
```

```sh tab="curl"
curl -X POST "$VMYARD_API_URL/v1/machines/$MACHINE_ID/commands/$COMMAND_ID/kill" \
  -H "Authorization: Bearer $VMYARD_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" -d '{}'
```

Killing is an operation. Read the status afterwards: a command is only `killed` once it reports so.

## Limits

Background commands, like everything inside a machine, need a running machine and Owner, Admin, or Operator access (or a `use_existing` key). Reading the status of a command reaches into the machine, so even reads need `use_existing`; a `read_only` key gets `403 insufficient_scope`.
