Documentation

Vault Endpoints

API reference for the vault item and vault setup routes.

Overview

These are the HTTP endpoints that move vault data between the browser and the database. There are two groups:

  • Vault items (/api/vault/items) — create, list, update, and delete the encrypted items.
  • Vault setup (/api/vault/init) — read the encrypted material needed to unlock or recover, initialize a vault, and re-wrap on a password change.

Every endpoint shares the same ground rules described next.

Conventions

  • Authentication. All routes require an authenticated Clerk session. With no valid session they return 401 Unauthorized. The user is resolved server-side from the session, so there is no user id in the request body.
  • Encryption is the client's job. These endpoints only ever accept and return ciphertext, iv, wrapped keys, and public KDF parameters. No route accepts or returns a plaintext secret, master password, or recovery phrase.
  • Content type. Request bodies are JSON; send Content-Type: application/json.
  • Errors. Failures return a JSON object of the form { "error": "..." } with an appropriate status code.

Vault items

GET /api/vault/items

Lists the authenticated user's items, most recently updated first. Returns ciphertext only — the server never decrypts.

Response 200

{
  "items": [
    {
      "id": "…",
      "type": "login",
      "ciphertext": "…",
      "iv": "…",
      "favorite": false,
      "folder_id": null,
      "created_at": "…",
      "updated_at": "…"
    }
  ]
}
StatusMeaning
200Items returned
401Not signed in

POST /api/vault/items

Creates a new item — but only if the user is under their plan's item limit and the vault is initialized. The limit check, insert, and counter increment happen in a single atomic statement (see Vault Items).

Request body

FieldTypeRequiredNotes
ciphertextstringYesThe encrypted secret payload
ivstringYesInitialization vector for this ciphertext
typestringNoOne of login, note, card, identity (default login)
folderIdstringNoTarget folder, or null (default null)
{ "type": "login", "ciphertext": "…", "iv": "…", "folderId": null }

Response 201

{ "item": { "id": "…", "type": "login", "ciphertext": "…", "iv": "…",
            "favorite": false, "folder_id": null,
            "created_at": "…", "updated_at": "…" } }
StatusMeaning
201Item created
400Missing ciphertext/iv, or an invalid type
401Not signed in
403Item limit reached, or the vault is not initialized

A 403 here is intentional and clean, not an error — it's what you get when the atomic insert finds no room under the plan limit (or no initialized vault).


PUT /api/vault/items/[id]

Updates an existing item. Because the secret is re-encrypted in the browser, an edit sends a fresh ciphertext and iv. Toggling the favorite flag goes through this same route.

Reconstructed from client usage (updateItem(id, secret, { favorite })). Treat the exact field names below as the client's contract and confirm against the [id] route source.

Request body

FieldTypeNotes
ciphertextstringNew encrypted payload
ivstringNew initialization vector
favoritebooleanUpdated favorite flag
StatusMeaning
200Item updated
400Missing/invalid fields
401Not signed in
404No such item for user

DELETE /api/vault/items/[id]

Permanently removes an item by id.

Reconstructed from client usage (deleteItem(id)); confirm against the [id] route source.

StatusMeaning
200Item deleted
401Not signed in
404No such item for user

The [id] route folder must be named literally [id] (with the brackets) for Next.js to match it; a missing or misnamed folder is a common cause of a POST/PUT falling through to an HTML page instead of the handler.


Vault setup

GET /api/vault/init

Returns the encrypted material the browser needs to unlock or recover the vault. Every value is ciphertext or a public KDF parameter — safe to hand to the authenticated owner.

Response 200 — vault exists

{
  "initialized": true,
  "kdf_algo": "…",
  "kdf_salt": "…",
  "kdf_params": { },
  "wrapped_vault_key": "…",
  "wrapped_vault_key_iv": "…",
  "recovery_wrapped_key": "…",
  "recovery_wrapped_key_iv": "…"
}

Response 200 — no user row yet

{ "initialized": false }
StatusMeaning
200Returns the material, or { initialized: false }
401Not signed in

POST /api/vault/init

First-time vault setup. Stores both wrapped keys, the salt, and the KDF parameters, and flips vault_initialized to true. This route never sees a master password, recovery phrase, or any plaintext secret — only opaque blobs.

If the user row doesn't exist yet (common in local dev, where the Clerk webhook can't reach localhost), it is created here from the authenticated user's Clerk data using ON CONFLICT DO NOTHING, so the webhook stays the source of truth if it later runs.

Request body — all required

Field
kdf_algo
kdf_salt
kdf_params
wrapped_vault_key
wrapped_vault_key_iv
recovery_wrapped_key
recovery_wrapped_key_iv

Response 201

{ "message": "Vault initialized" }
StatusMeaning
201Vault initialized
400A required field is missing or null
401Not signed in
409Vault already initialized

The update is guarded with WHERE vault_initialized = false, making setup idempotent and ensuring it can never overwrite an existing vault — which would lock the owner out of their data. A second attempt returns 409.


PUT /api/vault/init

Re-wraps the vault on a master-password change. Only the password-side material changes; the recovery-wrapped key and the Vault Key itself are left untouched, so the change is instant and existing items are not re-encrypted.

Request body — all required

FieldNotes
kdf_saltNew salt for the new password key
kdf_paramsNew KDF parameters
wrapped_vault_keyVault Key re-wrapped by the new key
wrapped_vault_key_ivIV for the new wrapping

Response 200

{ "message": "Master password updated" }
StatusMeaning
200Master password updated
400A required field is missing or null
401Not signed in
404Vault not initialized

The update is guarded with WHERE vault_initialized = true, so a re-wrap can only apply to an already-set-up vault; otherwise it returns 404.


Quick reference

Method & pathPurposeSuccess
GET /api/vault/itemsList items (ciphertext only)200
POST /api/vault/itemsCreate item (with limit check)201
PUT /api/vault/items/[id]Update item / toggle favorite200
DELETE /api/vault/items/[id]Delete item200
GET /api/vault/initRead unlock/recovery material200
POST /api/vault/initFirst-time vault setup201
PUT /api/vault/initRe-wrap on password change200

Where to go next

  • Vault Items — the structure of the data these item routes carry.
  • Key Management and KDF — what the init route's wrapped keys and params are.
  • Clerk User Sync — the separate webhook endpoint that mirrors profiles.