# Machines

> The virtual machines behind an app: create, read, start, stop, suspend, destroy, migrate; logs, snapshots, files and shell sessions.

A machine is one virtual machine of an app. A deploy creates machines for you; this family is for reading them, acting on one, and for the machines you create yourself (a computer, an agent's workspace). A machine is `requested`, `scheduled`, `starting`, `running`, `suspending`, `suspended`, `stopping`, `stopped`, `deleted` or `failed`.

The shell routes (`ry exec`, `ry ssh`, `ry proxy`) upgrade the connection to a WebSocket and are not plain JSON calls; use the CLI for them.

## Endpoints

- [`POST /v1/apps/:name/machines`](#post-v1appsnamemachines): Creates a machine in the app and waits until it runs. Add `?wait=false` for `202` right away and poll `GET /v1/machines/:id/wait`
- [`GET /v1/apps/:name/machines`](#get-v1appsnamemachines): The app's machines
- [`GET /v1/machines/:id`](#get-v1machinesid): One machine
- [`GET /v1/machines/:id/wait`](#get-v1machinesidwait): Holds the request until the machine reaches a state (`?state=`), at most `?timeout=` seconds; `408` when it runs out
- [`POST /v1/machines/:id/:action`](#post-v1machinesidaction): Acts on a machine: `start`, `stop`, `suspend` or `destroy`
- [`POST /v1/machines/:id/migrate`](#post-v1machinesidmigrate): Moves the machine to another host
- [`GET /v1/apps/:name/logs`](#get-v1appsnamelogs): The console output of the app's machines, with a cursor per machine for the next call
- [`POST /v1/machines/:id/checkpoints`](#post-v1machinesidcheckpoints): Takes a named snapshot of the machine: its disk, and with `memory: true` its running memory too
- [`GET /v1/machines/:id/checkpoints`](#get-v1machinesidcheckpoints): The machine's named snapshots. Add `?auto=1` to include the automatic ones
- [`DELETE /v1/machines/:id/checkpoints/:label`](#delete-v1machinesidcheckpointslabel): Forgets a snapshot
- [`POST /v1/machines/:id/checkpoints/:label/restore`](#post-v1machinesidcheckpointslabelrestore): Brings the machine back to that snapshot. With `keepCurrent: true` (the default) the current state is saved first
- [`GET /v1/machines/:id/files`](#get-v1machinesidfiles): Reads one file of the machine (`?path=`), as raw bytes
- [`PUT /v1/machines/:id/files`](#put-v1machinesidfiles): Writes one file of the machine (`?path=`); the body is the file's bytes
- [`GET /v1/machines/:id/files/list`](#get-v1machinesidfileslist): Lists a directory of the machine (`?path=`)
- [`GET /v1/machines/:id/screenshot`](#get-v1machinesidscreenshot): A screenshot of the machine's desktop, for a machine that has one
- [`GET /v1/machines/:id/exec`](#get-v1machinesidexec): Opens a shell session over a WebSocket. This is what `ry exec` and `ry ssh` use; it is not a plain JSON call
- [`GET /v1/machines/:id/exec-sessions`](#get-v1machinesidexec-sessions): The shell sessions open on the machine

## POST /v1/apps/:name/machines

Creates a machine in the app and waits until it runs. Add `?wait=false` for `202` right away and poll `GET /v1/machines/:id/wait`.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `name` | The app's name. |

### Query parameters

| Name | Description |
| --- | --- |
| `wait` | `false` answers `202` as soon as the row exists, before the boot; the default waits for the running machine. |

### Request body

JSON, the shape `CreateMachineRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `image` | string | required | An OCI reference such as `nginx:alpine`, or `build:<id>` for a build of your organization. 1 to 512 characters. |
| `class` | `shared-cpu-1x` \| `shared-cpu-2x` \| `shared-cpu-4x` \| `shared-cpu-8x` \| `performance-1x` \| `performance-2x` \| `performance-4x` \| `performance-8x` | optional | A named size instead of `vcpus`; the two must not contradict each other. |
| `vcpus` | integer | optional | Virtual CPUs. 1 to 16, default `1`. |
| `memoryMb` | integer | optional | Memory in MB. 128 to 16384, default `512`. |
| `env` | object of string | optional | Plain environment variables. Use secrets for credentials. |
| `diskMb` | integer | optional | The machine's disk in MB. Without it, the image's size plus headroom. 512 to 131072. |
| `volumeGb` | integer | optional | A volume of this size, attached to the machine. 1 to 500. |
| `start` | string | optional | The start command, run through `/bin/sh -c`. It replaces what detection chose or the image's `CMD`. 1 to 2000 characters. |
| `internalPort` | integer | optional | The port the app listens on. 1 to 65535, default `3000`. |
| `suspendPolicy` | object | optional | When the machine sleeps. Without it, a new app never sleeps; a deploy without it keeps the app's rule. |
| `suspendPolicy.mode` | `suspend` \| `stop` \| `off` | optional | `suspend` keeps the memory and resumes fast (machines up to 2048 MB; larger ones stop instead), `stop` shuts down, `off` never sleeps. default `"suspend"`. |
| `suspendPolicy.idleSeconds` | integer | optional | Seconds without requests and without CPU load before the machine sleeps. 10 to 86400, default `300`. |
| `suspendPolicy.stopAfterSeconds` | integer | optional | Second stage: after this many seconds since the last activity a suspended machine is stopped (its memory snapshot dropped, its disk kept). Only with `mode: "suspend"`, and greater than `idleSeconds`. 10 to 604800. |
| `healthCheck` | object | optional | An HTTP check a new machine must pass before it gets traffic. |
| `healthCheck.path` | string | optional | The path to call; empty means the port alone. default `""`. |
| `healthCheck.timeoutMs` | integer | optional | How long one probe may take. 100 to 30000, default `2000`. |
| `healthCheck.intervalMs` | integer | optional | Time between probes. 100 to 10000, default `500`. |
| `healthCheck.healthyThreshold` | integer | optional | Probes that must pass in a row. 1 to 10, default `1`. |
| `healthCheck.maxWaitMs` | integer | optional | How long a new machine may take in total before the deploy gives up on it. 1000 to 300000. |
| `restart` | object | optional | What happens when the process ends. Without it, a crashed machine is restarted. |
| `restart.policy` | `never` \| `on-failure` | optional | `never` or `on-failure`. default `"never"`. |
| `restart.retries` | integer | optional | Restarts before the machine stays down. 1 to 10, default `3`. |
| `killSignal` | `SIGTERM` \| `SIGINT` \| `SIGQUIT` \| `SIGHUP` \| `SIGUSR1` \| `SIGUSR2` \| `SIGWINCH` \| `SIGKILL` | optional | The signal sent before a machine is stopped or replaced. Default: the image's `STOPSIGNAL`, else `SIGTERM`. |
| `killTimeoutS` | integer | optional | Seconds between that signal and `SIGKILL`. 1 to 300. |
| `durableCheckpoint` | boolean | optional | Agent machine: the whole state (memory and disk) is checkpointed and survives a host failure. default `false`. |
| `checkpointIntervalS` | integer | optional | 5 to 3600. |
| `console` | boolean | optional | default `false`. |
| `command` | array of string | optional | Runs on a machine of its own, as an argument list. 1 to 64 items. |
| `diskIsCache` | boolean | optional |  |
| `readiness` | `port` \| `boot` | optional | `boot`: ready when the machine boots. `port`: ready when `internalPort` answers. |
| `exposePorts` | array of integer | optional | 0 to 8 items. |
| `egress` | object | optional | Outbound network: `allow-all`, `deny-all`, or `allowlist` with `domains` and `cidrs`. |
| `egress.mode` | `deny-all` \| `allowlist` | required | `view` to watch, `control` to take keyboard and mouse. |
| `egress.domains` | array of string | required | 0 to 100 items. |
| `egress.cidrs` | array of string | required | 0 to 10 items. |
| `region` | string | optional | The region to place the machine in. Without it, the organization's region. 2 to 12 characters. |

### Example request

```bash
curl -X POST "https://api.rytiv.app/v1/apps/<name>/machines" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"image":"rytiv/computer:0.1.0"}'
```

### Response

`201` with the machine (plus `bootMs`, the time the boot took):

```json
{
  "id": "<id>",
  "appName": "my-app",
  "config": {
    "image": "nginx:alpine",
    "vcpus": 1,
    "memoryMb": 512,
    "env": {
      "<key>": "<value>"
    },
    "internalPort": 3000,
    "suspendPolicy": {
      "mode": "suspend",
      "idleSeconds": 300
    },
    "durableCheckpoint": false,
    "console": false
  },
  "desiredState": "requested",
  "observedState": "requested",
  "region": "<region>",
  "createdAt": "2026-10-21T09:00:00Z"
}
```

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | The machine's id. |
| `appName` | string | always | The app it belongs to. |
| `config` | object | always | What it runs: image, size, environment and the settings of a deploy request. |
| `config.image` | string | always | An OCI reference such as `nginx:alpine`, or `build:<id>` for a build of your organization. 1 to 512 characters. |
| `config.class` | `shared-cpu-1x` \| `shared-cpu-2x` \| `shared-cpu-4x` \| `shared-cpu-8x` \| `performance-1x` \| `performance-2x` \| `performance-4x` \| `performance-8x` | if set | A named size instead of `vcpus`; the two must not contradict each other. |
| `config.vcpus` | integer | always | Virtual CPUs. 1 to 16, default `1`. |
| `config.memoryMb` | integer | always | Memory in MB. 128 to 16384, default `512`. |
| `config.env` | object of string | always | Plain environment variables. Use secrets for credentials. |
| `config.diskMb` | integer | if set | The machine's disk in MB. Without it, the image's size plus headroom. 512 to 131072. |
| `config.volumeGb` | integer | if set | A volume of this size, attached to the machine. 1 to 500. |
| `config.start` | string | if set | The start command, run through `/bin/sh -c`. It replaces what detection chose or the image's `CMD`. 1 to 2000 characters. |
| `config.internalPort` | integer | always | The port the app listens on. 1 to 65535, default `3000`. |
| `config.suspendPolicy` | object | always | When the machine sleeps. Without it, a new app never sleeps; a deploy without it keeps the app's rule. |
| `config.healthCheck` | object | if set | An HTTP check a new machine must pass before it gets traffic. |
| `config.restart` | object | if set | What happens when the process ends. Without it, a crashed machine is restarted. |
| `config.killSignal` | `SIGTERM` \| `SIGINT` \| `SIGQUIT` \| `SIGHUP` \| `SIGUSR1` \| `SIGUSR2` \| `SIGWINCH` \| `SIGKILL` | if set | The signal sent before a machine is stopped or replaced. Default: the image's `STOPSIGNAL`, else `SIGTERM`. |
| `config.killTimeoutS` | integer | if set | Seconds between that signal and `SIGKILL`. 1 to 300. |
| `config.durableCheckpoint` | boolean | always | Agent machine: the whole state (memory and disk) is checkpointed and survives a host failure. default `false`. |
| `config.checkpointIntervalS` | integer | if set | 5 to 3600. |
| `config.console` | boolean | always | default `false`. |
| `config.command` | array of string | if set | Runs on a machine of its own, as an argument list. 1 to 64 items. |
| `config.diskIsCache` | boolean | if set |  |
| `config.readiness` | `port` \| `boot` | if set | `boot`: ready when the machine boots. `port`: ready when `internalPort` answers. |
| `config.exposePorts` | array of integer | if set | 0 to 8 items. |
| `config.egress` | object | if set | Outbound network: `allow-all`, `deny-all`, or `allowlist` with `domains` and `cidrs`. |
| `desiredState` | `requested` \| `scheduled` \| `starting` \| `running` \| `suspending` \| `suspended` \| `stopping` \| `stopped` \| `deleted` \| `failed` | always | What the platform wants the machine to be. |
| `observedState` | `requested` \| `scheduled` \| `starting` \| `running` \| `suspending` \| `suspended` \| `stopping` \| `stopped` \| `deleted` \| `failed` | always | What it is right now. |
| `host` | string | if set | Internal; not shown to a customer. |
| `privateIp` | string | if set | The machine's address on the private network. |
| `role` | `primary` \| `standby` | if set | `primary` or `standby`, for a redundant database only. |
| `region` | string | always | Where it runs. Fixed for the machine's lifetime. 2 to 12 characters. |
| `checkpointAt` | string (ISO 8601) | if set | Agent machines: when the last durable checkpoint was taken. |
| `zone` | string | if set | The availability zone, where the host reports one. |
| `diskUsedMb` | integer | if set | How full the disk is, an upper bound. 0 to 9007199254740991. |
| `diskTotalMb` | integer | if set | The disk's size. 0 to 9007199254740991. |
| `diskAt` | string (ISO 8601) | if set | When the disk numbers were reported. |
| `restoreChunksDone` | integer | if set | 0 to 9007199254740991. |
| `restoreChunksTotal` | integer | if set | 0 to 9007199254740991. |
| `restoreAt` | string (ISO 8601) | if set |  |
| `readyAt` | string (ISO 8601) | if set |  |
| `net` | object or null | if set |  |
| `liveness` | `listening` \| `not-listening` \| `no-signal` | if set |  |
| `livenessReason` | string | if set |  |
| `livenessSince` | string (ISO 8601) | if set |  |
| `livenessAt` | string (ISO 8601) | if set |  |
| `createError` | string | if set |  |
| `createErrorCode` | string | if set |  |
| `createBootLog` | string | if set |  |
| `createWarning` | string | if set |  |
| `createdAt` | string (ISO 8601) | always | When it was created. |

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad-request` | The request is malformed or a field is out of range. `error` names every field; `issues` lists them. |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `build-not-found` | `build:<id>` is not a build of your organization. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |
| 409 | `conflict` | The request conflicts with the current state. |
| 409 | `volume-needs-single-machine` | An app with a volume runs exactly one machine. |
| 503 | `create-failed` | The host could not create the machine (image, start command or port). The sentence says which. |
| 503 | `no-capacity` | No host in the region has room for the machine right now; try again. |

## GET /v1/apps/:name/machines

The app's machines.

**Auth:** API key with full access or the scope `deploy` or `deploy:<app>` for this app, or the scope `read`. Every class of agent token may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `name` | The app's name. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/apps/<name>/machines" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a list of machines, each:

```json
[
  {
    "id": "<id>",
    "appName": "my-app",
    "config": {
      "image": "nginx:alpine",
      "vcpus": 1,
      "memoryMb": 512,
      "env": {
        "<key>": "<value>"
      },
      "internalPort": 3000,
      "suspendPolicy": {
        "mode": "suspend",
        "idleSeconds": 300
      },
      "durableCheckpoint": false,
      "console": false
    },
    "desiredState": "requested",
    "observedState": "requested",
    "region": "<region>",
    "createdAt": "2026-10-21T09:00:00Z"
  }
]
```

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | The machine's id. |
| `appName` | string | always | The app it belongs to. |
| `config` | object | always | What it runs: image, size, environment and the settings of a deploy request. |
| `config.image` | string | always | An OCI reference such as `nginx:alpine`, or `build:<id>` for a build of your organization. 1 to 512 characters. |
| `config.class` | `shared-cpu-1x` \| `shared-cpu-2x` \| `shared-cpu-4x` \| `shared-cpu-8x` \| `performance-1x` \| `performance-2x` \| `performance-4x` \| `performance-8x` | if set | A named size instead of `vcpus`; the two must not contradict each other. |
| `config.vcpus` | integer | always | Virtual CPUs. 1 to 16, default `1`. |
| `config.memoryMb` | integer | always | Memory in MB. 128 to 16384, default `512`. |
| `config.env` | object of string | always | Plain environment variables. Use secrets for credentials. |
| `config.diskMb` | integer | if set | The machine's disk in MB. Without it, the image's size plus headroom. 512 to 131072. |
| `config.volumeGb` | integer | if set | A volume of this size, attached to the machine. 1 to 500. |
| `config.start` | string | if set | The start command, run through `/bin/sh -c`. It replaces what detection chose or the image's `CMD`. 1 to 2000 characters. |
| `config.internalPort` | integer | always | The port the app listens on. 1 to 65535, default `3000`. |
| `config.suspendPolicy` | object | always | When the machine sleeps. Without it, a new app never sleeps; a deploy without it keeps the app's rule. |
| `config.healthCheck` | object | if set | An HTTP check a new machine must pass before it gets traffic. |
| `config.restart` | object | if set | What happens when the process ends. Without it, a crashed machine is restarted. |
| `config.killSignal` | `SIGTERM` \| `SIGINT` \| `SIGQUIT` \| `SIGHUP` \| `SIGUSR1` \| `SIGUSR2` \| `SIGWINCH` \| `SIGKILL` | if set | The signal sent before a machine is stopped or replaced. Default: the image's `STOPSIGNAL`, else `SIGTERM`. |
| `config.killTimeoutS` | integer | if set | Seconds between that signal and `SIGKILL`. 1 to 300. |
| `config.durableCheckpoint` | boolean | always | Agent machine: the whole state (memory and disk) is checkpointed and survives a host failure. default `false`. |
| `config.checkpointIntervalS` | integer | if set | 5 to 3600. |
| `config.console` | boolean | always | default `false`. |
| `config.command` | array of string | if set | Runs on a machine of its own, as an argument list. 1 to 64 items. |
| `config.diskIsCache` | boolean | if set |  |
| `config.readiness` | `port` \| `boot` | if set | `boot`: ready when the machine boots. `port`: ready when `internalPort` answers. |
| `config.exposePorts` | array of integer | if set | 0 to 8 items. |
| `config.egress` | object | if set | Outbound network: `allow-all`, `deny-all`, or `allowlist` with `domains` and `cidrs`. |
| `desiredState` | `requested` \| `scheduled` \| `starting` \| `running` \| `suspending` \| `suspended` \| `stopping` \| `stopped` \| `deleted` \| `failed` | always | What the platform wants the machine to be. |
| `observedState` | `requested` \| `scheduled` \| `starting` \| `running` \| `suspending` \| `suspended` \| `stopping` \| `stopped` \| `deleted` \| `failed` | always | What it is right now. |
| `host` | string | if set | Internal; not shown to a customer. |
| `privateIp` | string | if set | The machine's address on the private network. |
| `role` | `primary` \| `standby` | if set | `primary` or `standby`, for a redundant database only. |
| `region` | string | always | Where it runs. Fixed for the machine's lifetime. 2 to 12 characters. |
| `checkpointAt` | string (ISO 8601) | if set | Agent machines: when the last durable checkpoint was taken. |
| `zone` | string | if set | The availability zone, where the host reports one. |
| `diskUsedMb` | integer | if set | How full the disk is, an upper bound. 0 to 9007199254740991. |
| `diskTotalMb` | integer | if set | The disk's size. 0 to 9007199254740991. |
| `diskAt` | string (ISO 8601) | if set | When the disk numbers were reported. |
| `restoreChunksDone` | integer | if set | 0 to 9007199254740991. |
| `restoreChunksTotal` | integer | if set | 0 to 9007199254740991. |
| `restoreAt` | string (ISO 8601) | if set |  |
| `readyAt` | string (ISO 8601) | if set |  |
| `net` | object or null | if set |  |
| `liveness` | `listening` \| `not-listening` \| `no-signal` | if set |  |
| `livenessReason` | string | if set |  |
| `livenessSince` | string (ISO 8601) | if set |  |
| `livenessAt` | string (ISO 8601) | if set |  |
| `createError` | string | if set |  |
| `createErrorCode` | string | if set |  |
| `createBootLog` | string | if set |  |
| `createWarning` | string | if set |  |
| `createdAt` | string (ISO 8601) | always | When it was created. |

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## GET /v1/machines/:id

One machine.

**Auth:** API key with full access or the scope `read`. Every class of agent token may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The machine's id. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/machines/<id>" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with the machine:

```json
{
  "id": "<id>",
  "appName": "my-app",
  "config": {
    "image": "nginx:alpine",
    "vcpus": 1,
    "memoryMb": 512,
    "env": {
      "<key>": "<value>"
    },
    "internalPort": 3000,
    "suspendPolicy": {
      "mode": "suspend",
      "idleSeconds": 300
    },
    "durableCheckpoint": false,
    "console": false
  },
  "desiredState": "requested",
  "observedState": "requested",
  "region": "<region>",
  "createdAt": "2026-10-21T09:00:00Z"
}
```

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | The machine's id. |
| `appName` | string | always | The app it belongs to. |
| `config` | object | always | What it runs: image, size, environment and the settings of a deploy request. |
| `config.image` | string | always | An OCI reference such as `nginx:alpine`, or `build:<id>` for a build of your organization. 1 to 512 characters. |
| `config.class` | `shared-cpu-1x` \| `shared-cpu-2x` \| `shared-cpu-4x` \| `shared-cpu-8x` \| `performance-1x` \| `performance-2x` \| `performance-4x` \| `performance-8x` | if set | A named size instead of `vcpus`; the two must not contradict each other. |
| `config.vcpus` | integer | always | Virtual CPUs. 1 to 16, default `1`. |
| `config.memoryMb` | integer | always | Memory in MB. 128 to 16384, default `512`. |
| `config.env` | object of string | always | Plain environment variables. Use secrets for credentials. |
| `config.diskMb` | integer | if set | The machine's disk in MB. Without it, the image's size plus headroom. 512 to 131072. |
| `config.volumeGb` | integer | if set | A volume of this size, attached to the machine. 1 to 500. |
| `config.start` | string | if set | The start command, run through `/bin/sh -c`. It replaces what detection chose or the image's `CMD`. 1 to 2000 characters. |
| `config.internalPort` | integer | always | The port the app listens on. 1 to 65535, default `3000`. |
| `config.suspendPolicy` | object | always | When the machine sleeps. Without it, a new app never sleeps; a deploy without it keeps the app's rule. |
| `config.healthCheck` | object | if set | An HTTP check a new machine must pass before it gets traffic. |
| `config.restart` | object | if set | What happens when the process ends. Without it, a crashed machine is restarted. |
| `config.killSignal` | `SIGTERM` \| `SIGINT` \| `SIGQUIT` \| `SIGHUP` \| `SIGUSR1` \| `SIGUSR2` \| `SIGWINCH` \| `SIGKILL` | if set | The signal sent before a machine is stopped or replaced. Default: the image's `STOPSIGNAL`, else `SIGTERM`. |
| `config.killTimeoutS` | integer | if set | Seconds between that signal and `SIGKILL`. 1 to 300. |
| `config.durableCheckpoint` | boolean | always | Agent machine: the whole state (memory and disk) is checkpointed and survives a host failure. default `false`. |
| `config.checkpointIntervalS` | integer | if set | 5 to 3600. |
| `config.console` | boolean | always | default `false`. |
| `config.command` | array of string | if set | Runs on a machine of its own, as an argument list. 1 to 64 items. |
| `config.diskIsCache` | boolean | if set |  |
| `config.readiness` | `port` \| `boot` | if set | `boot`: ready when the machine boots. `port`: ready when `internalPort` answers. |
| `config.exposePorts` | array of integer | if set | 0 to 8 items. |
| `config.egress` | object | if set | Outbound network: `allow-all`, `deny-all`, or `allowlist` with `domains` and `cidrs`. |
| `desiredState` | `requested` \| `scheduled` \| `starting` \| `running` \| `suspending` \| `suspended` \| `stopping` \| `stopped` \| `deleted` \| `failed` | always | What the platform wants the machine to be. |
| `observedState` | `requested` \| `scheduled` \| `starting` \| `running` \| `suspending` \| `suspended` \| `stopping` \| `stopped` \| `deleted` \| `failed` | always | What it is right now. |
| `host` | string | if set | Internal; not shown to a customer. |
| `privateIp` | string | if set | The machine's address on the private network. |
| `role` | `primary` \| `standby` | if set | `primary` or `standby`, for a redundant database only. |
| `region` | string | always | Where it runs. Fixed for the machine's lifetime. 2 to 12 characters. |
| `checkpointAt` | string (ISO 8601) | if set | Agent machines: when the last durable checkpoint was taken. |
| `zone` | string | if set | The availability zone, where the host reports one. |
| `diskUsedMb` | integer | if set | How full the disk is, an upper bound. 0 to 9007199254740991. |
| `diskTotalMb` | integer | if set | The disk's size. 0 to 9007199254740991. |
| `diskAt` | string (ISO 8601) | if set | When the disk numbers were reported. |
| `restoreChunksDone` | integer | if set | 0 to 9007199254740991. |
| `restoreChunksTotal` | integer | if set | 0 to 9007199254740991. |
| `restoreAt` | string (ISO 8601) | if set |  |
| `readyAt` | string (ISO 8601) | if set |  |
| `net` | object or null | if set |  |
| `liveness` | `listening` \| `not-listening` \| `no-signal` | if set |  |
| `livenessReason` | string | if set |  |
| `livenessSince` | string (ISO 8601) | if set |  |
| `livenessAt` | string (ISO 8601) | if set |  |
| `createError` | string | if set |  |
| `createErrorCode` | string | if set |  |
| `createBootLog` | string | if set |  |
| `createWarning` | string | if set |  |
| `createdAt` | string (ISO 8601) | always | When it was created. |

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## GET /v1/machines/:id/wait

Holds the request until the machine reaches a state (`?state=`), at most `?timeout=` seconds; `408` when it runs out.

**Auth:** API key with full access or the scope `read`. Every class of agent token may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |

### Query parameters

| Name | Description |
| --- | --- |
| `state` | The state to wait for, for example `running` or `stopped`. |
| `timeout` | Seconds to hold the request, at most 60. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/machines/<id>/wait" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with the machine, once it is in the state:

```json
{
  "id": "<id>",
  "appName": "my-app",
  "config": {
    "image": "nginx:alpine",
    "vcpus": 1,
    "memoryMb": 512,
    "env": {
      "<key>": "<value>"
    },
    "internalPort": 3000,
    "suspendPolicy": {
      "mode": "suspend",
      "idleSeconds": 300
    },
    "durableCheckpoint": false,
    "console": false
  },
  "desiredState": "requested",
  "observedState": "requested",
  "region": "<region>",
  "createdAt": "2026-10-21T09:00:00Z"
}
```

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | The machine's id. |
| `appName` | string | always | The app it belongs to. |
| `config` | object | always | What it runs: image, size, environment and the settings of a deploy request. |
| `config.image` | string | always | An OCI reference such as `nginx:alpine`, or `build:<id>` for a build of your organization. 1 to 512 characters. |
| `config.class` | `shared-cpu-1x` \| `shared-cpu-2x` \| `shared-cpu-4x` \| `shared-cpu-8x` \| `performance-1x` \| `performance-2x` \| `performance-4x` \| `performance-8x` | if set | A named size instead of `vcpus`; the two must not contradict each other. |
| `config.vcpus` | integer | always | Virtual CPUs. 1 to 16, default `1`. |
| `config.memoryMb` | integer | always | Memory in MB. 128 to 16384, default `512`. |
| `config.env` | object of string | always | Plain environment variables. Use secrets for credentials. |
| `config.diskMb` | integer | if set | The machine's disk in MB. Without it, the image's size plus headroom. 512 to 131072. |
| `config.volumeGb` | integer | if set | A volume of this size, attached to the machine. 1 to 500. |
| `config.start` | string | if set | The start command, run through `/bin/sh -c`. It replaces what detection chose or the image's `CMD`. 1 to 2000 characters. |
| `config.internalPort` | integer | always | The port the app listens on. 1 to 65535, default `3000`. |
| `config.suspendPolicy` | object | always | When the machine sleeps. Without it, a new app never sleeps; a deploy without it keeps the app's rule. |
| `config.healthCheck` | object | if set | An HTTP check a new machine must pass before it gets traffic. |
| `config.restart` | object | if set | What happens when the process ends. Without it, a crashed machine is restarted. |
| `config.killSignal` | `SIGTERM` \| `SIGINT` \| `SIGQUIT` \| `SIGHUP` \| `SIGUSR1` \| `SIGUSR2` \| `SIGWINCH` \| `SIGKILL` | if set | The signal sent before a machine is stopped or replaced. Default: the image's `STOPSIGNAL`, else `SIGTERM`. |
| `config.killTimeoutS` | integer | if set | Seconds between that signal and `SIGKILL`. 1 to 300. |
| `config.durableCheckpoint` | boolean | always | Agent machine: the whole state (memory and disk) is checkpointed and survives a host failure. default `false`. |
| `config.checkpointIntervalS` | integer | if set | 5 to 3600. |
| `config.console` | boolean | always | default `false`. |
| `config.command` | array of string | if set | Runs on a machine of its own, as an argument list. 1 to 64 items. |
| `config.diskIsCache` | boolean | if set |  |
| `config.readiness` | `port` \| `boot` | if set | `boot`: ready when the machine boots. `port`: ready when `internalPort` answers. |
| `config.exposePorts` | array of integer | if set | 0 to 8 items. |
| `config.egress` | object | if set | Outbound network: `allow-all`, `deny-all`, or `allowlist` with `domains` and `cidrs`. |
| `desiredState` | `requested` \| `scheduled` \| `starting` \| `running` \| `suspending` \| `suspended` \| `stopping` \| `stopped` \| `deleted` \| `failed` | always | What the platform wants the machine to be. |
| `observedState` | `requested` \| `scheduled` \| `starting` \| `running` \| `suspending` \| `suspended` \| `stopping` \| `stopped` \| `deleted` \| `failed` | always | What it is right now. |
| `host` | string | if set | Internal; not shown to a customer. |
| `privateIp` | string | if set | The machine's address on the private network. |
| `role` | `primary` \| `standby` | if set | `primary` or `standby`, for a redundant database only. |
| `region` | string | always | Where it runs. Fixed for the machine's lifetime. 2 to 12 characters. |
| `checkpointAt` | string (ISO 8601) | if set | Agent machines: when the last durable checkpoint was taken. |
| `zone` | string | if set | The availability zone, where the host reports one. |
| `diskUsedMb` | integer | if set | How full the disk is, an upper bound. 0 to 9007199254740991. |
| `diskTotalMb` | integer | if set | The disk's size. 0 to 9007199254740991. |
| `diskAt` | string (ISO 8601) | if set | When the disk numbers were reported. |
| `restoreChunksDone` | integer | if set | 0 to 9007199254740991. |
| `restoreChunksTotal` | integer | if set | 0 to 9007199254740991. |
| `restoreAt` | string (ISO 8601) | if set |  |
| `readyAt` | string (ISO 8601) | if set |  |
| `net` | object or null | if set |  |
| `liveness` | `listening` \| `not-listening` \| `no-signal` | if set |  |
| `livenessReason` | string | if set |  |
| `livenessSince` | string (ISO 8601) | if set |  |
| `livenessAt` | string (ISO 8601) | if set |  |
| `createError` | string | if set |  |
| `createErrorCode` | string | if set |  |
| `createBootLog` | string | if set |  |
| `createWarning` | string | if set |  |
| `createdAt` | string (ISO 8601) | always | When it was created. |

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad-request` | The request is malformed or a field is out of range. `error` names every field; `issues` lists them. |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |
| 408 | `wait-timeout` | The wait ran out before the machine reached the state; ask again. |
| 410 | `gone` | The object existed and is gone. |

## POST /v1/machines/:id/:action

Acts on a machine: `start`, `stop`, `suspend` or `destroy`.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |
| `action` | `start`, `stop`, `suspend` or `destroy`. |

### Example request

```bash
curl -X POST "https://api.rytiv.app/v1/machines/<id>/<action>" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

### Response

`200` with the machine after the action:

```json
{
  "id": "<id>",
  "appName": "my-app",
  "config": {
    "image": "nginx:alpine",
    "vcpus": 1,
    "memoryMb": 512,
    "env": {
      "<key>": "<value>"
    },
    "internalPort": 3000,
    "suspendPolicy": {
      "mode": "suspend",
      "idleSeconds": 300
    },
    "durableCheckpoint": false,
    "console": false
  },
  "desiredState": "requested",
  "observedState": "requested",
  "region": "<region>",
  "createdAt": "2026-10-21T09:00:00Z"
}
```

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | The machine's id. |
| `appName` | string | always | The app it belongs to. |
| `config` | object | always | What it runs: image, size, environment and the settings of a deploy request. |
| `config.image` | string | always | An OCI reference such as `nginx:alpine`, or `build:<id>` for a build of your organization. 1 to 512 characters. |
| `config.class` | `shared-cpu-1x` \| `shared-cpu-2x` \| `shared-cpu-4x` \| `shared-cpu-8x` \| `performance-1x` \| `performance-2x` \| `performance-4x` \| `performance-8x` | if set | A named size instead of `vcpus`; the two must not contradict each other. |
| `config.vcpus` | integer | always | Virtual CPUs. 1 to 16, default `1`. |
| `config.memoryMb` | integer | always | Memory in MB. 128 to 16384, default `512`. |
| `config.env` | object of string | always | Plain environment variables. Use secrets for credentials. |
| `config.diskMb` | integer | if set | The machine's disk in MB. Without it, the image's size plus headroom. 512 to 131072. |
| `config.volumeGb` | integer | if set | A volume of this size, attached to the machine. 1 to 500. |
| `config.start` | string | if set | The start command, run through `/bin/sh -c`. It replaces what detection chose or the image's `CMD`. 1 to 2000 characters. |
| `config.internalPort` | integer | always | The port the app listens on. 1 to 65535, default `3000`. |
| `config.suspendPolicy` | object | always | When the machine sleeps. Without it, a new app never sleeps; a deploy without it keeps the app's rule. |
| `config.healthCheck` | object | if set | An HTTP check a new machine must pass before it gets traffic. |
| `config.restart` | object | if set | What happens when the process ends. Without it, a crashed machine is restarted. |
| `config.killSignal` | `SIGTERM` \| `SIGINT` \| `SIGQUIT` \| `SIGHUP` \| `SIGUSR1` \| `SIGUSR2` \| `SIGWINCH` \| `SIGKILL` | if set | The signal sent before a machine is stopped or replaced. Default: the image's `STOPSIGNAL`, else `SIGTERM`. |
| `config.killTimeoutS` | integer | if set | Seconds between that signal and `SIGKILL`. 1 to 300. |
| `config.durableCheckpoint` | boolean | always | Agent machine: the whole state (memory and disk) is checkpointed and survives a host failure. default `false`. |
| `config.checkpointIntervalS` | integer | if set | 5 to 3600. |
| `config.console` | boolean | always | default `false`. |
| `config.command` | array of string | if set | Runs on a machine of its own, as an argument list. 1 to 64 items. |
| `config.diskIsCache` | boolean | if set |  |
| `config.readiness` | `port` \| `boot` | if set | `boot`: ready when the machine boots. `port`: ready when `internalPort` answers. |
| `config.exposePorts` | array of integer | if set | 0 to 8 items. |
| `config.egress` | object | if set | Outbound network: `allow-all`, `deny-all`, or `allowlist` with `domains` and `cidrs`. |
| `desiredState` | `requested` \| `scheduled` \| `starting` \| `running` \| `suspending` \| `suspended` \| `stopping` \| `stopped` \| `deleted` \| `failed` | always | What the platform wants the machine to be. |
| `observedState` | `requested` \| `scheduled` \| `starting` \| `running` \| `suspending` \| `suspended` \| `stopping` \| `stopped` \| `deleted` \| `failed` | always | What it is right now. |
| `host` | string | if set | Internal; not shown to a customer. |
| `privateIp` | string | if set | The machine's address on the private network. |
| `role` | `primary` \| `standby` | if set | `primary` or `standby`, for a redundant database only. |
| `region` | string | always | Where it runs. Fixed for the machine's lifetime. 2 to 12 characters. |
| `checkpointAt` | string (ISO 8601) | if set | Agent machines: when the last durable checkpoint was taken. |
| `zone` | string | if set | The availability zone, where the host reports one. |
| `diskUsedMb` | integer | if set | How full the disk is, an upper bound. 0 to 9007199254740991. |
| `diskTotalMb` | integer | if set | The disk's size. 0 to 9007199254740991. |
| `diskAt` | string (ISO 8601) | if set | When the disk numbers were reported. |
| `restoreChunksDone` | integer | if set | 0 to 9007199254740991. |
| `restoreChunksTotal` | integer | if set | 0 to 9007199254740991. |
| `restoreAt` | string (ISO 8601) | if set |  |
| `readyAt` | string (ISO 8601) | if set |  |
| `net` | object or null | if set |  |
| `liveness` | `listening` \| `not-listening` \| `no-signal` | if set |  |
| `livenessReason` | string | if set |  |
| `livenessSince` | string (ISO 8601) | if set |  |
| `livenessAt` | string (ISO 8601) | if set |  |
| `createError` | string | if set |  |
| `createErrorCode` | string | if set |  |
| `createBootLog` | string | if set |  |
| `createWarning` | string | if set |  |
| `createdAt` | string (ISO 8601) | always | When it was created. |

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |
| 409 | `machine-busy` | Another operation on the machine is in progress; try again. |
| 503 | `create-failed` | The host could not create the machine (image, start command or port). The sentence says which. |
| 503 | `guest-exited` | The machine's process ended during the start. |
| 503 | `host-refused` | The host refused the action. |
| 503 | `host-unreachable` | The host did not answer. |
| 503 | `no-capacity` | No host in the region has room for the machine right now; try again. |
| 503 | `port-timeout` | The machine did not answer on its port in time. |
| 503 | `start-interrupted` | The start was interrupted on the host. |

## POST /v1/machines/:id/migrate

Moves the machine to another host.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |

### Example request

```bash
curl -X POST "https://api.rytiv.app/v1/machines/<id>/migrate" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON object and the fields of the object the handler builds.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |
| 409 | `conflict` | The request conflicts with the current state. |
| 409 | `create-failed` | The host could not create the machine (image, start command or port). The sentence says which. |
| 409 | `ephemeral` |  |
| 503 | `no-capacity` | No host in the region has room for the machine right now; try again. |

## GET /v1/apps/:name/logs

The console output of the app's machines, with a cursor per machine for the next call.

**Auth:** API key with full access or the scope `deploy` or `deploy:<app>` for this app, or the scope `read`. Every class of agent token may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `name` | The app's name. |

### Query parameters

| Name | Description |
| --- | --- |
| `machine` | Only this machine. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/apps/<name>/logs" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## POST /v1/machines/:id/checkpoints

Takes a named snapshot of the machine: its disk, and with `memory: true` its running memory too.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |

### Request body

JSON, the shape `CreateCheckpointRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `label` | string | required | A name for the snapshot: letters, digits, `.`, `-` and `_`, starting with a letter or digit. `auto-` is reserved. |
| `comment` | string | optional | A note kept with the snapshot. at most 500 characters. |
| `memory` | boolean | optional | `true` snapshots the running memory as well, so a restore resumes the very processes. default `false`. |

### Example request

```bash
curl -X POST "https://api.rytiv.app/v1/machines/<id>/checkpoints" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"label":"before-migration"}'
```

### Response

`201` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad-request` | The request is malformed or a field is out of range. `error` names every field; `issues` lists them. |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## GET /v1/machines/:id/checkpoints

The machine's named snapshots. Add `?auto=1` to include the automatic ones.

**Auth:** API key with full access or the scope `read`. Every class of agent token may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |

### Query parameters

| Name | Description |
| --- | --- |
| `includeAuto` |  |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/machines/<id>/checkpoints" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## DELETE /v1/machines/:id/checkpoints/:label

Forgets a snapshot.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |
| `label` | The snapshot's label. |

### Example request

```bash
curl -X DELETE "https://api.rytiv.app/v1/machines/<id>/checkpoints/<label>" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`204` with no body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## POST /v1/machines/:id/checkpoints/:label/restore

Brings the machine back to that snapshot. With `keepCurrent: true` (the default) the current state is saved first.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |
| `label` | The snapshot's label. |

### Request body

JSON, the shape `RestoreCheckpointRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `keepCurrent` | boolean | optional | Save the current state as a snapshot before restoring (default `true`). default `true`. |

### Example request

```bash
curl -X POST "https://api.rytiv.app/v1/machines/<id>/checkpoints/<label>/restore" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keepCurrent":true}'
```

### Response

`200` with a JSON object carrying `machineId`, `restored`, `manifest`, `memory`, `savedAs`.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad-request` | The request is malformed or a field is out of range. `error` names every field; `issues` lists them. |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## GET /v1/machines/:id/files

Reads one file of the machine (`?path=`), as raw bytes.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `production` needs `prodDataRead` for it; `sandbox` and `twin` may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/machines/<id>/files" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |
| 502 | `transport` |  |

## PUT /v1/machines/:id/files

Writes one file of the machine (`?path=`); the body is the file's bytes.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |

### Query parameters

| Name | Description |
| --- | --- |
| `mode` | The file mode, default `0644`. |
| `mkdirs` | `1` creates missing directories. |

### Request body

The raw bytes of the file.

### Example request

```bash
curl -X PUT "https://api.rytiv.app/v1/machines/<id>/files" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @hello.txt
```

### Response

`200` with a JSON object carrying `path`, `bytes`, `sha256`.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad-request` | The request is malformed or a field is out of range. `error` names every field; `issues` lists them. |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |
| 413 | `too-large` | The body is larger than the route accepts. |
| 502 | `transport` |  |

## GET /v1/machines/:id/files/list

Lists a directory of the machine (`?path=`).

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `production` needs `prodDataRead` for it; `sandbox` and `twin` may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/machines/<id>/files/list" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON object carrying `path`, `entries`.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |
| 502 | `transport` |  |

## GET /v1/machines/:id/screenshot

A screenshot of the machine's desktop, for a machine that has one.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `production` needs `prodDataRead` for it; `sandbox` and `twin` may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |

### Query parameters

| Name | Description |
| --- | --- |
| `display` |  |

### Request body

JSON, the shape `ExecScreenshot` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `display` | string | optional | default `":0"`. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/machines/<id>/screenshot" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"display":"<display>"}'
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad-request` | The request is malformed or a field is out of range. `error` names every field; `issues` lists them. |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## GET /v1/machines/:id/exec

Opens a shell session over a WebSocket. This is what `ry exec` and `ry ssh` use; it is not a plain JSON call.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused.

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/machines/<id>/exec" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

A JSON body on success.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |
| 426 | `upgrade-required` | The route needs a WebSocket upgrade; use the CLI. |

## GET /v1/machines/:id/exec-sessions

The shell sessions open on the machine.

**Auth:** API key with full access or the scope `read`. Every class of agent token may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |

### Query parameters

| Name | Description |
| --- | --- |
| `limit` | How many entries, at most. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/machines/<id>/exec-sessions" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |
