# Password sets

A password set holds the passwords a worker uses on websites you allow, and a value goes in and never comes out.

Some work needs a secret: an API key for a service, or a sign-in for an app. A password is how you give a worker one without showing it. You save the value once and name the websites it may go to. The worker asks to use it by name, and it never sees the value.

A password set is a named group of passwords. It is a vault in Claude Managed Agents, and a password is a credential. This page explains how a worker uses a password, what kinds there are, who may answer the first use, and what happens when you remove one.

## A value goes in and never comes out

No route returns a password's value. An answer shows the password's name, where it may go, and its settings, never the secret. The routes that save a password never take an `Idempotency-Key`, because Little Worker keeps no copy of a value to replay.

Here is how a worker uses a password:

1. The worker calls `password.use` with the password's name. It does not see the value.
2. The commands it runs next find a stand-in under the environment variable named in `secret_name`, such as `$BING_KEY`.
3. When a command sends that stand-in over HTTPS to one of the password's websites, the computer's proxy puts the real value in on the way out.
4. The proxy masks the value out of what those websites answer and out of everything the command prints, and Little Worker masks it again before anything is stored. Any password value in a tool use in a task's events is masked.

A password does not work on other websites, over plain HTTP, or for signing requests.

## Kinds of sets

| Set | What it holds |
| --- | --- |
| A worker's own set | The passwords saved on that worker. It goes with the worker, and it cannot be archived or deleted. It holds environment variable passwords only. |
| The workspace's shared set | Passwords every worker of the workspace may use, and app sign-ins. It cannot be archived or deleted. |
| A set you make | A set with a `display_name` and optional `metadata`. It can be archived and deleted. |

`GET /password_sets` lists them all, newest first. Add `include_archived=true` to see archived sets.

### Personal sets

A set you make can be personal. Send `owner: "me"`, or your own `user_` id, with `POST /password_sets`. A personal set belongs to you, and every answer shows `owner`: your `user_` id for a personal set, or `null` for a set the workspace shares.

- Only the tasks you start, in the app or with a key you made, may name a personal set in `password_set_ids`. Any other task gets `400` naming the set.
- Only you see the set. It is left out of everyone else's lists, and their reads of it are `404`.
- A set cannot be made for another member, and its `owner` never changes.
- A worker's own set and the shared set are never personal.

A task that signs in with another member's personal set takes messages, send backs and outcomes only from that member. Anyone else gets `403` with `error_code` `not_task_starter`.

## Which sets a task uses

A task signs in with the sets in its `password_set_ids`, in order. Left out, that is the worker's own set, then the workspace's shared set. Name your own list, up to 20 sets, and it replaces those defaults. The list cannot change after the task is made. When more than one set holds a sign-in for the same app, the first set in the list is the one used.

A [routine](/concepts/routines) has its own `password_set_ids`, and copies them onto each task it makes.

## Kinds of passwords

A password's `auth.type` says what it is.

| `auth.type` | What it is | Where it may be saved |
| --- | --- | --- |
| `environment_variable` | A secret a command reads through an environment variable, such as an API key. | Any set. |
| `static_bearer` | A fixed token for an app, such as an app's API key. Little Worker sends it as `Authorization: Bearer` to the app at `app_url`. | The shared set, or a set you made. |
| `mcp_oauth` | An app's sign-in. Little Worker sends the token to the app at `app_url` and refreshes it at `token_endpoint` when it is close to `expires_at`. | The shared set, or a set you made. |

A set holds one sign-in for each app, and a second is `409`. A taken `secret_name` in a set is also `409`.

### An environment variable password

```bash
curl https://core.thelittleworker.com/api/v1/password_sets/password_set_5a1e9d3b7c4f4a82b0d6e8f2c1a79b35/passwords \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "content-type: application/json" \
  -d '{
    "display_name": "Bing key",
    "auth": {
      "type": "environment_variable",
      "secret_name": "BING_KEY",
      "secret_value": "replace-with-the-real-key",
      "networking": { "type": "limited", "allowed_hosts": ["api.bing.microsoft.com"] }
    }
  }'
```

The answer has no `secret_value`:

```json
{
  "type": "password",
  "id": "password_91c7e3a5b2d84f06a4e9c0d7f3b1a258",
  "password_set_id": "password_set_5a1e9d3b7c4f4a82b0d6e8f2c1a79b35",
  "display_name": "Bing key",
  "metadata": {},
  "auth": {
    "type": "environment_variable",
    "secret_name": "BING_KEY",
    "networking": { "type": "limited", "allowed_hosts": ["api.bing.microsoft.com"] },
    "injection_location": { "header": true, "body": false }
  },
  "permission": "ask",
  "created_at": "2026-10-25T22:41:07.318Z",
  "updated_at": "2026-10-25T22:41:07.318Z",
  "archived_at": null
}
```

