Concepts
Vault
Encrypted credential store for agent secrets
The Vault stores third-party credentials (API keys, OAuth tokens, database passwords, etc.) that your agent needs at runtime. Credentials are encrypted at rest and only returned to an authenticated identity with the right scope.
How It Works
Store: User adds credential → Loomal encrypts it → stores ciphertext
Retrieve: Agent calls vault.get → Loomal decrypts → returns plaintext to agentCredentials are never stored in plaintext.
Credential Types
| Type | Secret Fields | Metadata |
|---|---|---|
LOGIN | password | uri, username |
API_KEY | key | service, prefix |
OAUTH | accessToken, refreshToken | provider, scopes, expiresAt |
TOTP | secret (base32 seed), backupCodes (string[]), usedBackupCodes (string[]) | issuer, account, algorithm |
SSH_KEY | privateKey, passphrase | publicKey, keyType |
DATABASE | password, connectionString | engine, host, port, database |
SMTP | password | host, port, username, encryption |
AWS | secretAccessKey, sessionToken | accessKeyId, region |
CERTIFICATE | privateKey | certificate, chain, domain, expiresAt |
CUSTOM | any key-value pairs | any key-value pairs |
Each credential is stored as one encrypted JSON blob. Secret fields go in data (encrypted), non-secret fields go in metadata (plaintext, for display).
Scopes
| Scope | Allows |
|---|---|
vault:read | List credentials, retrieve decrypted values |
vault:write | Store, update, and delete credentials |
MCP Tools
| Tool | Scope | Description |
|---|---|---|
vault.list | vault:read | List all credentials (metadata only) |
vault.get | vault:read | Get decrypted credential by name |
vault.totp | vault:read | Generate TOTP code from a credential |
vault.totp_use_backup | vault:write | Atomically consume one TOTP backup code |
vault.store | vault:write | Store or update a credential |
vault.delete | vault:write | Delete a credential |
API Endpoints
| Method | Path | Scope | Description |
|---|---|---|---|
GET | /v0/vault | vault:read | List credentials (metadata) |
GET | /v0/vault/:name | vault:read | Get decrypted credential |
GET | /v0/vault/:name/totp | vault:read | Generate TOTP code |
POST | /v0/vault/:name/totp/backup | vault:write | Consume one TOTP backup code |
PUT | /v0/vault/:name | vault:write | Store/update credential |
DELETE | /v0/vault/:name | vault:write | Delete credential |
Example: Store an API Key
curl -X PUT https://api.loomal.ai/v0/vault/stripe \
-H "Authorization: Bearer loid-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"type": "API_KEY",
"data": { "key": "sk_live_abc123..." },
"metadata": { "service": "stripe", "prefix": "sk_live_...c123" }
}'Example: Retrieve a Credential
curl https://api.loomal.ai/v0/vault/stripe \
-H "Authorization: Bearer loid-your-api-key"Returns the decrypted data along with metadata.
TOTP and Backup Codes
When you enable 2FA on a service, the provider gives you a TOTP secret (base32 seed) plus a set of single-use backup/recovery codes. Both are stored together in the same credential:
{
"type": "TOTP",
"data": {
"secret": "JBSWY3DPEHPK3PXP",
"backupCodes": ["abcd-1234", "efgh-5678", "ijkl-9012"]
},
"metadata": { "issuer": "GitHub" }
}vault.totp/GET /v0/vault/:name/totpreturns the live 6-digit code plusbackupCodesRemaining(a count). It does NOT return the actual codes.vault.get/GET /v0/vault/:namereturns the fulldata.backupCodes(unused) anddata.usedBackupCodes(audit trail) arrays. Thesecretandtotpfields are redacted; backup arrays are not — you need them readable to use them.vault.totp_use_backup/POST /v0/vault/:name/totp/backupis the safe consumption path: it atomically pops one code offdata.backupCodes, appends it todata.usedBackupCodes, and returns it. Agents should call this rather than mutating the array viavault.storeto avoid races and keep the audit trail intact.
Security
- Encrypted at rest — credentials are never stored in plaintext.
- Authenticated encryption — ciphertext is tamper-evident.
- Per-credential isolation — every credential is encrypted individually.
- Scoped access — credentials are bound to the owning identity. No other identity can read them.
- Usage tracking —
lastUsedAtupdates on every retrieval.