Skip to main content

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.
GET /api/passwords
Authentication: Required Rate limit: 30 requests per 60 seconds per user Query parameters:
ParameterTypeDefaultDescription
pageinteger1Page number
limitinteger1000Items 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

POST /api/passwords
Authentication: Required CSRF: Required Rate limit: 30 requests per 60 seconds per user Headers:
HeaderRequiredDescription
Idempotency-KeyNoDeduplicates 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"
}
FieldTypeRequiredDescription
namestringYesDisplay name of the service
wrapped_item_keystringYesPer-item AES key wrapped with vault key
encrypted_datastringYesAES-256-GCM encrypted credential payload
name_hashstringYesBlind index for name-based search
username_hashstringYesBlind index for username-based search
websitestringNoDomain name
categorystringNoCategory label
tagsstring[]NoArray of tag strings
domain_hashstringNoBlind index for domain-based search
search_tokensstring[]NoTokens for full-text search
strengthstringNoPassword 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:
{
  "success": true
}

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

GET /api/passwords/sync
Authentication: Required Rate limit: 60 requests per 60 seconds per user Query parameters:
ParameterTypeRequiredDescription
sinceISO-8601NoTimestamp 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

POST /api/passwords/sync
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:
HeaderRequiredDescription
Idempotency-KeyNoDeduplicates 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"
}
FieldTypeRequiredDescription
entriesarrayYesArray of encrypted credential objects (max: 5000)
providerstringYesSource: 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.