The rules for this kind:

- `secret_value` is 8 to 4,096 characters.
- `secret_name` is an environment variable name of up to 255 characters. Names the computer uses, such as `PATH` or `https_proxy`, are refused in any case. A name cannot change after the password is made.
- `networking.allowed_hosts` holds 1 to 16 hostnames, `*.` wildcards, or public IPv4 addresses. Private and special addresses are refused, and so are wildcards over shared hosting. A value goes to an IPv4 address only when the website's certificate lists it.
- `injection_location` says where the real value may go: `header` and `body`. Left out, it is `{ "header": true, "body": false }`. Inside it, a field left out is off, and both off is refused.

### An app sign-in

An `mcp_oauth` password holds `app_url`, `access_token`, `expires_at`, and a `refresh` with `client_id`, `refresh_token`, `token_endpoint`, `token_endpoint_auth`, `scope` and `resource`. An answer shows everything but the tokens and the client secret. `app_url`, `token_endpoint` and `client_id` cannot change.

`POST /password_sets/{password_set_id}/passwords/{password_id}/mcp_oauth_validate` checks a sign-in. Little Worker sends the app's MCP `initialize` with the token and, if the app answers `401` or `403`, tries the refresh token once. The answer has a `status` of `valid`, `invalid` or `unknown`, `has_refresh_token`, `validated_at`, and the failing `mcp_probe` and the `refresh` tried. Every secret in them is masked.

A `static_bearer` password has an `https` `app_url` and a `token` of 1 to 8,192 characters. An answer shows `app_url` and never the token. A change may send a new `token`, and `app_url` cannot change.

## Who answers a first use

Every password has a `permission`:

| `permission` | What it means |
| --- | --- |
| `ask` | The first use in a task waits for a person's Allow. |
| `always` | The worker uses the password without asking. |

Left out, it is `always` for a key made with **Can allow actions** and `ask` for any other key. Only such a key may set `always`. Any key may set `ask`. Only a person, in the app, may loosen a password that was saved in the app.

When a password's permission is `ask`, the worker's first use of it in a task stops the task for an approval. Only a person can answer that approval, in the app. A key that tries gets `403` with `error_code` `person_required`, whatever it was made with. The approval shows `tool_name` `password.use`, and its `input` holds only the password's `name`. See [Approvals](/concepts/approvals).

## Change, archive and delete

- **Change** a set with `POST /password_sets/{password_set_id}` to rename it or change its labels. `metadata` merges: a string sets a label, and `null` removes it. A set holds up to 16 labels. An archived set cannot change.
- **Archive** a set with `POST /password_sets/{password_set_id}/archive`. It wipes every value the set holds and keeps its records. It is one way. Archive a single password the same way, and its value is wiped.
- **Delete** a set with `DELETE /password_sets/{password_set_id}`. It answers `{ "type": "password_set_deleted", "id" }` and removes the set and its passwords. A single password deletes the same way and answers `password_deleted`.

A task at work reads a password for each command. So its next command uses a new value, and an archived or deleted password is not sent again. A command that already holds the old value keeps it.

A set that is archived or deleted pauses every [routine](/concepts/routines) that names it, with a `paused_reason` that says which. Unpausing such a routine is `400` until you change its `password_set_ids`.

> [!WARNING]
> If a password leaked, rotate it first at the website that issued it. Then replace its value here with `POST /password_sets/{password_set_id}/passwords/{password_id}`, or archive or delete it. Never save an API key for Little Worker itself as a password a worker can read.

Settings can send a notice when a set or a password is created, archived or deleted, and when an app sign-in's refresh fails (`password.refresh_failed`).

## Limits

| What | Limit |
| --- | --- |
| Passwords in a set | 256 |
| Sets a task names | 20 |
| Labels on a set | 16 |
| Set name | 255 characters |
| `secret_value` | 8 to 4,096 characters |
| `secret_name` | 255 characters |
| `allowed_hosts` | 1 to 16 entries |
| Token of a `static_bearer` password | 1 to 8,192 characters |

## Next steps

- [Approvals](/concepts/approvals): how the first use of a password is asked and answered.
- [Apps](/concepts/apps): the apps whose sign-ins a password holds.
- [Tasks](/concepts/tasks): how a task gets its `password_set_ids`.
