Kyndex
Guides

Document Upload With Client-Side Encryption

Walk through uploading an encrypted document from authentication through enclave processing to search verification.

Zero-Knowledge Guarantee: Every document you upload is encrypted on your device before it reaches the server. The server stores ciphertext, blind index tokens, and operational metadata (timestamps, processing status, size category) — it can never read your document content, metadata, or search queries. For more on this principle, see Zero-Knowledge Model.

Prerequisites

Before you begin, make sure you have:

  • A registered account with an active access token (see Getting Started for authentication setup)
  • A cryptography library capable of:
    • Symmetric authenticated encryption
    • Key wrapping
    • HMAC-based token generation
  • The platform public key (fetched in Step 1 below)

Overview

Loading diagram…
StepWhereWhat Happens
1. Get platform public keyServer → ClientFetch the platform's public key for wrapping your document encryption key
2. Reserve a document slotClient → ServerObtain a server-authoritative document ID and commitment nonce
3. Encrypt your documentYour deviceGenerate a key, encrypt with the commitment nonce in the AAD, wrap the key, compute search tokens
4. Create the document recordClient → ServerSend encrypted metadata, wrapped keys, and search tokens
5. Upload encrypted contentClient → ServerSend the raw encrypted bytes
6. Confirm processingClient → ServerPoll until the enclave finishes verification and seal generation
7. Verify searchabilityClient → ServerCompute a search token locally, query the server, decrypt results

Server-side processing (enclave verification, seal generation, and entity index generation) happens automatically after step 5.

Step 1 — Get The Platform Public Key

Before encrypting, fetch the platform's public key. You will use this key to wrap your document encryption key so the secure enclave can process your document later.

curl https://api.kyndex.co/v1/public-keys/server

Response:

{
  "key_id": "pmk-2026-01",
  "key_spec": "ECC_NIST_P256",
  "ecc_public_key": "<base64-public-key>",
  "algorithm": "hybrid",
  "description": "Platform Master Key for DEK wrapping (classical ECC portion)"
}

This endpoint is unauthenticated. The key changes rarely — cache it locally and refresh only when you receive a key rotation signal.

Step 2 — Reserve A Document Slot

Reserving a slot gives you a server-authoritative document ID and a commitment nonce that binds your encryption to this specific upload. The reservation expires in 5 minutes.

You must reserve before encrypting. The commitment_nonce from this step must be incorporated into the encryption AAD in Step 3. This binds the ciphertext to the specific document slot and prevents reuse.

curl -X POST https://api.kyndex.co/v1/documents/reservations \
  -H 'Authorization: Bearer <access_token>'

Response (201 Created):

{
  "document_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "commitment_nonce": "<base64-16-bytes>",
  "expires_in_seconds": 300
}

Use document_id as the reservation_id in Step 4. Incorporate the commitment_nonce into your encryption AAD in Step 3.

Why Two-Phase Upload?

  • The document ID is assigned by the server (compliance requirement)
  • The commitment nonce provides temporal binding — it proves that encryption happened after the reservation was created
  • The AAD binding prevents the ciphertext from being used with a different document

Think of the commitment nonce like a timestamp on a notarised document — it proves the encryption happened after the reservation was issued, preventing a pre-encrypted file from being swapped in.

Step 3 — Encrypt Your Document

All encryption happens on your device. The server never sees plaintext content, metadata, or keys. Your device needs to perform five operations:

Generate A Document Encryption Key

Create a fresh, random symmetric encryption key for this document. Every document gets its own unique key — no two documents share a key.

Encrypt The Document Content

Encrypt the raw document bytes using your new key with authenticated encryption. Include the commitment_nonce from Step 2 and the key-wrapping timestamp (wrap_ts) in the authentication context (AAD) to bind the ciphertext to this specific upload and prevent key replay.

Wrap The Key

Wrap your document encryption key twice:

  1. With your personal master key — so you can decrypt the document later
  2. With the platform public key (from Step 1) — so the secure enclave can process it

This dual wrapping ensures that both you and the enclave can access the key, but the server itself never can. Think of it as making two copies of a house key — one for yourself, one for the locksmith who needs to inspect the house. The server carries both copies but can't use either.

Compute Blind Index Tokens

Generate one-way cryptographic tokens for each searchable value (document type, text content, dates, tags). These tokens enable you to search your encrypted documents later without the server learning your search terms.

Each token is computed using your personal search key and a normalization step that ensures consistent matching (case-insensitive, whitespace-normalized). For more on how this works, see Encrypted Search.

Encrypt Document Metadata

Encrypt the document's metadata (title, filename, type, tags) separately using the same document key. The server stores only the encrypted metadata blob.

Step 4 — Create The Document Record

Send the encrypted metadata, wrapped keys, identity tokens, and search tokens to the server.

