---
title: "Execute a command inside a VM"
method: POST
path: "/v1/vms/{id}/exec"
tags: ["exec"]
---

# Execute a command inside a VM

`POST /v1/vms/{id}/exec`

Runs `command` inside the VM. Response shape is determined by the
client's `Accept` header:

- **`Accept: application/json`** (default, omitted, or `*/*`): buffered
  `ExecVMResponse` — the server collects all output and returns a
  single JSON object once the command exits. Per-stream output is
  capped at 4 MiB; overflow bytes are dropped and signalled via
  `stdoutTruncated` / `stderrTruncated`.
- **`Accept: application/x-ndjson`**: newline-delimited stream of
  `ExecEvent`s — zero or more `stdout`/`stderr` chunks followed by
  exactly one terminal `exit` event. Use this for incremental output
  (long builds, test runners, live logs). No server-side cap.

Both modes share the same request body. `timeoutSec` bounds
server-side execution; clients should set their own HTTP timeout in
addition.

502 responses are transient (the upstream VM host is unreachable or
returned an error). The SDK's `run()` helper does NOT auto-retry
these by default: exec is **not idempotent**, so if a 502 hides a
successful exec a retry may run the command twice. Callers opt in
with `max_retries=N` per call.

## Request body

- ExecVMRequest
  - `command` string[], required — Argv-style command. First element must be non-empty. For shell strings, wrap as `["sh", "-c", "<string>"]`.
  - `timeoutSec` integer — Server-side execution timeout in seconds. Must be positive when provided; omit to use the server default.
  - `stdin` string — Optional base64-encoded stdin blob, written to the child's stdin before the process starts reading much and then closed. Streaming stdin is not supported — pipe from a file inside the guest if you need that shape.

## Response `200`

Command completed. `application/json` (default) returns a single
`ExecVMResponse`; `application/x-ndjson` returns an event stream
terminated by one `exit` event.

- ExecVMResponse — Buffered response shape for `POST /v1/vms/{id}/exec` under `Accept: application/json`. The server collects the streamed events and returns this aggregate once the command exits. Per-stream output is capped at 4 MiB; overflow bytes are dropped and signalled via `stdoutTruncated` / `stderrTruncated`. Streaming clients (`Accept: application/x-ndjson`) receive every byte without a cap.
  - `exitCode` integer, required
  - `stdout` string, required
  - `stderr` string, required
  - `timedOut` boolean, required
  - `stdoutTruncated` boolean, required — True if the collector dropped stdout bytes past the 4 MiB cap.
  - `stderrTruncated` boolean, required — True if the collector dropped stderr bytes past the 4 MiB cap.
  - `durationMs` integer, required

## Other responses

- `400` — Invalid request
- `401` — Missing or invalid credentials
- `404` — Resource not found
- `409` — VM is not running
- `500` — Internal server error
- `502` — Upstream VM host unreachable or returned an error. Not retried by default (non-idempotent).

## Changes

- **2026-05-07** `78aacdd01600` — 2 info
  - added the new optional request property `stdin`
  - added the media type `application/x-ndjson` for the response with the status `200`
- **2026-04-22** `fdb53b5a79b3` — 1 warning, 4 info
  - deleted the `path` request parameter `id`
  - api operation id `execVM` removed and replaced with `execVm`
  - api tag `exec` added
  - api tag `VMs` removed
  - …1 more

[Change history](https://skmtc.dev/fastvm-org/apis/fastvm-api/changes/v1/vms/:id/exec/post.md)

---

[API](https://skmtc.dev/fastvm-org/apis/fastvm-api.md) · [All operations](https://skmtc.dev/fastvm-org/apis/fastvm-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/fastvm-org/fastvm-api/revisions/480d9714a7a5/schema)
