> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beta.b3os.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Vault

Store credentials as encrypted connections that B3OS injects into outbound requests at the egress proxy, so agents use them without ever seeing them.

The vault holds your credentials as **connections** (`conn_`). A connection's secret is written once and never returned. When an action, a routine step or a sandbox's action call needs it, the node's egress proxy adds it to the outgoing request, and only for the hosts the connection is bound to. Used that way, your agent, the model, pack code and logs never see the value. The one exception is a value you choose to hand over: a sandbox's `secrets` can place a connection's field in an environment variable, where code in that sandbox can read it (see [use a connection](#use-a-connection)).

Secrets are encrypted at rest with AES-256-GCM, under a key per organization that is itself wrapped by the node's master key.

## Store a connection

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/vault/connections \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"pack": "io.b3os/github", "key": "api", "label": "CI bot token", "secret": {"token": "<github token>"}}'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const connection = await b3os.vault.createConnection({
      pack: "io.b3os/github",
      key: "api",
      label: "CI bot token",
      secret: { token: process.env.GITHUB_TOKEN! },
    });
    console.log(connection.id, connection.hosts, connection.secret_fields);
    ```
  **Python**

```python
    import os
    from b3os import B3OS

    b3os = B3OS()
    connection = b3os.vault.create_connection({
        "pack": "io.b3os/github",
        "key": "api",
        "label": "CI bot token",
        "secret": {"token": os.environ["GITHUB_TOKEN"]},
    })
    print(connection["id"], connection["hosts"], connection["secret_fields"])
    ```
  **MCP**

Store credentials from your own code or the dashboard, not through an agent: a secret typed into a chat ends up in the model's context. Agents then use connections by ID without seeing them.

The response shows the connection's type, bound hosts and the names of its stored secret fields, never the values. Creating connections needs `vault:write`, which the default key does not have; create a key with it in the dashboard.

## Three kinds of connection

| Kind | Give | Injected into |
|---|---|---|
| For a pack | `pack` and `key`, the connection key in the pack's manifest | Requests to the hosts the pack declares for that key |
| Custom | `type` and `hosts` (exact names or `*.` wildcards, up to 8) | Requests to those hosts |
| Verification only | `type` and no `hosts` | Nothing, ever. B3OS reads it itself, for example to check a routine webhook's signature. |

`subject` stores one credential per end user of yours, so you can keep a connection for each of your customers and filter by it.

## Secret fields by type

| `type` | `secret` fields | Injected as |
|---|---|---|
| `api_key` | `api_key` | Wherever the pack's `inject` says |
| `bearer` | `token` | `Authorization: Bearer <token>` |
| `basic` | `username`, `password` | `Authorization: Basic ...` |
| `oauth2` | `access_token`, and optionally `refresh_token`, `expires_at`, `client_id`, `client_secret` | `Authorization: Bearer <access_token>` |
| `none` | none | Nothing |

In v0.1 you bring OAuth tokens you obtained yourself; hosted consent links for OAuth providers are planned. An OAuth connection whose access token expired and could not be refreshed has `status: expired`.

## Use a connection

- **Action calls** name it in `connections`, keyed by the action's connection key; see [calling actions](/actions/call#connections).
- **Routine steps** bind it under the step's `connections`.
- **Sandboxes** list it in `api.connections`, so their action calls may use it, or place one of its fields in an environment variable with `secrets`. The environment variable puts the value inside the sandbox, where its code can read it; prefer action calls, which keep it at the proxy. See [agents in sandboxes](/sandboxes/agents#give-the-sandbox-a-credential).

Naming a connection takes `vault:run` on top of the operation's own scope.

## Rotate and delete

`PATCH /v1/vault/connections/{connection_id}` with a new `secret` replaces all its fields at once: calls in flight finish with the old value and new calls use the new one. It can also change the label, subject, params and, for custom connections, the hosts. `DELETE` removes the connection and its secret; routine steps that bind it then fail with [`connection_required`](/errors#connection_required), and sandboxes granted it lose access.

## Scopes

| Scope | For |
|---|---|
| `vault:write` | Create, update and delete connections |
| `vault:read` | List and read connection metadata; values are never returned |
| `vault:run` | Use connections in action calls and routine steps |

A sandbox's grant gets vault scopes only when it names them exactly. [Limits and FAQ](/vault/limits) has the caps.