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

# Vaults and credentials

> Store secrets once, grant each session exactly the credentials it needs, and know what the agent can and cannot see.

A vault is a named group of credentials. You grant vaults to an agent or a session by id, and Recursion delivers each credential to the place it is needed. Secret values are write-only: no vault or credential read, in the API or the console, returns them.

The TypeScript samples assume the `rl` client from [Client setup](/managed-agents/api#client-setup).

## Before you begin

* You need the organization developer or admin role to create, change, or delete vaults and credentials. The organization user role can list vaults and read credential details, which never include secret values. See [Organizations and roles](/managed-agents/organizations-and-roles).
* For API calls, create a key under **API keys**. See [API keys](/managed-agents/api-keys).
* Decide how to split your secrets. Put credentials that should travel together in one vault. A common pattern is one vault per project, or one vault per end user, with your own user id in `metadata`.

## How credentials reach a session

```mermaid theme={"theme":"css-variables"}
flowchart LR
  vault["Vault"] --> defaults["Agent default vaults"]
  vault --> launch["Session vault_ids"]
  defaults --> session["Session"]
  launch --> session
  session --> mcp["MCP API token: attached to requests to its MCP server, outside the sandbox"]
  session --> env["Environment variable: set in the sandbox"]
```

| Type (`credential_type`) | Console name             | Used for                                                           | What the agent can see                                                                                                                                                                            |
| ------------------------ | ------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bearer_token`           | **MCP API token**        | Authenticating to one MCP server, matched by its URL.              | Nothing. The token is attached to MCP requests outside the sandbox. The agent sees only tool results.                                                                                             |
| `env_var`                | **Environment variable** | A CLI or SDK in the sandbox that reads a key from the environment. | The full value. It's a plaintext environment variable in the sandbox, so any command any agent in the session tree runs can read it, and it can appear in tool output and stay in the transcript. |

The API also accepts `webhook_secret` and `integration`. `webhook_secret` (**Webhook signing secret**) verifies deliveries to an [event source](/managed-agents/automations#store-the-signing-secret) and never reaches a sandbox. `integration` grants a [native integration](/managed-agents/integrations#native-integrations) connection through a vault. [Built-in integrations](/managed-agents/integrations) don't use vaults: you grant those apps on the agent.

<Warning>
  Never put a secret in a system prompt, a message, a file, environment settings, or any `metadata` field. Those values are stored in session history or returned by API reads.
</Warning>

## Create a vault

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Credential vaults**.
    2. Click **Create vault**.
    3. Enter a **Vault name**, then click **Create vault**.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const vault = await rl.managedAgents.createVault({
      body: {
        display_name: 'Release operations',
        metadata: { team: 'platform' },
      },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/vaults' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{"display_name": "Release operations", "metadata": {"team": "platform"}}'
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "vault_id": "26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31",
  "organization_id": "cl9x2k4f1000008l5h3g7a2bq",
  "display_name": "Release operations",
  "metadata": { "team": "platform" },
  "credential_count": 0,
  "created_at": "2026-09-17T09:12:44Z",
  "updated_at": "2026-09-17T09:12:44Z"
}
```

`display_name` is required and can be at most 256 characters. Keep `vault_id`: you grant vaults by id.

### Retry a create safely

To make a retried create return the same vault, send an `idempotency_key` of at most 128 characters in the body. The key has no expiry.

| You send                                                | Result                                          |
| ------------------------------------------------------- | ----------------------------------------------- |
| The same key and the same `display_name` and `metadata` | The existing vault. No second vault is created. |
| The same key with a different name or metadata          | `409 conflict` on `idempotency_key`.            |
| The same key after that vault was deleted               | `409 vault_creation_retired`. Use a new key.    |

## List, read, and rename vaults

`listVaults` returns every vault in your organization in one response. Each entry has a `credential_count`, so you don't need to open each vault. `getVault` returns one vault in the same shape as the create response.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Credential vaults**.
    2. To rename a vault, open its actions menu and click **Rename**.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const { vaults } = await rl.managedAgents.listVaults();

    const vault = await rl.managedAgents.getVault({
      vault_id: '26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31',
    });

    await rl.managedAgents.updateVault({
      vault_id: '26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31',
      body: { display_name: 'Release operations (prod)' },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/vaults' \
      -H "Authorization: Bearer $RECURSION_API_KEY"

    curl 'https://api.recursion.labelbox.com/managed-agents/v1/vaults/26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31' \
      -H "Authorization: Bearer $RECURSION_API_KEY"

    curl -X PATCH 'https://api.recursion.labelbox.com/managed-agents/v1/vaults/26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{"display_name": "Release operations (prod)"}'
    ```
  </Tab>
</Tabs>

An update changes only the fields you send. `metadata` is replaced as a whole object, not merged.

## Add a credential

This example adds an MCP API token. The response describes the credential and never includes `secret_value`.

<Tabs>
  <Tab title="Console">
    1. Open the vault and click **Add credential**.
    2. Set **Type** to **MCP API token**.
    3. Enter the **Server URL** and the **API token**.
    4. Optionally enter a **Label**.
    5. Under **Acknowledgement**, confirm that you understand the credential is shared, then click **Add credential**.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const credential = await rl.managedAgents.createVaultCredential({
      vault_id: '26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31',
      body: {
        credential_type: 'bearer_token',
        display_name: 'Project tracker',
        mcp_server_url: 'https://mcp.example.com/mcp',
        secret_value: process.env.TRACKER_TOKEN!,
      },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    jq -n --arg token "$TRACKER_TOKEN" '{
      credential_type: "bearer_token",
      display_name: "Project tracker",
      mcp_server_url: "https://mcp.example.com/mcp",
      secret_value: $token
    }' | curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/vaults/26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31/credentials' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      --data-binary @-
    ```

    Piping the body through `jq` keeps the token out of your shell history.
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "vault_id": "26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31",
  "credential_id": "91a7c4e2-5d38-46b0-a9f1-3c6e8d2b7a54",
  "organization_id": "cl9x2k4f1000008l5h3g7a2bq",
  "credential_type": "bearer_token",
  "display_name": "Project tracker",
  "mcp_server_url": "https://mcp.example.com/mcp",
  "injection_locations": ["headers"],
  "network_mode": "limited",
  "created_at": "2026-09-17T09:20:03Z",
  "updated_at": "2026-09-17T09:20:03Z"
}
```

To confirm the token works before a session uses it, [test the server](/managed-agents/mcp-servers#test-a-server-before-you-use-it).

### Other credential types

An environment variable needs `secret_name`, the variable name the sandbox sees. The console asks you to confirm that the agent can read the value.

```json theme={"theme":"css-variables"}
{
  "credential_type": "env_var",
  "display_name": "Acme API key",
  "secret_name": "ACME_API_KEY",
  "secret_value": "<value>"
}
```

### Credential fields

| Field                           | Applies to                | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `credential_type`               | All                       | Required. `bearer_token` or `env_var`.                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `secret_value`                  | All                       | Required. Encrypted at rest and never returned.                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `mcp_server_url`                | `bearer_token`            | Required. An `http` or `https` URL with no user name or password in it. It can't be changed later.                                                                                                                                                                                                                                                                                                                                                                                              |
| `secret_name`                   | `env_var`, `bearer_token` | For `env_var`, required: the environment variable name. It can't be changed in the console. For `bearer_token`, optional and API only: a request header name, such as `X-Api-Key`, that carries the raw token instead of `Authorization: Bearer`.                                                                                                                                                                                                                                               |
| `display_name`                  | All                       | Optional label.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `injection_locations`           | `bearer_token`            | Defaults to `["headers"]`. Accepts `headers` and `body`, but the token is always sent in a request header.                                                                                                                                                                                                                                                                                                                                                                                      |
| `network_mode`, `allowed_hosts` | All                       | Where you intend the secret to be used. `network_mode` is `limited` (default) or `unrestricted`. With `limited`, `allowed_hosts` lists hosts, and wildcards such as `*.example.com` are allowed. An explicit empty list is rejected. These settings are recorded with the credential and shown in the console. They don't limit where the secret goes. To control where the sandbox can connect, use the environment's [network policy](/managed-agents/environments-reference#network-policy). |
| `metadata`                      | All                       | Your own JSON, returned as-is. Never store secrets here.                                                                                                                                                                                                                                                                                                                                                                                                                                        |

## Grant credentials to a session

A session receives credentials in one of two ways:

* **Agent defaults.** Set `default_vault_ids` on the agent. Every session started without its own `vault_ids` receives those vaults. Optionally narrow them with `default_credential_refs`. See [Agents](/managed-agents/agents).
* **At launch.** Send `vault_ids` and, optionally, `credential_refs` when you start the session.

| You send at launch                               | The session receives                                                         |
| ------------------------------------------------ | ---------------------------------------------------------------------------- |
| Neither field                                    | The agent version's default vaults and default credential refs.              |
| `vault_ids` with one or more ids                 | Exactly those vaults. They replace the defaults; they are not added to them. |
| `vault_ids: []`                                  | No vaults.                                                                   |
| `credential_refs` with `vault_ids`               | Only the listed credentials, across all listed vaults.                       |
| `credential_refs: []` with non-empty `vault_ids` | Rejected with `400`, because it would grant nothing.                         |

`credential_refs` is an allowlist across every granted vault, not a filter inside one vault. If you list credentials from one vault only, the session receives nothing from the other vaults. List every credential the session needs.

**Order matters.** When two granted vaults hold a credential for the same MCP server URL or the same environment variable name, the vault listed first wins. Within one vault, store only one credential per server URL or variable name. The console marks a duplicate as shadowed, and it never reaches a session.

<Tabs>
  <Tab title="Console">
    1. Open the agent and click **Start session**.
    2. Under **Credential access**, check the vaults or individual credentials for this run. The agent's defaults start checked.
    3. Fill in the task, then click **Launch session**.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const session = await rl.managedAgents.startSession({
      'Idempotency-Key': 'release-status-2026-09-17',
      body: {
        agent_id: '5f0c2a1e-8b7d-4c3a-9e21-6d4f0b9a7c55',
        environment_id: '9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13',
        vault_ids: ['26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31'],
        credential_refs: [
          {
            vault_id: '26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31',
            credential_id: '91a7c4e2-5d38-46b0-a9f1-3c6e8d2b7a54',
          },
        ],
        message: 'Summarize the open release blockers.',
      },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/sessions' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Idempotency-Key: release-status-2026-09-17' \
      -H 'Content-Type: application/json' \
      -d '{
        "agent_id": "5f0c2a1e-8b7d-4c3a-9e21-6d4f0b9a7c55",
        "environment_id": "9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13",
        "vault_ids": ["26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31"],
        "credential_refs": [{
          "vault_id": "26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31",
          "credential_id": "91a7c4e2-5d38-46b0-a9f1-3c6e8d2b7a54"
        }],
        "message": "Summarize the open release blockers."
      }'
    ```
  </Tab>
</Tabs>

Every vault id must exist in your organization, or the start fails with `404` on `vault_ids`. Credentials are resolved when the session's sandbox is prepared, not at the start request. See [Sessions](/managed-agents/sessions) for the start response.

**What success means:** each MCP server authenticated by the grant contributes its tools to the session, and each environment variable is set in the sandbox. If an MCP server can't be reached or rejects the token, the session still starts without that server's tools and records an `mcp_discovery_failed` warning in its events.

### Credentials in multi-agent sessions

A subagent that is a copy of the same agent receives the same vaults and credential refs as its parent. A different agent from the roster receives only its own default vaults and credential refs, never the parent's. To share a credential, attach the same vault to both agents.

Environment-variable credentials work differently, because every agent in the tree shares one sandbox:

* They come only from the vaults granted to the root session, and they're set once, when the sandbox starts.
* Every agent in the tree, including roster agents, can read them.
* A roster agent's own environment-variable credentials are not added. Its session records a `delegated_env_credentials_unavailable` warning that names the missing variables. To make a variable available, grant its vault to the root session.

See [Multi-agent](/managed-agents/multi-agent#what-a-child-inherits) for the full inheritance rules.

## Rotate a credential

Send a new `secret_value` to store a new version of the secret. Omit it to keep the current value. You can also change `display_name`, `secret_name`, `injection_locations`, `network_mode`, `allowed_hosts`, and `metadata`. You can't change `credential_type` or `mcp_server_url`; delete the credential and add a new one instead.

<Tabs>
  <Tab title="Console">
    1. Open the vault. In the credential's actions menu, click **Edit**.
    2. Enter the new value in the **Replace** field, for example **Replace API token**.
    3. Click **Save credential**.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    await rl.managedAgents.updateVaultCredential({
      vault_id: '26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31',
      credential_id: '91a7c4e2-5d38-46b0-a9f1-3c6e8d2b7a54',
      body: { secret_value: process.env.NEW_TRACKER_TOKEN! },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    jq -n --arg token "$NEW_TRACKER_TOKEN" '{secret_value: $token}' | \
      curl -X PATCH 'https://api.recursion.labelbox.com/managed-agents/v1/vaults/26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31/credentials/91a7c4e2-5d38-46b0-a9f1-3c6e8d2b7a54' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      --data-binary @-
    ```
  </Tab>
</Tabs>

New sessions use the new value. An environment variable is set when a session's sandbox is created, so a running sandbox keeps the value it started with. Revoke the old secret at its provider only after running sessions that need it have finished.

## Delete a credential or a vault

Deleting a credential removes it from its vault. Deleting a vault hides the vault and every credential in it. In both cases, new sessions don't receive them, running sessions are not stopped, and there is no undo.

<Tabs>
  <Tab title="Console">
    1. To remove one credential, open the vault. In the credential's actions menu, click **Remove**, then confirm.
    2. To delete a vault, open it and click **Delete**, then confirm. The dialog warns you when agents still use the vault as a default.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    await rl.managedAgents.deleteVaultCredential({
      vault_id: '26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31',
      credential_id: '91a7c4e2-5d38-46b0-a9f1-3c6e8d2b7a54',
    });

    await rl.managedAgents.deleteVault({
      vault_id: '26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31',
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X DELETE 'https://api.recursion.labelbox.com/managed-agents/v1/vaults/26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31/credentials/91a7c4e2-5d38-46b0-a9f1-3c6e8d2b7a54' \
      -H "Authorization: Bearer $RECURSION_API_KEY"

    curl -X DELETE 'https://api.recursion.labelbox.com/managed-agents/v1/vaults/26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{ "deleted": true }
```

Through the API, an agent whose `default_vault_ids` still names a deleted vault can't start sessions with its defaults (`404` on `vault_ids`) until you save a new agent version without that vault, or launch with explicit `vault_ids`. The console's **Launch a session** drawer leaves the deleted vault out and says so.

## Read credentials

`listVaultCredentials` returns a vault's credentials, newest first, under `vault_credentials`. The list is empty when the vault is empty. `getVaultCredential` returns one. Neither returns secret values.

<Tabs>
  <Tab title="Console">
    1. Open the vault. The table lists each credential with its type and label.
    2. Expand a row to see its id, network settings, and, for an MCP API token, a connection check.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const { vault_credentials } = await rl.managedAgents.listVaultCredentials({
      vault_id: '26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31',
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/vaults/26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31/credentials' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

## What can go wrong

| Symptom or code                                   | Cause                                                                                                                                           | Fix                                                                                                                                  |
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `400 invalid_request` on `secret_value`           | The value is missing.                                                                                                                           | Send `secret_value`.                                                                                                                 |
| `400 invalid_request` on `mcp_server_url`         | The URL is missing, isn't `http` or `https`, or contains a user name or password.                                                               | Use the server's full endpoint URL and put the token in `secret_value`.                                                              |
| `400 invalid_request` on `secret_name`            | An `env_var` credential has no variable name.                                                                                                   | Send `secret_name`, for example `ACME_API_KEY`.                                                                                      |
| `400 invalid_request` on `allowed_hosts`          | `network_mode` is `limited` and `allowed_hosts` is an empty list.                                                                               | List at least one host, omit the field, or use `unrestricted`.                                                                       |
| `400 invalid_request` on `credential_refs`        | You sent `credential_refs: []` with vaults.                                                                                                     | Omit `credential_refs` to grant whole vaults, or list the credentials.                                                               |
| `404 not_found` on `vault_ids`                    | A vault in the start request doesn't exist, was deleted, or belongs to another organization.                                                    | Check the id with `listVaults`.                                                                                                      |
| `409 conflict` on `idempotency_key`               | The key already created a vault with different details.                                                                                         | Use the existing vault or a new key.                                                                                                 |
| A session's MCP tools are missing                 | The token's `mcp_server_url` doesn't match the server URL on the agent, the vault wasn't granted, or `credential_refs` left the credential out. | [Test the server](/managed-agents/mcp-servers#test-a-server-before-you-use-it) with the same vault, then check the session's grants. |
| An environment variable is missing in the sandbox | The credential wasn't granted, or another granted vault listed earlier defines the same name.                                                   | Check `vault_ids` order and `credential_refs`.                                                                                       |
| The console won't delete a vault                  | The vault holds a `webhook_secret` or `integration` credential, which the console treats as read-only.                                          | Delete it with `deleteVault`.                                                                                                        |
| A secret appears in the transcript                | It was an `env_var` credential, and a command printed it.                                                                                       | Rotate the secret. Prefer an MCP API token when the service has an MCP server.                                                       |

For every error code, see [Errors](/managed-agents/errors). For limits, see [Limits](/managed-agents/limits).

## Next steps

<CardGroup cols={2}>
  <Card title="Connect MCP servers" href="/managed-agents/mcp-servers">
    Add remote tools and authenticate them with a vault credential.
  </Card>

  <Card title="GitHub access" href="/managed-agents/github">
    Give `gh` a token from a vault.
  </Card>

  <Card title="Environments" href="/managed-agents/environments">
    Set up the sandbox and its network policy.
  </Card>

  <Card title="Security" href="/managed-agents/security">
    See what reaches the sandbox and how secrets are protected.
  </Card>
</CardGroup>
