# Command history API

> Search a machine's past commands, read one entry, and page through its stored stdout and stderr.

- [List command history](#list-command-history): `GET /v1/machines/{machine_id}/commands/history`
- [Get a history entry](#get-a-history-entry): `GET /v1/machines/{machine_id}/commands/history/{operation_id}`
- [Read stored command output](#read-stored-command-output): `GET /v1/machines/{machine_id}/commands/history/{operation_id}/logs`

## List command history

`GET /v1/machines/{machine_id}/commands/history`

- **API key:** `Authorization: Bearer vmy_…` with preset `use_existing`, `create_and_use`, `full_access`.
- **Browser session:** the `vmyard_session` cookie.
- **Who:** Owner, Admin, or Operator.

**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. |
| `q` | query | `string` | No | Case-insensitive text contained in the command, at most 128 characters. |
| `mode` | query | `string` | No | `all` (default), `sync`, or `detached`. |
| `limit` | query | `integer` | No | Page size, 1-100 (default 50). |
| `cursor` | query | `string` | No | `next_cursor` from the previous page. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | — | [`CommandHistoryPage`](/docs/api/schemas#commandhistorypage) |
| `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) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl "$VMYARD_API_URL/v1/machines/$MACHINE_ID/commands/history" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

```json title="Response 200"
{
  "items": [
    {
      "argv": [
        "string"
      ],
      "cancelled": false,
      "command": "npm test",
      "created_at": "string",
      "duration_ms": 0,
      "error_code": "string",
      "exit_code": 0,
      "finished_at": "string",
      "logs_url": "/v1/machines/6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f/commands/history/6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f/logs",
      "machine_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "mode": "sync",
      "operation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "output_purged": false,
      "started_at": "string",
      "status": "pending",
      "stderr_bytes": 0,
      "stdout_bytes": 0,
      "timed_out": false,
      "truncated": false
    }
  ],
  "next_cursor": "string"
}
```

## Get a history entry

`GET /v1/machines/{machine_id}/commands/history/{operation_id}`

- **API key:** `Authorization: Bearer vmy_…` with preset `use_existing`, `create_and_use`, `full_access`.
- **Browser session:** the `vmyard_session` cookie.
- **Who:** Owner, Admin, or Operator.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `machine_id` | path | `string (uuid)` | Yes |  |
| `operation_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. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | — | [`CommandHistoryEntryDto`](/docs/api/schemas#commandhistoryentrydto) |
| `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) |

```sh title="Request"
curl "$VMYARD_API_URL/v1/machines/$MACHINE_ID/commands/history/$OPERATION_ID" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

```json title="Response 200"
{
  "argv": [
    "string"
  ],
  "cancelled": false,
  "command": "npm test",
  "created_at": "string",
  "duration_ms": 0,
  "error_code": "string",
  "exit_code": 0,
  "finished_at": "string",
  "logs_url": "/v1/machines/6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f/commands/history/6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f/logs",
  "machine_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "mode": "sync",
  "operation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "output_purged": false,
  "started_at": "string",
  "status": "pending",
  "stderr_bytes": 0,
  "stdout_bytes": 0,
  "timed_out": false,
  "truncated": false
}
```

## Read stored command output

`GET /v1/machines/{machine_id}/commands/history/{operation_id}/logs`

While a streamed command runs, the relayed output is served and `complete` is `false`. After the retention period the output is removed and the answer is `410 output_expired`.

- **API key:** `Authorization: Bearer vmy_…` with preset `use_existing`, `create_and_use`, `full_access`.
- **Browser session:** the `vmyard_session` cookie.
- **Who:** Owner, Admin, or Operator.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `machine_id` | path | `string (uuid)` | Yes |  |
| `operation_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. |
| `stream` | query | `string` | No | `stdout` (default) or `stderr`. |
| `offset` | query | `integer` | No | Byte offset into the stream (default 0). |
| `limit` | query | `integer` | No | Bytes to return, 1 to 1048576 (default 65536). |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | — | [`CommandLogChunkDto`](/docs/api/schemas#commandlogchunkdto) |
| `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) |
| `410` | Retention removed this command's output | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl "$VMYARD_API_URL/v1/machines/$MACHINE_ID/commands/history/$OPERATION_ID/logs" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

```json title="Response 200"
{
  "complete": false,
  "data_base64": "UEFTUyAgc3JjL2FwcC50ZXN0LnRzCg==",
  "next_offset": 0,
  "offset": 0,
  "operation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "size": 0,
  "stream": "stdout",
  "truncated": false
}
```
