# client.secrets

> client.secrets — the secrets operations.

`client.secrets` — the `secrets` operations.

On `AsyncKindgi` every method is the same, awaited.

## `client.secrets.list()`

```python signature
list(
    *,
    env_name: str,
    scope_kind: Literal['tenant', 'org', 'project'],
    scope_id: str | UUID | None = None,
    inherit: bool | None = None,
    limit: int | None = None,
    cursor: str | None = None,
    name_prefix: str | None = None,
    include_revoked: Literal['true', 'false'] | None = None,
    timeout: float | None = None,
) -> SecretCollectionPage
```

List secret metadata. `GET /v1/secrets`

Cursor-paginated list of secret metadata at the given `(scope, envName)`, in name order, with `nextCursor` while more remain. Live secrets only unless `?includeRevoked=true`. `value` is ALWAYS absent (structural redaction).

## `client.secrets.set()`

```python signature
set(
    body: SecretSetRequest | Mapping[str, Any] | None = None,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
    **fields: Any,
) -> SecretSetResponse
```

Create a secret or add a version. `POST /v1/secrets`

Accepts a plaintext `value` (as do `POST /v1/secrets/:name/rotate` via `newValue` and `POST /v1/deployments/:deploymentId/secrets`); every other secrets route is metadata-only. Requires the `secrets:write` capability. `writeMode: create-new` returns 201 with the record; `add-version` returns 200. Existing-secret conflicts on `create-new` return 409 `secret-write-conflict`.

## `client.secrets.get()`

```python signature
get(
    name: str,
    /,
    *,
    env_name: str,
    scope_kind: Literal['tenant', 'org', 'project'],
    scope_id: str | UUID | None = None,
    inherit: bool | None = None,
    timeout: float | None = None,
) -> SecretRecord
```

Get secret metadata. `GET /v1/secrets/{name}`

## `client.secrets.revoke()`

```python signature
revoke(
    name: str,
    /,
    *,
    env_name: str,
    scope_kind: Literal['tenant', 'org', 'project'],
    scope_id: str | UUID | None = None,
    hard: bool | None = None,
    reason: str | None = None,
    timeout: float | None = None,
) -> SecretRevokeResult
```

Revoke a secret. `DELETE /v1/secrets/{name}`

Requires the `secrets:revoke` capability (soft-revoke) or `secrets:revoke:hard` when `?hard=true` (cryptographic erasure). Optional `?reason=<text>` records the revoke reason.

## `client.secrets.list_versions()`

```python signature
list_versions(
    name: str,
    /,
    *,
    env_name: str,
    scope_kind: Literal['tenant', 'org', 'project'],
    scope_id: str | UUID | None = None,
    limit: int | None = None,
    cursor: str | None = None,
    timeout: float | None = None,
) -> SecretVersionCollectionPage
```

List secret versions. `GET /v1/secrets/{name}/versions`

Cursor-paginated version metadata for a single secret. `value` is ALWAYS null on the wire.

## `client.secrets.get_version()`

```python signature
get_version(
    name: str,
    version_id: int,
    /,
    *,
    env_name: str,
    scope_kind: Literal['tenant', 'org', 'project'],
    scope_id: str | UUID | None = None,
    timeout: float | None = None,
) -> SecretVersionRecord
```

Get one secret-version record. `GET /v1/secrets/{name}/versions/{versionId}`

## `client.secrets.rotate()`

```python signature
rotate(
    name: str,
    body: SecretRotateRequest | Mapping[str, Any] | None = None,
    /,
    *,
    env_name: str | None = None,
    scope_kind: Literal['tenant', 'org', 'project'] | None = None,
    scope_id: str | UUID | None = None,
    idempotency_key: str | None = None,
    timeout: float | None = None,
    **fields: Any,
) -> SecretRotateResponseSync | SecretRotateResponseAsync
```

Rotate a secret (sync or async). `POST /v1/secrets/{name}/rotate`

Requires the `secrets:rotate` capability. The scope comes from body `scope`, else from `scopeKind` + `scopeId` in the query; one of them is required, and when both are present they must name the same scope. Authorization checks that scope. Sync providers return 201 with `{ kind: "sync", newVersionId, oldVersionId, oldVersionRevokedAt? }`. Async providers return 202 with `{ kind: "async", rotationId, statusUrl, eventsUrl }`; poll via `GET /v1/secrets/:name/rotations/:rotationId` or subscribe via SSE at the events URL. `kind` is the discriminant.

## `client.secrets.get_rotation_status()`

```python signature
get_rotation_status(
    name: str,
    rotation_id: str | UUID,
    /,
    *,
    env_name: str,
    scope_kind: Literal['tenant', 'org', 'project'],
    scope_id: str | UUID | None = None,
    timeout: float | None = None,
) -> RotationStatus
```

Get async rotation status. `GET /v1/secrets/{name}/rotations/{rotationId}`

## `client.secrets.rotation_events()`

```python signature
rotation_events(
    name: str,
    rotation_id: str | UUID,
    /,
    *,
    env_name: str,
    scope_kind: Literal['tenant', 'org', 'project'],
    scope_id: str | UUID | None = None,
    timeout: float | None = None,
) -> None
```

Subscribe to async rotation events (SSE). `GET /v1/secrets/{name}/rotations/{rotationId}/events`

`text/event-stream` — one `rotation-update` frame per status change; a `ping` heartbeat every 15s keeps proxies from timing the stream out; the stream closes on terminal state.
