Secrets and environment variables
Agents usually need configuration (a feature flag, a base URL) and credentials (an API key). The platform keeps these on two deliberately different surfaces: env vars you can read back, and secrets you can never read back. This page covers the per-agent surface — the quick way to configure one agent.
Secrets vs env vars
| Secrets | Env vars | |
|---|---|---|
| Read back? | No — key names only, values never returned | Yes — names and values |
| For | API keys, tokens, passwords | Non-sensitive config |
| Reaches the agent as | Environment variables | Environment variables |
| Change behavior | Rolls a new revision | Rolls a new revision |
Both arrive in your agent's process as ordinary environment variables (os.environ["MY_KEY"] in Python). The difference is what the platform will ever show back to you.
All routes on this page are management routes: sign in first, and only the agent's owner or a project admin can call them. Add ?project=<slug> when the same agent name exists in more than one project.
The per-agent surface below stores a value on exactly one agent, with no versioning. For shared, versioned, audited secrets, use the project secrets manager and bind secrets to agents — see use secrets in workloads.
Key naming rule
Every secret and env key must be a valid environment variable name: ^[A-Za-z_][A-Za-z0-9_]*$ — letters, digits, and underscores, not starting with a digit. Anything else is rejected with 400, and the two surfaces word it differently.
The secrets routes (PUT and PATCH /v1/agents/{name}/secrets) answer:
invalid secret key (must be a valid environment variable name): my-key
The env route (PATCH /v1/agents/{name}/env) answers:
invalid env var name: my-key
Manage secrets
Set secrets with the CLI
platformctl secrets set research-buddy DEMO_TOKEN=abc123
You should see:
set 1 secret(s) for research-buddy
This merges — it never touches keys you didn't name. There are no secrets list or secrets unset subcommands in the CLI today; use the API below for those.
The secrets API
| Method | Path | Behavior |
|---|---|---|
| GET | /v1/agents/{name}/secrets | List key names (never values) |
| PUT | /v1/agents/{name}/secrets | Replace the whole set with a flat map |
| PATCH | /v1/agents/{name}/secrets | Merge: set some keys, remove others |
List the key names:
curl -s "$CAI_API/v1/agents/research-buddy/secrets" \
-H "Authorization: Bearer $CAI_TOKEN"
You should see:
{"agent": "research-buddy", "keys": ["DEMO_TOKEN", "MODEL_API_KEY"]}
Merge with PATCH (this is what the CLI uses):
curl -s -X PATCH "$CAI_API/v1/agents/research-buddy/secrets" \
-H "Authorization: Bearer $CAI_TOKEN" -H 'content-type: application/json' \
-d '{"set": {"DEMO_TOKEN": "abc123"}, "remove": ["OLD_KEY"]}'
You should see:
{"agent": "research-buddy", "secrets_updated": true, "keys": ["DEMO_TOKEN", "MODEL_API_KEY"]}
A PATCH with neither set nor remove fails with 400 nothing to do: provide 'set' and/or 'remove'. Setting a key to an empty string in set removes it.
Replace everything with PUT:
curl -s -X PUT "$CAI_API/v1/agents/research-buddy/secrets" \
-H "Authorization: Bearer $CAI_TOKEN" -H 'content-type: application/json' \
-d '{"DEMO_TOKEN": "abc123", "OTHER_KEY": "xyz"}'
PUT drops every key not in your request body — including MODEL_API_KEY if one was set. Prefer PATCH (or platformctl secrets set, which PATCHes) unless you really mean "exactly these and nothing else". Empty values in a PUT body are dropped — blank means inherit.
Secret request bodies are capped at 1 MiB.
Manage env vars
| Method | Path | Behavior |
|---|---|---|
| GET | /v1/agents/{name}/env | Names and values |
| PATCH | /v1/agents/{name}/env | Merge with {"set": {...}, "remove": [...]} |
curl -s -X PATCH "$CAI_API/v1/agents/research-buddy/env" \
-H "Authorization: Bearer $CAI_TOKEN" -H 'content-type: application/json' \
-d '{"set": {"LOG_LEVEL": "debug"}}'
You should see:
{"agent": "research-buddy", "env_updated": true, "env": {"LOG_LEVEL": "debug"}}
Every change rolls a new revision
Revisions are immutable snapshots — a running revision never changes. So a secret or env change doesn't edit anything in place: it creates a brand-new revision with the new values baked in, and traffic moves to it once it's ready. Practical consequences:
- Changes are not instant. The new revision has to start before requests see the new values.
- A change is a deploy-shaped event: you'll see it in the revisions list, and you can roll traffic back.
The MODEL_API_KEY story
Every agent gets a model credential from the platform by default — you deploy, and inference just works on the platform's key. To point one agent at your own model credential (your own billing, your own rate limits), set MODEL_API_KEY as a per-agent secret:
platformctl secrets set research-buddy MODEL_API_KEY=sk-your-own-key
The per-agent secret overrides the platform default for that agent only. To verify which key is in force without exposing it, check the agent's GET /debug/config endpoint — it reports model_key_present: true/false and the resolved model, never the value.
The platform injects variables agents depend on — MODEL_BASE_URL, SANDBOX_URL, VALKEY_ADDR, QDRANT_URL, AGENT_NAME, and others. The console refuses to set these (with one deliberate exception: MODEL_API_KEY, the supported override above). The raw per-agent API does not enforce the reserved list, so with curl you can break your agent by shadowing a platform variable. Stick to your own key names.
Quick reference
| Question | Answer |
|---|---|
| Can I read a secret value back? | No. Names only, by design |
| Can I read env values back? | Yes |
| Key format | ^[A-Za-z_][A-Za-z0-9_]*$ |
| PUT vs PATCH | PUT replaces everything; PATCH merges |
| When do changes take effect? | When the new revision starts serving |
| Bring my own model key | Secret MODEL_API_KEY on the agent |
| Shared/versioned/audited secrets | Secrets manager |