Secrets
Declare and reference encrypted secrets in your circuit definition
Secrets
Circuits often need to call external services that require authentication — API keys, bearer tokens, database credentials. The secrets array in your circuit definition declares which secrets your circuit needs, and the platform stores their values in an encrypted vault. At execution time the values are injected via the ${{ secrets.* }} selector and never appear in logs, outputs, or the circuit JSON itself.
The secrets Array
The secrets key is a top-level field in your circuit definition. Each entry declares a secret by its logical key and a human-readable description.
| Property | Description | Required |
|---|---|---|
key | Logical name for the secret (used by steps/adapters via ${{ secrets.key }}) | Yes |
description | Human-readable explanation of what the secret is for | Yes |
"secrets": [
{
"key": "openai_api_key",
"description": "OpenAI API key used for the Authorization Bearer header"
},
{
"key": "database_url",
"description": "Postgres connection string for the analytics database"
}
]Referencing Secrets in Steps
Once declared, a secret is referenced anywhere in your circuit using the ${{ secrets.<key> }} selector. The most common use is in headers or body fields of a fetch or http_watch step:
"steps": [
{
"name": "chat_completion",
"function": "circuit.core.fetch",
"input": {
"url": "https://api.openai.com/v1/chat/completions",
"method": "POST",
"headers": {
"Authorization": "Bearer ${{ secrets.openai_api_key }}",
"Content-Type": "application/json"
},
"body": {
"model": "gpt-4o",
"messages": "${{ input.messages }}"
}
}
}
]Secrets can also be used in URLs, query parameters, or anywhere else a string expression is evaluated — for example "url": "https://api.example.com/v1/data?key=${{ secrets.api_key }}".
Declaring vs. Storing Values
Declaring a secret in the secrets array only tells the platform which keys your circuit expects — it does not set the value. Values are managed separately:
- Author declares
wandb_api_keyin the circuit JSON. - After uploading the circuit version, the author (or a collaborator with write access) navigates to the circuit repository → Settings → Secrets in the web UI.
- They enter the actual secret value, which is encrypted at rest and never displayed again.
- At execution time, the engine retrieves the decrypted value and substitutes it wherever
${{ secrets.wandb_api_key }}appears.
See Managing Secrets for the UI side of adding, editing, and deleting secret values.
Note: If a step references
${{ secrets.foo }}butfoowas never declared in thesecretsarray (or its value was never set in the UI), the circuit run will fail with a missing-secret error.
Best Practices
- Name descriptively: Use keys like
openai_api_keyrather thankey1— the key is visible to collaborators and in circuit JSON. - One secret per service: Avoid reusing the same key for multiple providers; rotate by updating the value, not by renaming the key.
- Scope to what's needed: Only declare secrets your circuit actually references. Unused declarations create confusion and expand the attack surface.
- Never hardcode values: The
secretsarray is for declarations only. Never paste an actual API key into the circuit JSON — it will be visible to anyone who can read the circuit. - Use the UI for values: Always set secret values through the Settings → Secrets page so they're encrypted at rest. If you accidentally commit a secret value, rotate it immediately.
Related
- Inputs — parameterize your circuit at runtime
- Selectors — the
${{ }}expression syntax - Managing Secrets — add, edit, and delete secret values in the UI