Kyndex
Guides

Account Recovery

Restore your account using a recovery key when you've lost your password in a zero-knowledge system.

Recovery in Literal works differently from most platforms. Because Literal is a zero-knowledge system, the server never has access to your password or your encryption keys. This means Literal cannot reset your password or recover your data on your behalf — but it also means your data is protected even if the server is compromised.

This guide explains how account recovery works, what you need to set up in advance, and how to walk through the recovery process step by step.

Overview

On traditional platforms, if you forget your password, the service can simply send you a reset link. That works because the service stores (or can access) your data independently of your password.

Literal is different. Your password is used to derive a personal master key on your device — this key protects all of your documents, private keys, and search indexes. The server never sees your password or your master key. If you lose your password, the server has no way to decrypt your data or generate a new key for you.

That's where the recovery key comes in. It's a separate secret, generated during account setup, that can unlock a backup of your master key. Think of it as a spare key to your house — it only works if you made a copy before locking yourself out.

For a deeper look at how keys are organized in Literal, see Key Hierarchy. For background on the zero-knowledge model itself, see Zero-Knowledge Model.

Prerequisites

Before you can recover your account, you need to have completed the recovery key setup while you still had access. There is no way to generate a recovery key after you've lost your password.

Generating Your Recovery Key

During account registration (or at any time while logged in), Literal generates a recovery key for you. This key is:

  • Created on your device, not by the server
  • Used to encrypt a backup of your personal master key
  • Stored in your account in encrypted form (encrypted with your master key, so the server can't read it)

When recovery key setup completes, two things are saved:

  1. Your recovery key, encrypted with your master key — so you can export it later while logged in
  2. A master key backup, encrypted with your recovery key — so the recovery flow can restore your master key if you lose your password

Both must be present for recovery to work. If either is missing, recovery is not available for that account.

Storing Your Recovery Key Safely

Your recovery key is your only backup. If you lose both your password and your recovery key, your data is permanently inaccessible. Literal cannot help you recover it — that's the security guarantee of zero-knowledge.

Best practices for storing your recovery key:

  • Write it down on paper and store it somewhere physically secure (e.g. a safe or lockbox)
  • Save it in a separate password manager — not the same one that stores your Literal password
  • Do not store it alongside your password — if someone gains access to both, they can access your account
  • Keep multiple copies in different physical locations if the data is critical to you

What Can And Cannot Be Recovered

What Is Recovered

When you complete the recovery process, the following are fully restored:

  • All your documents — every document you own is re-protected with your new password. Document contents are not lost.
  • Your encryption keys — your private keys are re-encrypted under your new credentials.
  • Your search indexes — encrypted search tokens are preserved, so you can continue searching your documents.
  • Your organization memberships — if you belong to any organizations (entities), your membership records remain intact.

What Is Not Recovered

  • Your old password — it is permanently replaced. There is no way to retrieve it.
  • Active sessions — all existing sessions are invalidated during recovery for security. You'll need to log in again on every device.
  • Shared access tokens — any access tokens associated with your old credentials are rotated. Shared documents will be re-linked automatically, but collaborators may briefly see interrupted access during the transition.

Recovery Process

If you've lost access to your password but have your recovery key, follow these steps.

Step 1 — Start The Recovery Flow

Navigate to the login page and select the "Forgot password?" or "Recover account" option. You'll be asked for your email address and recovery key.

Your device derives a recovery blind index from email + recovery key (Argon2id), then sends only the blind index to the server. The system retrieves your encrypted master key backup. This backup can only be decrypted with your recovery key — the server cannot read it, and never sees your plaintext email.

API Call: GET /auth/recovery

Fetch the encrypted UMK backup for your account:

curl "https://api.kyndex.co/v1/auth/recovery?id=<64-hex-char-recovery-blind-index>"

Response (200 OK):

{
  "umk_backup": "dW1rYmFja3VwZW5jcnlwdGVkd2l0aHJlY292ZXJ5a2V5...",
  "key_version": 1,
  "user_id": "550e8400-e29b-41d4-a716-446655440000"
}
FieldTypeDescription
umk_backupbase64 stringThe user master key encrypted with the recovery key. Your client decrypts this with your recovery key to restore the old UMK in memory.
key_versionintegerKey version for AAD reconstruction
user_idUUIDUser UUID for AAD reconstruction during recovery decrypt

Error Responses:

  • 404 Not Found — Recovery not available for this account (no recovery key backup stored)
  • 429 Too Many Requests — Rate limit exceeded
  • 500 Internal Server Error — Server error

Step 2 — Enter Your Recovery Key

Paste or type your recovery key when prompted. Your device uses this key to decrypt the master key backup, restoring your personal master key in memory.

At this point, your client has temporary access to your old master key — enough to decrypt your documents and private keys.

No API call is made. Your recovery key is used locally to decrypt the umk_backup from Step 1. The recovery key never leaves your device.

Step 3 — Set A New Password

Choose a new password. Your client derives a new personal master key from this password on your device. The new master key replaces the old one as the root of your key hierarchy.

No API call is made. Your device performs OPAQUE key exchange locally to derive the new master key from your password, and computes the new registration record that will be sent in Step 5.

Step 4 — Re-Protect Your Data

With both the old master key (from recovery) and the new master key (from your new password) available, your client automatically:

  1. Re-encrypts your private keys (encryption and signing) under the new master key
  2. Re-wraps every document key so each document is now protected by the new master key
  3. Optionally re-encrypts your recovery key under the new master key (so you can export it again later)
  4. Creates a new master key backup encrypted with the recovery key (so recovery remains available in the future)

This step happens entirely on your device. The server receives only the re-encrypted outputs — it never sees any plaintext keys.

No API call is made. All cryptographic operations (re-encryption, key wrapping, token generation) happen locally on your device. The outputs are prepared for Step 5.

Step 5 — Finalize And Log In

Once the re-encryption is complete, the client submits the updated credentials and re-wrapped keys to the server. The system:

  • Stores your new authentication credentials
  • Updates all document key records with the re-wrapped versions
  • Invalidates all previous sessions (for security)
  • Issues a new session so you can start using Literal immediately

API Call: POST /auth/recovery

Submit the re-encrypted credentials and re-wrapped document keys:

curl -X POST "https://api.kyndex.co/v1/auth/recovery?id=<recovery-blind-index>" \
  -H 'Content-Type: application/json' \
  -d '{
    "login_bidx": 42,
    "email_encrypted": "cmVlbmNyeXB0ZWRlbWFpbHdpdGhuZXdVTUs=...",
    "registration_record": "base64encodedOpaqueRecord...",
    "encryption_salt": "bmV3c2FsdDMyYnl0ZXNiYXNlNjRlbmNvZGVk...",
    "mlkem_private_encrypted": "cmVlbmNyeXB0ZWRNTEtFTXByaXZhdGVrZXk=...",
    "signing_private_encrypted": "cmVlbmNyeXB0ZWRTaWduaW5nUHJpdmF0ZUtleQ==...",
    "recovery_key_encrypted": "cmVlbmNyeXB0ZWRyZWNvdmVyeWtleQ==...",
    "umk_backup": "bmV3dW1rYmFja3VwZW5jcnlwdGVk...",
    "new_recovery_bidx": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
    "rewrapped_deks": [
      {
        "document_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "wrapped_dek_umk": "cmV3cmFwcGVkREVLd2l0aG5ld1VNSw==..."
      }
    ],
    "old_revocation_token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=...",
    "revocation_token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=..."
  }'

