# Snapshots and templates API

> Push a stopped machine, save it as a named template, and copy a machine with fork or restore.

- [Push a snapshot](#push-a-snapshot): `POST /v1/machines/{machine_id}/snapshots`
- [List a machine's snapshots](#list-a-machines-snapshots): `GET /v1/machines/{machine_id}/snapshots`
- [Get a machine's latest snapshot](#get-a-machines-latest-snapshot): `GET /v1/machines/{machine_id}/snapshots/latest`
- [Get a snapshot](#get-a-snapshot): `GET /v1/snapshots/{snapshot_id}`
- [Delete a snapshot](#delete-a-snapshot): `DELETE /v1/snapshots/{snapshot_id}`
- [Delete snapshots](#delete-snapshots): `POST /v1/snapshots/bulk-delete`
- [Delete a machine's snapshots](#delete-a-machines-snapshots): `DELETE /v1/machines/{machine_id}/snapshots`
- [Save a template](#save-a-template): `POST /v1/templates`
- [List templates](#list-templates): `GET /v1/templates`
- [Get a template](#get-a-template): `GET /v1/templates/{template}`
- [Delete a template](#delete-a-template): `DELETE /v1/templates/{template}`
- [Fork a machine](#fork-a-machine): `POST /v1/machines/{machine_id}/fork`
- [Restore a snapshot as a new machine](#restore-a-snapshot-as-a-new-machine): `POST /v1/machines/{machine_id}/restore`

## Push a snapshot

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

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `tier` | [`null \| SnapshotTierDto`](/docs/api/schemas#snapshottierdto) | No | Default `warm`. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `202` | The push is a durable operation; the snapshot is `pending` until it settles | [`SnapshotAccepted`](/docs/api/schemas#snapshotaccepted) |
| `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` | `machine_not_stopped`, `snapshot_limit_reached`, or `idempotency_conflict` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

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

```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",
  "snapshot_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "snapshot_url": "string",
  "status": "string"
}
```

## List a machine's snapshots

`GET /v1/machines/{machine_id}/snapshots`

- **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 |
| --- | --- | --- | --- | --- |
| `machine_id` | path | `string (uuid)` | Yes |  |
| `limit` | query | `integer` | No | 1-100, default 50. |
| `cursor` | query | `string (uuid)` | No | The last ID of the previous page. |
| `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` | — | [`SnapshotPage`](/docs/api/schemas#snapshotpage) |
| `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/machines/$MACHINE_ID/snapshots" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

```json title="Response 200"
{
  "items": [
    {
      "completed_at": "2026-10-12T05:51:01Z",
      "created_at": "2026-10-12T05:51:01Z",
      "disk_synced": false,
      "error_code": "string",
      "generation": 0,
      "id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "machine_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "manifest": "string",
      "name": "dev",
      "operation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "parent_manifest": "string",
      "size_bytes": 0,
      "source": "api",
      "status": "pending",
      "tier": "warm",
      "uploaded_bytes": 0
    }
  ],
  "next_cursor": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
}
```

## Get a machine's latest snapshot

`GET /v1/machines/{machine_id}/snapshots/latest`

The newest ready snapshot; pending and failed pushes and templates are skipped. A machine without one answers `404 no_snapshot`.

- **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 |
| --- | --- | --- | --- | --- |
| `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. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | The machine's newest ready snapshot; pending and failed pushes and templates are skipped | [`ReadySnapshotDto`](/docs/api/schemas#readysnapshotdto) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Unknown machine, or `no_snapshot`: the machine has no ready snapshot | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

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

```json title="Response 200"
{
  "completed_at": "2026-10-12T05:51:01Z",
  "created_at": "2026-10-12T05:51:01Z",
  "id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "size_bytes": 0,
  "source": "api",
  "tier": "warm"
}
```

## Get a snapshot

`GET /v1/snapshots/{snapshot_id}`

- **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 |
| --- | --- | --- | --- | --- |
| `snapshot_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` | — | [`SnapshotDto`](/docs/api/schemas#snapshotdto) |
| `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/snapshots/$SNAPSHOT_ID" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

```json title="Response 200"
{
  "completed_at": "2026-10-12T05:51:01Z",
  "created_at": "2026-10-12T05:51:01Z",
  "disk_synced": false,
  "error_code": "string",
  "generation": 0,
  "id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "machine_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "manifest": "string",
  "name": "dev",
  "operation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "parent_manifest": "string",
  "size_bytes": 0,
  "source": "api",
  "status": "pending",
  "tier": "warm",
  "uploaded_bytes": 0
}
```

## Delete a snapshot

`DELETE /v1/snapshots/{snapshot_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.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `snapshot_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 |
| --- | --- | --- |
| `204` | Deleted from the product; the runtime's store collects its data | — |
| `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` | `snapshot_pending`: the push has not settled | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X DELETE "$VMYARD_API_URL/v1/snapshots/$SNAPSHOT_ID" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

## Delete snapshots

`POST /v1/snapshots/bulk-delete`

1-100 IDs. Answers `200` with a result per snapshot (`deleted`, `not_found`, `pending`, or `template`) even when some were kept. See [Delete several snapshots](/docs/guides/snapshots#delete-several-snapshots).

- **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.

**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. |

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `snapshot_ids` | `string (uuid)[]` | Yes | 1-100 snapshot IDs; duplicates are reported once. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | Each snapshot's result; templates and pending pushes are kept | [`SnapshotDeleteResults`](/docs/api/schemas#snapshotdeleteresults) |
| `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) |
| `422` | No IDs, or more than 100 | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X POST "$VMYARD_API_URL/v1/snapshots/bulk-delete" \
  -H "Authorization: Bearer $VMYARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "snapshot_ids": [
    "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
  ]
}'
```

```json title="Response 200"
{
  "deleted": 0,
  "results": [
    {
      "result": "deleted",
      "snapshot_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
    }
  ]
}
```

## Delete a machine's snapshots

`DELETE /v1/machines/{machine_id}/snapshots`

Every snapshot of the machine, or one source's with `source` (`automatic` deletes the [automatic snapshots](/docs/guides/automatic-snapshots)). Pending pushes and templates are kept and reported.

- **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.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `machine_id` | path | `string (uuid)` | Yes |  |
| `source` | query | [`SnapshotSourceDto`](/docs/api/schemas#snapshotsourcedto) | No | Only this source's snapshots (`automatic` deletes the automatic ones); every snapshot of the machine by default. |
| `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` | Each of the machine's snapshots' result, oldest first; templates (without `source`) and pending pushes are kept | [`SnapshotDeleteResults`](/docs/api/schemas#snapshotdeleteresults) |
| `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 -X DELETE "$VMYARD_API_URL/v1/machines/$MACHINE_ID/snapshots" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

```json title="Response 200"
{
  "deleted": 0,
  "results": [
    {
      "result": "deleted",
      "snapshot_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
    }
  ]
}
```

## Save a template

`POST /v1/templates`

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `machine_id` | `string (uuid)` | Yes | A stopped machine to save as the template. |
| `name` | `string` | Yes | `[a-z0-9]` then up to 63 of `[a-z0-9._-]`; unique among the organization's templates. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `202` | A warm push saved as the template; `pending` until it settles | [`SnapshotAccepted`](/docs/api/schemas#snapshotaccepted) |
| `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` | `machine_not_stopped`, `template_name_taken`, `template_limit_reached`, or `idempotency_conflict` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

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

```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",
  "snapshot_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "snapshot_url": "string",
  "status": "string"
}
```

## List templates

`GET /v1/templates`

- **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 |
| --- | --- | --- | --- | --- |
| `limit` | query | `integer` | No | 1-100, default 50. |
| `cursor` | query | `string (uuid)` | No | The last ID of the previous page. |
| `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` | — | [`SnapshotPage`](/docs/api/schemas#snapshotpage) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl "$VMYARD_API_URL/v1/templates" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

```json title="Response 200"
{
  "items": [
    {
      "completed_at": "2026-10-12T05:51:01Z",
      "created_at": "2026-10-12T05:51:01Z",
      "disk_synced": false,
      "error_code": "string",
      "generation": 0,
      "id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "machine_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "manifest": "string",
      "name": "dev",
      "operation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "parent_manifest": "string",
      "size_bytes": 0,
      "source": "api",
      "status": "pending",
      "tier": "warm",
      "uploaded_bytes": 0
    }
  ],
  "next_cursor": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
}
```

## Get a template

`GET /v1/templates/{template}`

- **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 |
| --- | --- | --- | --- | --- |
| `template` | path | `string` | Yes | Template name or ID |
| `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` | — | [`SnapshotDto`](/docs/api/schemas#snapshotdto) |
| `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/templates/$TEMPLATE" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

```json title="Response 200"
{
  "completed_at": "2026-10-12T05:51:01Z",
  "created_at": "2026-10-12T05:51:01Z",
  "disk_synced": false,
  "error_code": "string",
  "generation": 0,
  "id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "machine_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "manifest": "string",
  "name": "dev",
  "operation_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "parent_manifest": "string",
  "size_bytes": 0,
  "source": "api",
  "status": "pending",
  "tier": "warm",
  "uploaded_bytes": 0
}
```

## Delete a template

`DELETE /v1/templates/{template}`

- **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.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `template` | path | `string` | Yes | Template name or ID |
| `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 |
| --- | --- | --- |
| `204` | Deleted; the name is free again | — |
| `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` | `snapshot_pending`: the save has not settled | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X DELETE "$VMYARD_API_URL/v1/templates/$TEMPLATE" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

## Fork a machine

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

Billing admission can refuse it: `402 billing_required` or `spend_cap_reached`, `403 trial_machine_class_not_allowed`, `409 plan_not_priced`, or `429 limit_reached`. See [Usage, billing, and limits](/docs/guides/billing#limits).

- **API key:** `Authorization: Bearer vmy_…` with preset `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`, [`ForkMachineRequest`](/docs/api/schemas#forkmachinerequest))

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `keep_ssh_keys` | `boolean` | No | Keep the login user's SSH `authorized_keys` in the copy (default `false`). The SSH gateway port is never inherited. |
| `live` | `boolean \| null` | No | Run the copy at once with the source's processes (default `true`); `false` creates it stopped. |
| `name` | `string \| null` | No | The copy's display name (default: the tenant alias it shares with the source). |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `202` | The copy is created by the operation; `machine_id` names the new machine | [`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) |
| `402` | — | [`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` | `machine_not_running`, `idempotency_conflict`, or a billing limit | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `429` | Too many requests; wait for `Retry-After`. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

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

```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": "create",
  "status": "pending"
}
```

## Restore a snapshot as a new machine

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

Billing admission can refuse it: `402 billing_required` or `spend_cap_reached`, `403 trial_machine_class_not_allowed`, `409 plan_not_priced`, or `429 limit_reached`. See [Usage, billing, and limits](/docs/guides/billing#limits).

- **API key:** `Authorization: Bearer vmy_…` with preset `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`, [`RestoreMachineRequest`](/docs/api/schemas#restoremachinerequest))

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `snapshot_id` | `string (uuid)` | Yes | A ready snapshot of this machine. |
| `keep_ssh_keys` | `boolean` | No | Keep the login user's SSH `authorized_keys` (default `false`). |
| `name` | `string \| null` | No | The new machine's display name. |
| `wake` | `boolean \| null` | No | Wake the new machine at once (default `true`); `false` creates it stopped. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `202` | A new machine is restored from the snapshot; `machine_id` names it | [`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) |
| `402` | — | [`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` | `snapshot_not_ready`, `idempotency_conflict`, or a billing limit | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `429` | Too many requests; wait for `Retry-After`. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

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

```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": "create",
  "status": "pending"
}
```
