Skip to main content

Workspace secrets

Give an agent a credential it can use inside its sandbox, without putting it in a prompt.

At a glance

Endpoint
/v1/secrets
Scopes
secrets:read, secrets:write
Delivery
Environment variable or file
Value readable
Never

An agent with a sandbox has a shell and network access, so it can already run any command line tool. A workspace secret is how you give it something to authenticate with, without putting the credential in a prompt or a file in your repository.

What this protects, and what it does not​

A stored value is encrypted at rest and no route returns it. There is no read-back endpoint, so nothing has to guard one.

The value does not appear in a response, an event, a log line, a stored record, or anything sent to the model provider. Pantheon never puts it anywhere the model reads.

The agent can disclose its own credential. It has a shell in its own sandbox, so it can read its own environment and its own files and print the value into its own output. That is inherent to handing a credential to a program you then ask to use it. Pantheon keeps the value out of everything it controls; it cannot stop an agent from reading what it was deliberately given.

So store narrow, short lived credentials: one scope, one mailbox, one repository, an hour rather than a year. Revoke at the source when the work is done. Deleting the secret here removes Pantheon's copy; it revokes nothing at the provider that issued it, and a sandbox already running still holds what it was handed.

Store a credential​

Names are unique within your workspace. Two workspaces may each hold a ZOHO_OAUTH_TOKEN and they are different entries that never meet.

Terminal
curl -sS -X POST "$PANTHEON_BASE_URL/v1/secrets" \
  -H "Authorization: Bearer $PANTHEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ZOHO_OAUTH_TOKEN",
    "value": "1000.the-access-token",
    "description": "Zoho Mail, read only, expires in one hour"
  }'
Example JSON response
{
  "id": "0f2c1a5e-9f1d-4a0a-9b1c-2f3e4d5a6b7c",
  "name": "ZOHO_OAUTH_TOKEN",
  "description": "Zoho Mail, read only, expires in one hour",
  "fingerprint": "3b9f1c77ad0e42a1",
  "created_by": "key:8f1d2c3b",
  "updated_by": "key:8f1d2c3b",
  "created_at": "2026-09-12T09:14:02Z",
  "updated_at": "2026-09-12T09:14:02Z"
}
ValueMeaning
nameUnique in your workspace, and the default environment variable name inside the sandbox. Letters, digits and underscores, starting with a letter or an underscore.
valueThe credential. Written once and never returned by any route.
descriptionWhat this credential is for. Never put the value here.
fingerprintA short keyed digest. Two entries with the same fingerprint hold the same value. It does not reveal the value and cannot be turned back into one.
created_by, updated_byThe signed-in user, or the API key, that last wrote this entry.

Rotating with PUT keeps the id and the name, so a session that already names this secret picks up the new value the next time its sandbox is provisioned.

RouteWhat it does
POST /v1/secretsStore a credential. Needs secrets:write.
GET /v1/secretsList names, descriptions and fingerprints. Needs secrets:read.
GET /v1/secrets/{name}One entry's metadata. Needs secrets:read.
PUT /v1/secrets/{name}Rotate the value, edit the description, or both. Needs secrets:write.
DELETE /v1/secrets/{name}Forget Pantheon's copy. Needs secrets:write.

Deliver it to a sandbox​

A session names which secrets its sandbox receives. They arrive in that sandbox and in no other, and a name your workspace does not hold is refused when the session is created rather than discovered missing at run time.

Terminal
curl -sS -X POST "$PANTHEON_BASE_URL/v1/sessions" \
  -H "Authorization: Bearer $PANTHEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_slug": "mail-reader",
    "secrets": [{"name": "ZOHO_OAUTH_TOKEN"}]
  }'
TypeScript
import { PantheonClient } from "@alethi/pantheon-chat";


const pantheon = new PantheonClient({
  baseUrl: process.env.PANTHEON_BASE_URL!,
  apiKey: process.env.PANTHEON_API_KEY!,
});


await pantheon.createSecret({
  name: "ZOHO_OAUTH_TOKEN",
  value: process.env.ZOHO_OAUTH_TOKEN!,
  description: "Zoho Mail, read only, expires in one hour",
});


const session = await pantheon.createSession({
  agentSlug: "mail-reader",
  secrets: [{ name: "ZOHO_OAUTH_TOKEN" }],
});
Python
from pantheon_chat import SyncPantheonClient, SecretBinding


