---
title: "Secrets"
description: "Manage project environments and sensitive values used by integrations and automation."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.heizen.work/llms.txt
> Use this file to discover all available pages before exploring further.

# Secrets

Project Secrets stores sensitive, project-scoped values in named environments. It is separate from [Test Environments](/studio/qa/environments), which describe the configuration used when executing tests.

![Project Secrets page showing environment tabs, secret entries, and the integration guide](/images/heizen-studio/secrets-page.png)

## Create an environment

1. **Open Secrets**

   Select a project and choose **Secrets** in the project sidebar.
2. **Add an environment**

   Create an environment such as `development`, `staging`, or `production`. Use
   names that make the intended deployment boundary clear.
3. **Add or update values**

   Open an environment and add the secret names and values required by the
   project. Save changes to create a new version.
4. **Use the integration guide**

   Open the built-in guide to connect the environment to an SDK or GitHub
   Action. Copy the integration instructions into the authorized deployment
   workflow.

## Integrate Secrets into an application

The `@heizen-labs/secrets-sdk` package provides both the `heizen-secrets` CLI
and the `SecretsClient` API. Install it once, then choose the integration that
matches how your application starts.

```sh
npm install @heizen-labs/secrets-sdk
pnpm add @heizen-labs/secrets-sdk
yarn add @heizen-labs/secrets-sdk
bun add @heizen-labs/secrets-sdk
```

### Run a command with the CLI

The CLI is the simplest option when your application already reads from
`process.env`. It fetches the selected environment and starts your command with
the values injected into the child process:

```bash
heizen-secrets run [<projectId> [environment]] [--api-key <key>] [--base-url <url>] [--override] -- <command> [args...]
```

For example:

```bash
heizen-secrets run proj_11 development -- nest start --watch
```

