API Docs
Authentication

Authentication API

All authentication endpoints are under /api/v1/auth.

Register

Create a new account. The client performs Argon2id key derivation locally and sends the resulting auth hash, encrypted private keys, and public keys.

POST /auth/register

Request

{
  "email": "alice@example.com",
  "name": "Alice",
  "authHash": "base64-encoded-auth-hash",
  "salt": "base64-encoded-salt",
  "masterPasswordPreflight": "base64-encoded-preflight",
  "kdfIterations": 3,
  "kdfMemoryKB": 65536,
  "kdfParallelism": 4,
  "encryptedPrivateKey": "base64-encoded-blob",
  "encryptedIdentityPrivateKey": "base64-encoded-blob",
  "publicKey": "base64-encoded-rsa-public-key",
  "publicKeySignature": "base64-encoded-ed25519-signature",
  "identityPublicKey": "base64-encoded-ed25519-public-key",
  "inviteToken": "raw-invite-token"
}
FieldTypeDescription
emailstringUser email address
namestringDisplay name
authHashstringBase64-encoded Argon2id hash derived from master password via HKDF(info="auth")
saltstringBase64-encoded random salt used for Argon2id derivation
masterPasswordPreflightstringBase64-encoded value for client-side password verification on future logins
kdfIterationsintegerArgon2id time parameter (default: 3)
kdfMemoryKBintegerArgon2id memory parameter in KB (default: 65536)
kdfParallelismintegerArgon2id parallelism parameter (default: 4)
encryptedPrivateKeystringBase64-encoded RSA private key encrypted with stretched key
encryptedIdentityPrivateKeystringBase64-encoded Ed25519 private key encrypted with stretched key
publicKeystringBase64-encoded RSA public key (PKCS#1)
publicKeySignaturestringBase64-encoded Ed25519 signature over the RSA public key
identityPublicKeystringBase64-encoded Ed25519 public key
inviteTokenstringRaw invite token (required when server registration mode is invite)

Response — 201 Created

{
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "role": "owner"
}

Example

curl -X POST https://vault.example.com/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alice@example.com",
    "name": "Alice",
    "authHash": "c2VjdXJlLWhhc2gtYnl0ZXM=",
    "salt": "cmFuZG9tLXNhbHQ=",
    "masterPasswordPreflight": "cHJlZmxpZ2h0LWJ5dGVz",
    "kdfIterations": 3,
    "kdfMemoryKB": 65536,
    "kdfParallelism": 4,
    "encryptedPrivateKey": "ZW5jcnlwdGVkLXJzYS1wcml2YXRl",
    "encryptedIdentityPrivateKey": "ZW5jcnlwdGVkLWVkMjU1MTktcHJpdmF0ZQ==",
    "publicKey": "cnNhLXB1YmxpYy1rZXk=",
    "publicKeySignature": "ZWQyNTUxOS1zaWduYXR1cmU=",
    "identityPublicKey": "ZWQyNTUxOS1wdWJsaWMta2V5",
    "inviteToken": "optional-raw-invite-token"
  }'

Errors

  • 400 INVALID -- Validation failed (missing or malformed field)
  • 400 INVITE_REQUIRED -- Invite token required (when registration mode is invite)
  • 400 REGISTRATION_DISABLED -- Open registration is disabled
  • 409 CONFLICT -- Email already taken
  • 429 RATE_LIMITED -- Too many requests

Prelogin

Retrieve KDF parameters for a given email. Used by the client to derive the auth hash before login.

GET /auth/prelogin?email=alice@example.com

Response — 200 OK

{
  "salt": "cmFuZG9tLXNhbHQ=",
  "iterations": 3,
  "memoryKB": 65536,
  "parallelism": 4
}

Enumeration prevention (H2): If the email does not exist, the server returns fake but deterministic KDF parameters derived from the email and a server-side pepper. This prevents attackers from discovering which emails are registered.

Example

curl "https://vault.example.com/api/v1/auth/prelogin?email=alice@example.com"

Login

Authenticate with email and auth hash. Returns tokens, encrypted keys, and the user's vault list.

