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

# Vaults

> Write-only credential storage, injected at the network boundary — values are never returned.

> [!NOTE]
>
> See the [Credential vault](https://vetta.sh/docs/identity/vault.md) guide for the injection model.

A vault holds credentials an agent needs but must never see. Secret values are **write-only**: they are accepted on create, injected at runtime, and **never returned** by any read.

## The vault object

**id** `string`

Unique id (e.g. `vlt_01H...`).

**object** `string`

Always `vault`.

**identity\_id** `string | null`

The [identity](https://vetta.sh/docs/api/identities.md) the vault belongs to, or `null` when it was created without one.

**display\_name** `string`

Human-readable name.

**credential\_count** `integer`

How many credentials it holds. The metadata itself comes from `GET /v1/vaults/{id}/credentials` — a vault read never embeds it.

**created\_at** `string`

Creation timestamp.

## Create a vault

`POST /v1/vaults` — scope `admin`

**display\_name** `string` *required*

Human-readable name.

**identity\_id** `string`

The identity that owns the vault. Defaults to `null`.

## Add a credential

`POST /v1/vaults/{id}/credentials`

**kind** `string` *required*

One of `env_var`, `static_bearer`, or `mcp_oauth`. **`env_var` is refused with `feature_not_configured`** —
substituting it needs an egress boundary on the sandbox's network path, and storing a secret under a
guarantee that cannot be kept is worse than refusing it. Use an MCP kind, which is injected before the
sandbox sees the value.

**key** `string` *required*

The reference name — for `env_var`, the environment variable name (e.g. `PAYMENTS_API_KEY`).

**host** `string`

Required for `env_var` — the one exact destination host the value may be substituted for. Never a wildcard.

**mcp\_server\_url** `string`

Required for `static_bearer` and `mcp_oauth` — the server URL the token is matched to.

**agent\_id** `string`

The one agent the token may be injected into. Omitted, any agent that declares `mcp_server_url` receives it. Fixed at create, like every other field: to re-bind, create a new credential and delete the old one.

**value** `string` *required*

The secret. **Write-only** — accepted here and never returned by any endpoint.

**expires\_at** `string`

Optional expiry, as an RFC 3339 timestamp with an offset.

```bash cURL theme={"system"}
curl -fsSL https://api.vetta.sh/v1/vaults/vlt_01H.../credentials \
  -H "authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{
    "kind": "mcp_oauth",
    "key": "Payments MCP",
    "mcp_server_url": "https://mcp.example.com/sse",
    "value": "••••••••"
  }'
```

```json Response theme={"system"}
{
  "id": "vcr_01H9CD...",
  "object": "vault_credential",
  "vault_id": "vlt_01H...",
  "kind": "mcp_oauth",
  "key": "Payments MCP",
  "host": null,
  "mcp_server_url": "https://mcp.example.com/sse",
  "agent_id": null,
  "placeholder_ref": "vetta_ref_9f2c...",
  "expires_at": null,
  "last_injected_at": null,
  "created_at": "2026-08-20T17:00:00Z"
}
```

> [!WARNING]
>
> Reads never include secret values. `GET` on a credential returns only metadata — `kind`, `key`, `host`, `mcp_server_url`, `agent_id`, `placeholder_ref`, `expires_at`, `last_injected_at`, `created_at`. There is no reveal route and no update route: rotation is create-new then delete-old.

## How injection works

* **`env_var`** — *not available on this deployment.* The design is an opaque placeholder in the sandbox with the real value substituted at the [egress boundary](https://vetta.sh/docs/computer/networking.md#egress-pinning-coming-soon); that boundary does not exist yet, so the route refuses the kind rather than storing a secret it cannot protect.
* **`static_bearer` / `mcp_oauth`** — injected server-side when the agent connects to the matching [MCP server](https://vetta.sh/docs/capabilities/tools.md#mcp-connector) URL. Matching is by URL: a credential is injected when its `mcp_server_url` equals one of the agent version's `mcp_servers[].url`. A credential with an `agent_id` is injected into that agent's sessions only. A credential for this platform's own `/v1/mcp` is never injected: bound to an agent under the key `ceo_<agent id>` in the vault named "Orchestrator control plane", it records that agent as your organization's CEO (`ceo_agent_id`), which gets a short-lived credential for each turn holding only what the people driving it may do. Only that one agent gets it: if you bound this URL to an agent under another key or vault name, bind it again as `ceo_<agent id>` in "Orchestrator control plane" (a company with exactly one such agent had it recorded for it when this shipped). There is no per-session credential selection.
* **A bound agent is an admin's to change.** Once a credential is bound to an agent, `PATCH`, rollback and `DELETE` on that agent need the `admin` scope. Without it they answer `403 forbidden`. A session, schedule or team roster pinned to a version older than the binding runs without the credential, unless that version is still the agent's current one.

At rest, each value is protected with envelope encryption: a per-record data key (AES-256-GCM) wrapped by a managed key service and bound to the organization and identity.

## Scopes

| Route | Scope |
| - | - |
| `POST /v1/vaults` | `admin` |
| `GET /v1/vaults` · `GET /v1/vaults/{id}` | `agents:read` |
| `DELETE /v1/vaults/{id}` | `admin` |
| `POST /v1/vaults/{id}/credentials` | `admin` |
| `GET /v1/vaults/{id}/credentials` | `agents:read` |
| `DELETE /v1/vaults/{id}/credentials/{cid}` | `admin` |

Writing a secret is an `admin` act; listing what exists is not. That split lets an agent-facing key confirm a credential is present without ever being able to add or remove one.

## Retrieve a vault

`GET /v1/vaults/{id}` — scope `agents:read`. Returns the vault object with `credential_count`. Metadata only, like every other read here — no secret value is reachable through this or any route.

```json Response theme={"system"}
{
  "id": "vlt_01H...",
  "object": "vault",
  "identity_id": "idn_01H...",
  "display_name": "payments",
  "credential_count": 3,
  "created_at": "2026-08-20T17:00:00Z"
}
```

## List vaults & credentials

```bash theme={"system"}
GET /v1/vaults                      # list vaults (cursor-paginated)
GET /v1/vaults/{id}/credentials     # list credential metadata (cursor-paginated)
```

Both accept `limit` and `after`. See [Pagination](https://vetta.sh/docs/api/pagination.md).

## Delete

```bash theme={"system"}
DELETE /v1/vaults/{id}                       # delete the vault
DELETE /v1/vaults/{id}/credentials/{cid}     # remove one credential
```

> [!WARNING]
>
> Deleting a vault **soft-deletes the record and hard-deletes its secrets**. The credential values are destroyed, not archived — this cannot be undone, and any agent that was relying on injection from this vault starts failing at the egress boundary on its next call.

```json Response theme={"system"}
{ "id": "vlt_01H...", "object": "vault", "deleted": true }
```

### Rotating a credential

There is no update route for a credential, and that is the contract rather than an omission. Rotate by **adding the new secret and then deleting the old one** — two records, two ids. That keeps `last_injected_at` attributable to exactly one secret, so an audit can still answer which value was in use at the moment of a given call.

> [card] [Back to core resources](https://vetta.sh/docs/api/agents.md)
>
> The Phase 1 resource set.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.