Skip to Content

Vault API

The Vault API provides programmatic access to the Credential Vault. It covers credentials, folder management, folder sharing, device trust, vault lock/unlock, PIN management, and audit logs.

All Vault endpoints sit under /api/vault/ and require a valid Bearer token. Every request except /api/vault/lock, /api/vault/unlock, and /api/vault/pin also requires that the authenticated user’s vault is currently unlocked — a locked vault returns HTTP 423 Locked with error code VAULT_LOCKED regardless of the caller’s permissions.

Vault access control is ownership-based: a user can only read, update, or delete credentials and folders they created, or resources shared with them via a VaultFolderShare record.


Authentication

All requests must include an Authorization: Bearer <token> header.

Authorization: Bearer <access_token>

Vault Lock

Get lock status

GET/api/vault/lockRequires: (any authenticated user)

Returns whether the caller’s vault is currently locked, when it was locked, and whether a PIN has been configured. This endpoint does not require the vault to be unlocked.

Success response

{ "locked": true, "lockedAt": "2025-06-08T10:00:00.000Z", "hasPin": true }

Lock the vault

POST/api/vault/lockRequires: (any authenticated user)

Locks the caller’s vault by setting the lock timestamp to the current time. Requires that a PIN has already been configured. If no PIN is set, the request is rejected with PIN_NOT_SET — you cannot lock a vault that has no unlock mechanism.

Request body: none required.

Success response

{ "success": true }

Error codes

CodeHTTPDescription
PIN_NOT_SET400No PIN has been configured for this user’s vault

Unlock the vault

POST/api/vault/unlockRequires: (any authenticated user)

Unlocks the caller’s vault by verifying the supplied PIN against the stored bcrypt hash. On success, clears the lock timestamp. After five consecutive failures within 15 minutes, the vault enforces a 15-minute lockout and all unlock attempts return 429.

Request body

{ "pin": "1234" }

Success response

{ "success": true }

Error codes

CodeHTTPDescription
INVALID_PIN403PIN does not match
TOO_MANY_ATTEMPTS429Five or more consecutive failures — retry after 15 minutes

PIN Management

Get PIN status

GET/api/vault/pinRequires: (any authenticated user)

Returns whether a PIN has been set for the caller’s vault. Does not require the vault to be unlocked.

Success response

{ "hasPin": false }

Set or change PIN

POST/api/vault/pinRequires: (any authenticated user)

Sets a new PIN or replaces the existing PIN. PIN must be 4–8 digits (numeric only). If a PIN already exists, currentPin is required and must match before the change is accepted. The new PIN is stored as a bcrypt hash (12 rounds).

Request body (first-time setup)

{ "pin": "9182" }

Request body (changing an existing PIN)

{ "currentPin": "9182", "pin": "4471" }

Success response

{ "pinSet": true }

Error codes

CodeHTTPDescription
INCORRECT_CURRENT_PIN403currentPin does not match the stored hash
VALIDATION_ERROR400PIN is not 4–8 digits, or currentPin is missing when a PIN already exists

Overview

Get vault overview

GET/api/vault/overviewRequires: (vault unlocked)

Returns a dashboard summary including credential health, 30-day audit statistics, and the ten most recent audit entries.

Success response

{ "ok": true, "data": { "health": { "score": 82, "total": 34, "weak": 2, "reused": 1, "old": 3, "expiring": 0, "compromised": 0 }, "auditStats": { "windowDays": 30, "totalEvents": 147, "reveals": 42, "copies": 18, "creates": 11, "deletes": 3, "shares": 5 }, "recentActivity": [ { "id": "audit_abc123", "action": "REVEAL", "resourceType": "CREDENTIAL", "resourceName": "Production DB – main", "userId": "user_xyz", "userName": "Alice Rossi", "timestamp": "2025-06-08T09:55:12.000Z", "ipAddress": "203.0.113.42", "riskScore": 5 } ] } }

Credentials

List credentials

GET/api/vault/credentialsRequires: (vault unlocked)

