Secrets
The named-secrets vault lets operators store API keys and tokens once, then have agents reference them by name as {{secret:NAME}}. The real value is substituted only on the outbound request — it never reaches the model, the prompt, the trace, or the audit log.
What this page is for
When an agent needs to call a REST API you haven’t built a provider for, you used to have two bad options: build a whole integration for a one-off endpoint, or let the agent paste the key inline as a tool argument — where it lands in the prompt, the execution trace, and the audit log, in full view of the model.
The secrets vault is the third path. Stash the credential once under a name; the agent references it in an http call as {{secret:NAME}}; the runtime swaps in the real value at the network boundary only.
Scopes: org-wide and per-project
Every secret is scoped to an organization and, optionally, a project:
- Org-wide secrets (managed at
/settings/secrets) are visible to agents on every project. - Project-scoped secrets (the Secrets tab on a project) resolve only for issues in that project.
A project-scoped secret wins over an org-default of the same name. That means two clients can each have a secret literally named api-key without colliding — an agent on each project resolves to its own value.
Creating a secret
From /settings/secrets (or a project’s Secrets tab), click New secret and fill the drawer:
- Name — a slug (
stripe-api-key,github-readonly-pat): lowercase letters, digits, and hyphens, 3–64 characters. This is what agents type inside{{secret:…}}. - Description — shown to operators and to agents (names and descriptions only — never the value).
- Value — the secret itself. It is encrypted at rest immediately and never shown again.
- Expires (optional) — an advisory date. You’ll get a rotation-reminder notification as it approaches; the secret keeps working (expiry is advisory in v1).
The list is metadata-only: name, description, version, and an expiry badge. There’s no “reveal” — the vault never returns the plaintext to the UI.
How agents use a secret
An agent sees a values-free ## Available Secrets section in its system prompt listing the names it can reach in the current project. It references one in an http call’s URL, a header, or the body:
{
"method": "GET",
"url": "https://api.acme.com/v1/things",
"headers": { "Authorization": "Bearer {{secret:acme-jwt}}" }
}
The http tool substitutes the real value only into the outbound request. If a referenced secret doesn’t exist in scope, the call is aborted loudly with a clear error — the {{secret:NAME}} placeholder is never sent to the host.
What never sees the plaintext
- The model’s context and the prompt — only names + descriptions.
- The execution trace.
- The audit log — the recorded tool arguments keep the
{{secret:NAME}}literal. Every resolution writes asecret.readaudit row (who used which secret, when) — but never the value.
Rotation and versioning
Rotating a secret creates a new version and bumps “current”; in-flight tool calls that already resolved the previous version keep working through a short grace window, after which old versions are garbage-collected. Set or clear an advisory expiry at rotation time.
Encryption
Values are encrypted at rest with ASP.NET Data Protection, the same mechanism used for MCP, OpenAPI, and webhook credentials. See Database encryption for the optional at-rest encryption that protects the whole database.
Audit
Every create, rotate, delete, and read is recorded in the audit log: secret.created, secret.rotated, secret.deleted, and secret.read — with the acting user or agent, the scope, and (for reads) the issue context. The secret.read rows are deliberately frequent: they’re your record of who used what, when.