> ## Documentation Index
> Fetch the complete documentation index at: https://growthx-chore-remove-output-wrapper.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Credentials

> Manage encrypted secrets across environments and workflows with scoped access and secure key resolution

AI apps need a lot of API keys, database passwords, and third-party tokens. Sharing `.env` files is risky, and coding agents shouldn't see your secrets. The credentials system keeps these secrets encrypted at rest in your repository as YAML files that you commit alongside your code — no external vault subscription needed. At runtime, a single encryption key decrypts everything. Each workflow can have its own scoped credentials that override shared defaults, giving you both team-wide consistency and per-workflow flexibility.

You manage credentials through the [CLI](/packages/cli) and read them in your steps with a simple dot-notation API.

## Setting Up Credentials

Initialize credentials for your project:

```bash theme={null}
output credentials init
```

This generates two files:

* `config/credentials.key` — your 256-bit encryption key (never commit this)
* `config/credentials.yml.enc` — the encrypted YAML file (safe to commit)

Add your secrets by opening the encrypted file in your editor:

```bash theme={null}
output credentials edit
```

The CLI decrypts the file, opens it in `$EDITOR`, and re-encrypts when you save. Fill in your secrets:

```yaml theme={null}
# config/credentials.yml.enc (decrypted view)
anthropic:
  api_key: sk-ant-xxxxx
openai:
  api_key: sk-xxxxx
stripe:
  secret_key: sk_live_xxxxx
```

Then read them in any step:

```typescript theme={null}
// src/workflows/billing/steps.ts
import { step, z } from '@outputai/core';
import { credentials } from '@outputai/credentials';

export const chargeCustomer = step({
  name: 'chargeCustomer',
  inputSchema: z.object({ customerId: z.string(), amount: z.number() }),
  outputSchema: z.object({ chargeId: z.string() }),
  fn: async (input) => {
    const stripeKey = credentials.require('stripe.secret_key');
    // use stripeKey to call the Stripe API...
  }
});
```

`credentials.require()` throws a `MissingCredentialError` if the path doesn't exist. Use `credentials.get(path, defaultValue)` when you want a safe fallback instead:

```typescript theme={null}
const region = credentials.get('aws.region', 'us-east-1');
```

## Wiring LLM Keys to Environment Variables

LLM SDKs like the Anthropic and OpenAI clients read their API keys from environment variables. Rather than duplicating these keys in both your `.env` file and your encrypted credentials, you can wire credentials directly into environment variables using the `credential:` prefix.

In your `.env` file, set the variable to `credential:<dot.path>` instead of the actual key:

```bash theme={null}
# .env
ANTHROPIC_API_KEY=credential:anthropic.api_key
OPENAI_API_KEY=credential:openai.api_key
```

When the worker starts, it scans `process.env` for any values starting with `credential:` and replaces them with the decrypted values from `config/credentials.yml.enc`. By the time your first workflow runs, `ANTHROPIC_API_KEY` contains the real key — no code changes needed.

This resolution is triggered by the `outputai.hookFiles` entry in your `package.json`:

```json package.json theme={null}
{
  "outputai": {
    "hookFiles": ["node_modules/@outputai/credentials/dist/hooks.js"]
  }
}
```

See [Error Hooks](/operations/error-hooks) for more on the `hookFiles` mechanism.

The worker logs confirmation on startup:

```
Startup hooks resolved env vars {"vars":["ANTHROPIC_API_KEY","OPENAI_API_KEY"]}
```

Real environment variable values are never overwritten. If `ANTHROPIC_API_KEY` is already set in the shell or injected by your CI/CD pipeline, the credential reference is ignored. This gives you a safe override path for production deployments without touching files.

## Scoping Credentials to Environments

You likely need different API keys for development and production. The credentials system supports environment-specific files that sit alongside the global defaults:

```bash theme={null}
# Initialize production credentials
output credentials init --environment production

# Initialize development credentials
output credentials init --environment development
```

This creates a `config/credentials/` directory with per-environment files:

```text theme={null}
config/
├── credentials.yml.enc              # Global (shared defaults)
├── credentials.key                  # Global key
└── credentials/
    ├── production.yml.enc           # Production overrides
    ├── production.key               # Production key
    ├── development.yml.enc          # Development overrides
    └── development.key              # Development key
```

At runtime, the system checks `NODE_ENV` and loads the matching environment file. Only `"production"` and `"development"` are recognized — other values fall back to the global file.

Edit environment-specific credentials the same way:

```bash theme={null}
output credentials edit --environment production
```

## Scoping Credentials to Workflows