Returns a paginated list of credentials owned by the caller or shared with the caller via a folder share. Secret fields are never returned in list responses — use the reveal endpoint to decrypt individual credentials.

Query parameters

ParameterTypeDescription
folderIdstringFilter to credentials in a specific folder
typestringFilter by credential type (e.g. LOGIN, API_KEY, SSH_KEY)
searchstringFull-text search against credential names
pageintegerPage number (default: 1)
limitintegerItems per page (default: 50, max: 100)

Success response

{ "ok": true, "data": { "data": [ { "id": "cred_abc123", "name": "Production DB – main", "type": "DATABASE", "folderId": "folder_xyz", "tags": ["production", "postgres"], "favorite": false, "healthStatus": "STRONG", "expiresAt": null, "createdAt": "2025-03-01T12:00:00.000Z", "updatedAt": "2025-05-20T08:30:00.000Z", "lastAccessedAt": "2025-06-08T09:55:12.000Z" } ], "pagination": { "page": 1, "limit": 50, "total": 34, "totalPages": 1 } } }

Create a credential

POST/api/vault/credentialsRequires: (vault unlocked)

Creates a new credential. Secret fields are encrypted with AES-256-GCM and stored in the encryptedData column. The type determines which secret fields are accepted.

Request body

{ "name": "Stripe live key", "type": "API_KEY", "folderId": "folder_abc", "tags": ["billing", "stripe"], "notes": "Used by the billing service. Rotate quarterly.", "expiresAt": "2026-01-01T00:00:00.000Z", "apiKey": "sk_live_..." }

Secret fields by type

TypeAccepted Secret Fields
LOGINusername, password, url
API_KEYapiKey
SSH_KEYprivateKey, username
CERTIFICATEprivateKey, data
WIFIpassword, data
DATABASEusername, password, url
SERVERusername, password, url
CUSTOMdata

Success response

{ "ok": true, "data": { "id": "cred_new123", "name": "Stripe live key", "type": "API_KEY", "folderId": "folder_abc", "tags": ["billing", "stripe"], "favorite": false, "healthStatus": "STRONG", "expiresAt": "2026-01-01T00:00:00.000Z", "createdAt": "2025-06-08T10:00:00.000Z" } }

Error codes

CodeHTTPDescription
VALIDATION_ERROR400name or type missing, or type is not a valid enum value
NOT_FOUND404folderId does not exist or is not accessible to the caller

Get a credential

GET/api/vault/credentials/[id]Requires: (vault unlocked)

Returns a single credential’s metadata. Secret fields are not included. Use the /reveal endpoint to decrypt secrets.

Success response: same shape as a single item in the list response, without pagination.

Error codes

CodeHTTPDescription
NOT_FOUND404Credential does not exist or is not accessible to the caller

Update a credential

PATCH/api/vault/credentials/[id]Requires: (vault unlocked)

Partially updates a credential. All fields are optional. Secret fields in the request body are only written when they are present and non-empty — omitting a secret field or sending an empty string leaves the existing encrypted value intact. This prevents metadata-only edits from accidentally erasing passwords.

Request body (partial)

{ "name": "Stripe live key (rotated 2025-06)", "tags": ["billing", "stripe", "rotated"], "expiresAt": "2027-01-01T00:00:00.000Z" }

Success response

{ "ok": true, "data": { "id": "cred_new123", "name": "Stripe live key (rotated 2025-06)", "updatedAt": "2025-06-08T10:30:00.000Z" } }

Error codes

CodeHTTPDescription
NOT_FOUND404Credential does not exist or is not accessible to the caller

Delete a credential

DELETE/api/vault/credentials/[id]Requires: (vault unlocked)

Permanently deletes the credential and its encrypted data. This action cannot be undone. The deletion is recorded in the audit log.

Success response

{ "success": true }

Error codes

CodeHTTPDescription
NOT_FOUND404Credential does not exist or is not accessible to the caller

Reveal a credential

POST/api/vault/credentials/[id]/revealRequires: (vault unlocked)

