# Function Secrets & Environment Variables

> Inject env vars into your Zavu function — API keys, DB URLs, anything sensitive. Encrypted at rest, scoped per function, rotatable from the dashboard.

## Secrets

Function secrets are encrypted at rest and injected as environment variables
at runtime. The function reads them via `process.env.<KEY>` just like in any
Node program.

```sh
npx zavudev fn secrets set SENDER_ID jn76vnxet8g5nq661by3v06y1581bmmn
npx zavudev fn secrets set DATABASE_URL postgresql://...
npx zavudev fn secrets set OPENAI_API_KEY sk-...
```

```ts
defineAgent({
  senderId: process.env.SENDER_ID!,
  ...
})

defineTool({
  ...,
  handler: async () => {
    const db = postgres(process.env.DATABASE_URL!)
    // ...
  }
})
```

## Setting secrets

### Inline

```sh
npx zavudev fn secrets set OPENAI_API_KEY sk-abc123
```

The value is in your shell history — fine for non-secret config, not great
for real keys.

### From a file

```sh
npx zavudev fn secrets set GOOGLE_SERVICE_ACCOUNT --from-file ./sa.json
```

Reads the file as UTF-8. Trailing whitespace is stripped. Useful for
multi-line values (PEM keys, JSON service accounts).

### From stdin (safest)

```sh
pbpaste | npx zavudev fn secrets set OPENAI_API_KEY -
# or
echo "$SECRET_FROM_VAULT" | npx zavudev fn secrets set KEY -
```

Nothing touches disk or shell history. The `-` placeholder tells the CLI to
read from stdin until EOF.

## Listing

```sh
npx zavudev fn secrets list
```

```
key              value    synced
─────────────  ────────  ──────
SENDER_ID       …1bmmn    yes
DATABASE_URL    …5432     yes
OPENAI_API_KEY  …sk-x     no
  1 secret not yet deployed — run `npx zavudev deploy` to sync.
```

Only the last 4 characters of each value are shown. **Plaintext is never
returned by the API** — even from the dashboard, only the same last-4 is
visible.

## Removing

```sh
npx zavudev fn secrets unset OLD_KEY
# alias
npx zavudev fn secrets rm OLD_KEY
```

## When changes take effect

Setting / unsetting a secret marks the function as **out of sync**. The next
`npx zavudev deploy` rebuilds the function with the new env vars. Until then, the
running function still has the old environment.

```
$ npx zavudev fn secrets set NEW_KEY value
✓ Created NEW_KEY (…alue)
  Environment updates on the next `npx zavudev deploy`. Run it now to apply.

$ npx zavudev deploy
... function now sees process.env.NEW_KEY
```

> **Tip:**
> You can batch secret changes before a single deploy. Setting 5 secrets in a
> row results in 1 sync (the next deploy), not 5.

## Constraints

| Constraint | Limit |
|---|---|
| Key format | `[A-Z_][A-Z0-9_]*` (uppercase env-var style) |
| Key length | ≤ 64 chars |
| Reserved prefixes | `AWS_`, `LAMBDA_`, `_HANDLER`, `_X_AMZN` (system-reserved) |
| Value size | ≤ 4096 chars |
| Total secrets per function | 50 |
| Total env size | 4 KB (runtime hard limit) |

For values larger than 4 KB (large JSON blobs, certificates), upload to S3 /
Convex storage and store a URL + auth header pair instead.

## Auto-provisioned secrets

Every function created by `npx zavudev fn init` gets these injected automatically —
you don't set them yourself:

| Key | Value | Purpose |
|---|---|---|
| `ZAVU_API_KEY` | A unique live API key scoped to this function's project | Lets the function call Zavu's REST API. |
| `ZAVU_API_BASE_URL` | The dashboard's Convex `.site` URL | So local-dev functions hit the right backend. |
| `ZAVU_PROJECT_ID` | The function's project ID | For logging / multi-tenant code. |
| `ZAVU_FUNCTION_ID` | This function's ID | For logging. |
| `ZAVU_FUNCTION_SLUG` | This function's slug | For logging / URL construction. |

Use them directly:

```ts
import Zavudev from "@zavudev/sdk"

const zavu = new Zavudev({
  apiKey: process.env.ZAVU_API_KEY!,
  baseURL: process.env.ZAVU_API_BASE_URL,
})
```

> **Note:**
> `ZAVU_API_KEY` is revoked automatically when you `npx zavudev fn delete` the
> function. If you reset it manually from the dashboard's API Keys page, the
> function will start failing — redeploy to provision a new one.
>
> The auto-key has `messages:send`, `messages:read`, `contacts:read` scopes.
> For other operations create a separate scoped key and inject it as a secret.

## Encryption

Values are encrypted with AES-256-GCM, key derived via PBKDF2 (100,000
iterations, SHA-256) from the platform encryption key. Encryption happens
server-side before the value is persisted, so plaintext never lives in our
database.

Your function receives the value at deploy time as a standard environment
variable, encrypted at rest by managed encryption keys. Inside the function,
`process.env.X` returns the value.

## Common patterns

  
**Rotating an API key**

    ```sh
    npx zavudev fn secrets set OPENAI_API_KEY sk-new-value
    npx zavudev deploy
    # The old key is now overwritten in storage; revoke it in OpenAI's dashboard.
    ```
  
  
**Different secrets per environment**

    Functions are project-scoped — if you have separate Zavu projects for
    staging vs prod, each has its own secrets.

    ```sh
    npx zavudev login                # pick staging project
    npx zavudev fn secrets set DB_URL postgres://staging…
    npx zavudev deploy

    npx zavudev login                # pick prod project
    npx zavudev fn secrets set DB_URL postgres://prod…
    npx zavudev deploy
    ```

    `npx zavudev whoami` shows the current project before each operation.
  
  
**Secrets in source control (don't)**

    Never commit secret values. Commit a `.zavu/secrets.example.yml` style
    file with key names + descriptions, and have a teammate's setup script
    prompt for actual values:

    ```sh
    # setup.sh
    while read line; do
      [[ -z "$line" ]] && continue
      key=$(echo "$line" | cut -d= -f1)
      desc=$(echo "$line" | cut -d= -f2-)
      read -s -p "Enter $key ($desc): " val
      echo
      npx zavudev fn secrets set "$key" "$val"
    done < secrets.spec
    ```
  
  
**CI / automation**

    Use `ZAVUDEV_API_KEY` env var so the CLI uses your CI's key:

    ```yaml
    # .github/workflows/deploy.yml
    env:
      ZAVUDEV_API_KEY: ${{ secrets.ZAVU_CI_KEY }}
    steps:
      - run: |
          npx zavudev fn secrets set DB_URL "${{ secrets.DB_URL }}"
          npx zavudev fn secrets set API_KEY "${{ secrets.API_KEY }}"
          npx zavudev deploy
    ```
  

## API equivalence

For automation, the secret endpoints are part of the public API:

```sh
# Set
curl -X PUT https://api.zavu.dev/v1/functions/$FN_ID/secrets/SENDER_ID \
  -H "Authorization: Bearer $KEY" \
  -d '{"value":"jn76…"}'

# List (values never returned)
curl https://api.zavu.dev/v1/functions/$FN_ID/secrets \
  -H "Authorization: Bearer $KEY"

# Unset
curl -X DELETE https://api.zavu.dev/v1/functions/$FN_ID/secrets/OLD_KEY \
  -H "Authorization: Bearer $KEY"
```

See the [API reference](https://www.zavu.dev/docs/api-reference) for full schemas.
