# Exec API

> Run a foreground command inside a machine, optionally streaming its output as NDJSON frames.

- [Run a command](#run-a-command): `POST /v1/machines/{machine_id}/exec`

## Run a command

`POST /v1/machines/{machine_id}/exec`

- **API key:** `Authorization: Bearer vmy_…` with preset `use_existing`, `create_and_use`, `full_access`.
- **Browser session:** the `vmyard_session` cookie and the `x-csrf-token` header.
- **Who:** Owner, Admin, or Operator.
- **Idempotency:** send a unique `Idempotency-Key`; replaying the same key and body returns the same operation.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `machine_id` | path | `string (uuid)` | Yes |  |
| `X-Organization-ID` | header | `string (uuid)` | No | Required with a browser session. Optional with an API key, which fixes the organization; when sent it must match. |

**Request body** (`application/json`, [`ExecRequest`](/docs/api/schemas#execrequest))

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `argv` | `string[] \| null` | No | Program and arguments, executed directly. Exactly one of `argv` or `command`. |
| `command` | `string \| null` | No | Shell command run as `/bin/sh -c <command>`, at most 8192 bytes. |
| `stream` | `boolean` | No | Respond with `application/x-ndjson` frames as output arrives instead of `202 Accepted`. |
| `timeout_ms` | `integer \| null` | No | Foreground limit in milliseconds, 1 to the runtime maximum (600000). Defaults to the maximum; the effective value is persisted with the operation. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | With `stream: true`: newline-delimited JSON frames (started, stdout, stderr, then exit or error). Replaying the same Idempotency-Key and body streams the persisted operation again. | [`ExecStreamFrame`](/docs/api/schemas#execstreamframe) |
| `202` | Accepted. Follow `operation_url` until the operation is terminal. | [`OperationAccepted`](/docs/api/schemas#operationaccepted) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | Refused for this caller: role, key preset, CSRF token, or organization (`forbidden`, `insufficient_scope`, `csrf_invalid`, `organization_mismatch`, `session_required`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not found in this organization (`resource_not_found`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `409` | Conflicts with the current state, such as a reused idempotency key with another body (`idempotency_conflict`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `413` | The request body is too large (`body_too_large`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request; `timeout_exceeds_runtime_limit` names the runtime maximum | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X POST "$VMYARD_API_URL/v1/machines/$MACHINE_ID/exec" \
  -H "Authorization: Bearer $VMYARD_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "command": "uname -a",
  "timeout_ms": 30000,
  "stream": false
}'
```

```json title="Response 202"
{
  "machine_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "operation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "operation_url": "/v1/operations/6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "requested_action": "exec",
  "status": "pending"
}
```