Decrypts the credential and returns its secret fields in plaintext. Rate-limited to 10 requests per minute per auth token. Every successful reveal is written to the audit log with the action REVEAL.

Request body

{ "deviceId": "device_abc123" }

deviceId is optional. When supplied, the reveal is associated with the named trusted device in the audit entry.

Success response (DATABASE credential example)

{ "ok": true, "data": { "id": "cred_abc123", "name": "Production DB – main", "type": "DATABASE", "secrets": { "username": "app_user", "password": "s3cr3t!Pass", "url": "postgres://db.example.com:5432/prod" } } }

Error codes

CodeHTTPDescription
NOT_FOUND404Credential does not exist or is not accessible
VAULT_LOCKED423Vault is locked
RATE_LIMITED429Reveal rate limit exceeded (10/min)

Copy a credential

POST/api/vault/credentials/[id]/copyRequires: (vault unlocked)

Identical to reveal but records a COPY audit action instead of REVEAL. Use this endpoint when building clipboard-copy integrations so that clipboard access is distinguishable from screen-display access in the audit log. Rate-limited to 10 requests per minute per auth token.

Request body: same as /reveal — deviceId optional.

Success response: same shape as /reveal.

Error codes: same as /reveal.


Folders

List folders

GET/api/vault/foldersRequires: (vault unlocked)

Returns folders accessible to the caller (owned or shared). Supports three query modes via the parentId parameter.

Query parameters

ParameterTypeDescription
parentIdstringroot = top-level folders only; a folder ID = direct children of that folder; absent = all folders flat

Success response

{ "ok": true, "data": [ { "id": "folder_abc", "name": "Infrastructure", "parentId": null, "color": "#6366f1", "icon": "server", "isShared": true, "isPersonal": false, "createdAt": "2025-01-15T09:00:00.000Z" } ] }

Create a folder

POST/api/vault/foldersRequires: (vault unlocked)

Creates a new folder. Optionally nest it under an existing folder with parentId.

Request body

{ "name": "Infrastructure", "parentId": null, "color": "#6366f1", "icon": "server" }

Success response

{ "success": true, "data": { "id": "folder_new456", "name": "Infrastructure", "parentId": null, "color": "#6366f1", "icon": "server", "isShared": false, "isPersonal": true, "createdAt": "2025-06-08T10:00:00.000Z" } }

Error codes

CodeHTTPDescription
VALIDATION_ERROR400name is missing
NOT_FOUND404parentId does not exist or is not accessible

Get a folder

GET/api/vault/folders/[id]Requires: (vault unlocked)

Returns a single folder including its current shares.

Success response

{ "ok": true, "data": { "id": "folder_abc", "name": "Infrastructure", "parentId": null, "color": "#6366f1", "icon": "server", "isShared": true, "shares": [ { "id": "share_xyz", "targetId": "user_111", "targetName": "Bob Verdi", "permission": "CONTRIBUTOR", "sharedAt": "2025-05-01T08:00:00.000Z" } ] } }

Update a folder

PATCH/api/vault/folders/[id]Requires: (vault unlocked)

Updates a folder’s name, color, icon, or parent. Circular parent references (moving a folder into one of its own descendants) are rejected with CIRCULAR_REF.

Request body (partial)

{ "name": "Infrastructure (prod)", "color": "#ef4444" }

Error codes

CodeHTTPDescription
NOT_FOUND404Folder does not exist or is not accessible
CIRCULAR_REF400The new parentId would create a circular hierarchy

Delete a folder

DELETE/api/vault/folders/[id]Requires: (vault unlocked)

Deletes the folder and all credentials inside it. This operation is irreversible.

Success response

{ "ok": true, "data": { "deleted": true } }

Move a folder

POST/api/vault/folders/[id]/moveRequires: (vault unlocked)

Moves a folder to a different parent. Pass null as parentId to promote the folder to the top level. Guards against circular moves.

Request body

{ "parentId": "folder_parent789" }

Success response

{ "ok": true, "data": { "moved": true } }

Error codes