Request Fields:

FieldTypeRequiredDescription
login_bidxintegerYesNew login bucket index (integer 0-8191) derived via OPRF from email and new password
email_encryptedbase64 stringYesEmail re-encrypted with new UMK
registration_recordbase64 stringYesOPAQUE registration record (the verifier, not a password hash) from your new password
encryption_saltbase64 stringYesNew salt for UMK derivation (32 bytes decoded)
mlkem_private_encryptedbase64 stringYesML-KEM private key re-encrypted with new UMK
signing_private_encryptedbase64 stringYesHybrid signing private key re-encrypted with new UMK (minimum 28 bytes decoded)
recovery_key_encryptedbase64 stringNoRecovery key re-encrypted with new UMK (optional, but recommended to preserve recovery capability)
umk_backupbase64 stringNoNew UMK backup encrypted with recovery key (optional, but recommended for future recovery)
new_recovery_bidxstringYesNew recovery blind index derived from new recovery key. Replaces the old one to prevent reuse.
rewrapped_deksarrayYesArray of document keys re-wrapped with new UMK (can be empty if no documents)
rewrapped_deks[].document_idUUIDYesDocument ID
rewrapped_deks[].wrapped_dek_umkbase64 stringYesDocument encryption key re-wrapped with new UMK
old_revocation_tokenbase64 stringNoRevocation token from the old session (32 bytes decoded). If provided, all old sessions are invalidated.
revocation_tokenbase64 stringYesNew revocation token derived from new UMK (32 bytes decoded). Used to create the new session.

Response (200 OK):

{
  "message": "Account recovery completed successfully",
  "access_token": "YWNjZXNzdG9rZW4zMmJ5dGVzYmFzZTY0ZW5jb2RlZA==...",
  "refresh_token": "cmVmcmVzaHRva2VuMzJieXRlc2Jhc2U2NA==...",
  "access_expires_at": "2026-03-10T12:30:45Z",
  "documents_updated": 5,
  "key_version": 2
}
FieldTypeDescription
messagestringConfirmation message
access_tokenbase64 stringShort-lived access token for a locked recovery session (no crypto tokens yet). Must be unlocked via Step 5b before full access.
refresh_tokenbase64 stringLong-lived refresh token used to unlock the session in Step 5b. Absent when the server sets it as an HttpOnly cookie instead.
access_expires_atISO 8601 timestampWhen the access token expires
documents_updatedintegerNumber of document keys that were re-wrapped and stored
key_versionintegerNew key version after recovery. Use this as x_key_version in encrypted requests with the new master key.

