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

# Secrets

> Store API keys and other values encrypted, and pass them to sandbox processes as environment variables without putting them in your code or images.

Secrets hold values such as API keys that processes in your sandboxes need. A value is encrypted on your machine before it's uploaded, and the API never returns it. You pass a secret to a process by name, and the value is decrypted on the host running the sandbox and set in that process's environment.

The same secrets can be [filled into login forms](/docs/sessions/credentials) in browser sessions, and grouped in [vaults](/docs/sandboxes/vaults) so you can pass a whole set by one name.

<Note>
  Secrets are in public preview and are enabled for teams on request. Please contact us at [info@hyperbrowser.ai](mailto:info@hyperbrowser.ai) or on our support chat to enable them for your team. A restricted API key needs `secrets.manage` to create and change secrets and vaults, `secrets.read` to read their metadata, and `secrets.use` to pass them to sandboxes. Secrets are available in the Node SDK from 1.2.1 and the Python SDK from 1.12.0; secret types, allowed origins and vaults need Node SDK 1.3.0 and Python SDK 1.13.0. In the dashboard, only team owners and admins can manage them.
</Note>

## Create a secret

<CodeGroup>
  ```typescript Node.js theme={null}
  import { Hyperbrowser } from "@hyperbrowser/sdk";

  const client = new Hyperbrowser({ apiKey: process.env.HYPERBROWSER_API_KEY });

  await client.secrets.create({
    name: "provider-key",
    description: "Provider API key",
    value: process.env.PROVIDER_KEY!,
  });
  ```

  ```python Python theme={null}
  import os
  from hyperbrowser import Hyperbrowser

  client = Hyperbrowser(api_key=os.environ["HYPERBROWSER_API_KEY"])

  client.secrets.create({
      "name": "provider-key",
      "description": "Provider API key",
      "value": os.environ["PROVIDER_KEY"],
  })
  ```

  ```bash CLI theme={null}
  hx secrets create provider-key --description "Provider API key" --from-file ./provider-key.txt
  ```
</CodeGroup>

Values are strings or bytes and are stored byte for byte, trailing newlines included. The CLI reads values from a file or stdin, never from command-line arguments, so they stay out of your shell history. The SDKs and CLI encrypt the value before uploading it; to do this yourself, see [Add a secret version](/docs/api-reference/add-a-secret-version).

### Secret types

A secret's type is set when you create it and can't change.