CodeHTTPDescription
NOT_FOUND404Folder or target parent does not exist
CIRCULAR_REF400Move would create a circular hierarchy

Folder Shares

List shares

GET/api/vault/folders/[id]/sharesRequires: (vault unlocked)

Returns all active shares for the specified folder.

Success response

{ "ok": true, "data": [ { "id": "share_xyz", "folderId": "folder_abc", "targetId": "user_111", "targetName": "Bob Verdi", "targetType": "user", "permission": "CONTRIBUTOR", "sharedAt": "2025-05-01T08:00:00.000Z" } ] }

Share a folder

POST/api/vault/folders/[id]/sharesRequires: (vault unlocked)

Shares a folder with a user. The targetId may be either a Keycloak subject ID or a TenantUser.id — the API auto-resolves and provisions the target user if needed. An in-app notification is sent to the recipient. Fires a real-time vault:folder:shared event to the tenant Socket.IO room.

Request body

{ "targetId": "kc_sub_abc123", "targetName": "Bob Verdi", "permission": "CONTRIBUTOR" }

targetName is optional — it is used as the display name if the user cannot be resolved by the IAM system at request time.

Permission values: VIEWER, USER, CONTRIBUTOR, ADMIN

Success response

{ "ok": true, "data": { "id": "share_new999", "folderId": "folder_abc", "targetId": "user_111", "targetName": "Bob Verdi", "permission": "CONTRIBUTOR", "sharedAt": "2025-06-08T10:05:00.000Z" } }

Error codes

CodeHTTPDescription
NOT_FOUND404Folder does not exist
VALIDATION_ERROR400targetId or permission is missing or invalid
ALREADY_SHARED409A share for this [folderId, targetId] pair already exists

Update a share

PATCH/api/vault/folders/[id]/shares/[shareId]Requires: (vault unlocked)

Changes the permission level of an existing share. shareId may be the share record ID or the targetId of the recipient.

Request body

{ "permission": "VIEWER" }

Success response

{ "ok": true, "data": { "id": "share_xyz", "permission": "VIEWER" } }

Revoke a share

DELETE/api/vault/folders/[id]/shares/[shareId]Requires: (vault unlocked)

Removes a share, immediately revoking the recipient’s access to the folder and its credentials. Fires a real-time vault:folder:share:removed event to the tenant Socket.IO room.

Success response

{ "ok": true, "data": { "revoked": true } }

Devices

List devices

GET/api/vault/devicesRequires: (vault unlocked)

Returns all devices registered by the caller, including their current trust status.

Success response

{ "ok": true, "data": [ { "id": "device_abc", "name": "Work MacBook", "fingerprintHash": "a3f9c2...", "userAgent": "Mozilla/5.0 ...", "ipAddress": "203.0.113.42", "location": "Milan, IT", "status": "TRUSTED", "trustedAt": "2025-04-10T14:00:00.000Z", "lastSeenAt": "2025-06-08T09:55:12.000Z", "createdAt": "2025-04-10T13:59:00.000Z" } ] }

Register a device

POST/api/vault/devicesRequires: (vault unlocked)

Registers a new device. The fingerprintHash must be a SHA-256 hash that uniquely identifies the device. New devices start with status PENDING.

Request body

{ "name": "Work MacBook", "fingerprintHash": "a3f9c2d1...", "userAgent": "Mozilla/5.0 ...", "ipAddress": "203.0.113.42", "location": "Milan, IT" }

userAgent, ipAddress, and location are optional metadata fields.

Success response

{ "ok": true, "data": { "id": "device_new456", "name": "Work MacBook", "status": "PENDING", "createdAt": "2025-06-08T10:00:00.000Z" } }

Error codes

CodeHTTPDescription
VALIDATION_ERROR400name or fingerprintHash missing
DUPLICATE_FINGERPRINT409A device with this fingerprint hash is already registered for this user

Update device status

PATCH/api/vault/devices/[id]Requires: (vault unlocked)

Changes the trust status of a device. Use this to trust a pending device, revoke a trusted device, or restore a revoked device back to pending for re-review.

