# Secrets

A secret is a named value on a product, such as DATABASE_URL, with its own version history in each environment the product is deployed to. Reads return non-production values only. Production values reach a deployment through a pull with a vault key, or once in the response that generated them.

## The Secret object

A named value on a product with one version history per environment. Production values never appear.

### Attributes

- `id` (string, required): Unique ID of the secret.
- `name` (string, required): Letters, digits and underscores, starting with a letter or underscore, at most 255 characters and unique on the product, such as DATABASE_URL.
- `createdAt` (string, required): When the secret was created, as an ISO 8601 timestamp.
- `product` (object, required): The product the secret belongs to.
  - `id` (string, required): Unique ID of the product.
  - `slug` (string, required): The product's slug, such as storefront.
  - `name` (string, required): The product's display name.
  - `type` (string, one of: `website`, `api`, `cli`, `mobile_app`, `desktop_app`, `agent`, required): What kind of software the product is.
- `environments` (array of objects, required): The active environments the product is deployed to, in the order it was deployed.
  - `id` (string, required): Unique ID of the environment.
  - `slug` (string, required): The environment's slug, such as staging.
  - `name` (string, required): The environment's display name.
  - `class` (string, one of: `production`, `non-production`, required): Production holds real customer data; everything else is non-production.
- `values` (map of objects, required): The current version in each of those environments, keyed by environment slug.
  - `version` (integer, required): The current version number, or 0 when the environment has no version yet.
  - `isSet` (boolean, required): Whether the environment has a value now.
  - `generator` (nullable object): How the value was generated, or null when it was set by hand. Absent while the environment has no version.
    - When `type` is `token`: A random token.
      - `type` (string, required): Random bytes. Always `token`.
      - `bytes` (integer, required): How many random bytes, from 16 to 512.
      - `encoding` (string, one of: `hex`, `base64`, `base64url`, required): How the bytes are written as text.
    - When `type` is `password`: A random password with at least one character from each class it uses.
      - `type` (string, required): A random password. Always `password`.
      - `length` (integer, required): How many characters, from 8 to 256.
      - `lowercase` (boolean, required): Whether it uses a to z.
      - `uppercase` (boolean, required): Whether it uses A to Z.
      - `digits` (boolean, required): Whether it uses 0 to 9.
      - `symbols` (boolean, required): Whether it uses the symbols -_.~+=.
    - When `type` is `uuid`: A random version 4 UUID.
      - `type` (string, required): A random version 4 UUID. Always `uuid`.
    - When `type` is `ed25519`: An Ed25519 key pair. The value is the PKCS#8 PEM private key, and `publicKey` holds the SPKI PEM public key.
      - `type` (string, required): An Ed25519 key pair. Always `ed25519`.
  - `publicKey` (nullable string): The SPKI PEM public key of a generated Ed25519 key pair, or null. Absent while the environment has no version.
  - `restoredFrom` (nullable integer): The earlier version the current version restored, or null. Absent while the environment has no version.
  - `createdAt` (string): When the current version was written, as an ISO 8601 timestamp. Absent while the environment has no version.
  - `actor` (nullable object): Who wrote the current version, or null when they can no longer be found. Absent while the environment has no version.
    - `id` (string, required): Unique ID of the user or account API key.
    - `type` (string, one of: `user`, `accountApiKey`, required): Whether a user or an account API key wrote the version.
    - `name` (string, required): The user's name, or their email when the name is empty, or the key's name.
  - `value` (string): The plaintext value. Present only for a set value in a non-production environment.

### Example