Error Responses:

  • 400 Bad Request — Invalid request body (missing fields, malformed base64, invalid registration record)
  • 404 Not Found — Account not found or recovery not available
  • 429 Too Many Requests — Rate limit exceeded
  • 500 Internal Server Error — Server error

Locked Session: The access token returned is a locked recovery session — it has no owner_token or user_member_token embedded yet. Your client cannot make authenticated requests that require these tokens (document access, search queries, etc.) until you complete Step 5b.

Step 5b — Unlock Your Session (Token Rotation)

The access token from Step 5 is locked — it cannot access your documents yet because it lacks the cryptographic tokens (owner_token, user_member_token, and search tokens) derived from your new master key. This step recomputes those tokens and upgrades your session to unlocked.

API Call: POST /auth/recovery/tokens

Submit the new cryptographic tokens derived from your new UMK:

curl -X POST https://api.kyndex.co/v1/auth/recovery/tokens \
  -H 'Authorization: Bearer <locked_access_token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "refresh_token": "cmVmcmVzaHRva2VuMzJieXRlc2Jhc2U2NA==...",
    "owner_token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=...",
    "user_member_token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=...",
    "owner_tokens": [
      {
        "old_token": "oldOwnerToken32BytesBase64...",
        "new_token": "newOwnerToken32BytesBase64..."
      }
    ],
    "grantor_tokens": [
      {
        "old_token": "oldGrantorToken32BytesBase64...",
        "new_token": "newGrantorToken32BytesBase64..."
      }
    ],
    "doc_tokens": [
      {
        "old_token": "oldDocToken32BytesBase64...",
        "new_token": "newDocToken32BytesBase64..."
      }
    ]
  }'

Request Fields:

FieldTypeRequiredDescription
refresh_tokenbase64 stringNoRefresh token from Step 5 response. Omit when using HttpOnly cookie transport — the server reads it from the cookie automatically.
owner_tokenbase64 stringYesHMAC(new ConsumerBIK, "owner:" || user_id) derived from new UMK. Will be embedded in the unlocked access token.
user_member_tokenbase64 stringYesHMAC(new ConsumerBIK, "my-memberships:" || user_id) derived from new UMK. Will be embedded in the unlocked access token.
owner_tokensarrayNoBatch rotation of owner tokens for fast searches. Each entry has old_token (from old UMK) and new_token (from new UMK).
grantor_tokensarrayNoBatch rotation of grantor tokens for grants you've issued. Each entry has old_token and new_token.
doc_tokensarrayNoBatch rotation of per-document tokens. Each entry has old_token and new_token.

Response (200 OK):

{
  "access_token": "YWNjZXNzdG9rZW4zMmJ5dGVzYmFzZTY0ZW5jb2RlZA==...",
  "access_expires_at": "2026-03-10T12:30:45Z",
  "rotated": {
    "owner_tokens": 1,
    "grantor_tokens": 0,
    "doc_tokens": 5,
    "user_member_tokens": 0
  },
  "tokens_rotated_at": "2026-03-10T11:45:30Z"
}
FieldTypeDescription
access_tokenbase64 stringNew unlocked access token with crypto tokens embedded. Use this for all authenticated requests.
access_expires_atISO 8601 timestampWhen the new access token expires
rotated.owner_tokensintegerNumber of owner tokens rotated (typically 1 if you have a personal search index)
rotated.grantor_tokensintegerNumber of grantor tokens rotated (grants you've issued)
rotated.doc_tokensintegerNumber of document tokens rotated
rotated.user_member_tokensintegerAlways 0 (entity membership tokens are rotated separately via a different mechanism)
tokens_rotated_atISO 8601 timestampWhen the rotation completed

Error Responses:

  • 400 Bad Request — Invalid refresh token or malformed tokens
  • 401 Unauthorized — Invalid or expired locked access token
  • 429 Too Many Requests — Rate limit exceeded
  • 500 Internal Server Error — Server error

Session Unlocked: After this step completes, your new access token is fully unlocked with all crypto tokens embedded. You can now make authenticated requests (search, access documents, manage grants, etc.) with complete access to your account. Your old sessions have been invalidated.

Error Handling

"Recovery Not Available For This Account"

This error means the account does not have a recovery key backup stored. This can happen if:

  • Recovery key setup was never completed during or after registration
  • The recovery key backup was removed or corrupted

Unfortunately, without a recovery key backup, there is no way to recover the account. This is an inherent property of zero-knowledge systems — the server cannot decrypt your data on your behalf.

Lost Recovery Key

If you've lost your recovery key but still know your password, you can:

  1. Log in normally with your password
  2. Generate a new recovery key from your account settings
  3. Store the new recovery key safely (see best practices above)

If you've lost both your password and your recovery key, the account and its data are permanently inaccessible.

On this page