# Secrets

> Write-only secrets.

Write-only secrets. Reads return names and metadata only.

| Operation | Method and path |
| --- | --- |
| [List secrets (names and metadata only)](#list-secrets-names-and-metadata-only) | `GET /v1/projects/{project}/environments/{environment}/secrets` |
| [Set a secret (write-only)](#set-a-secret-write-only) | `POST /v1/projects/{project}/environments/{environment}/secrets` |
| [Get a secret's metadata](#get-a-secrets-metadata) | `GET /v1/projects/{project}/environments/{environment}/secrets/{secret}` |
| [Unset a secret](#unset-a-secret) | `DELETE /v1/projects/{project}/environments/{environment}/secrets/{secret}` |
| [Rotate a secret to a new value](#rotate-a-secret-to-a-new-value) | `POST /v1/projects/{project}/environments/{environment}/secrets/{secret}/rotate` |

## List secrets (names and metadata only)

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

- **Operation:** `listSecrets`
- **Scopes:** `variables:read`
- **CLI:** `guv secrets list`
- **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. |
| `service` | query | string |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | A page of SecretMetadata objects. | [SecretMetadataList](/docs/api/objects/secret-metadata-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/secrets \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "data": [
    {
      "id": "sec_01jbx2m9k7q4r8t5v6w3y0z1sc",
      "name": "stripe-secret-key",
      "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
      "service_id": null,
      "version": 3,
      "injected_as": null,
      "build": false,
      "description": "Stripe live secret key",
      "display": "••••••• (set 2 min ago by Stewart Webb)",
      "set_by": {
        "type": "user",
        "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
        "display": "Stewart Webb"
      },
      "referenced_by": [
        {
          "service": "web",
          "variable": "STRIPE_SECRET_KEY"
        }
      ],
      "last_used_at": "2026-10-23T14:04:00Z",
      "last_used_by_deployment_id": "dep_01jbx2m9k7q4r8t5v6w3y0z1dp",
      "decrypts_last_7_days": 14,
      "created_at": "2026-10-01T09:00:00Z",
      "updated_at": "2026-10-23T14:00:00Z"
    }
  ],
  "next_cursor": "string",
  "has_more": false
}
```

## Set a secret (write-only)

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

Encrypts immediately with the environment's data key; the value is never stored in plaintext, logged or returned. Setting an existing name creates a new version.

- **Operation:** `setSecret`
- **Scopes:** `secrets:write-only`
- **CLI:** `guv secrets set`
- **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): [SecretSetRequest](/docs/api/objects/secret-set-request/)

| Field | Type | Description |
| --- | --- | --- |
| `name` * | string |  |
| `value` * | string | Never logged, never stored in plaintext, never returned. Write-only: never returned. |
| `service` | string | Service name or id. Omit for a secret shared by the environment. |
| `build` | boolean | Default `false`. |
| `description` | string |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. With `dry_run=true`: a Plan (application/vnd.guv.plan+json) and nothing changes. | [SecretMetadata](/docs/api/objects/secret-metadata/) |

**Errors:** `400`, `401`, `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/secrets \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "string",
  "value": "string",
  "service": "string",
  "build": false,
  "description": "string"
}'
```

**Example response**

```json
{
  "id": "sec_01jbx2m9k7q4r8t5v6w3y0z1sc",
  "name": "stripe-secret-key",
  "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
  "service_id": null,
  "version": 3,
  "injected_as": null,
  "build": false,
  "description": "Stripe live secret key",
  "display": "••••••• (set 2 min ago by Stewart Webb)",
  "set_by": {
    "type": "user",
    "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
    "display": "Stewart Webb"
  },
  "referenced_by": [
    {
      "service": "web",
      "variable": "STRIPE_SECRET_KEY"
    }
  ],
  "last_used_at": "2026-10-23T14:04:00Z",
  "last_used_by_deployment_id": "dep_01jbx2m9k7q4r8t5v6w3y0z1dp",
  "decrypts_last_7_days": 14,
  "created_at": "2026-10-01T09:00:00Z",
  "updated_at": "2026-10-23T14:00:00Z"
}
```

## Get a secret's metadata

```http
GET /v1/projects/{project}/environments/{environment}/secrets/{secret}
```

- **Operation:** `getSecret`
- **Scopes:** `variables: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. |
| `secret` * | path | string | Secret name. |
| `service` | query | string |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. | [SecretMetadata](/docs/api/objects/secret-metadata/) |

**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/secrets/stripe-secret-key \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "id": "sec_01jbx2m9k7q4r8t5v6w3y0z1sc",
  "name": "stripe-secret-key",
  "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
  "service_id": null,
  "version": 3,
  "injected_as": null,
  "build": false,
  "description": "Stripe live secret key",
  "display": "••••••• (set 2 min ago by Stewart Webb)",
  "set_by": {
    "type": "user",
    "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
    "display": "Stewart Webb"
  },
  "referenced_by": [
    {
      "service": "web",
      "variable": "STRIPE_SECRET_KEY"
    }
  ],
  "last_used_at": "2026-10-23T14:04:00Z",
  "last_used_by_deployment_id": "dep_01jbx2m9k7q4r8t5v6w3y0z1dp",
  "decrypts_last_7_days": 14,
  "created_at": "2026-10-01T09:00:00Z",
  "updated_at": "2026-10-23T14:00:00Z"
}
```

## Unset a secret

```http
DELETE /v1/projects/{project}/environments/{environment}/secrets/{secret}
```

Fails with `conflict` while a running deployment references it, unless `?force=true`.

- **Operation:** `unsetSecret`
- **Scopes:** `secrets:write-only`
- **CLI:** `guv secrets unset`
- **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. |
| `secret` * | path | string | Secret name. |
| `service` | query | string |  |
| `force` | query | boolean |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | Dry run. Nothing was changed; this is what would happen. | [Plan](/docs/api/objects/plan/) |
| `204` No Content | Done. No body. | none |

**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/secrets/stripe-secret-key \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
```

## Rotate a secret to a new value

```http
POST /v1/projects/{project}/environments/{environment}/secrets/{secret}/rotate
```

- **Operation:** `rotateSecret`
- **Scopes:** `secrets:write-only`
- **CLI:** `guv secrets rotate`
- **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. |
| `secret` * | path | string | Secret name. |
| `service` | query | string |  |

**Request body** (`application/json`, required): [SecretRotateRequest](/docs/api/objects/secret-rotate-request/)

| Field | Type | Description |
| --- | --- | --- |
| `value` * | string | Write-only: never returned. |
| `redeploy` | boolean | Redeploy services that use the secret once the new version is stored. Default `false`. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. With `dry_run=true`: a Plan (application/vnd.guv.plan+json) and nothing changes. | [SecretMetadata](/docs/api/objects/secret-metadata/) |

**Errors:** `400`, `401`, `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/secrets/stripe-secret-key/rotate \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "value": "string",
  "redeploy": false
}'
```

**Example response**

```json
{
  "id": "sec_01jbx2m9k7q4r8t5v6w3y0z1sc",
  "name": "stripe-secret-key",
  "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
  "service_id": null,
  "version": 3,
  "injected_as": null,
  "build": false,
  "description": "Stripe live secret key",
  "display": "••••••• (set 2 min ago by Stewart Webb)",
  "set_by": {
    "type": "user",
    "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
    "display": "Stewart Webb"
  },
  "referenced_by": [
    {
      "service": "web",
      "variable": "STRIPE_SECRET_KEY"
    }
  ],
  "last_used_at": "2026-10-23T14:04:00Z",
  "last_used_by_deployment_id": "dep_01jbx2m9k7q4r8t5v6w3y0z1dp",
  "decrypts_last_7_days": 14,
  "created_at": "2026-10-01T09:00:00Z",
  "updated_at": "2026-10-23T14:00:00Z"
}
```