POST /auth/login

Request

{
  "email": "alice@example.com",
  "authHash": "base64-encoded-auth-hash",
  "deviceName": "Chrome on macOS"
}

Response — 200 OK

{
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "role": "owner",
  "accessToken": "eyJhbGciOi...",
  "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4=",
  "sessionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "refreshExpiresAt": "2026-04-13T12:00:00Z",
  "encryptedPrivateKey": "base64-encoded-blob",
  "encryptedIdentityPrivateKey": "base64-encoded-blob",
  "publicKey": "base64-encoded-rsa-public-key",
  "publicKeySignature": "base64-encoded-ed25519-signature",
  "identityPublicKey": "base64-encoded-ed25519-public-key",
  "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"
    }
  ]
}

Example

curl -X POST https://vault.example.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alice@example.com",
    "authHash": "c2VjdXJlLWhhc2gtYnl0ZXM=",
    "deviceName": "Chrome on macOS"
  }'

Refresh Token

Exchange a valid refresh token for a new access/refresh token pair.

POST /auth/refresh

Request

{
  "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4="
}

Response — 200 OK

{
  "accessToken": "eyJhbGciOi...",
  "refreshToken": "bmV3LXJlZnJlc2gtdG9rZW4=",
  "refreshExpiresAt": "2026-04-13T12:00:00Z"
}

Example

curl -X POST https://vault.example.com/api/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4="}'

Logout

Revoke a refresh token and end the associated session.

POST /auth/logout

Request

{
  "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4="
}

Response — 204 No Content

No response body.

Example

curl -X POST https://vault.example.com/api/v1/auth/logout \
  -H "Content-Type: application/json" \
  -d '{"refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4="}'

Step-Up Authentication

Re-verify the master password to obtain a short-lived step-up token. Required before sensitive operations like changing password, purging items, or managing TOTP.

POST /auth/step-up

Requires: JWT access token in Authorization header.

Request

{
  "authHash": "base64-encoded-auth-hash"
}

Response — 200 OK

{
  "accessToken": "eyJhbGciOi..."
}

The returned access token contains a step_up claim and expires in 5 minutes.

⚠️

The step-up token replaces your current access token. Use it for the privileged operation, then continue with a normal token from /auth/refresh.

Example

curl -X POST https://vault.example.com/api/v1/auth/step-up \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{"authHash": "c2VjdXJlLWhhc2gtYnl0ZXM="}'

Change Password

Change the master password. Requires a step-up token. Re-encrypts private keys with the new stretched key and revokes all existing sessions.

POST /auth/password/change

Requires: JWT access token with step_up claim.

Request

{
  "oldAuthHash": "base64-encoded-old-auth-hash",
  "newAuthHash": "base64-encoded-new-auth-hash",
  "encryptedPrivateKey": "base64-encoded-new-blob",
  "encryptedIdentityPrivateKey": "base64-encoded-new-blob"
}

Response — 200 OK

{
  "accessToken": "eyJhbGciOi...",
  "refreshToken": "bmV3LXJlZnJlc2gtdG9rZW4=",
  "refreshExpiresAt": "2026-04-13T12:00:00Z"
}
⚠️

This operation revokes all sessions across all devices. The response contains new tokens for the current session only. All other devices will need to log in again with the new master password.

Example

Obtain a step-up token

STEP_UP_TOKEN=$(curl -s -X POST https://vault.example.com/api/v1/auth/step-up \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{"authHash": "b2xkLWF1dGgtaGFzaA=="}' | jq -r '.accessToken')

Change the password

curl -X POST https://vault.example.com/api/v1/auth/password/change \
  -H "Authorization: Bearer $STEP_UP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "oldAuthHash": "b2xkLWF1dGgtaGFzaA==",
    "newAuthHash": "bmV3LWF1dGgtaGFzaA==",
    "encryptedPrivateKey": "bmV3LWVuY3J5cHRlZC1yc2EtcHJpdmF0ZQ==",
    "encryptedIdentityPrivateKey": "bmV3LWVuY3J5cHRlZC1lZDI1NTE5LXByaXZhdGU="
  }'