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
/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
/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
| Code | HTTP | Description |
|---|---|---|
PIN_NOT_SET | 400 | No PIN has been configured for this user’s vault |
Unlock the vault
/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
| Code | HTTP | Description |
|---|---|---|
INVALID_PIN | 403 | PIN does not match |
TOO_MANY_ATTEMPTS | 429 | Five or more consecutive failures — retry after 15 minutes |
PIN Management
Get PIN status
/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
/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
| Code | HTTP | Description |
|---|---|---|
INCORRECT_CURRENT_PIN | 403 | currentPin does not match the stored hash |
VALIDATION_ERROR | 400 | PIN is not 4–8 digits, or currentPin is missing when a PIN already exists |
Overview
Get vault overview
/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
/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
| Parameter | Type | Description |
|---|---|---|
folderId | string | Filter to credentials in a specific folder |
type | string | Filter by credential type (e.g. LOGIN, API_KEY, SSH_KEY) |
search | string | Full-text search against credential names |
page | integer | Page number (default: 1) |
limit | integer | Items 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
/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
| Type | Accepted Secret Fields |
|---|---|
LOGIN | username, password, url |
API_KEY | apiKey |
SSH_KEY | privateKey, username |
CERTIFICATE | privateKey, data |
WIFI | password, data |
DATABASE | username, password, url |
SERVER | username, password, url |
CUSTOM | data |
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
| Code | HTTP | Description |
|---|---|---|
VALIDATION_ERROR | 400 | name or type missing, or type is not a valid enum value |
NOT_FOUND | 404 | folderId does not exist or is not accessible to the caller |
Get a credential
/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
| Code | HTTP | Description |
|---|---|---|
NOT_FOUND | 404 | Credential does not exist or is not accessible to the caller |
Update a credential
/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
| Code | HTTP | Description |
|---|---|---|
NOT_FOUND | 404 | Credential does not exist or is not accessible to the caller |
Delete a credential
/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
| Code | HTTP | Description |
|---|---|---|
NOT_FOUND | 404 | Credential does not exist or is not accessible to the caller |
Reveal a credential
/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
| Code | HTTP | Description |
|---|---|---|
NOT_FOUND | 404 | Credential does not exist or is not accessible |
VAULT_LOCKED | 423 | Vault is locked |
RATE_LIMITED | 429 | Reveal rate limit exceeded (10/min) |
Copy a credential
/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
/api/vault/foldersRequires: (vault unlocked)Returns folders accessible to the caller (owned or shared). Supports three query modes via the
parentId parameter.
Query parameters
| Parameter | Type | Description |
|---|---|---|
parentId | string | root = 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
/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
| Code | HTTP | Description |
|---|---|---|
VALIDATION_ERROR | 400 | name is missing |
NOT_FOUND | 404 | parentId does not exist or is not accessible |
Get a folder
/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
/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
| Code | HTTP | Description |
|---|---|---|
NOT_FOUND | 404 | Folder does not exist or is not accessible |
CIRCULAR_REF | 400 | The new parentId would create a circular hierarchy |
Delete a folder
/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
/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
| Code | HTTP | Description |
|---|---|---|
NOT_FOUND | 404 | Folder or target parent does not exist |
CIRCULAR_REF | 400 | Move would create a circular hierarchy |
Folder Shares
List shares
/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
/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
| Code | HTTP | Description |
|---|---|---|
NOT_FOUND | 404 | Folder does not exist |
VALIDATION_ERROR | 400 | targetId or permission is missing or invalid |
ALREADY_SHARED | 409 | A share for this [folderId, targetId] pair already exists |
Update a share
/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
/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
/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
/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
| Code | HTTP | Description |
|---|---|---|
VALIDATION_ERROR | 400 | name or fingerprintHash missing |
DUPLICATE_FINGERPRINT | 409 | A device with this fingerprint hash is already registered for this user |
Update device status
/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
/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
| Code | HTTP | Description |
|---|---|---|
NOT_FOUND | 404 | Device does not exist or belongs to a different user |
Audit Log
List audit entries
/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
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (default: 1) |
limit | integer | Items 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
}
}
}User Search
Search users for sharing
/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
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | Yes | Search 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.
| Event | Triggered By |
|---|---|
vault:credential:created | POST /api/vault/credentials |
vault:credential:updated | PATCH /api/vault/credentials/[id] |
vault:credential:deleted | DELETE /api/vault/credentials/[id] |
vault:folder:created | POST /api/vault/folders |
vault:folder:updated | PATCH /api/vault/folders/[id] |
vault:folder:deleted | DELETE /api/vault/folders/[id] |
vault:folder:shared | POST /api/vault/folders/[id]/shares |
vault:folder:share:removed | DELETE /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
| Code | HTTP | Description |
|---|---|---|
VAULT_LOCKED | 423 | The vault is locked. Unlock with POST /api/vault/unlock before retrying. |
UNAUTHORIZED | 401 | Missing or invalid Bearer token |
FORBIDDEN | 403 | Token is valid but the caller does not own the requested resource |
NOT_FOUND | 404 | Resource does not exist or belongs to a different user/tenant |
RATE_LIMITED | 429 | Request rate limit exceeded |
VALIDATION_ERROR | 400 | One 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.
Related
- Credential Vault Console — Console guide covering the full feature set
- Security API — IP rules, brute-force, and CAPTCHA endpoints
- Audit Logs API — Tenant-wide audit log for all Auris operations