Sometimes a single workflow needs its own secrets — a dedicated API key with different permissions, or a third-party token that only one workflow uses. You can scope credentials to a specific workflow:

```bash theme={null}
output credentials init --workflow payment_processing
```

This creates credentials files inside the workflow directory:

```text theme={null}
src/workflows/payment_processing/
├── workflow.ts
├── steps.ts
├── credentials.yml.enc             # Workflow-specific secrets
└── credentials.key                 # Workflow-specific key
```

When a step runs inside this workflow, its credentials are **deep-merged** over the global ones. Workflow values win at the same path; global values fill in the rest.

**Global credentials:**

```yaml theme={null}
anthropic:
  api_key: sk-ant-global
aws:
  region: us-east-1
  secret: aws-global-secret
```

**Workflow credentials** (`payment_processing`):

```yaml theme={null}
anthropic:
  api_key: sk-ant-workflow-specific
stripe:
  secret_key: sk_live_workflow
```

**What `credentials.get()` sees inside the workflow:**

```yaml theme={null}
anthropic:
  api_key: sk-ant-workflow-specific   # Overridden by workflow
aws:
  region: us-east-1                   # From global
  secret: aws-global-secret           # From global
stripe:
  secret_key: sk_live_workflow        # Added by workflow
```

<Note>
  `--environment` and `--workflow` are mutually exclusive flags. Environment scoping applies at the global level; workflow scoping applies per-workflow.
</Note>

## Deploying to Production

In local development, the `.key` files on disk handle decryption. In production, CI/CD, and Docker — where you don't have key files — set the decryption key as an environment variable.

The system resolves keys with a fallback chain:

| Scope       | Environment Variable                        | Key File                                           |
| ----------- | ------------------------------------------- | -------------------------------------------------- |
| Global      | `OUTPUT_CREDENTIALS_KEY`                    | `config/credentials.key`                           |
| Environment | `OUTPUT_CREDENTIALS_KEY_PRODUCTION`         | `config/credentials/production.key`                |
| Workflow    | `OUTPUT_CREDENTIALS_KEY_PAYMENT_PROCESSING` | `src/workflows/payment_processing/credentials.key` |

The resolution order is: environment variable first, then key file. Workflow keys fall back to the global key if neither a workflow-specific env var nor a key file is found.

### Docker

The encrypted credentials file must be present in the image — the key alone isn't enough. Make sure your Dockerfile includes:

```dockerfile theme={null}
COPY config/ ./config/
```

The `config/` directory is not cleaned up after the build because the encrypted file is read at runtime, not build time.

Pass the decryption key as an environment variable in your Docker Compose or container config:

```yaml theme={null}
# docker-compose.yml
services:
  worker:
    image: your-worker-image
    environment:
      - OUTPUT_CREDENTIALS_KEY=${OUTPUT_CREDENTIALS_KEY}
      - NODE_ENV=production
```

### CI/CD

Store the key in your CI provider's secrets and expose it as an environment variable:

```yaml theme={null}
# GitHub Actions example
jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      OUTPUT_CREDENTIALS_KEY: ${{ secrets.OUTPUT_CREDENTIALS_KEY }}
      OUTPUT_CREDENTIALS_KEY_PRODUCTION: ${{ secrets.OUTPUT_CREDENTIALS_KEY_PRODUCTION }}
    steps:
      - run: output workflow run my_workflow
```

## Key Management

### Never commit key files

Add this to your `.gitignore`:

```gitignore theme={null}
# Credential encryption keys
*.key
```

The encrypted `.yml.enc` files are safe to commit — they can't be decrypted without the key.

### Sharing keys with your team

Key files are created with `0o600` permissions (owner read/write only). Share keys with teammates through a secure channel — a password manager, encrypted messaging, or your organization's secret sharing tool. Each developer needs the key file at the expected path, or the matching environment variable set.

### Rotating keys

To rotate a key:

1. Decrypt your current credentials: `output credentials show > /tmp/creds.yml`
2. Re-initialize with `--force` to generate a new key: `output credentials init --force`
3. Edit and paste your secrets back: `output credentials edit`
4. Distribute the new key to your team and update environment variables in production
5. Securely delete the temporary file: `rm /tmp/creds.yml`

### Verifying credentials

Use the CLI to check that credentials are correctly set up:

```bash theme={null}
# Show all decrypted credentials
output credentials show

# Get a specific value
output credentials get anthropic.api_key

# Check a workflow's credentials
output credentials get stripe.secret_key --workflow payment_processing
```

For the full API reference, see the [`@outputai/credentials` package documentation](/packages/credentials).
