Vault
Verified request, response and authorization contracts.
The Vault stores a company's logins, cards, text secrets such as API keys and tokens, and files such as service accounts and certificates. Each item has a kind (login, card, text or file) and a variable name, env_name. The member who saves an item owns it: their chat agent and every Run they start can use all of their items. share adds others: private (the default; nobody else), workspace (every Run in the company) or areas with area_ids (Runs in those Areas). The owner or a workspace owner/admin can change or delete an item. Admins see every item's metadata; members see their own items and the items shared with them. People manage items in Settings → Vault.
A Run receives the items of the member who started it, the items shared with its Area and the items shared with the workspace. When two items use the same variable, the member's own item wins over an Area item, and an Area item over a workspace item. A text item arrives as the environment variable named by env_name. Logins, cards and files are written as private files for the Run, and env_name holds the file's path; login and card files are JSON. The Run's prompt lists item names, never values. Removing a member deletes their private items; items they shared stay with the company.
Repository variables are separate: KEY=value text written into one repository's checkout .env for Runs that check it out, never into the process environment and never into another repository. Workspace owners and admins manage them.
Security. Values are encrypted at rest (AES-256-GCM with a rotating keyring) and write-only: no response, CLI output or MCP result contains one, so you replace a value and never read it back. Delivered values are redacted from Run output. A secret request mints a one-time link to /secrets/new: the token rides in the URL fragment, the server stores only its SHA-256 hash, and the link expires after one hour and works once. Saving through a link resumes whoever asked: a Run's Task receives a follow-up and a chat agent is woken. Never ask for a secret in chat; send a link instead.
Cards. Melso stores card numbers encrypted, but it is not a PCI-DSS certified card vault. Give agents virtual cards or cards with a low spending limit.
Run tokens (mrt_…) can list items and create requests; every write rejects them with 403. Request bodies are strict JSON and unknown fields return 400. The CLI (melso secret list, set, delete, request and totp) and the MCP tools (secrets_list, secrets_set, secrets_update, secrets_delete, secrets_request) wrap these routes.
Examples use the environment variables from Overview. UUID placeholders must be replaced with your own IDs.
GET /api/secrets
Auth: Workspace member or Run token. Admins see every item; members see their own items and the items shared with them.
Request: None.
Response: {items:VaultItem[]}, ordered by kind, then name. Repository variables are not listed.
Status: 200; 503 vault unavailable. Authentication and resource-access errors follow Overview.
curl -sS -X GET "$MELSO_URL/api/secrets" \
-H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID"POST /api/secrets
Auth: Workspace member; Run tokens rejected. The caller owns the item.
Request: Exactly one value: text:string (at most 64 KB), login:{url,username,password,totp_secret?}, card:{cardholder?,number,exp_month,exp_year,cvc,billing_address?} or file:{filename,content_base64} (at most 1 MB decoded). Optional kind (must match the value); name (a login or card's display name, or a text item's variable name, which env_name can also supply); env_name (required for a file; derived from name for a login or card, such as STRIPE_LOGIN); share and area_ids; replace:boolean to overwrite your item with the same variable name instead of returning 409. totp_secret is the base32 2FA setup key. A new item without share is private; a replaced item keeps its sharing.
Response: {item:VaultItem}
Status: 201; 400 invalid value, name, Area or reserved variable (MELSO_*, HOME, PATH and other runtime keys); 403 Run token; 409 variable already used; 503 vault unavailable. Authentication and resource-access errors follow Overview.
curl -sS -X POST "$MELSO_URL/api/secrets" \
-H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID" \
-H "Content-Type: application/json" -d '{"kind":"text","name":"OPENAI_API_KEY","text":"replace-with-the-key","share":"workspace"}'PATCH /api/secrets/{id}
Auth: The item's owner or a workspace owner/admin; Run tokens rejected.
Request: Path: item UUID. At least one of name (a login or card's display name, or a text item's variable; ignored for a file, whose name is its file name); env_name (login, card or file); a replacement value of the item's kind (text, login, card or file); share, with area_ids when sharing with Areas. area_ids requires share. The kind never changes.
Response: {item:VaultItem}
Status: 200; 400 nothing to change or invalid input; 403 not the owner or an admin, or a Run token; 404 unknown item; 409 variable already used; 503 vault unavailable. Authentication and resource-access errors follow Overview.
curl -sS -X PATCH "$MELSO_URL/api/secrets/$ID" \
-H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID" \
-H "Content-Type: application/json" -d '{"share":"areas","area_ids":["00000000-0000-4000-8000-000000000001"]}'DELETE /api/secrets/{id}
Auth: The item's owner or a workspace owner/admin; Run tokens rejected.
Request: Path: item UUID.
Response: Empty.
Status: 204; 403 not the owner or an admin, or a Run token; 404 unknown item; 503 vault unavailable. Authentication and resource-access errors follow Overview.
curl -sS -X DELETE "$MELSO_URL/api/secrets/$ID" \
-H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID"GET /api/secrets/repository
Auth: Workspace owner/admin, or a workspace API key with admin scope; Run tokens rejected.
Request: Query repository_url:string, an http(s) or SSH repository URL. It is normalized to https://host/owner/repo.
Response: {repository_url:string,variables:RepositoryVariable[]}; keys only.
Status: 200; 400 invalid repository URL; 403 not an owner/admin, or a Run token; 503 vault unavailable. Authentication and resource-access errors follow Overview.
curl -sS -G "$MELSO_URL/api/secrets/repository" --data-urlencode "repository_url=https://github.com/example/repo.git" \
-H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID"PUT /api/secrets/repository
Auth: Workspace owner/admin, or a workspace API key with admin scope; Run tokens rejected.
Request: repository_url:string and variables:object, mapping each key to a string value or to null to keep its stored value. The map replaces the repository's whole .env: a key left out is removed, and {} clears it.
Response: {repository_url:string,variables:RepositoryVariable[]}
Status: 200; 400 invalid key or value, missing variables, or null for a key with no stored value; 403 not an owner/admin, or a Run token; 503 vault unavailable. Authentication and resource-access errors follow Overview.
curl -sS -X PUT "$MELSO_URL/api/secrets/repository" \
-H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID" \
-H "Content-Type: application/json" -d '{"repository_url":"https://github.com/example/repo.git","variables":{"API_URL":"https://api.example.com"}}'POST /api/secret-requests
Auth: Workspace member, or the Run token of an active Run. The saved item belongs to the caller; for a Run, to the member who started it.
Request: kind (login, card, text or file) and optional non-secret prefill: name, env_name, url, username, cardholder, note (at most 500 characters, shown on the page), share, area_ids. A text request needs its variable name in name or env_name; a file request needs env_name.
Response: {request:{id,kind,expires_at},url:string}. url is https://<app>/secrets/new#<token>; the token is returned only here.
Status: 201; 400 invalid input; 403 Run token without an active Run; 503 vault unavailable. Authentication and resource-access errors follow Overview.
When a Run asked, saving sends its Task a follow-up from the member naming the item and its variable, which starts the next Run. A request from a member or an MCP client resumes nothing; list the Vault after the person saves.
curl -sS -X POST "$MELSO_URL/api/secret-requests" \
-H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID" \
-H "Content-Type: application/json" -d '{"kind":"login","url":"dashboard.stripe.com","username":"ops@example.com","note":"Download monthly payouts"}'POST /api/secret-requests/lookup
Auth: Public; the token in the body is the credential. Rate limited per IP.
Request: token:string, the link's URL fragment.
Response: {request:VaultRequestView}. Reading does not use the link.
Status: 200; 404 unknown token; 429 rate limited; 503 vault unavailable.
curl -sS -X POST "$MELSO_URL/api/secret-requests/lookup" \
-H "Content-Type: application/json" -d "{\"token\":\"$REQUEST_TOKEN\"}"POST /api/secret-requests/fulfill
Auth: Public; the token in the body is the credential. Rate limited per IP.
Request: token:string and one value of the request's kind: text, login, card or file, in the same shapes as POST /api/secrets. Optional name renames a login or card; the variable of a text or file request is fixed.
Response: {item:{kind,name,env_name}}
Status: 200; 400 invalid value or wrong kind, and the link stays usable; 404 unknown token; 409 the variable holds another kind; 410 link_expired or link_used; 429 rate limited; 503 vault unavailable.
Saving stores the item for the requester, replacing their item with the same variable name, and uses the link in one transaction.
curl -sS -X POST "$MELSO_URL/api/secret-requests/fulfill" \
-H "Content-Type: application/json" \
-d "{\"token\":\"$REQUEST_TOKEN\",\"login\":{\"url\":\"dashboard.stripe.com\",\"username\":\"ops@example.com\",\"password\":\"replace-with-the-password\"}}"POST /api/mcp/upload-secret
Auth: MCP OAuth token; uses its granted workspace. Run tokens rejected.
Request: Multipart file:binary (at most 1 MB) and env_name:string, the variable that will hold the file's path. Optional filename, share and area_ids (repeat the field or separate IDs with commas). Replaces your file with the same variable.
Response: {item:VaultItem}
Status: 201; 400 invalid upload or variable; 403 Run token; 409 the variable holds another kind; 503 vault unavailable. Authentication and resource-access errors follow Overview.
Append /upload-secret to the MCP resource URL; the same handler is mounted at /mcp/upload-secret. The file's bytes never pass through a model.
curl -sS -X POST "$MELSO_URL/api/mcp/upload-secret" \
-H "Authorization: Bearer $MCP_TOKEN" -F "file=@service-account.json" -F "env_name=GCP_SERVICE_ACCOUNT"VaultItem
Source: server/internal/vault/items.go (Item). Referenced nested objects retain their handler-defined fields; clients should tolerate additional fields.
| Field | JSON type |
|---|---|
id | string |
kind | string: login, card, text or file |
name | string |
env_name | string |
share | string: private, workspace or areas |
area_ids | string[] |
summary | VaultSummary |
size_bytes | integer |
owner | {id:string,name:string,email:string} |
last_used_at | string or null |
created_at | string |
updated_at | string |
can_manage | boolean |
VaultSummary
Source: server/internal/vault/values.go (Summary). It never holds a secret; empty fields are omitted.
| Field | JSON type |
|---|---|
site | string (may be omitted) |
url | string (may be omitted) |
username | string (may be omitted) |
has_totp | boolean (may be omitted) |
brand | string (may be omitted) |
last4 | string (may be omitted) |
exp_month | integer (may be omitted) |
exp_year | integer (may be omitted) |
filename | string (may be omitted) |
RepositoryVariable
Source: server/internal/vault/repository.go (RepositoryVariable).
| Field | JSON type |
|---|---|
key | string |
updated_at | string |
VaultRequestView
Source: server/internal/vault/requests.go (RequestView). It never holds a secret.
| Field | JSON type |
|---|---|
id | string |
kind | string |
status | string: pending, used or expired |
prefill | object with name, env_name, url, username, cardholder and note, each omitted when empty |
share | string |
area_ids | string[] |
workspace_name | string |
owner_name | string |
expires_at | string |