List passwords
Retrieve all password entries for the authenticated user. Password and username fields are null in the response — decrypt client-side using the wrapped_item_key and encrypted_data fields.
Authentication: Required
Rate limit: 30 requests per 60 seconds per user
Query parameters:
| Parameter | Type | Default | Description |
|---|
page | integer | 1 | Page number |
limit | integer | 1000 | Items per page (max: 2000) |
Response 200 OK:
{
"data": [
{
"id": "pwd_12345",
"user_id": "usr_abc",
"name": "Netflix",
"website": "netflix.com",
"url": "https://netflix.com",
"username": null,
"password": null,
"notes": null,
"category": null,
"tags": ["streaming"],
"encryption_version": 3,
"wrapped_item_key": "base64...",
"encrypted_data": "base64...",
"domain_hash": "hex...",
"name_hash": "hex...",
"username_hash": "hex...",
"search_tokens": ["net", "netf", "netfl"],
"share_count": 2,
"has_totp": true,
"has_passkey": false,
"passkey_label": null,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-02-15T10:30:00Z"
}
],
"pagination": {
"page": 1,
"limit": 1000,
"total": 234
}
}
In V3 encryption, plaintext metadata fields (username, password, notes, category) are always null. All sensitive data is stored in encrypted_data, which the client decrypts using the wrapped_item_key and their vault key. The name, website, url, and tags fields are stored in plaintext for search and display.
Create password
Authentication: Required
CSRF: Required
Rate limit: 30 requests per 60 seconds per user
Headers:
| Header | Required | Description |
|---|
Idempotency-Key | No | Deduplicates requests. Cached for 1 hour in Redis. |
Request body:
{
"name": "Netflix",
"website": "netflix.com",
"category": "streaming",
"tags": ["entertainment"],
"wrapped_item_key": "base64-wrapped-key",
"encrypted_data": "base64-encrypted-payload",
"domain_hash": "hex-hash",
"name_hash": "hex-hash",
"username_hash": "hex-hash",
"search_tokens": ["net", "netf", "netfl", "netflix"],
"strength": "strong"
}
| Field | Type | Required | Description |
|---|
name | string | Yes | Display name of the service |
wrapped_item_key | string | Yes | Per-item AES key wrapped with vault key |
encrypted_data | string | Yes | AES-256-GCM encrypted credential payload |
name_hash | string | Yes | Blind index for name-based search |
username_hash | string | Yes | Blind index for username-based search |
website | string | No | Domain name |
category | string | No | Category label |
tags | string[] | No | Array of tag strings |
domain_hash | string | No | Blind index for domain-based search |
search_tokens | string[] | No | Tokens for full-text search |
strength | string | No | Password strength indicator |
Response 201 Created:
{
"success": true,
"data": {
"id": "pwd_12345",
"user_id": "usr_abc",
"name": "Netflix",
"encryption_version": 3,
"password": null,
"created_at": "2026-03-04T12:00:00Z"
}
}
Error 503 Service Unavailable — password limit exceeded.
Update password
PATCH /api/passwords/{id}
Authentication: Required
CSRF: Required
All fields are optional. Only provided fields are updated.
Response 200 OK:
{
"success": true,
"data": {
"id": "pwd_12345",
"updated_at": "2026-03-04T12:00:00Z"
}
}
Delete password
Soft-deletes the entry (sets deleted_at timestamp). Soft-deleted entries appear as tombstones in sync responses.
DELETE /api/passwords/{id}
Authentication: Required
CSRF: Required
Response 200 OK:
Reveal password
Returns the encrypted envelope for client-side decryption. Logs an audit event with IP, device, and timestamp.
POST /api/passwords/{id}/reveal
Authentication: Required
CSRF: Required
Rate limit: 20 requests per 60 seconds per user (fail-closed)
This endpoint uses fail-closed rate limiting. If the rate limiter is unavailable (Redis down), the request is denied rather than allowed.
Response 200 OK:
{
"envelope": {
"wrappedItemKey": "base64-wrapped-key",
"encryptedData": "base64-encrypted-payload",
"version": 3
}
}
Error 409 Conflict — entry uses legacy encryption (not V3). Migrate before revealing.
Match by URL
Find entries matching a URL hostname. Used by browser extensions for autofill.
GET /api/passwords/match?url={url}
Authentication: Required
Response 200 OK:
{
"matches": [
{
"id": "pwd_12345",
"name": "Netflix",
"username_hash": "hex...",
"url": "https://netflix.com/login",
"wrapped_item_key": "base64...",
"encrypted_data": "base64..."
}
]
}
Delta sync
Offline-first sync endpoint for extensions and mobile clients. Supports full sync and delta sync with tombstones.
Pull changes
Authentication: Required
Rate limit: 60 requests per 60 seconds per user
Query parameters:
| Parameter | Type | Required | Description |
|---|
since | ISO-8601 | No | Timestamp of last sync. Omit for full sync. Max age: 30 days. |
Response 200 OK:
{
"changes": [
{
"id": "pwd_12345",
"name": "Netflix",
"_deleted": false,
"wrapped_item_key": "base64...",
"encrypted_data": "base64...",
"updated_at": "2026-03-04T12:00:00Z"
},
{
"id": "pwd_67890",
"_deleted": true,
"deleted_at": "2026-03-03T08:00:00Z"
}
],
"serverTimestamp": "2026-03-04T12:05:00Z",
"fullSyncRequired": false
}
Use serverTimestamp as the since parameter for the next sync request. If fullSyncRequired is true, discard local state and perform a full sync.
Push changes
Authentication: Required
CSRF: Required
Rate limit: 30 requests per 60 seconds per user
Request body:
{
"mutations": [
{
"action": "create",
"id": "client-generated-uuid",
"data": {
"encrypted_data": "base64...",
"wrapped_item_key": "base64...",
"encryption_version": 3,
"name": "New Service",
"website": "example.com"
},
"clientUpdatedAt": "2026-03-04T12:00:00Z"
},
{
"action": "update",
"id": "pwd_12345",
"data": { "encrypted_data": "base64-updated..." },
"clientUpdatedAt": "2026-03-04T12:01:00Z"
},
{
"action": "delete",
"id": "pwd_67890",
"clientUpdatedAt": "2026-03-04T12:02:00Z"
}
]
}
Limits: Maximum 100 mutations per batch. V3 encryption only.
Conflict resolution: Last-write-wins. If server.updated_at > clientUpdatedAt, the server version takes precedence.
Response 200 OK:
{
"results": [
{ "id": "client-uuid", "status": "applied", "serverEntry": { ... } },
{ "id": "pwd_12345", "status": "conflict_server_wins", "serverEntry": { ... } },
{ "id": "pwd_67890", "status": "applied" }
],
"serverTimestamp": "2026-03-04T12:05:00Z"
}
Import passwords
Bulk import credentials from other password managers. All entries must be pre-encrypted client-side (V3).
POST /api/passwords/import
Authentication: Required
CSRF: Required
Step-up auth: Requires recent authentication
Headers:
| Header | Required | Description |
|---|
Idempotency-Key | No | Deduplicates requests. Cached for 1 hour. |
Request body:
{
"entries": [
{
"wrapped_item_key": "base64...",
"encrypted_data": "base64...",
"domain_hash": "hex...",
"name_hash": "hex...",
"username_hash": "hex...",
"search_tokens": ["token1", "token2"]
}
],
"provider": "chrome"
}
| Field | Type | Required | Description |
|---|
entries | array | Yes | Array of encrypted credential objects (max: 5000) |
provider | string | Yes | Source: chrome, bitwarden, lastpass, 1password, dashlane, generic |
Entries are processed in batches of 50.
Response 200 OK:
{
"success": true,
"imported": 1250,
"encryption_version": 3,
"errors": ["Batch at row 501 failed"]
}
The legacy plaintext CSV import path (/api/integrations/import) is deprecated and returns 400. All imports must use V3 client-side encryption.