Request body

{ "status": "TRUSTED" }

Valid values: TRUSTED, PENDING, REVOKED

Success response

{ "ok": true, "data": { "id": "device_new456", "status": "TRUSTED", "trustedAt": "2025-06-08T10:05:00.000Z" } }

Revoke a device

POST/api/vault/devices/[id]/revokeRequires: (vault unlocked)

Immediately revokes a device by setting its status to REVOKED. This is a dedicated shortcut endpoint — the same result can also be achieved with PATCH /api/vault/devices/[id] passing {"status": "REVOKED"}.

Request body: none required.

Success response

{ "ok": true, "data": { "id": "device_abc", "status": "REVOKED" } }

Error codes

CodeHTTPDescription
NOT_FOUND404Device does not exist or belongs to a different user

Audit Log

List audit entries

GET/api/vault/auditRequires: (vault unlocked)

Returns a paginated audit log of all vault operations performed by the caller’s tenant. Entries are ordered by timestamp descending.

Query parameters

ParameterTypeDescription
pageintegerPage number (default: 1)
limitintegerItems per page (default: 50, max: 100)

Success response

{ "ok": true, "data": { "data": [ { "id": "audit_abc123", "action": "REVEAL", "resourceType": "CREDENTIAL", "resourceId": "cred_abc", "resourceName": "Production DB – main", "userId": "user_xyz", "userName": "Alice Rossi", "ipAddress": "203.0.113.42", "userAgent": "Mozilla/5.0 ...", "deviceId": "device_abc", "riskScore": 5, "timestamp": "2025-06-08T09:55:12.000Z" } ], "pagination": { "page": 1, "limit": 50, "total": 147, "totalPages": 3 } } }

Search users for sharing

GET/api/vault/users/searchRequires: (vault unlocked)

Searches the Keycloak realm for users to share a folder with. Returns up to 20 results. Excludes the caller from results. When the query string is shorter than 2 characters the endpoint returns HTTP 200 with an empty array rather than an error.

Query parameters

ParameterTypeRequiredDescription
qstringYesSearch term (matched against email, name, surname, username). Fewer than 2 characters returns an empty result.

Success response

{ "ok": true, "data": [ { "id": "kc_sub_abc123", "email": "[email protected]", "name": "Bob", "surname": "Verdi", "username": "bobverdi" } ] }

Real-Time Events

The Vault emits Socket.IO events to the room vault:{tenantId} after mutating operations. Clients subscribed to this room receive live updates without polling.

EventTriggered By
vault:credential:createdPOST /api/vault/credentials
vault:credential:updatedPATCH /api/vault/credentials/[id]
vault:credential:deletedDELETE /api/vault/credentials/[id]
vault:folder:createdPOST /api/vault/folders
vault:folder:updatedPATCH /api/vault/folders/[id]
vault:folder:deletedDELETE /api/vault/folders/[id]
vault:folder:sharedPOST /api/vault/folders/[id]/shares
vault:folder:share:removedDELETE /api/vault/folders/[id]/shares/[shareId]

Event payload shape

{ "actorId": "user_xyz", "entityId": "cred_abc123", "timestamp": "2025-06-08T10:05:00.000Z" }

Common Error Codes

CodeHTTPDescription
VAULT_LOCKED423The vault is locked. Unlock with POST /api/vault/unlock before retrying.
UNAUTHORIZED401Missing or invalid Bearer token
FORBIDDEN403Token is valid but the caller does not own the requested resource
NOT_FOUND404Resource does not exist or belongs to a different user/tenant
RATE_LIMITED429Request rate limit exceeded
VALIDATION_ERROR400One or more required fields are missing or invalid

Error responses follow the standard Auris envelope: {"ok": false, "error": {"code": "...", "message": "..."}}. Success response shapes vary by endpoint — refer to each endpoint’s example above. Mutating endpoints that do not return a resource body (lock, unlock, delete) return {"success": true}. Endpoints that return a resource return it directly or wrapped in {"success": true, "data": ...} as shown in each section.