# Sandboxes

> Short-lived machines with a deadline: create from a template, run commands, read and write files, extend, end.

A sandbox is a machine with a deadline. It boots the image of a template you already have (`app:<name>` or `build:<id>`), runs commands, holds files, and is removed at its deadline together with everything on its disk. Each sandbox has an id like `sbx-1a2b3c4d5e6f` and its own address `https://<id>.rytiv.app`. See [Sandboxes](https://docs.rytiv.com/sandboxes.md) for the full description.

## Endpoints

- [`POST /v1/sandboxes`](#post-v1sandboxes): Creates a sandbox from a template and answers when its machine runs
- [`GET /v1/sandboxes`](#get-v1sandboxes): Running sandboxes, newest first. `?state=all` adds the ones that ended in the last 14 days
- [`GET /v1/sandboxes/:id`](#get-v1sandboxesid): One sandbox
- [`DELETE /v1/sandboxes/:id`](#delete-v1sandboxesid): Ends the sandbox now. Its machine and disk are gone
- [`POST /v1/sandboxes/:id/timeout`](#post-v1sandboxesidtimeout): Sets a new deadline: now plus `ttlSeconds`
- [`POST /v1/sandboxes/:id/exec`](#post-v1sandboxesidexec): Runs one command in the sandbox and answers with its exit code and output
- [`GET /v1/sandboxes/:id/files`](#get-v1sandboxesidfiles): Reads one file (`?path=`), as raw bytes
- [`PUT /v1/sandboxes/:id/files`](#put-v1sandboxesidfiles): Writes one file (`?path=`, optional `mode` and `mkdirs=1`); the body is the file's bytes
- [`GET /v1/sandboxes/:id/files/list`](#get-v1sandboxesidfileslist): Lists a directory (`?path=`)

## POST /v1/sandboxes

Creates a sandbox from a template and answers when its machine runs.

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

### Request body

JSON, the shape `CreateSandboxRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `template` | string | required | `app:<name>` boots the image an app of yours runs now; `build:<id>` a build of yours. |
| `ttlSeconds` | integer | optional | Lifetime from now, in seconds. 10 to 86400, default `300`. |
| `vcpus` | integer | optional | Virtual CPUs. 1 to 16. |
| `memoryMb` | integer | optional | Memory in MB. 128 to 16384. |
| `diskMb` | integer | optional | The machine's disk in MB. Without it, the image's size plus headroom. 512 to 131072. |
| `env` | object of string | optional | Plain environment variables. Use secrets for credentials. |
| `ports` | array of integer | optional | More ports to reach from outside, each at `https://<port>-<id>.rytiv.app`. 0 to 8 items. |
| `internalPort` | integer | optional | The port the app listens on. 1 to 65535. |
| `readiness` | `boot` \| `port` | optional | `boot`: ready when the machine boots. `port`: ready when `internalPort` answers. default `"boot"`. |
| `access` | `signed` \| `public` | optional | `signed`: the sandbox's URLs need the access token from the create answer. `public`: anyone with the URL. default `"signed"`. |
| `idleSeconds` | integer | optional | Seconds without activity before the sandbox sleeps. 10 to 86400. |
| `metadata` | object of string | optional | Key/value pairs for your own bookkeeping. |
| `egress` | object | optional | Outbound network: `allow-all`, `deny-all`, or `allowlist` with `domains` and `cidrs`. |
| `egress.mode` | `allow-all` \| `deny-all` \| `allowlist` | required | `view` to watch, `control` to take keyboard and mouse. |
| `egress.domains` | array of string | optional | 0 to 100 items. |
| `egress.cidrs` | array of string | optional | 0 to 10 items. |

### Example request

```bash
curl -X POST "https://api.rytiv.app/v1/sandboxes" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"template":"app:my-app"}'
```

### Response

`201` with the sandbox:

```json
{
  "id": "<id>",
  "template": "app:my-app",
  "state": "starting",
  "createdAt": "2026-10-21T09:00:00Z",
  "expiresAt": "2026-10-21T09:00:00Z",
  "endedAt": "2026-10-21T09:00:00Z",
  "endReason": "expired",
  "machineId": "<machineId>",
  "vcpus": -9007199254740991,
  "memoryMb": -9007199254740991,
  "readiness": "boot",
  "access": "signed",
  "url": "<url>",
  "ports": [
    {
      "port": -9007199254740991,
      "url": "<url>"
    }
  ],
  "metadata": {
    "<key>": "<value>"
  },
  "egress": {
    "mode": "allow-all",
    "domains": [
      "www.example.com"
    ],
    "cidrs": [
      "<cidr>"
    ]
  },
  "createMs": -9007199254740991
}
```

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always |  |
| `template` | string | always | `app:<name>` boots the image an app of yours runs now; `build:<id>` a build of yours. |
| `state` | `starting` \| `running` \| `sleeping` \| `ended` | always |  |
| `createdAt` | string (ISO 8601) | always | When it was created. |
| `expiresAt` | string (ISO 8601) | always | When the copy is deleted (a moment). Not together with `ttlSeconds`. |
| `endedAt` | string (ISO 8601) or null | always |  |
| `endReason` | `expired` \| `killed` \| `failed` \| `app-deleted` or null | always |  |
| `machineId` | string or null | always |  |
| `vcpus` | integer | always | Virtual CPUs. -9007199254740991 to 9007199254740991. |
| `memoryMb` | integer | always | Memory in MB. -9007199254740991 to 9007199254740991. |
| `readiness` | `boot` \| `port` | always | `boot`: ready when the machine boots. `port`: ready when `internalPort` answers. |
| `access` | `signed` \| `public` | always | `signed`: the sandbox's URLs need the access token from the create answer. `public`: anyone with the URL. |
| `url` | string | always |  |
| `ports` | array of object | always | More ports to reach from outside, each at `https://<port>-<id>.rytiv.app`. |
| `metadata` | object of string | always | Key/value pairs for your own bookkeeping. |
| `egress` | object | always | Outbound network: `allow-all`, `deny-all`, or `allowlist` with `domains` and `cidrs`. |
| `egress.mode` | `allow-all` \| `deny-all` \| `allowlist` | always | `view` to watch, `control` to take keyboard and mouse. |
| `egress.domains` | array of string | always |  |
| `egress.cidrs` | array of string | always |  |
| `createMs` | integer | always | -9007199254740991 to 9007199254740991. |
| `accessToken` | object | if set | With `access: "signed"`: the token the sandbox's URLs need, the header to send it in, and its expiry. |
| `accessToken.token` | string | always |  |
| `accessToken.expiresAt` | string (ISO 8601) | always | When the copy is deleted (a moment). Not together with `ttlSeconds`. |
| `accessToken.header` | string | always |  |

### 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 | `template-not-found` | No such app or build in your organization. |
| 409 | `conflict` | The request conflicts with the current state. |
| 409 | `template-not-runnable` | The build is a static site, or the app runs no machine. |
| 429 | `too-many-sandboxes` | The organization's sandbox limit is reached. |
| 503 | `no-capacity` | No host in the region has room for the machine right now; try again. |

## GET /v1/sandboxes

Running sandboxes, newest first. `?state=all` adds the ones that ended in the last 14 days.

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

### Query parameters

| Name | Description |
| --- | --- |
| `state` | `all` adds the sandboxes that ended in the last 14 days. |

### Example request

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

### Response

`200` with a list of sandboxes, each:

```json
[
  {
    "id": "<id>",
    "template": "app:my-app",
    "state": "starting",
    "createdAt": "2026-10-21T09:00:00Z",
    "expiresAt": "2026-10-21T09:00:00Z",
    "endedAt": "2026-10-21T09:00:00Z",
    "endReason": "expired",
    "machineId": "<machineId>",
    "vcpus": -9007199254740991,
    "memoryMb": -9007199254740991,
    "readiness": "boot",
    "access": "signed",
    "url": "<url>",
    "ports": [
      {
        "port": -9007199254740991,
        "url": "<url>"
      }
    ],
    "metadata": {
      "<key>": "<value>"
    },
    "egress": {
      "mode": "allow-all",
      "domains": [
        "www.example.com"
      ],
      "cidrs": [
        "<cidr>"
      ]
    }
  }
]
```

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | `sbx-` and 12 hex digits. |
| `template` | string | always | `app:<name>` boots the image an app of yours runs now; `build:<id>` a build of yours. |
| `state` | `starting` \| `running` \| `sleeping` \| `ended` | always | `starting`, `running`, `sleeping` or `ended`. |
| `createdAt` | string (ISO 8601) | always | When it was created. |
| `expiresAt` | string (ISO 8601) | always | The deadline. |
| `endedAt` | string (ISO 8601) or null | always | When it ended. |
| `endReason` | `expired` \| `killed` \| `failed` \| `app-deleted` or null | always | `expired`, `killed`, `failed` or `app-deleted`. |
| `machineId` | string or null | always | The machine behind it. |
| `vcpus` | integer | always | Virtual CPUs. -9007199254740991 to 9007199254740991. |
| `memoryMb` | integer | always | Memory in MB. -9007199254740991 to 9007199254740991. |
| `readiness` | `boot` \| `port` | always | `boot`: ready when the machine boots. `port`: ready when `internalPort` answers. |
| `access` | `signed` \| `public` | always | `signed`: the sandbox's URLs need the access token from the create answer. `public`: anyone with the URL. |
| `url` | string | always | `https://<id>.rytiv.app`. |
| `ports` | array of object | always | More ports to reach from outside, each at `https://<port>-<id>.rytiv.app`. |
| `metadata` | object of string | always | Key/value pairs for your own bookkeeping. |
| `egress` | object | always | Outbound network: `allow-all`, `deny-all`, or `allowlist` with `domains` and `cidrs`. |
| `egress.mode` | `allow-all` \| `deny-all` \| `allowlist` | always | `view` to watch, `control` to take keyboard and mouse. |
| `egress.domains` | array of string | always |  |
| `egress.cidrs` | array of string | always |  |

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

## GET /v1/sandboxes/:id

One sandbox.

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

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The sandbox's id (`sbx-...`). |

### Example request

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

### Response

`200` with the sandbox:

```json
{
  "id": "<id>",
  "template": "app:my-app",
  "state": "starting",
  "createdAt": "2026-10-21T09:00:00Z",
  "expiresAt": "2026-10-21T09:00:00Z",
  "endedAt": "2026-10-21T09:00:00Z",
  "endReason": "expired",
  "machineId": "<machineId>",
  "vcpus": -9007199254740991,
  "memoryMb": -9007199254740991,
  "readiness": "boot",
  "access": "signed",
  "url": "<url>",
  "ports": [
    {
      "port": -9007199254740991,
      "url": "<url>"
    }
  ],
  "metadata": {
    "<key>": "<value>"
  },
  "egress": {
    "mode": "allow-all",
    "domains": [
      "www.example.com"
    ],
    "cidrs": [
      "<cidr>"
    ]
  }
}
```

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | `sbx-` and 12 hex digits. |
| `template` | string | always | `app:<name>` boots the image an app of yours runs now; `build:<id>` a build of yours. |
| `state` | `starting` \| `running` \| `sleeping` \| `ended` | always | `starting`, `running`, `sleeping` or `ended`. |
| `createdAt` | string (ISO 8601) | always | When it was created. |
| `expiresAt` | string (ISO 8601) | always | The deadline. |
| `endedAt` | string (ISO 8601) or null | always | When it ended. |
| `endReason` | `expired` \| `killed` \| `failed` \| `app-deleted` or null | always | `expired`, `killed`, `failed` or `app-deleted`. |
| `machineId` | string or null | always | The machine behind it. |
| `vcpus` | integer | always | Virtual CPUs. -9007199254740991 to 9007199254740991. |
| `memoryMb` | integer | always | Memory in MB. -9007199254740991 to 9007199254740991. |
| `readiness` | `boot` \| `port` | always | `boot`: ready when the machine boots. `port`: ready when `internalPort` answers. |
| `access` | `signed` \| `public` | always | `signed`: the sandbox's URLs need the access token from the create answer. `public`: anyone with the URL. |
| `url` | string | always | `https://<id>.rytiv.app`. |
| `ports` | array of object | always | More ports to reach from outside, each at `https://<port>-<id>.rytiv.app`. |
| `metadata` | object of string | always | Key/value pairs for your own bookkeeping. |
| `egress` | object | always | Outbound network: `allow-all`, `deny-all`, or `allowlist` with `domains` and `cidrs`. |
| `egress.mode` | `allow-all` \| `deny-all` \| `allowlist` | always | `view` to watch, `control` to take keyboard and mouse. |
| `egress.domains` | array of string | always |  |
| `egress.cidrs` | array of string | always |  |

### 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. |
| 404 | `sandbox-not-found` | No such sandbox in your organization. |

## DELETE /v1/sandboxes/:id

Ends the sandbox now. Its machine and disk are gone.

**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 sandbox's id (`sbx-...`). |

### Example request

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

### Response

`202` with a JSON object carrying `id`, `state`.

### 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. |
| 404 | `sandbox-not-found` | No such sandbox in your organization. |
| 409 | `sandbox-ended` | The sandbox has ended. |

## POST /v1/sandboxes/:id/timeout

Sets a new deadline: now plus `ttlSeconds`.

**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 sandbox's id. |

### Request body

JSON, the shape `SetSandboxTimeoutRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ttlSeconds` | integer | required | Lifetime from now, in seconds. 10 to 86400. |

### Example request

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

### Response

`200` with the sandbox with its new deadline:

```json
{
  "id": "<id>",
  "template": "app:my-app",
  "state": "starting",
  "createdAt": "2026-10-21T09:00:00Z",
  "expiresAt": "2026-10-21T09:00:00Z",
  "endedAt": "2026-10-21T09:00:00Z",
  "endReason": "expired",
  "machineId": "<machineId>",
  "vcpus": -9007199254740991,
  "memoryMb": -9007199254740991,
  "readiness": "boot",
  "access": "signed",
  "url": "<url>",
  "ports": [
    {
      "port": -9007199254740991,
      "url": "<url>"
    }
  ],
  "metadata": {
    "<key>": "<value>"
  },
  "egress": {
    "mode": "allow-all",
    "domains": [
      "www.example.com"
    ],
    "cidrs": [
      "<cidr>"
    ]
  }
}
```

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | `sbx-` and 12 hex digits. |
| `template` | string | always | `app:<name>` boots the image an app of yours runs now; `build:<id>` a build of yours. |
| `state` | `starting` \| `running` \| `sleeping` \| `ended` | always | `starting`, `running`, `sleeping` or `ended`. |
| `createdAt` | string (ISO 8601) | always | When it was created. |
| `expiresAt` | string (ISO 8601) | always | The deadline. |
| `endedAt` | string (ISO 8601) or null | always | When it ended. |
| `endReason` | `expired` \| `killed` \| `failed` \| `app-deleted` or null | always | `expired`, `killed`, `failed` or `app-deleted`. |
| `machineId` | string or null | always | The machine behind it. |
| `vcpus` | integer | always | Virtual CPUs. -9007199254740991 to 9007199254740991. |
| `memoryMb` | integer | always | Memory in MB. -9007199254740991 to 9007199254740991. |
| `readiness` | `boot` \| `port` | always | `boot`: ready when the machine boots. `port`: ready when `internalPort` answers. |
| `access` | `signed` \| `public` | always | `signed`: the sandbox's URLs need the access token from the create answer. `public`: anyone with the URL. |
| `url` | string | always | `https://<id>.rytiv.app`. |
| `ports` | array of object | always | More ports to reach from outside, each at `https://<port>-<id>.rytiv.app`. |
| `metadata` | object of string | always | Key/value pairs for your own bookkeeping. |
| `egress` | object | always | Outbound network: `allow-all`, `deny-all`, or `allowlist` with `domains` and `cidrs`. |
| `egress.mode` | `allow-all` \| `deny-all` \| `allowlist` | always | `view` to watch, `control` to take keyboard and mouse. |
| `egress.domains` | array of string | always |  |
| `egress.cidrs` | array of string | always |  |

### 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. |
| 404 | `sandbox-not-found` | No such sandbox in your organization. |
| 409 | `sandbox-ended` | The sandbox has ended. |

## POST /v1/sandboxes/:id/exec

Runs one command in the sandbox and answers with its exit code and output.

**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 sandbox's id. |

### Request body

JSON, the shape `SandboxExecRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `cmd` | string or array of string | required | A string runs through `/bin/sh -c`; an array is the argument list, no shell. |
| `cwd` | string | optional | The working directory, an absolute path. at most 4096 characters. |
| `env` | object of string | optional | Plain environment variables. Use secrets for credentials. |
| `timeoutMs` | integer | optional | How long the command may run. 1000 to 600000, default `60000`. |
| `stdin` | string | optional | Text handed to the command's standard input. at most 1048576 characters. |

### Example request

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

### Response

`200` with the result:

```json
{
  "exitCode": -9007199254740991,
  "signal": "<signal>",
  "timedOut": true,
  "durationMs": -9007199254740991,
  "stdout": "<stdout>",
  "stderr": "<stderr>",
  "stdoutTruncated": true,
  "stderrTruncated": true
}
```

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `exitCode` | integer or null | always | The command's exit code. |
| `signal` | string or null | always | The signal that ended it, if one did. |
| `timedOut` | boolean | always | Whether `timeoutMs` ended it. |
| `durationMs` | integer | always | How long it ran. -9007199254740991 to 9007199254740991. |
| `stdout` | string | always |  |
| `stderr` | string | always |  |
| `stdoutTruncated` | boolean | always | Whether `stdout` was cut at 1 MiB. |
| `stderrTruncated` | boolean | always | Whether `stderr` was cut at 1 MiB. |

### 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. |
| 404 | `sandbox-not-found` | No such sandbox in your organization. |
| 409 | `sandbox-ended` | The sandbox has ended. |
| 409 | `sandbox-starting` | The sandbox is not running yet. |
| 503 | `audit-unavailable` | The audit record of the session could not be written, so it was not opened. |

## GET /v1/sandboxes/:id/files

Reads one file (`?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 sandbox's id. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/sandboxes/<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. |
| 404 | `sandbox-not-found` | No such sandbox in your organization. |
| 409 | `sandbox-ended` | The sandbox has ended. |
| 409 | `sandbox-starting` | The sandbox is not running yet. |
| 502 | `transport` |  |
| 503 | `audit-unavailable` | The audit record of the session could not be written, so it was not opened. |

## PUT /v1/sandboxes/:id/files

Writes one file (`?path=`, optional `mode` and `mkdirs=1`); 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 sandbox'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/sandboxes/<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. |
| 404 | `sandbox-not-found` | No such sandbox in your organization. |
| 409 | `sandbox-ended` | The sandbox has ended. |
| 409 | `sandbox-starting` | The sandbox is not running yet. |
| 413 | `too-large` | The body is larger than the route accepts. |
| 502 | `transport` |  |
| 503 | `audit-unavailable` | The audit record of the session could not be written, so it was not opened. |

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

Lists a directory (`?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 sandbox's id. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/sandboxes/<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. |
| 404 | `sandbox-not-found` | No such sandbox in your organization. |
| 409 | `sandbox-ended` | The sandbox has ended. |
| 409 | `sandbox-starting` | The sandbox is not running yet. |
| 502 | `transport` |  |
| 503 | `audit-unavailable` | The audit record of the session could not be written, so it was not opened. |