```json
{
  "id": "Sc4VnJ7qTr2X",
  "name": "DATABASE_URL",
  "createdAt": "2026-10-01T16:20:00.000Z",
  "product": {
    "id": "Pr4GtY9sLm3B",
    "slug": "storefront",
    "name": "Storefront",
    "type": "website"
  },
  "environments": [
    {
      "id": "En8RqZ2vKp5D",
      "slug": "staging",
      "name": "Staging",
      "class": "non-production"
    },
    {
      "id": "En1MwX6cTb7F",
      "slug": "production",
      "name": "Production",
      "class": "production"
    }
  ],
  "values": {
    "staging": {
      "version": 3,
      "isSet": true,
      "generator": null,
      "publicKey": null,
      "restoredFrom": null,
      "createdAt": "2026-10-14T09:05:12.000Z",
      "actor": {
        "id": "Us3NfP8kLq2V",
        "type": "user",
        "name": "Maya Patel"
      },
      "value": "postgres://storefront@db.staging.acme.dev:5432/storefront"
    },
    "production": {
      "version": 2,
      "isSet": true,
      "generator": null,
      "publicKey": null,
      "restoredFrom": null,
      "createdAt": "2026-10-01T16:20:00.000Z",
      "actor": {
        "id": "Us3NfP8kLq2V",
        "type": "user",
        "name": "Maya Patel"
      }
    }
  }
}
```

## List every product's secrets

`GET /v1/workspaces/{slug}/vault`

Returns each active product with the environments it is deployed to and each secret's current version there. Non-production values are included; production values never are.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.

### Returns

Returns an array of objects with these fields. The status is 200. Every active product in the workspace.

- `id` (string, required): Unique ID of the product.
- `workspaceId` (string, required): Unique ID of the workspace the product belongs to.
- `name` (string, required): The product's display name.
- `slug` (string, required): The product's slug, such as storefront.
- `type` (string, one of: `website`, `api`, `cli`, `mobile_app`, `desktop_app`, `agent`, required): What kind of software the product is.
- `description` (nullable string, required): A short description, or null.
- `repository` (nullable string, required): The source repository, such as acme/storefront, or null.
- `archivedAt` (nullable string, required): Always null: the vault lists only active products.
- `createdAt` (string, required): When the product was created, as an ISO 8601 timestamp.
- `updatedAt` (string, required): When the product last changed, as an ISO 8601 timestamp.
- `environments` (array of objects, required): The active environments the product is deployed to, in the order it was deployed.
  - `id` (string, required): Unique ID of the environment.
  - `slug` (string, required): The environment's slug, such as staging.
  - `name` (string, required): The environment's display name.
  - `class` (string, one of: `production`, `non-production`, required): Production holds real customer data; everything else is non-production.
- `secrets` (array of objects, required): The product's secrets.
  - `id` (string, required): Unique ID of the secret.
  - `name` (string, required): Letters, digits and underscores, starting with a letter or underscore, at most 255 characters and unique on the product, such as DATABASE_URL.
  - `values` (map of objects, required): The current version in each environment the product is deployed to, keyed by environment slug.
    - `isSet` (boolean, required): Whether the environment has a value now.
    - `version` (integer, required): The current version number, or 0 when the environment has no version yet.
    - `publicKey` (string): The SPKI PEM public key, present when the value is a generated Ed25519 key pair.
    - `value` (string): The plaintext value. Present only for a set value in a non-production environment.

### Request

