Skip to main content
A secret is a named value stored on the workspace that ish substitutes into an outgoing request at dispatch time. The reason it exists is the chatbot modality: when a study points at a chatbot endpoint, ish makes the HTTP call itself, and the call usually needs an API key in a header. You do not want that key sitting in the endpoint config as plain text. You store it once as a secret, reference it by name, and ish resolves it on the wire. This is the mental model and the why. For the exact flags, see the ish secret reference.

What a secret is

Each secret is a key, a write-only value, and a little metadata: an optional description, a scope, and timestamps. The key is the part you reference; the value is the part ish keeps. Keys are uppercase by convention and validate against a strict shape: start with a letter, then letters, digits, and underscores only (for example GROQ_API_KEY). Anything else is rejected before the call leaves your machine. Secrets live on the workspace, alongside site access, the sources you upload, and the people in your pool. Every study in that workspace can reference them. Configure a key once and every chatbot study you run in that workspace resolves it the same way.
Secrets are not for interactive, media, or document studies. ish only renders {{secret:KEY}} when it dispatches the request itself, which is the chatbot path. A browser-rendered URL gated behind a wall is a site access concern, not a secret one.

How a secret gets used

A chatbot endpoint config can carry placeholders anywhere in the rendered request, most often in a header:
Authorization: Bearer {{secret:GROQ_KEY}}
X-API-Key:     {{secret:CUSTOMER_BOT_KEY}}
When a study dispatches, the renderer looks up each {{secret:KEY}} in the workspace’s secret store and substitutes the stored value. The config you commit holds only the placeholder; the value never appears in it.
A missing key renders as the empty string, not an error. The request still goes out, just without the value, so the bot will most likely answer with a 401. That is the signal that a secret is unset or misspelled. Check the key before you check the bot.

Values are write-only

You set a value; you never read it back. The backend’s list endpoint returns keys and metadata only, and the CLI strips any value-shaped field client-side before printing as a second line of defense. ish secret list shows you which keys exist, their type, and their scope. It cannot show you what they hold, by design. To rotate a value, set the key again with the new value; to remove it, delete the key. This is why the input matters more than the output. Three input modes feed a value in, and you pick exactly one per call (passing two is a usage error, exit 2):

Stdin

--value-stdin reads from a pipe. Best when the value comes from another process (gcloud secrets, op read, an env var). The value never lands in your shell history.

File

--value-file <path> reads from disk. Pass - as the path to read stdin instead.

Positional

A bare value on the command line. Convenient, but it lands in shell history. Avoid it in scripts.
Prefer stdin or a file for any real key, so the secret stays out of your shell history. A bare - as the positional value is accepted as shorthand for --value-stdin: printf %s "$KEY" | ish secret set GROQ_API_KEY -.

Scope

Every secret carries a scope, set with --scope and defaulting to agent:
ScopeSet withIntent
agentdefaultThe secret an autonomous agent reaches for when wiring up a chatbot study.
project--scope projectA workspace-wide value shared across the project.
Scope is a label on where the secret belongs, not a second gate on top of workspace membership. Pick the one that describes who owns the value. Both resolve into a {{secret:KEY}} placeholder the same way.

Reserved keys belong to site access

Three key families are reserved and cannot be created or deleted as plain secrets: BASIC_AUTH_*, SESSION_COOKIE_*, and LOGIN_* (matched case-insensitively on the prefix). These are the credentials behind site access, and they are written in pairs (a username with its password, a cookie name with its value) so the backend’s pairing rule is never half-satisfied. Both ish secret set and ish secret delete reject a reserved key (exit 2, error_code: "validation_error") and point you at ish workspace site-access instead. The guard sits on delete as well as set, so a secret delete LOGIN_PASSWORD --yes can never silently drop a live site-access credential. Always manage those through the site access surface.

Secret or inline header?

Not every header value needs to be a secret. The line is sensitivity and how often it changes:
  • Inline it in the endpoint config when the value is the same across every environment and not sensitive: a vendor name, an API version, a content type.
  • Store it as a secret when the value is per-workspace, rotates, or should not be committed to a config file: an API key, a bearer token.

Where to go next

Site access

The reserved-key sibling: credentials for reaching a gated URL in an interactive study, written in pairs.

Workspaces

Where secrets live, and what else a workspace holds.

ish secret reference

Every flag for ish secret list, set, and delete.

chatbot tools

Wiring a chatbot endpoint, where {{secret:KEY}} placeholders get used.