# Shared secrets

A key you set once for the whole account — an Anthropic or Stripe key, say. Every function of every project reads it as `env.NAME`, including projects created later. A project's own secrets are on the [Functions](functions.md) page; a project secret masks the shared secret of the same name.

The value passes through one channel: the dashboard, under a signed-in human. **No tool exposes these routes**, and no route ever returns a value. Your agent reads the names — and the note you write for it — through `list_secrets`, project by project.

All five routes take an account key of full scope. A key limited to some projects gets `403`: they act on the whole account. The four writes also need a signed-in human (`X-Account-Session`).

| Method and path | Effect |
|---|---|
| `GET /v1/account/secrets` | The account's shared secrets, sorted by name — never a value. Each carries `name`, `note`, `updated_at`, `allow_restricted_keys`, `generation`, `application` (`complete`, `functions_done`, `functions_left`), `masked_in` (the projects where a project secret masks it) and `reach` (what a new value would reach). `digest` (the SHA-256 of the value) appears only with a signed-in human. `removals` lists the removals still in progress. Works on the key alone: an agent can see what is left to apply, never apply it |
| `PUT /v1/account/secrets/:name` `{value, note?, allow_restricted_keys?}` | Set or replace. **Needs a signed-in human.** Writes the registry first, then starts the first wave. A missing field is left unchanged on a replacement; `allow_restricted_keys` is `false` at creation. A NEW name that would give a project more than 126 names is refused with `413` before anything is written; replacing a value is never refused |
| `PATCH /v1/account/secrets/:name` `{note?, allow_restricted_keys?}` | Change the note, or close the secret to agent keys limited to some projects. **Needs a signed-in human.** `allow_restricted_keys` only accepts `false` here — reopening goes through `PUT`, so through the value typed again. Closing starts a wave that takes the value off the functions those keys deployed |
| `DELETE /v1/account/secrets/:name` | Remove. **Needs a signed-in human** — an agent does not remove a key you set for all your projects. The registry first, then the first removal wave; the response carries `already_gone`. Replaying answers `already_gone: true` without a wave |
| `POST /v1/account/secrets/:name/apply` `{generation}` | **Apply the rest**: resumes the named generation at the first function not yet done. **Needs a signed-in human.** `409` when a wave is already in flight on that name, or when the generation no longer applies; `429` with `Retry-After` when no call can go out. Idempotent on `Idempotency-Key` |

## What a write answers

The four writes share one shape: `200` with `name`, `generation`, `application` (`complete`, `functions_done`, `functions_left`) and `functions: [{project_id, slug, applied, reason}]` — what the wave did. When it stopped early, or could not start, it also carries `stopped`. `DELETE` adds `already_gone`.

A value reaches the functions already running **a few at a time**: one request sends at most 20 administration calls to our infrastructure provider, and stops at the first of its bounds — those 20 calls, a spent budget (`stopped: budget`), a clock deadline (`stopped: deadline`), or the provider being busy (`stopped: platform_busy`). The registry is written either way. Call **Apply the rest** until `application.complete` is `true`.

A function that did not get the value carries its `reason`: `capacity` (it would hold too many variables), `plain_text_var` (it already has a `vars` entry of that name), `refused` (another refusal from the provider) — three that no replay repairs, and that its next deployment picks up — or `retry` (a passing failure).

## What does not happen

A shared secret never reaches another account's project: the registry is keyed by account, and a row only decrypts under its own account.

Removing a shared secret is **followed through to the last function**: a redeployment that leaves a secret out does not remove it, so the product takes it off script by script. Until the last one has lost it, the secret stays under `removals`.

Bounds are on the [limits](../../concepts/limits.md) page. The screen that types the values is [Secrets](../../dashboard/secrets.md).
