---
title: Secret Policies
path: reference/secret-policies
status: published
---

# Secret Policies Reference

Endpoint reference for **value-generation policies** — recipes ScaiVault uses to mint new secret values during rotation, signup, or on-demand preview. (Not to be confused with [access policies](./policies), which control *who* can read secrets.)

**Base path:** `/v1/secret-policies/`

## Model

A secret policy says: "when you need to generate a new value for this kind of secret, follow this recipe." Recipes have one or more fields; each field has a generator and parameters.

```json
{
  "id": "sp_db_passwords",
  "name": "Database Passwords",
  "policy_type": "password",
  "fields": [
    {
      "name": "password",
      "generator": "random",
      "config": {
        "length": 32,
        "charset": "safe",
        "require_upper": true,
        "require_lower": true,
        "require_digit": true,
        "require_symbol": true
      }
    }
  ],
  "is_active": true,
  "description": "Strong 32-char passwords for database users",
  "created_at": "...",
  "updated_at": "..."
}
```

When a rotation policy with `auto_generate: true` fires for a secret, ScaiVault runs the linked secret-policy's recipe and writes the result as the new version. You can also generate previews for testing.

## Policy types

| Type | Typical fields | Common use |
|------|----------------|------------|
| `password` | `password` | Database users, service passwords |
| `api_key` | `api_key`, optional `api_secret` | API tokens |
| `oauth_client` | `client_id`, `client_secret` | OAuth credentials |
| `ssh_key` | `private_key`, `public_key` | SSH key pairs |
| `custom` | Whatever you define | Anything else |

`policy_type` is a hint to the UI; it doesn't constrain `fields`. The combination of fields is what determines the generated shape.

## Generators

Each field declares a `generator` and a `config`:

| Generator | Config keys | Output |
|-----------|-------------|--------|
| `random` | `length`, `charset`, `require_*` | Random string from the charset |
| `hex` | `length` | Random lowercase hex of N chars |
| `uuid` | `version` (4/7) | UUIDv4 or UUIDv7 |
| `template` | `template` | Substitutes `{{random:N}}`, `{{uuid}}`, `{{hex:N}}`, `{{timestamp}}` placeholders |
| `rsa_keypair` | `bits` (2048/4096) | PEM-encoded private + public |
| `ec_keypair` | `curve` (P-256/P-384) | PEM-encoded private + public |
| `ssh_keypair` | `key_type` (ed25519/rsa), `comment` | OpenSSH-formatted private + public |
| `bcrypt_hash` | `length`, `rounds` | Random password and its bcrypt hash |
| `static` | `value` | Literal value — useful for templates |

## Charsets

| Name | Characters |
|------|------------|
| `alphanumeric` | `a-zA-Z0-9` |
| `alpha` | `a-zA-Z` |
| `numeric` | `0-9` |
| `hex` | `0-9a-f` |
| `safe` | Alphanumeric plus `_-.` — safe in URLs and shells |
| `printable` | All printable ASCII except space |
| `symbols` | `!@#$%^&*()_-+=[]{}|;:,.<>?` |
| `alphanumeric+symbols` | Alphanumeric plus the symbols set |

`require_upper`, `require_lower`, `require_digit`, `require_symbol` boolean flags force at least one character from each class. Useful for satisfying password complexity requirements at downstream systems.

## Endpoints

### GET /v1/secret-policies

List policies in the current tenant.

Query: `policy_type`, `active_only`, `limit`, `cursor`.

Response:

```json
{
  "data": [
    {"id": "sp_abc", "name": "...", "policy_type": "password", "is_active": true, "fields_count": 1}
  ],
  "cursor": null,
  "has_more": false
}
```

**Scope:** `policies:read` or `admin`.

### POST /v1/secret-policies

Create.

Body:

| Field | Required | Description |
|-------|----------|-------------|
| `name` | Yes | Tenant-unique |
| `policy_type` | Yes | See type table |
| `fields` | Yes | Array of `{name, generator, config}` |
| `description` | No | |

