Skip to main content

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.
zavu fn secrets set SENDER_ID jn76vnxet8g5nq661by3v06y1581bmmn
zavu fn secrets set DATABASE_URL postgresql://...
zavu fn secrets set OPENAI_API_KEY sk-...
defineAgent({
  senderId: process.env.SENDER_ID!,
  ...
})

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

Setting secrets

Inline

zavu 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

zavu 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)

pbpaste | zavu fn secrets set OPENAI_API_KEY -
# or
echo "$SECRET_FROM_VAULT" | zavu fn secrets set KEY -
Nothing touches disk or shell history. The - placeholder tells the CLI to read from stdin until EOF.

Listing

zavu 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 `zavu 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

zavu fn secrets unset OLD_KEY
# alias
zavu fn secrets rm OLD_KEY

When changes take effect

Setting / unsetting a secret marks the function as out of sync. The next zavu deploy rebuilds the function with the new env vars. Until then, the running function still has the old environment.
$ zavu fn secrets set NEW_KEY value
✓ Created NEW_KEY (…alue)
  Environment updates on the next `zavu deploy`. Run it now to apply.

$ zavu deploy
... function now sees process.env.NEW_KEY
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

ConstraintLimit
Key format[A-Z_][A-Z0-9_]* (uppercase env-var style)
Key length≤ 64 chars
Reserved prefixesAWS_, LAMBDA_, _HANDLER, _X_AMZN (system-reserved)
Value size≤ 4096 chars
Total secrets per function50
Total env size4 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 zavu fn init gets these injected automatically — you don’t set them yourself:
KeyValuePurpose
ZAVU_API_KEYA unique live API key scoped to this function’s projectLets the function call Zavu’s REST API.
ZAVU_API_BASE_URLThe dashboard’s Convex .site URLSo local-dev functions hit the right backend.
ZAVU_PROJECT_IDThe function’s project IDFor logging / multi-tenant code.
ZAVU_FUNCTION_IDThis function’s IDFor logging.
ZAVU_FUNCTION_SLUGThis function’s slugFor logging / URL construction.
Use them directly:
import Zavudev from "@zavudev/sdk"

const zavu = new Zavudev({
  apiKey: process.env.ZAVU_API_KEY!,
  baseURL: process.env.ZAVU_API_BASE_URL,
})
ZAVU_API_KEY is revoked automatically when you zavu 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

zavu fn secrets set OPENAI_API_KEY sk-new-value
zavu deploy
# The old key is now overwritten in storage; revoke it in OpenAI's dashboard.
Functions are project-scoped — if you have separate Zavu projects for staging vs prod, each has its own secrets.
zavu login                # pick staging project
zavu fn secrets set DB_URL postgres://staging…
zavu deploy

zavu login                # pick prod project
zavu fn secrets set DB_URL postgres://prod…
zavu deploy
zavu whoami shows the current project before each operation.
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:
# 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
  zavu fn secrets set "$key" "$val"
done < secrets.spec
Use ZAVUDEV_API_KEY env var so the CLI uses your CI’s key:
# .github/workflows/deploy.yml
env:
  ZAVUDEV_API_KEY: ${{ secrets.ZAVU_CI_KEY }}
steps:
  - run: |
      zavu fn secrets set DB_URL "${{ secrets.DB_URL }}"
      zavu fn secrets set API_KEY "${{ secrets.API_KEY }}"
      zavu deploy

API equivalence

For automation, the secret endpoints are part of the public API:
# 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 for full schemas.