> ## 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.

<Note>
  Creating, changing, or deleting vaults and credentials needs the organization developer or admin role. The organization user role can list vaults and read credential details, which never include secret values. See [Organizations and roles](/recursion/organizations-and-roles). For API calls, create a key under **API keys**. See [API keys](/recursion/api-keys).
</Note>

<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

Create one vault for each set of credentials that should travel together. A common pattern is one vault per project, or one vault per end user, with your own user id in `metadata`.

<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="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 'Idempotency-Key: release-operations-vault' \
      -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": "org_01a08a705220724f9a2fe1bcd8638c9d",
  "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. Next, add a credential to the vault.

<Accordion title="Retry a create safely">
  `createVault` accepts an optional `Idempotency-Key` header. Use one stable key when an ambiguous network failure may require a retry. The key also determines the vault id, so an unchanged vault remains recoverable after the response receipt expires; reusing it after the vault changes or is deleted returns a conflict. See [Idempotent mutations](/recursion/api#idempotent-mutations).

  Send `Idempotency-Key` as exactly one header value containing 1 to 256 visible ASCII characters. The removed body field `idempotency_key` is rejected with `400 invalid_request`.

  | You send | Result |
  | - | - |
  | The same key, exact HTTP method, escaped path, raw query, and raw body bytes while the receipt exists | The original response, with `Idempotency-Replayed: true`. No second vault is created. |
  | The same key with any different request-identity bytes while the receipt exists | `409 idempotency_conflict`. Use a new key for a new request. |
  | The same key while the first create still runs | `409 idempotency_in_progress` with `Retry-After`. Wait, then resend the same request byte for byte. |
  | The same key and create body after the response receipt expires, while the vault is unchanged | The existing vault. No second vault is created. |
  | The same key after that vault changed or was deleted | `409 conflict` on `Idempotency-Key`. Use a new key. |
  | No key, followed by an ambiguous transport failure | Do not automatically retry. The API cannot guarantee that a second unkeyed request will not create another vault. |
</Accordion>

## Add a credential

Choose the type by what the agent may see. A **Token for an MCP server** authenticates to one MCP server and is attached to its requests outside the sandbox, so the agent sees only tool results. Any command in the sandbox can read an **Environment variable**. See [How credentials reach a session](#how-credentials-reach-a-session) for the full comparison.

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

<Tabs>
  <Tab title="Console">
    1. Open the vault and click **Add credential**.
    2. Under **What will use this secret?**, choose **Token for an MCP server**.
    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="cURL">
    ```bash theme={"theme":"css-variables"}
    jq -n '{
      credential_type: "bearer_token",
      display_name: "Project tracker",
      mcp_server_url: "https://mcp.example.com/mcp",
      secret_value: env.TRACKER_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 @-
    ```

    Export `TRACKER_TOKEN` first. `jq` reads it from the environment, so the token stays out of your shell history and out of process arguments.
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "vault_id": "26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31",
  "credential_id": "91a7c4e2-5d38-46b0-a9f1-3c6e8d2b7a54",
  "organization_id": "org_01a08a705220724f9a2fe1bcd8638c9d",
  "credential_type": "bearer_token",
  "display_name": "Project tracker",
  "mcp_server_url": "https://mcp.example.com/mcp",
  "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](/recursion/mcp-servers#test-a-server-before-you-use-it).

A vault never limits where the sandbox can connect. An environment-variable secret can be sent to any host the environment's [network policy](/recursion/environments-reference#network-policy) allows.

<Accordion title="Add an environment variable">
  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>"
  }
  ```
</Accordion>

<Accordion title="Credential fields and retries">
  | Field | Applies to | Description |
  | - | - | - |
  | `credential_type` | All | Required. `bearer_token` or `env_var`. |
  | `secret_value` | All | Required. At most 64 KiB. 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`. Without it, the token is sent as `Authorization: Bearer <token>`. |
  | `display_name` | All | Optional label. |
  | `metadata` | All | Your own JSON, returned as-is. Never store secrets here. |

  To retry a create after an ambiguous network failure without adding the credential twice, send one stable `Idempotency-Key` header and resend the same request. See [Idempotent mutations](/recursion/api#idempotent-mutations).
</Accordion>

## 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](/recursion/agents).
* **At launch.** Send `vault_ids` and, optionally, `credential_refs` when you start the session.

<Tabs>
  <Tab title="Console">
    1. Open the agent and click **Launch session**.
    2. Under **Credentials**, 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="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](/recursion/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 a warning in its events: `mcp_auth_rejected` for a rejected token, `mcp_discovery_failed` otherwise.

<Accordion title="What the session receives">
  | 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.

  An agent version saved without `default_credential_refs` keeps the credentials its default vaults held when you saved it. A credential you add to one of those vaults later doesn't reach the agent's sessions until you save a new agent version that includes it.
</Accordion>

<Accordion title="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](/recursion/multi-agent#what-a-child-inherits) for the full inheritance rules.
</Accordion>

## 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`, 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="cURL">
    ```bash theme={"theme":"css-variables"}
    jq -n '{secret_value: env.NEW_TRACKER_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 @-
    ```

    Export `NEW_TRACKER_TOKEN` first. If it isn't exported, `jq` sends `null`, which keeps the current secret: the request succeeds without rotating.
  </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.

## List vaults and credentials

`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. `listVaultCredentials` returns a vault's credentials, newest first, under `vault_credentials`; the list is empty when the vault is empty. `getVaultCredential` returns one credential. Neither credential read returns secret values.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Credential vaults**.
    2. To rename a vault, open its actions menu and click **Rename**.
    3. Open a vault. The table lists each credential with its type and label.
    4. Expand a row to see its id and, for a token for an MCP server, a connection check.
  </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)"}'

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

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

## 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="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 }
```

<Accordion title="What happens to sessions and agents that used it">
  A session that used a deleted vault keeps running without its credentials. From its next turn, the vault's MCP tokens no longer reach it: MCP servers it declared are called without them, and servers only the vault provided become unavailable. A sandbox that's already running keeps the environment variables it started with; a sandbox started later, such as after an idle session's compute is released, doesn't get them. The session can record a `session_vault_deleted` warning naming the vault.

  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.
</Accordion>

## How credentials reach a session

A session uses the vaults you send at launch, or the agent's default vaults otherwise. Each credential then goes to one of two places.

```mermaid theme={"theme":"css-variables"}
flowchart TD
  launch["Session vault_ids"] -->|"when set"| session["Session"]
  defaults["Agent default vaults"] -->|"otherwise"| session
  session --> mcp["Token for an MCP server<br>sent outside the sandbox"]
  session --> env["Environment variable<br>set in the sandbox"]
```

| Type (`credential_type`) | Console name | Used for | What the agent can see |
| - | - | - | - |
| `bearer_token` | **Token for an MCP server** | 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. See **How environment variables are masked** below. |

<Accordion title="How environment variables are masked">
  Tool output masks the current value, the value without leading or trailing whitespace, the value as JSON and shell quoting print it, and its plain base64 and URL-encoded forms as `[REDACTED:vault_credential]` before the agent or the transcript sees it. It can still appear if a command transforms it some other way, if output is cut off partway through it, if it's shorter than 8 characters, if it was rotated or deleted after the sandbox started, or in a file the agent writes.
</Accordion>

A vault can also hold `integration` credentials, which grant a [native integration](/recursion/integrations#native-integrations) connection per session. [Built-in integrations](/recursion/integrations) don't use vaults: you grant those apps on the agent.

## What can go wrong

The most common problems:

* **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](/recursion/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`.
* **`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`.
* **A secret appears in the transcript.** An `env_var` value was printed in a form the mask doesn't recognize. Rotate the secret, and prefer a token for the MCP server when the service has an MCP server.

<Accordion title="Every vault and credential error">
  | Symptom or code | Cause | Fix |
  | - | - | - |
  | `400 invalid_request` on `Idempotency-Key` | The removed body field `idempotency_key` is present, or the header is malformed or sent more than once. | Remove the body field. If you want retry protection, send exactly one header value of 1 to 256 visible ASCII characters. |
  | `400 invalid_request` on `secret_value` | The value is missing or over 64 KiB. | Send `secret_value`, at most 64 KiB. |
  | `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 `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 idempotency_conflict` | The header key was reused with different method, path, query, or body bytes while its receipt exists. | Use a new key for a new request. |
  | `409 idempotency_in_progress` | The first create with this key is still running. | Wait for `Retry-After`, then resend the same request byte for byte. |
  | `409 conflict` on `Idempotency-Key` | The response receipt expired and the vault this key created was changed or deleted. | Use a new key. |
  | `503 idempotency_unavailable` | The API could not check or reserve the key. The create did not run. | Resend the same request with the same key. |
  | `429 rate_limit_exceeded` | Too many requests. | `Retry-After` tells you how long to wait. Retry only when the operation's strategy in **Endpoints** permits replay. |
  | 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](/recursion/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`. |
  | A session has a `session_vault_deleted` warning | A vault granted to the session was deleted after it started, so the session runs without its credentials. | Start a new session with a live vault. |
  | The console won't delete a vault | The vault holds a credential the console shows as read-only, such as an `integration` credential. | Delete it with `deleteVault`. |
  | A secret appears in the transcript | It was an `env_var` credential that a command printed in a form the mask doesn't recognize, or one of the other cases in **How environment variables are masked**. | Rotate the secret. Prefer a token for the MCP server when the service has an MCP server. |
</Accordion>

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

## Next steps

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

  <Card title="GitHub access" href="/recursion/github">
    Grant agents access to the repositories you choose.
  </Card>

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

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