**Scope:** `policies:write` or `admin`.

### GET /v1/secret-policies/{id}

Returns the full policy.

### PATCH /v1/secret-policies/{id}

Partial update (any subset of `name`, `description`, `fields`, `is_active`).

### DELETE /v1/secret-policies/{id}

Returns `409 policy_in_use` if any rotation policy or secret references it.

### POST /v1/secret-policies/{id}/generate

Generate a preview value without storing anything. Returns the generated fields. By default values are masked (`••••••`) — pass `?show=true` to get plaintext (audit-logged).

Response:

```json
{
  "policy_id": "sp_abc",
  "fields": {
    "password": "kX9#mP2$vL5@nQ8&wR3!T7^bY4*"
  },
  "generated_at": "..."
}
```

**Scope:** `policies:read` (mask) or `policies:write` (plaintext).

### GET /v1/secret-policies/_types

Catalog of available policy types, generators, and charsets — useful for building UIs.

Response:

```json
{
  "types": ["password", "api_key", "oauth_client", "ssh_key", "custom"],
  "generators": ["random", "hex", "uuid", "template", "rsa_keypair", "ec_keypair", "ssh_keypair", "bcrypt_hash", "static"],
  "charsets": ["alphanumeric", "alpha", "numeric", "hex", "safe", "printable", "symbols", "alphanumeric+symbols"]
}
```

## Examples

### Strong password (32 chars, all classes)

```json
{
  "name": "Strong Password",
  "policy_type": "password",
  "fields": [{
    "name": "password",
    "generator": "random",
    "config": {
      "length": 32,
      "charset": "alphanumeric+symbols",
      "require_upper": true,
      "require_lower": true,
      "require_digit": true,
      "require_symbol": true
    }
  }]
}
```

### API key + secret pair

```json
{
  "name": "API Key Pair",
  "policy_type": "api_key",
  "fields": [
    {"name": "api_key", "generator": "hex", "config": {"length": 64}},
    {"name": "api_secret", "generator": "random", "config": {"length": 48, "charset": "safe"}}
  ]
}
```

### Templated database username

```json
{
  "name": "DB Service User",
  "policy_type": "custom",
  "fields": [
    {"name": "username", "generator": "template", "config": {"template": "svc_{{random:8}}"}},
    {"name": "password", "generator": "random", "config": {"length": 24, "charset": "safe"}}
  ]
}
```

Template placeholders: `{{random:N}}` (alphanumeric), `{{hex:N}}`, `{{uuid}}`, `{{timestamp}}`, `{{date:YYYY-MM-DD}}`.

### Ed25519 SSH key pair

```json
{
  "name": "SSH Host Key",
  "policy_type": "ssh_key",
  "fields": [{
    "name": "key",
    "generator": "ssh_keypair",
    "config": {"key_type": "ed25519", "comment": "scaivault-managed"}
  }]
}
```

The generated value contains both `private_key` and `public_key` sub-fields.

## Attaching to a rotation policy

```bash
curl -X PATCH https://scaivault.scailabs.ai/v1/rotation/policies/rot_quarterly \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"secret_policy_id": "sp_db_passwords", "auto_generate": true}'
```

On each rotation, ScaiVault generates a fresh value from `sp_db_passwords` and writes it as the secret's new version.

## Attaching to a single secret

For secrets that have `auto_generate: true` set at the rotation level, you can override the recipe per secret:

```bash
curl -X PUT https://scaivault.scailabs.ai/v1/secrets/app/db/password \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {"password": "initial-value"},
    "options": {
      "rotation_policy_id": "rot_quarterly",
      "secret_policy_id": "sp_db_passwords"
    }
  }'
```

## Related

- [Rotation](./rotation) — schedule that drives generation.
- [Secrets](../core-concepts/secrets) — what the generated values become.
- [Policies (access)](./policies) — the *other* kind of policy.