| Type | Value | As an environment variable | In a browser session |
| - | - | - | - |
| `secret` (default) | Any string. | The value. | Typed into login forms once it has [allowed origins](/docs/sessions/credentials#store-a-login). |
| `totp` | An authenticator's base32 seed, or an `otpauth://totp/...` URI. | The one-time code current when the process starts. It isn't refreshed. | The code current at fill time. |
| `op_token` | A [1Password service account token](/docs/sandboxes/vaults#1password-vaults). | Never. | Never. |

The seed of a `totp` secret never reaches a process or a page, only its codes. Spaces, hyphens and lowercase letters in a base32 seed are fine. A URI may set `algorithm` (`SHA1`, `SHA256` or `SHA512`), `digits` (6 or 8) and `period` (1–300 seconds); the defaults are SHA1, 6 digits and 30 seconds. An `op_token` is only used to read a 1Password vault's values, and can't be an environment variable, a vault entry or a browser credential.

Allowed origins only limit where a secret is typed in a browser. They don't limit sandboxes: any process that names a secret, or a vault holding it, receives its value, or for a `totp` secret its current code. Keep a secret meant only for browser fills out of vaults you use with sandboxes.

<CodeGroup>
  ```typescript Node.js theme={null}
  await client.secrets.create({ name: "ci-otp", type: "totp", value: process.env.CI_TOTP_SEED! });
  ```

  ```python Python theme={null}
  client.secrets.create({"name": "ci-otp", "type": "totp", "value": os.environ["CI_TOTP_SEED"]})
  ```

  ```bash CLI theme={null}
  hx secrets create ci-otp --type totp --from-file ./otp-seed.txt
  ```
</CodeGroup>

## Pass secrets to a command

Pass secrets by name, keyed by the environment variable to set. A plain name uses the latest version.

<CodeGroup>
  ```typescript Node.js theme={null}
  const sandbox = await client.sandboxes.create({ imageName: "node" });

  const result = await sandbox.exec("node app.js", {
    secrets: { API_KEY: "provider-key" },
  });
  console.log(result.exitCode, result.stdout);
  ```

  ```python Python theme={null}
  sandbox = client.sandboxes.create({"image_name": "node"})

  result = sandbox.exec("node app.js", secrets={"API_KEY": "provider-key"})
  print(result.exit_code, result.stdout)
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.hyperbrowser.ai/api/sandbox/SANDBOX_ID/processes \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"command": "node app.js", "secrets": {"API_KEY": {"name": "provider-key"}}}'
  ```

  ```bash CLI theme={null}
  hx vm exec <sandbox-id> --secret API_KEY=provider-key -- node app.js
  ```
</CodeGroup>

For long-running processes, pass secrets to `sandbox.processes.start`. The process handle reports which version each variable received:

<CodeGroup>
  ```typescript Node.js theme={null}
  const worker = await sandbox.processes.start("node worker.js", {
    secrets: { API_KEY: "provider-key" },
  });
  console.log(worker.secrets);
  ```

  ```python Python theme={null}
  worker = sandbox.processes.start("node worker.js", secrets={"API_KEY": "provider-key"})
  print(worker.secrets)
  ```

  ```bash CLI theme={null}
  hx vm process start <sandbox-id> --secret API_KEY=provider-key 'node worker.js'
  ```
</CodeGroup>

To pin a version, pass `{ name, version }` instead of a name, or `--secret-version API_KEY=2` in the CLI.

## Pass a vault

A [vault](/docs/sandboxes/vaults) names a set of variables, so you can pass them all at once. Pass `vault` with or without `secrets`:

<CodeGroup>
  ```typescript Node.js theme={null}
  const result = await sandbox.exec("node app.js", {
    vault: "app-env",
    secrets: { API_KEY: "provider-key-staging" },
  });
  ```

  ```python Python theme={null}
  result = sandbox.exec(
      "node app.js",
      vault="app-env",
      secrets={"API_KEY": "provider-key-staging"},
  )
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.hyperbrowser.ai/api/sandbox/SANDBOX_ID/processes \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "command": "node app.js",
      "vault": "app-env",
      "secrets": {"API_KEY": {"name": "provider-key-staging"}}
    }'
  ```

  ```bash CLI theme={null}
  hx vm exec <sandbox-id> --vault app-env --secret API_KEY=provider-key-staging -- node app.js
  ```
</CodeGroup>

The vault's variables come first and `secrets` is merged over them, so `secrets` wins when both set a variable. Together they can set at most 32 variables. A variable from either can't also be set with `env`.

The response lists every variable with its `source` (`{"type": "vault", "name": "app-env"}` or `{"type": "secrets"}`) and, when it replaced one, `overrides`:

```json theme={null}
{
  "secrets": [
    { "env": "API_KEY", "name": "provider-key-staging", "secretId": "…", "version": 2,
      "source": { "type": "secrets" }, "overrides": [{ "type": "vault", "name": "app-env" }] },
    { "env": "DB_URL", "name": "app-db-url", "secretId": "…", "version": 1,
      "source": { "type": "vault", "name": "app-env" } }
  ]
}
```

A `totp` variable also has `"secretType": "totp"`. A variable from a 1Password vault has `reference` (its `op://` path) and `tokenSecret` instead of `name`, `secretId` and `version`.

## Pass secrets to an image's startup command

When you create a sandbox from an image with a startup command, you can pass secrets, a vault, or both to that command:

<CodeGroup>
  ```typescript Node.js theme={null}
  await client.sandboxes.create({
    imageName: "my-image",
    vault: "app-env",
    secrets: { API_KEY: "provider-key" },
  });
  ```

  ```python Python theme={null}
  client.sandboxes.create({
      "image_name": "my-image",
      "vault": "app-env",
      "secrets": {"API_KEY": "provider-key"},
  })
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.hyperbrowser.ai/api/sandbox \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"imageName": "my-image", "vault": "app-env", "secrets": {"API_KEY": {"name": "provider-key"}}}'
  ```

  ```bash CLI theme={null}
  hx vm create my-image --vault app-env --secret API_KEY=provider-key
  ```
</CodeGroup>

The response's `startupSecrets` lists what the command received, in the same form as above. Startup secrets work for image launches on Firecracker sandboxes only. Otherwise the create fails with `SECRET_STARTUP_UNSUPPORTED`: create the sandbox without secrets, and pass them to `exec` or `processes.start`.

## Rotate and delete

Storing a new value adds a version, and commands use the latest version unless you pin one. Each method takes a secret's name or ID.

<CodeGroup>
  ```typescript Node.js theme={null}
  await client.secrets.addVersion("provider-key", process.env.NEW_PROVIDER_KEY!);
  const detail = await client.secrets.get("provider-key"); // metadata and versions, never the value
  await client.secrets.deleteVersion("provider-key", 1);
  await client.secrets.delete("provider-key");
  ```

  ```python Python theme={null}
  client.secrets.add_version("provider-key", os.environ["NEW_PROVIDER_KEY"])
  detail = client.secrets.get("provider-key")  # metadata and versions, never the value
  client.secrets.delete_version("provider-key", 1)
  client.secrets.delete("provider-key")
  ```

  ```bash CLI theme={null}
  printf '%s' "$NEW_PROVIDER_KEY" | hx secrets set provider-key --stdin
  hx secrets get provider-key
  hx secrets delete-version provider-key 1
  hx secrets delete provider-key
  ```
</CodeGroup>

The latest version can't be deleted on its own; add a newer one or delete the whole secret. A secret a vault uses can't be deleted, nor a version a vault pins; change the vault first. Deleting doesn't revoke a value a process, sandbox or snapshot already received. If a value may have leaked, rotate it where it was issued.

## How secrets are protected

* **Encrypted before upload.** Values are encrypted on your machine and decrypted only by the host running the sandbox that uses them. No endpoint returns a value.
* **One process at a time.** A secret is set only in the environment of the process you pass it to; other processes don't inherit it.
* **Tracked through snapshots.** A sandbox that received a secret, and every snapshot taken of it, is marked as containing secrets. Restoring such a snapshot needs the same access as passing secrets.

A process can read its own environment, and so can anything else in the sandbox running as the same user, or as root.

## Limits

* 100 secrets per team, 10 versions per secret, and 32 variables per process or sandbox.
* Values are 1 byte to 32 KiB of UTF-8 without NUL bytes.
* Names are 1–128 letters, digits, `_` or `-`, starting with a letter or digit.

## Errors

| Code | Status | Meaning |
| - | - | - |
| `SECRET_NOT_FOUND`, `VAULT_NOT_FOUND` | 404 | No secret or vault with that name or ID. |
| `SECRET_TYPE_MISMATCH` | 409 | The secret can't be an environment variable, such as an `op_token`. |
| `SECRET_SOURCES_INVALID` | 400 | `env` sets a variable that `vault` or `secrets` also sets, the vault has no entries, or together they set more than 32 variables. |
| `SECRET_IN_USE`, `SECRET_VERSION_IN_USE` | 409 | A vault uses the secret or pins the version. |
| `SECRET_VALUE_INVALID` | 422 | The stored value can't be used, such as an invalid TOTP seed. Add a new version. |
| `SECRET_STARTUP_UNSUPPORTED` | 400 | The image has no startup command to pass secrets to. |
| `SECRET_PROCESS_NOT_STARTED` | 503 | The sandbox host couldn't be reached and no process started. Safe to retry. |
| `SECRET_PROCESS_OUTCOME_UNKNOWN` | 502 | The start may have reached the sandbox; check its processes before retrying. |
| `SECRET_TOTP_UNSUPPORTED` | 501 | The sandbox's host can't deliver TOTP secrets yet, and nothing started. Retryable on sandbox create only, since a new sandbox may run on an upgraded host. |
| `SECRET_ONEPASSWORD_UNSUPPORTED` | 501 | The sandbox's host doesn't support 1Password vaults yet, and nothing started. Retryable on sandbox create only. |
| `SECRETS_ADMIN_REQUIRED` | 403 | A signed-in team member who isn't an owner or admin tried to manage secrets. |
| `SECRETS_FEATURE_DISABLED` | 403 | Secrets aren't enabled for your team yet. Please contact support to enable them. |
| `SECRETS_NOT_CONFIGURED` | 503 | The platform has no secrets key configured. Not retryable. |

1Password vaults have [their own errors](/docs/sandboxes/vaults#1password-errors).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.