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

# Use Secrets in a Sandbox

This page shows an end-to-end example of using a Secret in a sandbox: create the Secret, create a sandbox that references it, run code inside the sandbox that authenticates to an external service, and kill the sandbox when done. The sandbox only ever sees an opaque placeholder — the outbound proxy substitutes the real value for requests to hosts on the Secret's allow list.

## Prerequisites

Set the upstream credential before running:

```bash CLI icon="terminal" theme={"system"}
export OPENAI_API_KEY="your-openai-api-key"
```

## End-to-end example

The example creates a Secret, launches a sandbox with `secret_envs`, runs a command inside the sandbox that calls the upstream API using the placeholder, and finally kills the sandbox.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  import os
  import uuid

  from novita_sandbox import Novita

  secret_name = f"openai-example-{uuid.uuid4().hex[:12]}"
  openai_api_key = os.environ["OPENAI_API_KEY"]
  novita = Novita()

  # 1. Create a team-scoped Secret. The real value is stored encrypted;
  #    substitution is only allowed for hosts in the allow list.
  novita.secret.create(
      name=secret_name,
      value=openai_api_key,
      hosts=["api.openai.com"],
      description="Sandbox Secrets example",
  )

  # 2. Create a sandbox and map an env var to the Secret name.
  #    The env var holds a placeholder, not the real value.
  sandbox = novita.sandbox.create(
      secret_envs={"OPENAI_API_KEY": secret_name},
  )

  try:
      # 3. Run code inside the sandbox. It reads the placeholder from the env var
      #    and sends it in an HTTPS header; the proxy substitutes the real value
      #    in flight because api.openai.com is on the allow list.
      result = sandbox.commands.run(
          'curl -s https://api.openai.com/v1/models '
          '-H "Authorization: Bearer $OPENAI_API_KEY"'
      )
      print(result.stdout)
  finally:
      # 4. Kill the sandbox when done.
      sandbox.kill()
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import 'dotenv/config'
  import { randomUUID } from 'crypto'
  import { Novita } from 'novita-sandbox'

  const secretName = `openai-example-${randomUUID().slice(0, 12)}`
  const openaiApiKey = process.env.OPENAI_API_KEY
  const novita = new Novita()

  // 1. Create a team-scoped Secret. The real value is stored encrypted;
  //    substitution is only allowed for hosts in the allow list.
  await novita.secret.create({
    name: secretName,
    value: openaiApiKey,
    hosts: ['api.openai.com'],
    description: 'Sandbox Secrets example',
  })

  // 2. Create a sandbox and map an env var to the Secret name.
  //    The env var holds a placeholder, not the real value.
  const sandbox = await novita.sandbox.create({
    secretEnvs: { OPENAI_API_KEY: secretName },
  })

  try {
    // 3. Run code inside the sandbox. It reads the placeholder from the env var
    //    and sends it in an HTTPS header; the proxy substitutes the real value
    //    in flight because api.openai.com is on the allow list.
    const result = await sandbox.commands.run(
      'curl -s https://api.openai.com/v1/models -H "Authorization: Bearer $OPENAI_API_KEY"'
    )
    console.log(result.stdout)
  } finally {
    // 4. Kill the sandbox when done.
    await sandbox.kill()
  }
  ```
</CodeGroup>

<Note>
  The code inside the sandbox only ever reads the placeholder (for example `novita_secret_<random_hex>`). The real key is substituted at the proxy layer and is never present in the sandbox's environment variables, filesystem, or process arguments.
</Note>

## How each step works

| Step                                       | What happens                                                                                                                                                                                                                                                                     |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Novita.secret``.create(...)`              | Creates a team-scoped Secret and its first immutable version. Returns `SecretBinding` metadata (no real value). The `hosts` allow list controls which hosts the real value may be substituted for. Returns `409 Conflict` if an active Secret with the same name already exists. |
| `Novita.s``andbox.create(secret_envs=...)` | Maps environment variable names to Secret names. The env var is set to a placeholder, not the real value. If any referenced Secret cannot be resolved, sandbox creation fails.                                                                                                   |
| `sandbox.commands.run(...)`                | Runs a command inside the sandbox. When it makes an HTTPS request to an allow-listed host with the placeholder in a header, the proxy replaces the placeholder with the real value before the request leaves.                                                                    |
| `sandbox.kill()`                           | Shuts down and removes the sandbox. The Secret itself is unaffected and can be reused by other sandboxes.                                                                                                                                                                        |

<Warning>
  `secret_envs` differs from regular `envs`: `envs` injects the given value directly, while `secret_envs` interprets the value as a Secret name and injects its placeholder. The same env var name cannot appear in both. Substitution only happens in HTTPS request header values for hosts on the allow list — request bodies, URL query parameters, plain HTTP, and WebSocket content are forwarded as-is.
</Warning>
