# Agents API

> Run a coding agent (Claude Code, Codex, or Pi) inside a running machine: prompt it, follow its events, steer or interrupt a running turn, and resume conversations.

- [Prompt an agent](#prompt-an-agent): `POST /v1/machines/{machine_id}/agents/prompt`
- [Follow an agent's events](#follow-an-agents-events): `GET /v1/machines/{machine_id}/agents/events`
- [List agent conversations](#list-agent-conversations): `GET /v1/machines/{machine_id}/agents/conversations`
- [Steer a running agent](#steer-a-running-agent): `POST /v1/machines/{machine_id}/agents/steer`
- [Interrupt a running agent](#interrupt-a-running-agent): `POST /v1/machines/{machine_id}/agents/interrupt`
- [List agent models](#list-agent-models): `GET /v1/provider-models`

## Prompt an agent

`POST /v1/machines/{machine_id}/agents/prompt`

Starts a turn of a coding agent in the machine, in a new conversation or one you resume. The agent's provider keys are the machine's environment variables, so a machine created without an environment gives it none. Follow the turn with `GET /v1/machines/{machine_id}/agents/events`.

- **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`, [`AgentPromptRequest`](/docs/api/schemas#agentpromptrequest))

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `attachments` | [`AgentAttachmentRequest[]`](/docs/api/schemas#agentattachmentrequest) | No | At most 16 files, 16 MiB together, written to the machine for the agent to read. |
| `conversation_id` | `string (uuid) \| null` | No | Continue this conversation; omit to start a new one. |
| `model` | `string \| null` | No | A model the agent accepts (see `GET /v1/provider-models`); the agent's default when omitted. Pi takes `provider/model`. |
| `provider` | [`null \| AgentProviderDto`](/docs/api/schemas#agentproviderdto) | No | Required for a new conversation; a conversation keeps its provider. |
| `reasoning_effort` | [`null \| ReasoningEffortDto`](/docs/api/schemas#reasoningeffortdto) | No |  |
| `text` | `string` | No | The prompt, at most 256 KiB. A prompt needs text or an attachment. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `202` | The turn runs in the machine; follow `events_url` | [`AgentTurnDto`](/docs/api/schemas#agentturndto) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | `forbidden`, `insufficient_scope`, or `organization_suspended` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Unknown machine, or `conversation_not_found` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `409` | `agent_turn_running`, `provider_mismatch`, `idempotency_conflict`, `machine_not_running`, `machine_not_created`, or `machine_deleted` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `413` | The request body is too large (`body_too_large`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `502` | `guest_action_failed`: the machine could not start the turn (the turn is failed) | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `503` | `agents_unavailable`, or `runtime_unavailable` (retry with the same key) | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

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

```json title="Response 202"
{
  "conversation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "created_at": "2026-10-12T05:51:01Z",
  "error_code": "string",
  "events_url": "string",
  "exit_code": 0,
  "finished_at": "2026-10-12T05:51:01Z",
  "interrupt_requested": false,
  "machine_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "model": "string",
  "provider": "claude",
  "reasoning_effort": "minimal",
  "resume": false,
  "started_at": "2026-10-12T05:51:01Z",
  "status": "starting",
  "turn_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
}
```

## Follow an agent's events

`GET /v1/machines/{machine_id}/agents/events`

JSON by `cursor` (with `wait_seconds` to long-poll), or server-sent events with `Accept: text/event-stream`. Each SSE event's `id` is the cursor after it: reconnect with `Last-Event-ID` to resume without repeats. The stream ends with an `idle` event once no turn is running.

- **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. |
| `Last-Event-ID` | header | `integer \| null` | No | SSE: resume after this event's cursor (overrides `cursor`) |
| `conversation_id` | query | `string (uuid)` | Yes |  |
| `cursor` | query | `integer` | No | The `cursor` of an earlier answer (or an SSE event's `id`); events after it are returned. Default 0, the conversation's first event. |
| `wait_seconds` | query | `integer` | No | JSON only: wait up to this many seconds (0-30, default 0) for an event when none is ready. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | With `Accept: text/event-stream`: an SSE stream of `agent` events (`id` is the cursor after the event), ending with an `idle` event once no turn is running and every event was sent. Otherwise JSON: the events after `cursor`, waiting up to `wait_seconds` for one. | [`AgentEventsDto`](/docs/api/schemas#agenteventsdto) |
| `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` | Unknown machine, or `conversation_not_found` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `409` | `machine_not_running`, `machine_not_created`, or `machine_deleted` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `502` | The machine's host or a provider failed to answer. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `503` | Not available on this server. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl "$VMYARD_API_URL/v1/machines/$MACHINE_ID/agents/events?conversation_id=conversation_id" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

```json title="Response 200"
{
  "active": false,
  "conversation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "cursor": 0,
  "events": [
    {
      "code": "string",
      "id": "string",
      "input": "string",
      "input_id": "string",
      "is_error": false,
      "kind": "prompt",
      "message": "string",
      "model": "string",
      "name": "dev",
      "outcome": "completed",
      "output": "string",
      "provider": "claude",
      "reasoning_effort": "minimal",
      "restarted": false,
      "result": "string",
      "resumed": false,
      "seq": 0,
      "session_id": "string",
      "text": "string",
      "time_ms": 0,
      "turn_id": "string",
      "type": "turn_started",
      "usage": {
        "cached_input_tokens": 0,
        "cost_micro_usd": 0,
        "input_tokens": 0,
        "output_tokens": 0
      }
    }
  ],
  "latest_turn": {
    "conversation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
    "created_at": "2026-10-12T05:51:01Z",
    "error_code": "string",
    "events_url": "string",
    "exit_code": 0,
    "finished_at": "2026-10-12T05:51:01Z",
    "interrupt_requested": false,
    "machine_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
    "model": "string",
    "provider": "claude",
    "reasoning_effort": "minimal",
    "resume": false,
    "started_at": "2026-10-12T05:51:01Z",
    "status": "starting",
    "turn_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
  }
}
```

## List agent conversations

`GET /v1/machines/{machine_id}/agents/conversations`

- **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. |
| `limit` | query | `integer` | No | 1-100 (default 50). |
| `cursor` | query | `string (uuid)` | No | The `next_cursor` of the previous page. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | — | [`AgentConversationListDto`](/docs/api/schemas#agentconversationlistdto) |
| `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/agents/conversations" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

```json title="Response 200"
{
  "conversations": [
    {
      "created_at": "2026-10-12T05:51:01Z",
      "events_url": "string",
      "id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "latest_turn": {
        "conversation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
        "created_at": "2026-10-12T05:51:01Z",
        "error_code": "string",
        "events_url": "string",
        "exit_code": 0,
        "finished_at": "2026-10-12T05:51:01Z",
        "interrupt_requested": false,
        "machine_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
        "model": "string",
        "provider": "claude",
        "reasoning_effort": "minimal",
        "resume": false,
        "started_at": "2026-10-12T05:51:01Z",
        "status": "starting",
        "turn_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
      },
      "machine_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "provider": "claude",
      "status": "idle",
      "turn_count": 0,
      "updated_at": "2026-10-12T05:51:01Z"
    }
  ],
  "next_cursor": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
}
```

## Steer a running agent

`POST /v1/machines/{machine_id}/agents/steer`

Adds instructions to the running turn. The agent confirms when it takes them with an `input_accepted` event carrying the answer's `input_id`.

- **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`, [`AgentSteerRequest`](/docs/api/schemas#agentsteerrequest))

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `conversation_id` | `string (uuid)` | Yes |  |
| `text` | `string` | Yes | More instructions for the running turn, at most 256 KiB. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `202` | Written to the running turn's inbox; the agent confirms it with an `input_accepted` event | [`AgentInputAccepted`](/docs/api/schemas#agentinputaccepted) |
| `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` | Unknown machine, or `conversation_not_found` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `409` | `no_active_turn`, `idempotency_conflict`, or the machine is not running | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `502` | The machine's host or a provider failed to answer. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `503` | Not available on this server. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X POST "$VMYARD_API_URL/v1/machines/$MACHINE_ID/agents/steer" \
  -H "Authorization: Bearer $VMYARD_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "conversation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "text": "string"
}'
```

```json title="Response 202"
{
  "conversation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "input_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "kind": "steer",
  "stopped": false,
  "turn_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
}
```

## Interrupt a running agent

`POST /v1/machines/{machine_id}/agents/interrupt`

Asks the running turn to stop; with `force` the agent is killed at once. The turn ends with outcome `interrupted`.

- **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`, [`AgentInterruptRequest`](/docs/api/schemas#agentinterruptrequest))

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `conversation_id` | `string (uuid)` | Yes |  |
| `force` | `boolean` | No | Also kill the runner and the agent at once instead of letting the agent stop on its own (it gets 10 s, then SIGTERM, then SIGKILL). |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `202` | The running turn is asked to stop (or, with `force`, killed); it ends with outcome `interrupted` | [`AgentInputAccepted`](/docs/api/schemas#agentinputaccepted) |
| `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` | Unknown machine, or `conversation_not_found` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `409` | `no_active_turn`, `idempotency_conflict`, or the machine is not running | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `502` | The machine's host or a provider failed to answer. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `503` | Not available on this server. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X POST "$VMYARD_API_URL/v1/machines/$MACHINE_ID/agents/interrupt" \
  -H "Authorization: Bearer $VMYARD_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "conversation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
}'
```

```json title="Response 202"
{
  "conversation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "input_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "kind": "steer",
  "stopped": false,
  "turn_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
}
```

## List agent models

`GET /v1/provider-models`

- **API key:** `Authorization: Bearer vmy_…` with preset `read_only`, `use_existing`, `create_and_use`, `full_access`.
- **Browser session:** the `vmyard_session` cookie.
- **Who:** Any member.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `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` | — | [`ProviderModelsDto`](/docs/api/schemas#providermodelsdto) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`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/provider-models" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

```json title="Response 200"
{
  "providers": [
    {
      "models": [
        {
          "attachments": false,
          "context_tokens": 0,
          "id": "string",
          "name": "dev",
          "provider": "anthropic",
          "reasoning": false,
          "release_date": "string"
        }
      ],
      "provider": "claude",
      "reasoning_efforts": [
        "minimal"
      ]
    }
  ],
  "source": "live"
}
```