```bash
curl https://api.qa.clankership.net/v1/workspaces/{slug}/vault \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
[
  {
    "id": "Pr4GtY9sLm3B",
    "workspaceId": "Wk7Qm2RtXa9L",
    "name": "Storefront",
    "slug": "storefront",
    "type": "website",
    "description": "The customer-facing shop.",
    "repository": "acme/storefront",
    "archivedAt": null,
    "createdAt": "2026-10-01T16:20:00.000Z",
    "updatedAt": "2026-10-14T09:05:12.000Z",
    "environments": [
      {
        "id": "En8RqZ2vKp5D",
        "slug": "staging",
        "name": "Staging",
        "class": "non-production"
      },
      {
        "id": "En1MwX6cTb7F",
        "slug": "production",
        "name": "Production",
        "class": "production"
      }
    ],
    "secrets": [
      {
        "id": "Sc4VnJ7qTr2X",
        "name": "DATABASE_URL",
        "values": {
          "staging": {
            "isSet": true,
            "version": 3,
            "value": "postgres://storefront@db.staging.acme.dev:5432/storefront"
          },
          "production": {
            "isSet": true,
            "version": 2
          }
        }
      }
    ]
  }
]
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## List a product's secrets

`GET /v1/workspaces/{slug}/products/{productSlug}/secrets`

Returns the product's secrets with their current version in each environment the product is deployed to. Non-production values are included; production values never are. Fails with NotFound when the product does not exist or is archived.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `productSlug` (string, in path, required): The product's slug, such as storefront.

### Returns

Returns an array of objects with these fields. The status is 200. The product's secrets.

- `id` (string, required): Unique ID of the secret.
- `name` (string, required): Letters, digits and underscores, starting with a letter or underscore, at most 255 characters and unique on the product, such as DATABASE_URL.
- `values` (map of objects, required): The current version in each environment the product is deployed to, keyed by environment slug.
  - `isSet` (boolean, required): Whether the environment has a value now.
  - `version` (integer, required): The current version number, or 0 when the environment has no version yet.
  - `publicKey` (string): The SPKI PEM public key, present when the value is a generated Ed25519 key pair.
  - `value` (string): The plaintext value. Present only for a set value in a non-production environment.

### Request

```bash
curl https://api.qa.clankership.net/v1/workspaces/{slug}/products/{productSlug}/secrets \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
[
  {
    "id": "Sc4VnJ7qTr2X",
    "name": "DATABASE_URL",
    "values": {
      "staging": {
        "isSet": true,
        "version": 3,
        "value": "postgres://storefront@db.staging.acme.dev:5432/storefront"
      },
      "production": {
        "isSet": true,
        "version": 2
      }
    }
  }
]
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Create a secret

`POST /v1/workspaces/{slug}/products/{productSlug}/secrets`

Adds a secret name to the product and sets any `values` sent, returning one result per value. Viewers cannot create secrets, a member's production values come back `skipped`, and a name already on the product fails with Conflict.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `productSlug` (string, in path, required): The product's slug, such as storefront.
- `name` (string, required): Letters, digits and underscores, starting with a letter or underscore, at most 255 characters and unique on the product, such as DATABASE_URL.
- `values` (map of strings): First values keyed by environment slug, each at most 64 KiB. The product must be deployed to each environment. `null` or an empty string records it as unset.
- `requestId` (string): Your own ID for this request, up to 100 characters, stored with each first value. Unlike other secret writes, a retry fails with Conflict once the first attempt has created the name. 1 to 100 characters.

### Returns

Returns an array of objects with these fields. The status is 200. One result per product and environment, sorted by product and then environment slug.

- `product` (string, required): The product's slug, such as storefront.
- `environment` (string, required): The environment's slug, such as staging.
- `status` (string, one of: `written`, `skipped`, required): `written` when the environment holds the value, or `skipped` when the caller's role may not write there, such as a member writing production.
- `version` (integer): The version that holds the value. A retry with the same `requestId` returns the first write's version. Absent when skipped.
- `publicKey` (string): The SPKI PEM public key, present when the value is an Ed25519 key pair.
- `value` (string): The generated value. Present only when a generation sends `reveal: true`, and never on a retry.

### Request

```bash
curl -X POST https://api.qa.clankership.net/v1/workspaces/{slug}/products/{productSlug}/secrets \
  -H "Authorization: Bearer $CLANKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "DATABASE_URL",
    "values": {
      "staging": "postgres://storefront@db.staging.acme.dev:5432/storefront"
    },
    "requestId": "5b0e8f3a-2c41-4d9e-9a7b-61f2c3d4e5a6"
  }'
```

### Response

