Skip to main content
Agent Vault agents follow a standard HTTP protocol to interact with the server. Agents handle this automatically — the canonical reference lives in the agent skill file, also served at /v1/skills/cli. This page is a reference for understanding what happens under the hood.
Agents must never extract, log, or display credential values. They reference credentials by key name only.

Bearer token

Every request an agent makes to Agent Vault requires a bearer token. How the agent gets one depends on how it connects:
MethodHow the agent gets a token
agent-vault vault runAgent Vault sets AGENT_VAULT_TOKEN on the child process automatically
agent-vault agent createOperator runs the command, supplies the returned token via AGENT_VAULT_TOKEN
Both methods deliver the same core connection details:
VariableDescription
AGENT_VAULT_ADDRBase URL of the Agent Vault server (e.g. http://127.0.0.1:14321)
AGENT_VAULT_TOKENBearer token for all Agent Vault requests.

Session types

There are two session types:
  • Vault-scoped sessions — created by agent-vault vault run. The vault is embedded in the session. No extra headers needed.
  • Instance-level agent tokens — created via agent-vault agent create. The agent must include an X-Vault header on /discover and /v1/proposals requests to select which vault to use.

The X-Vault header

Instance-level agent tokens must include X-Vault: {vault_name} on /discover and /v1/proposals requests:
GET {AGENT_VAULT_ADDR}/discover
Authorization: Bearer {AGENT_VAULT_TOKEN}
X-Vault: my-vault
Agents created via agent-vault vault run do not need this header — the vault is embedded in the session token.

Discover services

Before making any proxied request, the agent calls /discover to learn which hosts have credentials configured in the vault.
GET {AGENT_VAULT_ADDR}/discover
Authorization: Bearer {AGENT_VAULT_TOKEN}
X-Vault: my-vault
The X-Vault header is required for instance-level agent tokens. Vault-scoped sessions (from vault run) can omit it.
Response
{
  "vault": "my-vault",
  "services": [
    { "name": "stripe", "host": "api.stripe.com" },
    { "name": "github", "host": "*.github.com" },
    { "name": "slack-bot", "host": "slack.com/api/*" },
    { "name": "slack-conn", "host": "slack.com/api/apps.connections.*" }
  ],
  "available_credentials": ["GITHUB_TOKEN", "SLACK_BOT_TOKEN", "SLACK_CONNECTION_TOKEN", "STRIPE_KEY"]
}
  • services lists the services the agent can reach through Agent Vault (each entry has name and host; host carries the joined inline form, so a path-scoped service shows up as e.g. slack.com/api/*). Requests to any other host go direct. When two services share the same bare host but scope to different paths (e.g. Slack here), distinguish them by name in subsequent operations.
  • available_credentials lists credential key names in the vault (values are never exposed). Agents use these to avoid creating duplicate slots in proposals.

Route requests through HTTPS_PROXY / HTTP_PROXY

The canonical way for an agent to reach an upstream host is to call the real URL directly. agent-vault vault run pre-configures HTTPS_PROXY/HTTP_PROXY and the CA trust chain on the child process, so every standard HTTP client — curl, fetch, requests, axios, the Go stdlib, SDKs like stripe-node, CLIs like gh and stripe — transparently routes through the broker. Agent Vault intercepts the CONNECT (for https:// upstreams) or the absolute-form forward-proxy request (for http:// upstreams), matches the target host against the vault’s services, and injects the stored credential into the auth header for that service. Other client headers (vendor headers like anthropic-version, tracing IDs, etc.) flow through unchanged — see Header forwarding for the precise rules.
GET https://api.stripe.com/v1/charges?limit=10
The agent writes this line unchanged. No URL rewriting. No Authorization header. No credential in the code.

Environment set by vault run

VariablePurpose
HTTPS_PROXYPoints at the MITM listener (http://{token}:{vault}@host:14322) — plain HTTP, deploy on a trusted/private network
HTTP_PROXYSame URL as HTTPS_PROXY — plain http:// upstreams route through the same listener via absolute-form forward-proxy requests
NO_PROXYlocalhost,127.0.0.1 — so agent-to-vault traffic skips the proxy
NODE_USE_ENV_PROXY1 — enables Node.js v22.21+ built-in proxy support for fetch() and http.get()/https.get()
SSL_CERT_FILE, NODE_EXTRA_CA_CERTS, REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE, GIT_SSL_CAINFO, DENO_CERTPoint standard HTTP libraries at the Agent Vault root CA so the re-signed upstream TLS certificates validate
The MITM listener accepts both CONNECT host:port (HTTPS upstreams) and absolute-form forward-proxy requests (POST http://host/path HTTP/1.1) on the same port. Plain-HTTP upstreams are intercepted, audited, and credential-injected just like HTTPS upstreams.

Propose changes

When an agent needs access to a service that is not in the vault’s services, it creates a proposal. Each proposal bundles services (host access) and credential slots (credentials the human provides at approval time).
POST {AGENT_VAULT_ADDR}/v1/proposals
Authorization: Bearer {AGENT_VAULT_TOKEN}
Content-Type: application/json

{
  "services": [
    {
      "action": "set",
      "name": "stripe",
      "host": "api.stripe.com",
      "auth": { "type": "bearer", "token": "STRIPE_KEY" }
    }
  ],
  "credentials": [
    {
      "action": "set",
      "key": "STRIPE_KEY",
      "description": "Stripe API key",
      "obtain": "https://dashboard.stripe.com/apikeys",
      "obtain_instructions": "Developers > API Keys > Reveal test key"
    }
  ],
  "message": "Need Stripe API key for billing feature",
  "user_message": "I need access to your Stripe account to build the checkout page."
}
The response includes an approval_url that the agent presents to the user:
Response (201)
{
  "id": 1,
  "status": "pending",
  "vault": "default",
  "approval_url": "http://localhost:14321/approve/1?token=av_appr_...",
  "message": "Proposal created. Approve here: http://localhost:14321/approve/1?token=av_appr_..."
}
The agent then polls GET /v1/proposals/{id} until the status changes from pending (every 3s for the first 30s, then every 10s). Once applied, the agent retries its original request.
Every credential key referenced in a service’s auth config must resolve to either a credential slot in the same proposal or an existing credential in available_credentials. Otherwise the request returns 400.
See Proposals for the full proposal lifecycle, including storing credentials back and removing access.

Error handling

StatusMeaningWhat the agent does
401Invalid or expired tokenRe-check AGENT_VAULT_TOKEN. Contact operator for a new token or rotation.
403 forbiddenHost not allowed (only fires when the vault has unmatched_host_policy=deny; the default is passthrough)Create a proposal to request access. The response includes a proposal_hint.
403 service_disabledHost is configured but disabledSurface to the user — don’t create a duplicate proposal.
409 multiple services match host …Service mutation (PATCH/DELETE) used a bare host that matches several path-scoped servicesRetry with the canonical service name from /discover. The response body includes a candidates array.
429Rate limitedRespect the Retry-After header.
502Missing credential or upstream unreachableTell the user a credential may need to be added.
Path-based service matching changed how request log rows are filtered. The matched_service field in GET /v1/vaults/{vault}/logs rows holds the canonical service name for new rows; rows written before the upgrade still carry the matched host. Operators querying ?service= should use the slug for new rows and the host for old rows. The matched service’s host pattern is not returned in log rows — recover it by listing GET /v1/vaults/{vault}/services and filtering by name, or reading /discover.

Security constraints

  • Never extract, log, or display credential values
  • Never hardcode tokens. Always read from AGENT_VAULT_TOKEN.
  • Only reach hosts returned by /discover. For unlisted hosts, create a proposal.
  • If a credential_not_found error occurs, inform the user which key is missing.