pantheon = SyncPantheonClient(base_url, api_key)


pantheon.create_secret(
    name="ZOHO_OAUTH_TOKEN",
    value=zoho_oauth_token,
    description="Zoho Mail, read only, expires in one hour",
)


session = pantheon.create_session(
    agent_slug="mail-reader",
    secrets=[SecretBinding(name="ZOHO_OAUTH_TOKEN")],
)

By default the secret becomes an environment variable of the same name. The agent's shell sees $ZOHO_OAUTH_TOKEN and nothing else changes.

Tools that read a file instead​

Plenty of command line tools do not read a credential from the environment. They read a file, often under a directory named by an environment variable. Both are supported, and a binding can do either or both.

Terminal
curl -sS -X POST "$PANTHEON_BASE_URL/v1/sessions" \
  -H "Authorization: Bearer $PANTHEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_slug": "mail-reader",
    "secrets": [{
      "name": "ZOHO_OAUTH_TOKEN",
      "env_var": null,
      "file_path": "/home/daytona/.config/zog/token.json"
    }],
    "env": {"ZOG_CONFIG_DIR": "/home/daytona/.config/zog"}
  }'
TypeScript
const session = await pantheon.createSession({
  agentSlug: "mail-reader",
  secrets: [{
    name: "ZOHO_OAUTH_TOKEN",
    envVar: null,
    filePath: "/home/daytona/.config/zog/token.json",
  }],
  env: { ZOG_CONFIG_DIR: "/home/daytona/.config/zog" },
});
Python
from pantheon_chat import file_secret


session = pantheon.create_session(
    agent_slug="mail-reader",
    secrets=[file_secret("ZOHO_OAUTH_TOKEN", "/home/daytona/.config/zog/token.json")],
    env={"ZOG_CONFIG_DIR": "/home/daytona/.config/zog"},
)
ValueMeaning
nameWhich stored secret to deliver.
env_varEnvironment variable to set. Defaults to name when no file_path is given. Send null with a file_path to deliver the value only as a file.
file_pathAbsolute path inside the sandbox. The file is created with mode 600 before the first run.
env (on the session)Plain environment variables. Stored as written and returned by /v1/sessions, so this is for settings, not credentials.

Two rules keep this unambiguous. Two secrets may not set the same variable or write the same file, and a plain env entry may not shadow a secret. Either is refused with HTTP 422 rather than resolved by list order.

The file is written with mode 600 before the first run, so only the sandbox user can read it. The path must be absolute and may not contain ...

What is recorded​

Every mutation writes an audit row in the same transaction as the change, readable at /v1/audit/records: secret.created, secret.updated, secret.deleted, and secret.attached when a session names one.

Each row carries the name and the fingerprint. None of them carries the value. The sandbox record shows which secrets a sandbox received, by name, for the same reason.

Scopes​

secrets:read lists and reads metadata. secrets:write creates, rotates and deletes. Neither can read a stored value, because no route returns one.

A write scope does not imply the matching read, as everywhere else in this API. Naming secrets on a session needs sessions:write and secrets:read together.

Pitfalls​

  • HTTP 503 SECRETS_NOT_CONFIGURED: The deployment has no PANTHEON_SECRETS_ENCRYPTION_KEY. On Pantheon Cloud this should never happen; if you self-host, generate a Fernet key and set it. See Self-host.
  • HTTP 400 SECRET_NOT_FOUND when creating a session: The session named a secret this workspace does not hold. Store it first, and check for a typo in the name.
  • HTTP 409 SECRET_EXISTS: A secret of that name already exists. Rotate it with PUT rather than creating a second one.
  • HTTP 422 on a session: Two bindings claim the same variable or file, or a plain env entry names the same variable as a secret. Choose one source per target.
  • The variable is missing inside the sandbox: The secret was deleted after the session named it. Resolution skips a name that no longer exists rather than failing the run; store it again and the next sandbox will have it.
  • The agent printed the credential: Expected, and unavoidable: it has a shell in that sandbox. Rotate the credential at its source and store a narrower one.

Search docs

Esc

Ask Bami

Bami answers inside your signed-in dashboard. Write your question here, then paste it into Bami with Cmd/Ctrl+/.