```json
[
  {
    "product": "storefront",
    "environment": "staging",
    "status": "written",
    "version": 4
  }
]
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Get a secret

`GET /v1/workspaces/{slug}/products/{productSlug}/secrets/{name}`

Returns the secret with its product, the environments the product is deployed to, and the current version in each. The plaintext value appears only for non-production environments. Fails with Conflict while the product is archived.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `productSlug` (string, in path, required): The product's slug, such as storefront.
- `name` (string, in path, required): The secret's name, such as DATABASE_URL.

### Returns

Returns the [Secret](https://docs.qa.clankership.net/api/secrets/object.md) object. The status is 200.

### Request

```bash
curl https://api.qa.clankership.net/v1/workspaces/{slug}/products/{productSlug}/secrets/{name} \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
{
  "id": "Sc4VnJ7qTr2X",
  "name": "DATABASE_URL",
  "createdAt": "2026-10-01T16:20:00.000Z",
  "product": {
    "id": "Pr4GtY9sLm3B",
    "slug": "storefront",
    "name": "Storefront",
    "type": "website"
  },
  "environments": [
    {
      "id": "En8RqZ2vKp5D",
      "slug": "staging",
      "name": "Staging",
      "class": "non-production"
    },
    {
      "id": "En1MwX6cTb7F",
      "slug": "production",
      "name": "Production",
      "class": "production"
    }
  ],
  "values": {
    "staging": {
      "version": 3,
      "isSet": true,
      "generator": null,
      "publicKey": null,
      "restoredFrom": null,
      "createdAt": "2026-10-14T09:05:12.000Z",
      "actor": {
        "id": "Us3NfP8kLq2V",
        "type": "user",
        "name": "Maya Patel"
      },
      "value": "postgres://storefront@db.staging.acme.dev:5432/storefront"
    },
    "production": {
      "version": 2,
      "isSet": true,
      "generator": null,
      "publicKey": null,
      "restoredFrom": null,
      "createdAt": "2026-10-01T16:20:00.000Z",
      "actor": {
        "id": "Us3NfP8kLq2V",
        "type": "user",
        "name": "Maya Patel"
      }
    }
  }
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Update a secret

`PATCH /v1/workspaces/{slug}/products/{productSlug}/secrets/{name}`

Renames the secret, sets or clears values, or both, in one transaction, and returns one result per value sent. Viewers cannot update secrets, and a member's production values come back `skipped`. A taken name, a stale `expectedVersions` entry or a `requestId` already used for a different write fails with Conflict, and nothing is saved.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `productSlug` (string, in path, required): The product's slug, such as storefront.
- `name` (string, in path, required): The secret's name, such as DATABASE_URL.
- `newName` (string): A new name for the secret, unique on the product.
- `values` (map of strings): New values keyed by environment slug, each at most 64 KiB. `null` or an empty string clears a value, and environments left out keep theirs.
- `expectedVersions` (map of integers): The version you last saw in each environment you are changing, keyed by environment slug, with 0 for one that has no version. If any has changed since, nothing is saved and the request fails with Conflict.
- `requestId` (string): Your own ID for this request, up to 100 characters. Sending the same ID again returns the first result instead of writing twice. 1 to 100 characters.

### Returns

Returns an array of objects with these fields. The status is 200. One result per product and environment, sorted by product and then environment slug.

- `product` (string, required): The product's slug, such as storefront.
- `environment` (string, required): The environment's slug, such as staging.
- `status` (string, one of: `written`, `skipped`, required): `written` when the environment holds the value, or `skipped` when the caller's role may not write there, such as a member writing production.
- `version` (integer): The version that holds the value. A retry with the same `requestId` returns the first write's version. Absent when skipped.
- `publicKey` (string): The SPKI PEM public key, present when the value is an Ed25519 key pair.
- `value` (string): The generated value. Present only when a generation sends `reveal: true`, and never on a retry.

### Request

```bash
curl -X PATCH https://api.qa.clankership.net/v1/workspaces/{slug}/products/{productSlug}/secrets/{name} \
  -H "Authorization: Bearer $CLANKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "values": {
      "staging": "postgres://storefront@db-2.staging.acme.dev:5432/storefront"
    },
    "expectedVersions": {
      "staging": 3
    },
    "requestId": "5b0e8f3a-2c41-4d9e-9a7b-61f2c3d4e5a6"
  }'
```

### Response

```json
[
  {
    "product": "storefront",
    "environment": "staging",
    "status": "written",
    "version": 4
  }
]
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Delete a secret

`DELETE /v1/workspaces/{slug}/products/{productSlug}/secrets/{name}`

Deletes the secret and every version of it in every environment. Viewers cannot delete secrets, and only admins and owners can delete one that has a production value.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `productSlug` (string, in path, required): The product's slug, such as storefront.
- `name` (string, in path, required): The secret's name, such as DATABASE_URL.

### Returns

Returns an object with these fields. The status is 200.

- `ok` (boolean, required): Always true: the change was made. Always `true`.

### Request

```bash
curl -X DELETE https://api.qa.clankership.net/v1/workspaces/{slug}/products/{productSlug}/secrets/{name} \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
{
  "ok": true
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Set a secret across products and environments

`POST /v1/workspaces/{slug}/vault/assignments`

Writes one value to every product and environment pair, up to 200 pairs, in one transaction, and creates the secret name on any product that lacks it. Every pair must be a deployment whose product and environment are active. Viewers cannot assign values, and a member's production pairs come back `skipped`.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `products` (array of strings, required): Slugs of the products to write, such as storefront.
- `environments` (array of strings, required): Slugs of the environments to write, such as staging. Each product must be deployed to each of them.
- `name` (string, required): Letters, digits and underscores, starting with a letter or underscore, at most 255 characters and unique on the product, such as DATABASE_URL.
- `value` (nullable string, required): The value to set, at most 64 KiB. `null` or an empty string clears it.
- `requestId` (string): Your own ID for this request, up to 100 characters. Sending the same ID again returns the first result instead of writing twice. 1 to 100 characters.

### Returns

Returns an array of objects with these fields. The status is 200. One result per product and environment, sorted by product and then environment slug.

- `product` (string, required): The product's slug, such as storefront.
- `environment` (string, required): The environment's slug, such as staging.
- `status` (string, one of: `written`, `skipped`, required): `written` when the environment holds the value, or `skipped` when the caller's role may not write there, such as a member writing production.
- `version` (integer): The version that holds the value. A retry with the same `requestId` returns the first write's version. Absent when skipped.
- `publicKey` (string): The SPKI PEM public key, present when the value is an Ed25519 key pair.
- `value` (string): The generated value. Present only when a generation sends `reveal: true`, and never on a retry.

### Request

```bash
curl -X POST https://api.qa.clankership.net/v1/workspaces/{slug}/vault/assignments \
  -H "Authorization: Bearer $CLANKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "products": [
      "storefront"
    ],
    "environments": [
      "staging"
    ],
    "name": "LOG_LEVEL",
    "value": "debug",
    "requestId": "5b0e8f3a-2c41-4d9e-9a7b-61f2c3d4e5a6"
  }'
```

### Response

```json
[
  {
    "product": "storefront",
    "environment": "staging",
    "status": "written",
    "version": 4
  }
]
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Generate a secret value

`POST /v1/workspaces/{slug}/vault/generations`

Generates one value per environment on the server and writes it to every selected product there, so products share a value within an environment and no value crosses environments. Targets, limits and roles work as when setting a secret across products. Values come back only with `reveal: true`, and a retry with the same `requestId` returns the first versions without values.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `products` (array of strings, required): Slugs of the products to write, such as storefront.
- `environments` (array of strings, required): Slugs of the environments to write, such as staging. Each product must be deployed to each of them.
- `name` (string, required): Letters, digits and underscores, starting with a letter or underscore, at most 255 characters and unique on the product, such as DATABASE_URL.
- `generator` (object, required): What to generate. Options left out use their defaults.
  - When `type` is `token`: A random token.
    - `type` (string, required): Random bytes. Always `token`.
    - `bytes` (integer): How many random bytes, from 16 to 512. Defaults to 32. From 16 to 512.
    - `encoding` (string, one of: `hex`, `base64`, `base64url`): How to write the bytes as text: hex (the default), base64 or base64url.
  - When `type` is `password`: A random password with at least one character from each class it uses.
    - `type` (string, required): A random password. Always `password`.
    - `length` (integer): How many characters, from 8 to 256. Defaults to 32. From 8 to 256.
    - `lowercase` (boolean): Whether to use a to z. Defaults to true; leave at least one class on.
    - `uppercase` (boolean): Whether to use A to Z. Defaults to true; leave at least one class on.
    - `digits` (boolean): Whether to use 0 to 9. Defaults to true; leave at least one class on.
    - `symbols` (boolean): Whether to use the symbols -_.~+=, which need no quoting in shells or URLs. Defaults to true; leave at least one class on.
  - When `type` is `uuid`: A random version 4 UUID.
    - `type` (string, required): A random version 4 UUID. Always `uuid`.
  - When `type` is `ed25519`: An Ed25519 key pair.
    - `type` (string, required): An Ed25519 key pair: the value is the PKCS#8 PEM private key and the result's `publicKey` the SPKI PEM public key. Always `ed25519`.
- `reveal` (boolean): Return each generated value in this response. Defaults to false.
- `requestId` (string): Your own ID for this request, up to 100 characters. Sending the same ID again returns the first result instead of writing twice. 1 to 100 characters.

### Returns

Returns an array of objects with these fields. The status is 200. One result per product and environment, sorted by product and then environment slug.

- `product` (string, required): The product's slug, such as storefront.
- `environment` (string, required): The environment's slug, such as staging.
- `status` (string, one of: `written`, `skipped`, required): `written` when the environment holds the value, or `skipped` when the caller's role may not write there, such as a member writing production.
- `version` (integer): The version that holds the value. A retry with the same `requestId` returns the first write's version. Absent when skipped.
- `publicKey` (string): The SPKI PEM public key, present when the value is an Ed25519 key pair.
- `value` (string): The generated value. Present only when a generation sends `reveal: true`, and never on a retry.

### Request

```bash
curl -X POST https://api.qa.clankership.net/v1/workspaces/{slug}/vault/generations \
  -H "Authorization: Bearer $CLANKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "products": [
      "storefront"
    ],
    "environments": [
      "staging",
      "production"
    ],
    "name": "SESSION_SECRET",
    "generator": {
      "type": "token",
      "bytes": 32
    },
    "requestId": "5b0e8f3a-2c41-4d9e-9a7b-61f2c3d4e5a6"
  }'
