Vaults API
All vault endpoints require a JWT access token in the Authorization header.
List Vaults
Retrieve all vaults the authenticated user has access to, including their encrypted vault keys.
GET /vaultsResponse — 200 OK
{
"vaults": [
{
"vaultId": "660e8400-e29b-41d4-a716-446655440000",
"vaultName": "Personal",
"vaultType": "personal",
"encryptedVaultKey": "base64-encoded-wrapped-key",
"senderId": "550e8400-e29b-41d4-a716-446655440000",
"wrapSignature": "base64-encoded-signature",
"role": "owner",
"createdAt": "2026-04-01T10:00:00Z",
"updatedAt": "2026-04-01T10:00:00Z"
}
]
}| Field | Description |
|---|---|
vaultId | UUID of the vault |
vaultName | Plaintext vault name |
vaultType | personal or shared |
encryptedVaultKey | Base64-encoded vault key, wrapped for this user |
senderId | UUID of the user who wrapped the key |
wrapSignature | Ed25519 signature over the wrapped key (for MITM prevention) |
role | User's role in this vault: owner, admin, or member |
Example
curl -H "Authorization: Bearer <access_token>" \
https://vault.example.com/api/v1/vaultsCreate Vault
Create a new vault. The client generates a 256-bit vault key, wraps it, and signs the wrap.
POST /vaultsRequest
{
"name": "Work Credentials",
"type": "personal",
"encryptedVaultKey": "base64-encoded-wrapped-key",
"wrapSignature": "base64-encoded-signature"
}| Field | Type | Description |
|---|---|---|
name | string | Vault display name |
type | string | personal or shared |
encryptedVaultKey | string | Base64-encoded vault key wrapped with the user's RSA public key (shared) or AES Key Wrap (personal) |
wrapSignature | string | Base64-encoded Ed25519 signature over the wrapped key bytes |
Response — 201 Created
{
"vaultId": "770e8400-e29b-41d4-a716-446655440000",
"vaultName": "Work Credentials",
"vaultType": "personal",
"encryptedVaultKey": "base64-encoded-wrapped-key",
"senderId": "550e8400-e29b-41d4-a716-446655440000",
"wrapSignature": "base64-encoded-signature",
"role": "owner",
"createdAt": "2026-04-06T12:00:00Z",
"updatedAt": "2026-04-06T12:00:00Z"
}Example
curl -X POST https://vault.example.com/api/v1/vaults \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Work Credentials",
"type": "personal",
"encryptedVaultKey": "d3JhcHBlZC12YXVsdC1rZXk=",
"wrapSignature": "c2lnbmF0dXJlLW92ZXItd3JhcA=="
}'The vault key is a random 256-bit AES key generated by the client. For personal vaults it is wrapped with AES-256-KW using the user's stretched key. For shared vaults it is wrapped with RSA-OAEP-SHA256 using each member's public key.
Share Vault (Add Member)
Add a new member to a shared vault. The client wraps the vault key with the recipient's RSA public key and signs the wrap.
POST /vaults/:vaultId/membersRequest
{
"recipientUserId": "user-uuid",
"encryptedVaultKey": "base64-encoded-wrapped-key",
"wrapSignature": "base64-encoded-signature",
"role": "member"
}| Field | Type | Required | Description |
|---|---|---|---|
recipientUserId | string | yes | UUID of the user to invite |
encryptedVaultKey | string | yes | Vault key wrapped with recipient's RSA public key |
wrapSignature | string | yes | Ed25519 signature over the wrapped key bytes |
role | string | yes | Role to assign: admin or member |
Response — 204 No Content
No response body.
Errors
400 INVALID-- Validation failed404 NOT_FOUND-- Vault or recipient not found
Example
curl -X POST https://vault.example.com/api/v1/vaults/660e8400-.../members \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{
"recipientUserId": "550e8400-e29b-41d4-a716-446655440001",
"encryptedVaultKey": "d3JhcHBlZC1mb3ItcmVjaXBpZW50",
"wrapSignature": "c2lnbmF0dXJlLW92ZXItd3JhcA==",
"role": "member"
}'Remove Member
Remove a member from a shared vault. Returns whether a rekey is required and the remaining members with their wrapped keys.
DELETE /vaults/:vaultId/members/:userIdResponse — 200 OK
{
"rekeyRequired": true,
"remainingMembers": [
{
"vaultId": "660e8400-...",
"vaultName": "Shared Vault",
"vaultType": "shared",
"encryptedVaultKey": "base64-wrapped-key",
"senderId": "550e8400-...",
"wrapSignature": "base64-signature",
"role": "owner"
}
]
}Errors
400 INVALID-- Cannot remove the last owner404 NOT_FOUND-- Vault or member not found
Example
curl -X DELETE -H "Authorization: Bearer <access_token>" \
https://vault.example.com/api/v1/vaults/660e8400-.../members/550e8400-...After removing a member, the client must rekey the vault if rekeyRequired is true. This involves generating a new vault key, re-encrypting all items, and re-wrapping the key for each remaining member.
Rekey Vault
Atomically re-encrypt all items with a new vault key and re-wrap the key for all remaining members. Typically called after removing a member.
POST /vaults/:vaultId/rekeyRequest
{
"newKeys": [
{
"userId": "user-uuid",
"encryptedVaultKey": "base64-new-wrapped-key",
"wrapSignature": "base64-signature"
}
],
"items": [
{
"itemId": "item-uuid",
"encryptedData": "base64-re-encrypted-blob",
"encryptedName": "base64-re-encrypted-name"
}
]
}| Field | Type | Description |
|---|---|---|
newKeys | array | One entry per remaining member with the new wrapped vault key |
newKeys[].userId | string | Member's user ID |
newKeys[].encryptedVaultKey | string | New vault key wrapped for this member |
newKeys[].wrapSignature | string | Ed25519 signature over the new wrapped key |
items | array | All items re-encrypted with the new vault key |
items[].itemId | string | Item ID |
items[].encryptedData | string | Item data re-encrypted with new vault key |
items[].encryptedName | string | Item name re-encrypted with new vault key |
Response — 204 No Content
No response body.
Errors
400 INVALID-- Validation failed (missing members or items)404 NOT_FOUND-- Vault not found
Example
curl -X POST https://vault.example.com/api/v1/vaults/660e8400-.../rekey \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{
"newKeys": [
{
"userId": "550e8400-...",
"encryptedVaultKey": "bmV3LXdyYXBwZWQta2V5",
"wrapSignature": "bmV3LXNpZ25hdHVyZQ=="
}
],
"items": [
{
"itemId": "880e8400-...",
"encryptedData": "cmUtZW5jcnlwdGVkLWRhdGE=",
"encryptedName": "cmUtZW5jcnlwdGVkLW5hbWU="
}
]
}'