# Store a secret

> Store, replace and list a tenant's secrets with kindgi secrets, and what each environment keeps.

This guide stores, replaces and lists your tenant's secrets with `kindgi secrets`, without a value going through your shell's history or a command line.

Tools' declared secrets, HTTP tools' credentials, model providers' keys and MCP endpoints' credentials are all secrets of your tenant. Each is looked up by name in an **environment**.

## Before you start

- **A runtime,** such as `kindgi dev`, with Kindgi 0.1's CLI.
- **`openssl`,** to make a key for the examples.

## Set one

A secret belongs to an environment and a scope. Set it, and type the value when it asks:

- **TypeScript**

  ```sh
  pnpm exec kindgi secrets set RECEIPT_SIGNING_KEY --env=local --scope=tenant
  ```

- **Python**

  ```sh
  uv run kindgi secrets set RECEIPT_SIGNING_KEY --env=local --scope=tenant
  ```

- **Java**

  ```sh
  ./kindgiw secrets set RECEIPT_SIGNING_KEY --env=local --scope=tenant
  ```

- **Scala**

  ```sh
  ./kindgiw secrets set RECEIPT_SIGNING_KEY --env=local --scope=tenant
  ```

```text
Value:
Re-enter to confirm:
```

It prompts twice, without echoing, then prints the secret's name, scope, environment and version as JSON, and `Set RECEIPT_SIGNING_KEY at tenant in local.`

- **`RECEIPT_SIGNING_KEY`** is the secret's name, as a tool declares it.
- **`--env`** is the environment the secret belongs to. A runtime resolves secrets in the environment it serves, which `KINDGI_ENV` names. `kindgi dev` serves `local`.
- **`--scope`** is where it lives: `tenant`, `org:<orgId>` or `project:<projectId>`. It's required.

In a script, pipe the value in with `--from-stdin`. Or read it from a file only you can read, with `--from-file=<path>`: a file others can read is refused. Here `openssl rand -hex 32 | tr -d '\n'` makes a 64-character key, without a newline:

- **TypeScript**

  ```sh
  openssl rand -hex 32 | tr -d '\n' | pnpm exec kindgi secrets set RECEIPT_SIGNING_KEY --env=local --scope=tenant --from-stdin
  ```

- **Python**

  ```sh
  openssl rand -hex 32 | tr -d '\n' | uv run kindgi secrets set RECEIPT_SIGNING_KEY --env=local --scope=tenant --from-stdin
  ```

- **Java**

  ```sh
  openssl rand -hex 32 | tr -d '\n' | ./kindgiw secrets set RECEIPT_SIGNING_KEY --env=local --scope=tenant --from-stdin
  ```

- **Scala**

  ```sh
  openssl rand -hex 32 | tr -d '\n' | ./kindgiw secrets set RECEIPT_SIGNING_KEY --env=local --scope=tenant --from-stdin
  ```

```text excerpt
…
  Set RECEIPT_SIGNING_KEY at tenant in local.
```

The secret is available to the next call. Nothing restarts.

## Replace one

`set` refuses a name that exists:

```text
Version conflict: RECEIPT_SIGNING_KEY already exists (version 1). To store a new version, retry with --write-mode=add-version.
```

Write a new value with `--write-mode=add-version`:

- **TypeScript**

  ```sh
  openssl rand -hex 32 | tr -d '\n' | pnpm exec kindgi secrets set RECEIPT_SIGNING_KEY --env=local --scope=tenant --from-stdin --write-mode=add-version
  ```

- **Python**

  ```sh
  openssl rand -hex 32 | tr -d '\n' | uv run kindgi secrets set RECEIPT_SIGNING_KEY --env=local --scope=tenant --from-stdin --write-mode=add-version
  ```

- **Java**

  ```sh
  openssl rand -hex 32 | tr -d '\n' | ./kindgiw secrets set RECEIPT_SIGNING_KEY --env=local --scope=tenant --from-stdin --write-mode=add-version
  ```

- **Scala**

  ```sh
  openssl rand -hex 32 | tr -d '\n' | ./kindgiw secrets set RECEIPT_SIGNING_KEY --env=local --scope=tenant --from-stdin --write-mode=add-version
  ```