```

### Response

```json
[
  {
    "product": "storefront",
    "environment": "staging",
    "status": "written",
    "version": 4
  }
]
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## List a secret's versions

`GET /v1/workspaces/{slug}/products/{productSlug}/secrets/{name}/environments/{environmentSlug}/versions`

Returns up to 100 versions of the secret in one environment, newest first, with who wrote each. Send the oldest `version` you have as `before` to get the next page. `values=true` fails with Forbidden for a production environment.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `productSlug` (string, in path, required): The product's slug, such as storefront.
- `name` (string, in path, required): The secret's name, such as DATABASE_URL.
- `environmentSlug` (string, in path, required): The environment's slug, such as staging.
- `values` (string, one of: `true`, `false`, in query): `true` adds each version's plaintext value. Production environments refuse it.
- `before` (string, in query): Return only versions older than this version number, to fetch the next page.

### Returns

Returns an array of objects with these fields. The status is 200. Up to 100 versions, newest first.

- `version` (integer, required): The version number. Each write to an environment adds one, starting at 1.
- `isSet` (boolean, required): False when this version cleared the value.
- `generator` (nullable object, required): How the value was generated, or null when it was set by hand.
  - When `type` is `token`: A random token.
    - `type` (string, required): Random bytes. Always `token`.
    - `bytes` (integer, required): How many random bytes, from 16 to 512.
    - `encoding` (string, one of: `hex`, `base64`, `base64url`, required): How the bytes are written as text.
  - When `type` is `password`: A random password with at least one character from each class it uses.
    - `type` (string, required): A random password. Always `password`.
    - `length` (integer, required): How many characters, from 8 to 256.
    - `lowercase` (boolean, required): Whether it uses a to z.
    - `uppercase` (boolean, required): Whether it uses A to Z.
    - `digits` (boolean, required): Whether it uses 0 to 9.
    - `symbols` (boolean, required): Whether it uses the symbols -_.~+=.
  - When `type` is `uuid`: A random version 4 UUID.
    - `type` (string, required): A random version 4 UUID. Always `uuid`.
  - When `type` is `ed25519`: An Ed25519 key pair. The value is the PKCS#8 PEM private key, and `publicKey` holds the SPKI PEM public key.
    - `type` (string, required): An Ed25519 key pair. Always `ed25519`.