curl -X POST https://api.kyndex.co/v1/documents \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "reservation_id": "<document_id-from-step-2>",
    "metadata_encrypted": "<base64-encrypted-metadata>",
    "wrapped_dek_umk": "<base64-key-wrapped-with-your-master-key>",
    "wrapped_dek_pmk": "<base64-key-wrapped-with-platform-key>",
    "owner_token": "<base64-32-bytes>",
    "doc_token": "<base64-32-bytes>",
    "wrap_ts": 1710288000,
    "consumer_tokens": [
      { "token": "<base64-blind-index>", "index_type": "doc_type" },
      { "token": "<base64-blind-index>", "index_type": "text_content" }
    ],
    "size_bucket": 2
  }'

Request Fields

FieldRequiredDescription
reservation_idYesThe document_id returned from the reservation step
metadata_encryptedYesBase64-encoded encrypted document metadata
wrapped_dek_umkYesDocument key wrapped with your personal master key (base64)
wrapped_dek_pmkYesDocument key wrapped with the platform public key (base64)
owner_tokenYesCryptographic owner identity token (base64, 32 bytes)
doc_tokenYesCryptographic document identity token (base64, 32 bytes)
wrap_tsYesUnix epoch seconds when DEK was wrapped
consumer_tokensNoArray of blind index tokens for searchable encryption
size_bucketNoDocument size category (1–5)

Response (201 Created):

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "pending_upload",
  "created_at": "2026-01-15T10:30:00.000Z",
  "upload_instructions": "Upload encrypted blob to /v1/documents/{id}/content using PUT"
}

The document is now in pending_upload status — the metadata and keys are stored, and the server is waiting for the encrypted content.

Step 5 — Upload Encrypted Content

Send the raw encrypted document bytes as a binary body. The Content-Length header is required.

curl -X PUT https://api.kyndex.co/v1/documents/<document-id>/content \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/octet-stream' \
  -H 'Content-Length: <byte-count>' \
  --data-binary @encrypted-document.bin

Response (201 Created):

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "pending_ocr",
  "message": "Document uploaded successfully, processing queued"
}

The document transitions to pending_ocr and server-side processing begins automatically.

The maximum upload size is 100 MB. There is a secondary 10 MB threshold that affects how content is returned on download — files under 10 MB are returned as a direct binary stream, while larger files are served as a streaming response. The 10 MB threshold has no effect on upload.

What Happens Next: The secure enclave unwraps the document key using the platform's private key, temporarily decrypts the document for verification and seal generation, then discards all plaintext. The encrypted blob remains the only stored copy. No server operator can observe or interfere with this process.

Step 6 — Confirm Enclave Processing

After upload, the document moves through several processing states automatically. Poll the document status to track progress:

curl https://api.kyndex.co/v1/documents/<document-id> \
  -H 'Authorization: Bearer <access_token>'

Document Processing States

StateDescription
pending_uploadRecord created, waiting for encrypted content
pending_ocrContent uploaded, queued for enclave processing
processingSecure enclave is actively processing the document
processedEnclave processing complete — verification seal generated
failedProcessing failed — the document must be re-uploaded

Once status is processed, enclave processing is complete. Consumer blind index tokens submitted at creation are immediately available for personal search. Documents processed in an organization context also receive entity-scoped indexes generated by the enclave during this step.

For a deeper look at what happens at each stage, see Document Lifecycle.

Step 7 — Verify Searchability

Once your document reaches processed status, verify that it is searchable by performing a search using blind index tokens.

Compute a search token on your device (using the same search key and normalization process you used during upload), then send it to the search endpoint:

curl -X POST https://api.kyndex.co/v1/search \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "<base64-search-token-32-bytes>",
    "scope": "consumer",
    "index_types": ["doc_type", "text_content"]
  }'

Response:

{
  "documents": [
    {
      "doc_token": "<base64-document-reference>",
      "metadata_encrypted": "<base64-encrypted-metadata>",
      "wrapped_dek_umk": "<base64-key-wrapped-with-your-master-key>"
    }
  ],
  "count": 1
}

The server returns encrypted results — decrypt them on your device using your personal master key. The server never learns what you searched for or what the results contain.

Search Scopes

  • consumer — search your personal documents. Results include your personally-wrapped key.
  • entity — search within an organization. Requires entity_token. Results include an organization-scoped wrapped key.

For the full explanation of how encrypted search works, see Encrypted Search.

Error Handling

All error responses follow the RFC 9457 Problem Details format with Content-Type: application/problem+json:

{
  "type": "https://api.kyndex.co/errors/INVALID_REQUEST",
  "title": "Bad Request",
  "status": 400,
  "detail": "The request body failed validation",
  "instance": "/v1/documents"
}

Common Errors During Upload

StatusErrorWhat To Do
400Invalid or expired reservationReservations expire after 5 minutes. Create a new one and re-encrypt with the new commitment nonce.
401UnauthorizedYour access token has expired. Refresh it and retry.
413Payload too largeThe encrypted document exceeds the 100 MB limit. Reduce the document size before encrypting.
429Rate limitedToo many requests. Wait and retry with exponential backoff.

Now that you've uploaded your first document, explore these related guides and concepts:

On this page