# Volumes

> Persistent disks: create, list, resize, attach to an app, detach, delete.

A volume is a disk that outlives the machine it is attached to. An app with a volume runs exactly one machine; a deploy stops that machine before the new one starts. A computer's home is a volume named `<computer>-home`.

## Endpoints

- [`POST /v1/volumes`](#post-v1volumes): Creates a volume
- [`GET /v1/volumes`](#get-v1volumes): The organization's volumes
- [`PATCH /v1/volumes/:id`](#patch-v1volumesid): Resizes a volume or changes its settings
- [`DELETE /v1/volumes/:id`](#delete-v1volumesid): Deletes a volume that is not attached
- [`DELETE /v1/volumes/:id/attachment`](#delete-v1volumesidattachment): Detaches the volume from its app

## POST /v1/volumes

Creates a volume.

**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 `CreateVolumeRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | required |  |
| `sizeGb` | integer | optional | Size in GB. 1 to 500, default `10`. |
| `mountPath` | string | optional | default `"/data"`. |
| `class` | `standard` \| `durable` | optional | A named size instead of `vcpus`; the two must not contradict each other. default `"standard"`. |
| `flushIntervalS` | integer | optional | 15 to 86400. |

### Example request

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

### Response

`201` with the volume:

```json
{
  "id": "<id>",
  "name": "data",
  "orgId": "<orgId>",
  "sizeGb": 1,
  "mountPath": "<mountPath>",
  "class": "standard",
  "flushIntervalS": 86400,
  "leaseGen": 0,
  "durableGen": 0,
  "createdAt": "2026-10-21T09:00:00Z"
}
```

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | The volume's id. |
| `name` | string | always |  |
| `orgId` | string | always | The organization that owns it. |
| `sizeGb` | integer | always | Its size. 1 to 500. |
| `mountPath` | string | always |  |
| `class` | `standard` \| `durable` | always | A named size instead of `vcpus`; the two must not contradict each other. default `"standard"`. |
| `flushIntervalS` | integer | always | 2 to 86400, default `86400`. |
| `machineId` | string | if set |  |
| `appName` | string | if set |  |
| `leaseHost` | string | if set |  |
| `leaseGen` | integer | always | 0 to 9007199254740991, default `0`. |
| `durableGen` | integer | always | 0 to 9007199254740991, default `0`. |
| `durableAt` | string (ISO 8601) | if set | When the last copy to durable storage completed. |
| `walDurableAt` | string (ISO 8601) | 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. |
| 409 | `name-taken` | The name is used somewhere on Rytiv (an app, a database, a volume or a bucket). |

## GET /v1/volumes

The organization's volumes.

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

### Example request

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

### Response

`200` with a list of volumes, each:

```json
[
  {
    "id": "<id>",
    "name": "data",
    "orgId": "<orgId>",
    "sizeGb": 1,
    "mountPath": "<mountPath>",
    "class": "standard",
    "flushIntervalS": 86400,
    "leaseGen": 0,
    "durableGen": 0,
    "createdAt": "2026-10-21T09:00:00Z"
  }
]
```

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | The volume's id. |
| `name` | string | always |  |
| `orgId` | string | always | The organization that owns it. |
| `sizeGb` | integer | always | Its size. 1 to 500. |
| `mountPath` | string | always |  |
| `class` | `standard` \| `durable` | always | A named size instead of `vcpus`; the two must not contradict each other. default `"standard"`. |
| `flushIntervalS` | integer | always | 2 to 86400, default `86400`. |
| `machineId` | string | if set |  |
| `appName` | string | if set |  |
| `leaseHost` | string | if set |  |
| `leaseGen` | integer | always | 0 to 9007199254740991, default `0`. |
| `durableGen` | integer | always | 0 to 9007199254740991, default `0`. |
| `durableAt` | string (ISO 8601) | if set | When the last copy to durable storage completed. |
| `walDurableAt` | string (ISO 8601) | 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. |

## PATCH /v1/volumes/:id

Resizes a volume or changes its settings.

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

### Request body

JSON, the shape `UpdateVolumeRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `sizeGb` | integer | optional | Size in GB. 1 to 500. |
| `flushIntervalS` | integer | optional | 15 to 86400. |

### Example request

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

### Response

`200` with the volume:

```json
{
  "id": "<id>",
  "name": "data",
  "orgId": "<orgId>",
  "sizeGb": 1,
  "mountPath": "<mountPath>",
  "class": "standard",
  "flushIntervalS": 86400,
  "leaseGen": 0,
  "durableGen": 0,
  "createdAt": "2026-10-21T09:00:00Z"
}
```

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | The volume's id. |
| `name` | string | always |  |
| `orgId` | string | always | The organization that owns it. |
| `sizeGb` | integer | always | Its size. 1 to 500. |
| `mountPath` | string | always |  |
| `class` | `standard` \| `durable` | always | A named size instead of `vcpus`; the two must not contradict each other. default `"standard"`. |
| `flushIntervalS` | integer | always | 2 to 86400, default `86400`. |
| `machineId` | string | if set |  |
| `appName` | string | if set |  |
| `leaseHost` | string | if set |  |
| `leaseGen` | integer | always | 0 to 9007199254740991, default `0`. |
| `durableGen` | integer | always | 0 to 9007199254740991, default `0`. |
| `durableAt` | string (ISO 8601) | if set | When the last copy to durable storage completed. |
| `walDurableAt` | string (ISO 8601) | 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. |

## DELETE /v1/volumes/:id

Deletes a volume that is not attached.

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

### Example request

```bash
curl -X DELETE "https://api.rytiv.app/v1/volumes/<id>" \
  -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. |
| 409 | `volume-attached` |  |

## DELETE /v1/volumes/:id/attachment

Detaches the volume from its app.

**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 DELETE "https://api.rytiv.app/v1/volumes/<id>/attachment" \
  -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. |
