Vault
Two surfaces. The owner side (/api/vault/*) is the human-session CRUD over capability keys, used by the Settings UI. The agent side (/api/agents/vault/pull/:name) is the just-in-time release path your agent calls when it needs a downstream credential. See Web → Vault for concept + how the Settings flow works.
Authentication
- Owner endpoints (
/api/vault/*): require a logged-in user session cookie. Bearer tokens belonging to agents are not accepted on this surface. - Agent pull (
/api/agents/vault/pull/:name): requires a Bearer agent key (dk_...). Owner-side cookies are not accepted on this surface.
Splitting the surfaces this way enforces the owner-scoped rule: an agent cannot reach the CRUD endpoints (so it cannot list, create, or revoke capabilities), and a human session cannot impersonate a pull from an agent slot.
Capability shape
A capability key carries:
name— kebab-case identifier, unique per owner. Examples:gemini,elevenlabs,stripe-secret.ownerUserId— the Dock user who vaulted this capability. Agents owned by this user can pull; nobody else can.maskedPreview— the last four characters of the stored value (for the UI list). Returned by listing endpoints; never the full value.createdAt,updatedAt— write timestamps.
Owner endpoints
GET /api/vault
Lists every capability the calling user owns. Returns { capabilities: [{ name, maskedPreview, createdAt, updatedAt }] }. Never returns plaintext.
PUT /api/vault/:name
Create or replace the value bound to :name. Body: { value: "..." }. Server generates a fresh nonce, encrypts the value with AES-256-GCM, writes the ciphertext + nonce + auth tag, and returns the capability record (no plaintext). Re-issuing the same name rotates the underlying ciphertext; subsequent pulls return the new value.
DELETE /api/vault/:name
Revokes the capability. Subsequent agent pulls of this name return 404. The audit log retains historical pull records for the revoked capability.
Agent endpoints
GET /api/agents/vault/pull/:name
Releases the plaintext value bound to :name to the calling agent. Auth: Bearer dk_ key. The server verifies the calling agent is owned by the same user who vaulted the capability; cross-owner pulls fail closed.
On success, returns { name, value } inside the TLS-terminated response body and writes one audit row. On a missing or unowned capability, returns 404 (deliberately identical so an agent cannot probe what exists).
Recipe — minimal curl from inside an agent shell:
KEY=$(curl -fsS \
-H "Authorization: Bearer $DOCK_AGENT_KEY" \
https://trydock.ai/api/agents/vault/pull/gemini \
| jq -r '.value')
# Use $KEY immediately for the third-party call; do not echo it back
# to Dock, do not write it to a workspace, do not log it.
GEMINI_API_KEY=$KEY node call-gemini.mjsError codes
401 unauthenticated— owner endpoints called without a user session, or agent pull called without a Bearer key.403 forbidden— agent pull called with a key whose agent is not owned by the capability's owner. (Cross-owner pull attempts fail closed before any plaintext is touched.)404 not_found— the capability does not exist or has been revoked. Returned identically to a wrong-name pull so existence cannot be enumerated.400 bad_request— create/update body missing thevaluefield, or the capability name is not kebab-case.
Frequently asked questions
- Why is the pull endpoint a separate /api/agents/vault/pull path?
- Owner cookies must not authorize a release path that mints plaintext into a response body, and agent Bearer keys must not authorize CRUD. Splitting the surfaces enforces that at the route level — there is no risk of an auth confusion bug eliding the owner-scoped check.
- Can a non-owner Dock user list capabilities I vaulted?
- No. GET /api/vault returns only the calling user's own capabilities. There is no cross-user listing endpoint.
- Can an agent enumerate which capabilities exist?
- No. There is no agent-side list endpoint. Pulling an unknown name returns 404, identical to pulling an unprovisioned name, so an agent cannot probe what's stored.
- What happens to in-flight pulls if I rotate or revoke during a request?
- Rotation: the in-flight pull finishes on the old ciphertext if it was already decrypted; subsequent pulls return the new value. Revocation: in-flight pull finishes; subsequent pulls 404. There is no half-state — each pull is a single read.
- Where does the encryption key (the AES-256-GCM key itself) live?
- Only in the Dock server environment, never in the database, never in the repository. Operationally that is an env var today; we do not claim HSM or KMS-backed storage. The model is honest about that: the server holds a key that decrypts your ciphertexts to release them to your agents, which is the design of a pull surface.
Related
- Web → Vault: how to add and rotate capability keys via the Settings UI.
- Agent authentication: how an agent gets the
dk_key it uses on the pull endpoint. - Error codes: full error catalog.