production, staging), team, or sensitivity tier without sharing keys.
Storage modes
A profile is created with one of two storage modes, fixed for the lifetime of the profile.- Zero-knowledge
- Server-managed
Encryption happens client-side (browser or local daemon). The server never sees plaintext or root keys. Items inside the profile are encrypted with per-item DEKs wrapped by the profile’s root key.Use when: you need maximum confidentiality, all consumers run locally, and remote agents do not need access.
Schema
The unique constraint
(organizationId, name) enforces one profile name per organization. A partial unique constraint (organizationId, externalId) (where externalId is set) supports idempotent provisioning.
Personal accounts are capped at a single profile: the default profile is seeded at account creation, and profiles.create rejects an additional profile on a personal org with PROFILE_LIMIT_EXCEEDED. Team organizations are uncapped.
Bootstrapping a zero-knowledge profile
A ZK profile starts empty after creation. Before items can be added, the client must generate the encryption material and callPOST /v1/profiles/{profileId}/bootstrap.
1
Pick a master password
Strong, unique, never written to disk. Argon2id makes brute-force expensive but not impossible against weak passwords.
2
Derive the KEK
Argon2id with 64 MiB memory and 3 iterations produces a 32-byte KEK from the password and a fresh 16-byte salt.
3
Generate the root key
32 random bytes. The root key never leaves the client; only its KEK-wrapped form is sent to the server.
4
Wrap and ship
POST /v1/profiles/{profileId}/bootstrap accepts { wrappedRootKey, kdfSalt, kdfParams } and persists the profile envelope.5
Set up recovery
the recovery-setup operation accepts a
recoveryWrappedRootKey (root key encrypted by a 256-bit recovery key shown to the user once). Without it, losing the master password is unrecoverable.Operations
Cascades
Deleting a profile is blocked if it contains items. Delete the items first or use the dashboard to soft-delete. Audit entries persist. Rotating a profile root key requires the client to rewrap every item DEK in the profile. Partial payloads returnROTATE_KEY_INCOMPLETE with the missing item IDs in meta.missingItemIds.