Locally you don't need an API key: the first run signs you in through Studio
(see [Developer tokens for local development](#developer-tokens-for-local-development)).

Add the command to a package script when it should be used for local startup:

```json
{
  "scripts": {
"dev": "heizen-secrets run proj_11 development -- nest start --watch"
  }
}
```

#### Configure the CLI from environment variables

```bash
export HEIZEN_STUDIO_PROJECT_ID=proj_11
export HEIZEN_STUDIO_ENVIRONMENT_NAME=development
export HEIZEN_STUDIO_API_KEY=xxx  # CI/CD only; omit locally to sign in instead

heizen-secrets run -- nest start --watch
```

The CLI also reads `.env.local` and `.env` files. Each value is resolved in this
order:

| Value       | Resolution order                                                                                                                              |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Project ID  | CLI `<projectId>`, `HEIZEN_STUDIO_PROJECT_ID`, `.env.local`, `.env`                                                                           |
| Environment | CLI `<environment>`, `HEIZEN_STUDIO_ENVIRONMENT_NAME`, `.env.local`, `.env`                                                                   |
| Credential  | CLI `--api-key`, `HEIZEN_STUDIO_API_KEY`, `.env.local`, `.env`, cached developer token, then browser sign-in (interactive terminals only) |

Positionals are not interchangeable. The first positional value is always the
project ID and the second is always the environment name:

```bash
# Correct: project ID first, environment second
heizen-secrets run proj_123 env_1 -- pnpm dev

# Incorrect: env_1 is treated as a project ID
heizen-secrets run env_1 -- pnpm dev
```

You can pass an API key independently of the positionals:

```bash
# Project ID and environment from env; API key from the CLI
heizen-secrets run --api-key sk_xxx -- pnpm dev
```

If a required value cannot be resolved, the CLI exits with a clear missing
project ID, environment name, or credentials error.

The CLI injects secrets only into the spawned process. It does not print them,
write them to files, mutate the parent process environment, or export them back
to the shell. The spawned application can still print its own environment values
if its code chooses to do so.

### Developer tokens for local development

API keys are meant for CI/CD and deployed runtimes. On your own machine, run the
CLI without an API key and it signs you in through Heizen Studio:

```
Authentication required for project "proj_11".
Open this URL to sign in with Heizen Studio:

  https://api.studio.heizen.work/oauth/authorize?...

Press Enter to open it in your browser (Ctrl+C to cancel)…
```

Press Enter to open the browser. If you are already signed in to Studio, it
returns to the terminal immediately with no second login or approval screen, and
the command continues with your secrets. Later runs reuse the cached token
without prompting.

- The token is tied to you and to one API host, and it expires after 24 hours.
  When it expires, the next run signs you in again.
- One sign-in covers every project you can access. Your access is checked on
  every fetch, so if you are removed from a project, the token stops working for
  that project immediately.
- Any project member (Admin, Member, or Guest) can pull that project's published
  secrets. Only project admins can view or edit secret values in Studio.
- Treat the token like a password: anyone holding it can read the published
  secrets of every project you can access until it expires.
- Tokens are cached in `~/.heizen/config` (`%USERPROFILE%\.heizen\config` on
  Windows), readable only by you. Set `HEIZEN_CONFIG_PATH` to change the location.
- An API key always takes priority. If you use one in an interactive terminal,
  the CLI reminds you that API keys are intended for CI/CD.
- Sign-in never happens in CI, without a terminal, or with
  `HEIZEN_NO_INTERACTIVE=1`. The CLI fails with
  `No Heizen credentials found. Set HEIZEN_STUDIO_API_KEY (CI/CD) or run "heizen-secrets login" in a terminal.`
  instead of waiting for a browser.
- `--optional` never triggers sign-in: it uses a cached token if there is one
  and otherwise skips the fetch.

Manage the cached token with:

```bash
heizen-secrets login [<projectId>] [--base-url <url>]           # sign in and cache a token
heizen-secrets logout [<projectId>] [--all] [--base-url <url>]  # delete the cached token(s)
heizen-secrets status [<projectId>] [--base-url <url>]          # show which credential would be used
```

`logout` only removes the local copy; the token stays valid until it expires.

### Load secrets in GitHub Actions

The Secrets page can generate a workflow step for loading an environment into a
file used by a CI job:

```yaml
- name: Fetch environment from Heizen
  uses: heizen-labs/load-secrets@v1
  with:
project-id: proj_11
environment: development
output-path: ./.env
api-key: ${{ secrets.HEIZEN_STUDIO_API_KEY }}
```

Store `HEIZEN_STUDIO_API_KEY` in **GitHub → Settings → Secrets and variables →
Actions**. Keep the generated `.env` file out of build artifacts and clean it up
when the job no longer needs it.

### Load secrets in application code

Use `SecretsClient` when the application needs to load values during startup or
when it needs a custom target object instead of `process.env`:

```ts
import { SecretsClient } from "@heizen-labs/secrets-sdk";

const client = new SecretsClient({
  projectId: process.env.HEIZEN_STUDIO_PROJECT_ID,
  environment: process.env.HEIZEN_STUDIO_ENVIRONMENT_NAME,
  apiKey: process.env.HEIZEN_STUDIO_API_KEY,
});

await client.loadSecrets();

console.log(process.env.DATABASE_URL);
```

`projectId`, `environment`, and `apiKey` can be passed as options or resolved
from their matching environment variables. Instead of `apiKey`, you can pass a
developer token as `accessToken`; `SecretsClient` never opens a browser or
prompts for sign-in. `baseUrl` defaults to
`https://api.studio.heizen.work` and can be overridden for another API host.

By default, `loadSecrets()` writes only missing values into `process.env`. You
can replace existing values or use a custom target object:

```ts
await client.loadSecrets({ override: true });

const envObject: Record<string, string | undefined> = {};
await client.loadSecrets({ target: envObject });
```

The method returns `Promise<Record<string, string>>`. SDK failures are thrown as
`SecretsClientError`, which includes the error message and HTTP status.

### Create an API key for CI/CD

1. Open `https://studio.heizen.work` and select the project.
2. Open **Project Settings → API Keys**.
3. Create a key with `secrets:read` permission.
4. Restrict the key's [allowed IP addresses](/studio/settings#restrict-a-key-to-specific-ips)
   to the CI runner or VPC range that will use it, so a leaked key cannot be
   used from anywhere else.
5. Store it as `HEIZEN_STUDIO_API_KEY` in the runtime or CI secret store.

Never commit the key or secret values.

## Manage versions

Every saved value is versioned. Use the version history to review when a value changed and to identify the version used by an integration. Rename or remove an environment only after checking the workflows that depend on it.

Project admins can create, rename, delete, and edit environments. Other project members may see environment names and integration instructions without being allowed to edit secret values.

> **Caution**
>
> Treat secret values like production credentials. Do not paste them into wiki
> pages, requirements, bug reports, test cases, screenshots, or chat. Use
> separate values for each environment and rotate credentials when access
> changes.

## Review who fetched secrets

Every SDK, CLI, and GitHub Action fetch is recorded, whether it succeeded or
was rejected. Choose **Access log** on the Secrets page to review them.

Each row records:

- The API key used — or the key prefix alone when the key was invalid or revoked.
  Fetches made with a developer token show **Developer token** instead
- The Studio user the key belongs to, or the developer who made the fetch
- The secret set that was requested
- The outcome: **Success** or **Denied**, with the rejection reason on hover
- The names of the keys returned — never their values
- The caller's IP address and, where it can be resolved, its city and country

A fetch that asks for several secrets produces one row listing all of the key
names, not one row per secret. When the geo-location service cannot resolve an
address the row still records the IP and shows **Location unavailable**.

Filter by date range, API key, or user to answer a specific question — for
example, whether a CI key has been used from an IP or country you do not
recognise. Denied rows are the strongest early signal that a key has leaked:
investigate a burst of them immediately and rotate the key from **Project
Settings → API Keys**.

The same entries also appear in the project [Audit Log](/studio/audit-log)
under the **Secret Access** entity type.

## How Secrets works

Secrets is a project-scoped, versioned store. The environment name selects the boundary, while the integration guide explains how an approved runtime or GitHub Action retrieves the values. [Project API keys](/studio/settings) are a different feature: they authenticate API clients to Studio and should not be used as application secrets.

Source: https://docs.heizen.work/studio/secrets/index.mdx
