# Environment Variables

> Store encrypted API keys and credentials.

**URL:** https://inference.sh/docs/secrets/environment
**Last updated:** 2026-10-18

---

Store encrypted API keys and credentials.

---

## Overview

Environment secrets are encrypted key-value pairs injected into your agent's runtime. Use them for API keys, tokens, and credentials you already have.

---

## Adding a secret

### Via the web app

From the **secrets** tab:

1. Open **Vaults** in the sidebar, select the **default** vault, and open the **secrets** tab ([`/vaults?tab=secrets`](https://app.inference.sh/vaults?tab=secrets))
2. Click **add secret**
3. Enter a key (e.g., `OPENAI_API_KEY`) and value
4. Click **save**

When an app is missing a required secret, the workspace setup dialog lists each key with **add**. **add** opens this tab with the add dialog prefilled and returns you to the app after save (see [How they work with apps](/docs/secrets/overview#how-they-work-with-apps)).

From the **credentials** tab, **add credential → environment variable** runs the same flow. For an API key that belongs to a provider in the catalog, use **add credential → API key**. Picking a provider links the secret to that provider's credential (BYOK), and a **who can use it** selector sets the credential scope. When you type a provider name we do not list yet, the key is still saved as a team secret (optionally with a website for the logo) — it is not the same as registering an OAuth app; for OAuth to a new service, use **connect an account → not listed? add your own OAuth provider** ([auth providers](/docs/api/rest/auth-providers)).

If you add a secret on the **secrets** tab (or with `belt secrets set`) using a key name that belongs to exactly one catalog **API key** provider — the same name as that provider's standard API key environment variable — the platform links it to your team's credential for that provider automatically. You do not need to pick the provider in the UI. Custom key names, names shared by more than one provider, and OAuth app credential names (for example your own `X_CLIENT_ID` / `X_CLIENT_SECRET` for BYOK) stay plain environment secrets; configure BYOK OAuth through **add credential** instead.

**add credential** and **add secret** require the workspace admin role. Other members can see the list of secrets with masked values. They cannot reveal, create, update, or delete them.

### Via CLI

```bash
# Set a secret (creates or updates; prompts for the value without echo)
belt secrets set OPENAI_API_KEY

# Pipe the value in scripts, with a description
printf %s "$OPENAI_API_KEY" | belt secrets set OPENAI_API_KEY -d "For GPT-4 API calls"

# Save as a provider's credential in one step (same as POST /secrets with provider)
belt secrets set GRAFANA_API_KEY --provider grafana
belt secrets set ACME_API_KEY --value-stdin --provider acme --provider-name "Acme CRM" --provider-website acme.com < key.txt

# Link an existing vault key to a provider without changing the value
belt secrets attach GRAFANA_URL grafana
belt secrets detach GRAFANA_API_KEY

# List all secrets
belt secrets list

# Get a specific secret (shows masked value)
belt secrets get OPENAI_API_KEY

# Delete a secret
belt secrets delete OPENAI_API_KEY
```

Use `--as user|team|org` (or `--team` for `--as team`) on `belt secrets set --provider …` and `belt secrets attach` to set [connection scope](/docs/secrets/credentials#connection-scope). Updating an existing key with `--provider` updates the value, then attaches it when the key was already in the vault.

For catalog **API key** providers, `belt credentials connect <provider>` prompts for each declared key (masked when the provider marks them sensitive), saves them through the secrets path, and reports connected when the server does. In CI or scripts, run the printed `belt secrets set <KEY> --value-stdin --provider <provider> < value-file` lines instead.

Your agents can now access it via environment variables:

```python
import os
api_key = os.environ["OPENAI_API_KEY"]
```

---

## Security

| Feature | Benefit |
|---------|---------|
| **Encrypted at rest** | AES-256-GCM encryption |
| **Never logged** | Not exposed in task outputs or logs |
| **Workspace scoped** | Other workspaces cannot access your secrets |
| **Runtime only** | Injected only when apps run, not stored in code |
| **Selective injection** | Only apps that declare the secret receive it |

---

## Using in apps

Apps declare which secrets they need:

```yaml
# inf.yml
secrets:
  - key: OPENAI_API_KEY
    description: For GPT-4 API calls

  - key: DATABASE_URL
    description: PostgreSQL connection string
    optional: true
```

**Required secrets** must be set before the app can run.

**Optional secrets** won't block execution if missing.

---

## Best practices

### Use descriptive names

```
✓ OPENAI_API_KEY
✓ MY_STRIPE_SECRET_KEY
✓ AWS_ACCESS_KEY_ID

✗ KEY1
✗ TOKEN
✗ SECRET
```

Some names are [reserved for platform settings](/docs/api/rest/secrets#reserved-platform-keys) (for example `STRIPE_SECRET_KEY`, `SMTP_PASSWORD`). Use a team-specific prefix when the obvious name is reserved.

### Don't commit secrets

Never put secrets in:
- Version control
- App code
- Configuration files

Store them in **vaults → secrets** instead.

### Rotate regularly

1. Generate a new key in the external service
2. Update the secret in **vaults → secrets**
3. Revoke the old key

No app changes needed.

### Use separate keys per environment

If you have staging and production:
- Use different API keys for each
- Set different secrets per workspace/environment

---

## Common secrets

| Secret | Service | Description |
|--------|---------|-------------|
| `OPENAI_API_KEY` | OpenAI | GPT-4, DALL-E, Whisper |
| `ANTHROPIC_API_KEY` | Anthropic | Claude models |
| `REPLICATE_API_TOKEN` | Replicate | ML model hosting |
| `HUGGINGFACE_TOKEN` | Hugging Face | Model downloads |
| `AWS_ACCESS_KEY_ID` | AWS | S3, Lambda, etc. |
| `AWS_SECRET_ACCESS_KEY` | AWS | (paired with above) |
| `MY_STRIPE_SECRET_KEY` (not `STRIPE_SECRET_KEY` — reserved) | Stripe | Payment processing |
| `SENDGRID_API_KEY` | SendGrid | Email delivery |
| `TWILIO_AUTH_TOKEN` | Twilio | SMS, voice |

---

## Secrets vs Credentials

| Feature | Secrets | Credentials |
|---------|---------|--------------|
| Setup | You provide the key | OAuth flow or service account |
| Management | Manual rotation | Auto token refresh |
| Best for | APIs with static keys | OAuth services (Google, X.com) |
| Examples | OpenAI, Stripe, AWS | Gmail, Google Sheets, Twitter |

**Use secrets** when you have an API key.

**Use [credentials](/docs/credentials/overview)** for OAuth services where we handle token management.

---

## Troubleshooting

### "Secret not found"

- Check the exact key name (case-sensitive)
- Verify the secret is set in **vaults → secrets**
- Make sure the app declares the secret in requirements

### "Permission denied"

- Secrets are workspace-scoped, so you need access to the workspace
- Check you're in the correct workspace: `belt me` (look for a workspace override message)
- Switch workspaces with `belt auth switch` if secrets were created in a different workspace

### App not receiving secret

- The app must declare the secret in `requirements.secrets`
- Only declared secrets are injected for security

---

## Next

→ [Credentials overview](/docs/credentials/overview)

