# Volumes

> Persistent volumes on NVMe, optionally replicated (HA).

Persistent volumes on NVMe, optionally replicated (HA).

| Operation | Method and path |
| --- | --- |
| [List volumes](#list-volumes) | `GET /v1/projects/{project}/environments/{environment}/volumes` |
| [Create a volume](#create-a-volume) | `POST /v1/projects/{project}/environments/{environment}/volumes` |
| [Get a volume](#get-a-volume) | `GET /v1/projects/{project}/environments/{environment}/volumes/{volume}` |
| [Grow, change HA, or attach to a service](#grow-change-ha-or-attach-to-a-service) | `PATCH /v1/projects/{project}/environments/{environment}/volumes/{volume}` |
| [Delete a volume (7-day soft delete)](#delete-a-volume-7-day-soft-delete) | `DELETE /v1/projects/{project}/environments/{environment}/volumes/{volume}` |

## List volumes

```http
GET /v1/projects/{project}/environments/{environment}/volumes
```

- **Operation:** `listVolumes`
- **Scopes:** `projects:read`
- **Paginated:** `?limit=` and `?cursor=`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |
| `environment` * | path | string | Environment id (`env_…`) or name. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | A page of Volume objects. | [VolumeList](/docs/api/objects/volume-list/) |

**Errors:** `400`, `401`, `403`, `404`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl https://api.guv.dev/v1/projects/shopfront/environments/production/volumes \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "data": [
    {
      "id": "vol_01jbx2m9k7q4r8t5v6w3y0z1vl",
      "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
      "name": "rota-cache",
      "service_id": "svc_01jbx2m9k7q4r8t5v6w3y0z1rw",
      "mount_path": "/data",
      "size_gb": 5,
      "used_bytes": 734003200,
      "used_percent": 13.7,
      "ha": false,
      "status": "attached",
      "resilience_note": "This volume is on one server. Turn on HA for automatic failover.",
      "backups": {
        "last_snapshot_at": "2026-10-23T14:00:00Z",
        "last_offsite_at": "2026-10-23T03:00:00Z"
      },
      "managed_by_manifest": true,
      "created_at": "2026-10-22T09:30:00Z"
    }
  ],
  "next_cursor": "string",
  "has_more": false
}
```

## Create a volume

```http
POST /v1/projects/{project}/environments/{environment}/volumes
```

- **Operation:** `createVolume`
- **Scopes:** `projects:write`
- **Approval:** In environments with protection turned on (usually production), changes by agents and by roles outside `approver_roles` return `approval_required`.
- **Idempotency-Key:** required; supports `?dry_run=true`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |
| `environment` * | path | string | Environment id (`env_…`) or name. |

**Request body** (`application/json`, required): [VolumeCreate](/docs/api/objects/volume-create/)

| Field | Type | Description |
| --- | --- | --- |
| `name` * | string |  |
| `size_gb` * | integer |  |
| `ha` | boolean | Default `false`. |
| `service` | string |  |
| `mount_path` | string |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | Dry run. Nothing was changed; this is what would happen. | [Plan](/docs/api/objects/plan/) |
| `202` Accepted | Accepted. The work continues in the background; follow the operation. | [Operation](/docs/api/objects/operation/) |

**Errors:** `400`, `401`, `402`, `403`, `404`, `409`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X POST https://api.guv.dev/v1/projects/shopfront/environments/production/volumes \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "string",
  "size_gb": 1,
  "ha": false,
  "service": "string",
  "mount_path": "string"
}'
```

**Example response**

```json
{
  "id": "op_01jbx2m9k7q4r8t5v6w3y0z1op",
  "kind": "deployment.create",
  "status": "running",
  "summary": "Deploying web to production",
  "workspace_id": "ws_01jbx2m9k7q4r8t5v6w3y0z1ws",
  "project_id": "prj_01jbx2m9k7q4r8t5v6w3y0z1pr",
  "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
  "resource": {
    "type": "deployment",
    "id": "dep_01jbx2m9k7q4r8t5v6w3y0z1dp",
    "url": "/v1/deployments/dep_01jbx2m9k7q4r8t5v6w3y0z1dp"
  },
  "progress": {
    "current_step": "build",
    "completed_steps": 1,
    "total_steps": 6,
    "percent": 20
  },
  "steps": [
    {
      "name": "resolve",
      "title": "Reading your settings",
      "status": "succeeded",
      "attempt": 1,
      "started_at": "2026-10-23T14:02:00Z",
      "finished_at": "2026-10-23T14:02:01Z"
    },
    {
      "name": "build",
      "title": "Building your app",
      "status": "running",
      "attempt": 1,
      "started_at": "2026-10-23T14:02:01Z",
      "finished_at": null
    }
  ],
  "created_by": {
    "type": "user",
    "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
    "display": "Stewart Webb"
  },
  "created_at": "2026-10-23T14:02:00Z",
  "links": {
    "self": "/v1/operations/op_01jbx2m9k7q4r8t5v6w3y0z1op",
    "events": "/v1/operations/op_01jbx2m9k7q4r8t5v6w3y0z1op/events",
    "resource": "/v1/deployments/dep_01jbx2m9k7q4r8t5v6w3y0z1dp"
  }
}
```

## Get a volume

```http
GET /v1/projects/{project}/environments/{environment}/volumes/{volume}
```

- **Operation:** `getVolume`
- **Scopes:** `projects:read`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |
| `environment` * | path | string | Environment id (`env_…`) or name. |
| `volume` * | path | string | Volume id (`vol_…`) or name. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. | [Volume](/docs/api/objects/volume/) |

**Errors:** `400`, `401`, `403`, `404`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl https://api.guv.dev/v1/projects/shopfront/environments/production/volumes/uploads \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "id": "vol_01jbx2m9k7q4r8t5v6w3y0z1vl",
  "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
  "name": "rota-cache",
  "service_id": "svc_01jbx2m9k7q4r8t5v6w3y0z1rw",
  "mount_path": "/data",
  "size_gb": 5,
  "used_bytes": 734003200,
  "used_percent": 13.7,
  "ha": false,
  "status": "attached",
  "resilience_note": "This volume is on one server. Turn on HA for automatic failover.",
  "backups": {
    "last_snapshot_at": "2026-10-23T14:00:00Z",
    "last_offsite_at": "2026-10-23T03:00:00Z"
  },
  "managed_by_manifest": true,
  "created_at": "2026-10-22T09:30:00Z"
}
```

## Grow, change HA, or attach to a service

```http
PATCH /v1/projects/{project}/environments/{environment}/volumes/{volume}
```

- **Operation:** `updateVolume`
- **Scopes:** `projects:write`
- **Approval:** In environments with protection turned on (usually production), changes by agents and by roles outside `approver_roles` return `approval_required`.
- **Idempotency-Key:** required; supports `?dry_run=true`
- **Concurrency:** send the `ETag` you read as `If-Match`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |
| `environment` * | path | string | Environment id (`env_…`) or name. |
| `volume` * | path | string | Volume id (`vol_…`) or name. |

**Request body** (`application/json`, required): [VolumeUpdate](/docs/api/objects/volume-update/)

| Field | Type | Description |
| --- | --- | --- |
| `size_gb` | integer | Grow only. |
| `ha` | boolean |  |
| `service` | string or null |  |
| `mount_path` | string or null |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | Dry run. Nothing was changed; this is what would happen. | [Plan](/docs/api/objects/plan/) |
| `202` Accepted | Accepted. The work continues in the background; follow the operation. | [Operation](/docs/api/objects/operation/) |

**Errors:** `400`, `401`, `402`, `403`, `404`, `409`, `412`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X PATCH https://api.guv.dev/v1/projects/shopfront/environments/production/volumes/uploads \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "size_gb": 1,
  "ha": false,
  "service": "string",
  "mount_path": "string"
}'
```

**Example response**

```json
{
  "id": "op_01jbx2m9k7q4r8t5v6w3y0z1op",
  "kind": "deployment.create",
  "status": "running",
  "summary": "Deploying web to production",
  "workspace_id": "ws_01jbx2m9k7q4r8t5v6w3y0z1ws",
  "project_id": "prj_01jbx2m9k7q4r8t5v6w3y0z1pr",
  "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
  "resource": {
    "type": "deployment",
    "id": "dep_01jbx2m9k7q4r8t5v6w3y0z1dp",
    "url": "/v1/deployments/dep_01jbx2m9k7q4r8t5v6w3y0z1dp"
  },
  "progress": {
    "current_step": "build",
    "completed_steps": 1,
    "total_steps": 6,
    "percent": 20
  },
  "steps": [
    {
      "name": "resolve",
      "title": "Reading your settings",
      "status": "succeeded",
      "attempt": 1,
      "started_at": "2026-10-23T14:02:00Z",
      "finished_at": "2026-10-23T14:02:01Z"
    },
    {
      "name": "build",
      "title": "Building your app",
      "status": "running",
      "attempt": 1,
      "started_at": "2026-10-23T14:02:01Z",
      "finished_at": null
    }
  ],
  "created_by": {
    "type": "user",
    "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
    "display": "Stewart Webb"
  },
  "created_at": "2026-10-23T14:02:00Z",
  "links": {
    "self": "/v1/operations/op_01jbx2m9k7q4r8t5v6w3y0z1op",
    "events": "/v1/operations/op_01jbx2m9k7q4r8t5v6w3y0z1op/events",
    "resource": "/v1/deployments/dep_01jbx2m9k7q4r8t5v6w3y0z1dp"
  }
}
```

## Delete a volume (7-day soft delete)

```http
DELETE /v1/projects/{project}/environments/{environment}/volumes/{volume}
```

- **Operation:** `deleteVolume`
- **Scopes:** `projects:delete`
- **Approval:** Always needs a human approval with a passkey, whoever asks, including agents with full scopes.
- **Idempotency-Key:** required; supports `?dry_run=true`
- **Concurrency:** send the `ETag` you read as `If-Match`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |
| `environment` * | path | string | Environment id (`env_…`) or name. |
| `volume` * | path | string | Volume id (`vol_…`) or name. |
| `confirm` | query | string | Typed confirmation for destructive actions. Must equal the resource name (or slug/hostname). Missing or wrong values return `confirmation_required`. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | Dry run. Nothing was changed; this is what would happen. | [Plan](/docs/api/objects/plan/) |
| `202` Accepted | Accepted. The work continues in the background; follow the operation. | [Operation](/docs/api/objects/operation/) |

**Errors:** `400`, `401`, `403`, `404`, `409`, `412`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X DELETE https://api.guv.dev/v1/projects/shopfront/environments/production/volumes/uploads \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
```

**Example response**

```json
{
  "id": "op_01jbx2m9k7q4r8t5v6w3y0z1op",
  "kind": "deployment.create",
  "status": "running",
  "summary": "Deploying web to production",
  "workspace_id": "ws_01jbx2m9k7q4r8t5v6w3y0z1ws",
  "project_id": "prj_01jbx2m9k7q4r8t5v6w3y0z1pr",
  "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
  "resource": {
    "type": "deployment",
    "id": "dep_01jbx2m9k7q4r8t5v6w3y0z1dp",
    "url": "/v1/deployments/dep_01jbx2m9k7q4r8t5v6w3y0z1dp"
  },
  "progress": {
    "current_step": "build",
    "completed_steps": 1,
    "total_steps": 6,
    "percent": 20
  },
  "steps": [
    {
      "name": "resolve",
      "title": "Reading your settings",
      "status": "succeeded",
      "attempt": 1,
      "started_at": "2026-10-23T14:02:00Z",
      "finished_at": "2026-10-23T14:02:01Z"
    },
    {
      "name": "build",
      "title": "Building your app",
      "status": "running",
      "attempt": 1,
      "started_at": "2026-10-23T14:02:01Z",
      "finished_at": null
    }
  ],
  "created_by": {
    "type": "user",
    "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
    "display": "Stewart Webb"
  },
  "created_at": "2026-10-23T14:02:00Z",
  "links": {
    "self": "/v1/operations/op_01jbx2m9k7q4r8t5v6w3y0z1op",
    "events": "/v1/operations/op_01jbx2m9k7q4r8t5v6w3y0z1op/events",
    "resource": "/v1/deployments/dep_01jbx2m9k7q4r8t5v6w3y0z1dp"
  }
}
```