It prints the JSON, with the new `versionId`, and the same `Set RECEIPT_SIGNING_KEY at tenant in local.` line.

`--if-version=<n>` makes the write conditional: it fails if someone changed the secret since version `n`.

## List them

`list` and `get` show names and versions, never values. Get one by its name, environment and scope:

- **TypeScript**

  ```sh
  pnpm exec kindgi secrets get RECEIPT_SIGNING_KEY --env=local --scope=tenant
  ```

- **Python**

  ```sh
  uv run kindgi secrets get RECEIPT_SIGNING_KEY --env=local --scope=tenant
  ```

- **Java**

  ```sh
  ./kindgiw secrets get RECEIPT_SIGNING_KEY --env=local --scope=tenant
  ```

- **Scala**

  ```sh
  ./kindgiw secrets get RECEIPT_SIGNING_KEY --env=local --scope=tenant
  ```

```json
{
  "name": "RECEIPT_SIGNING_KEY",
  "scope": {
    "kind": "tenant",
    "tenantId": "bb22c936-5111-429a-bfce-09650af85492"
  },
  "envName": "local",
  "currentVersion": 1,
  "createdAt": "1970-01-01T00:00:00.000Z",
  "updatedAt": "1970-01-01T00:00:00.000Z"
}
```

`kindgi secrets list --env=local --scope=tenant` lists the environment's secrets by name, 25 to a page (`--limit` takes up to 100). While more remain, the page has a `nextCursor`: pass it as `--cursor` for the next page. `--include-revoked` lists revoked secrets too, with their `revokedAt` and `revokeReason`. Paging and `--include-revoked` need a runtime of 0.1.8 or later.

## Under `kindgi dev`

`kindgi dev`'s secrets store is the pack's env files: your app's, and Kindgi's own. [Keep local values in env files](https://docs.kindgi.com/v0.1.8/guides/secrets/env-files.md) describes them. So the store behaves like them:

- `set` writes the value to Kindgi's own `.kindgi/secrets.env`, readable only by you, which your app doesn't load. With `--app`, it writes your app's own env file instead, for a value your app reads too.
- Every name in the env files is a secret of `local`, and `list` shows them all. Their timestamps are the epoch, as in `get`'s answer.
- A name has one value: replacing it with `add-version` overwrites it, and its version stays 1.
- The files are the same for every scope, so `--scope` doesn't separate anything here.
- It refuses what an env file can't hold as given: a name that isn't capital letters, digits and underscores, starts with a digit, or is `KINDGI_*` (`secret-name-invalid`; a `kindgi.` name gets `reserved-name-prefix`, as in every store), and a value no env-file quoting can hold so that it reads back unchanged (`secret-value-invalid`). Each message says why. Store such a value encoded (base64, say).
- There's nothing to rotate or revoke. Those commands say so:

```text
Error [secret-operation-unsupported]: Dev secrets live in your env files (.env, .env.local, .kindgi/secrets.env) and have no versions to rotate. Edit the value there, or run `kindgi secrets set RECEIPT_SIGNING_KEY --write-mode=add-version`.
```

```text
Error [secret-operation-unsupported]: Dev secrets live in your env files (.env, .env.local, .kindgi/secrets.env) and have no revocation. Remove HTTPBIN_TOKEN from those files.
```

Versions, scopes, rotation and revocation are what a deployment's secrets store adds. That store is Postgres, with `KINDGI_SECRETS_BACKEND=postgres`, or [your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md). The [environment variable reference](https://docs.kindgi.com/v0.1.8/reference/env-vars.md#kindgi_secrets_backend) has the setting. Every flag is in the [`kindgi secrets` reference](https://docs.kindgi.com/v0.1.8/reference/cli/secrets.md).

## Next

- [Give a tool a secret](https://docs.kindgi.com/v0.1.8/guides/tools/give-a-tool-a-secret.md)
- [Keep local values in env files](https://docs.kindgi.com/v0.1.8/guides/secrets/env-files.md)
- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)
