Skip to main content

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

SecretsEnv vars
Read back?No — key names only, values never returnedYes — names and values
ForAPI keys, tokens, passwordsNon-sensitive config
Reaches the agent asEnvironment variablesEnvironment variables
Change behaviorRolls a new revisionRolls 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.

Sharing a secret across agents?

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

MethodPathBehavior
GET/v1/agents/{name}/secretsList key names (never values)
PUT/v1/agents/{name}/secretsReplace the whole set with a flat map
PATCH/v1/agents/{name}/secretsMerge: 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 replaces the whole set

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

MethodPathBehavior
GET/v1/agents/{name}/envNames and values
PATCH/v1/agents/{name}/envMerge 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.

Reserved names

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

QuestionAnswer
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 PATCHPUT replaces everything; PATCH merges
When do changes take effect?When the new revision starts serving
Bring my own model keySecret MODEL_API_KEY on the agent
Shared/versioned/audited secretsSecrets manager