- `publicKey` (nullable string, required): The SPKI PEM public key of a generated Ed25519 key pair, or null. It is not secret, so production versions show it too.
- `restoredFrom` (nullable integer, required): The earlier version this version restored, or null.
- `createdAt` (string, required): When the version was written, as an ISO 8601 timestamp.
- `actor` (nullable object, required): Who wrote the version, or null when they can no longer be found.
  - `id` (string, required): Unique ID of the user or account API key.
  - `type` (string, one of: `user`, `accountApiKey`, required): Whether a user or an account API key wrote the version.
  - `name` (string, required): The user's name, or their email when the name is empty, or the key's name.
- `value` (nullable string): The plaintext value, or null for a version that cleared it. Present only when the request sends `values=true`.

### Request

```bash
curl https://api.qa.clankership.net/v1/workspaces/{slug}/products/{productSlug}/secrets/{name}/environments/{environmentSlug}/versions \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
[
  {
    "version": 3,
    "isSet": true,
    "generator": null,
    "publicKey": null,
    "restoredFrom": null,
    "createdAt": "2026-10-14T09:05:12.000Z",
    "actor": {
      "id": "Us3NfP8kLq2V",
      "type": "user",
      "name": "Maya Patel"
    },
    "value": "postgres://storefront@db.staging.acme.dev:5432/storefront"
  }
]
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Restore a secret version

`POST /v1/workspaces/{slug}/products/{productSlug}/secrets/{name}/environments/{environmentSlug}/restore`

Writes a new version with an earlier version's value, generator and public key, and records which version it restored. Restoring a version that cleared the value clears it again. Viewers cannot restore, and members cannot restore production versions.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `productSlug` (string, in path, required): The product's slug, such as storefront.
- `name` (string, in path, required): The secret's name, such as DATABASE_URL.
- `environmentSlug` (string, in path, required): The environment's slug, such as staging.
- `version` (integer, required): The version to restore. Greater than 0.
- `requestId` (string): Your own ID for this request, up to 100 characters. Sending the same ID again returns the first result instead of writing twice. 1 to 100 characters.

### Returns

Returns an array of objects with these fields. The status is 200. One result per product and environment, sorted by product and then environment slug.

- `product` (string, required): The product's slug, such as storefront.
- `environment` (string, required): The environment's slug, such as staging.
- `status` (string, one of: `written`, `skipped`, required): `written` when the environment holds the value, or `skipped` when the caller's role may not write there, such as a member writing production.
- `version` (integer): The version that holds the value. A retry with the same `requestId` returns the first write's version. Absent when skipped.
- `publicKey` (string): The SPKI PEM public key, present when the value is an Ed25519 key pair.
- `value` (string): The generated value. Present only when a generation sends `reveal: true`, and never on a retry.

### Request

```bash
curl -X POST https://api.qa.clankership.net/v1/workspaces/{slug}/products/{productSlug}/secrets/{name}/environments/{environmentSlug}/restore \
  -H "Authorization: Bearer $CLANKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "version": 2,
    "requestId": "5b0e8f3a-2c41-4d9e-9a7b-61f2c3d4e5a6"
  }'
```

### Response

```json
[
  {
    "product": "storefront",
    "environment": "staging",
    "status": "written",
    "version": 4
  }
]
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Pull an environment's secrets

`GET /v1/workspaces/{slug}/products/{productSlug}/environments/{environmentSlug}/secrets/pull`

Returns the current value of every set secret for one deployment, keyed by secret name; the product and environment must be active. Production pulls need a vault key (`ctvk_…`) scoped to the product and environment, and every attempt is recorded in the vault audit log. Non-production pulls take a user API key, account API key or CLI login for the workspace and refuse vault keys.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `productSlug` (string, in path, required): The product's slug, such as storefront.
- `environmentSlug` (string, in path, required): The environment's slug, such as staging.

### Returns

Returns a map of strings. The status is 200. Each set secret's current value, keyed by secret name. Unset secrets are left out.

### Request

```bash
curl https://api.qa.clankership.net/v1/workspaces/{slug}/products/{productSlug}/environments/{environmentSlug}/secrets/pull \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
{
  "DATABASE_URL": "postgres://storefront@db.staging.acme.dev:5432/storefront",
  "LOG_LEVEL": "debug"
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.
