Skip to main content
InviziPoll docs

Documentation for encrypted, anonymous workplace polling.

Answers stay ciphertext on our servers — these guides show you exactly how. Written and maintained by the team that builds it, for poll authors, workspace owners, and developers.

Documentation surfaces index with live article counts by section.

Start here

Three paths through the docs

Pick the role you're in today. Each path is ordered — finish it and you'll have a working, verifiable poll.

PATH 02 · 14 MIN

Setting up your team

Roles, SSO, provisioning and folder permissions for a workspace you can hand over.

  1. 01Inviting a team member
  2. 02SSO sign-in policy & JIT provisioning
  3. 03Folders, key hierarchy & access
  4. 04Workspace settings and branding
1 articlefor owners
PATH 03 · 21 MIN

Going to production

Client-side encryption, key wrapping, and what ciphertext we can and cannot see.

  1. 01Encryption model overview
  2. 02Key hierarchy & poll wraps
  3. 03Threat model
  4. 04API, webhooks & exports
2 articlesfor developers

Security & architecture

Read the encryption model before you trust it

Every response is sealed in the respondent's browser against a per-poll public key. The server stores the sealed blob and nothing else — no plaintext, no identity, no IP-linked record.

  • Client-side seal. ML-KEM-768 encapsulation plus AES-256-GCM for the payload.
  • Wrapped poll keys. Private keys are wrapped to admin devices, never held in plaintext by us.
  • Aggregate-only decryption. Results decrypt in the admin's browser above your minimum-n threshold.
What we cannot doRecover an individual answer, or map a response back to a person — by architecture, not policy. The threat model documents the residual risks too.

Read the encryption model overview →

seal-response.tsillustrative — not wire-accurate
// runs in the respondent's browser — nothing leaves in plaintext
const pollKey = await fetchPollPublicKey(pollId);   // ML-KEM-768
const { ciphertext, sharedSecret } = await mlKem768.encapsulate(pollKey);

const sealed = await aesGcm.seal({
  key:   sharedSecret,
  data:  encode({ q1: "unlikely", q2: "…" }),
  aad:   pollId,
});

await post("/v1/responses", {
  pollId,
  kem: ciphertext,        // encapsulated secret
  payload: sealed,        // opaque blob · size varies
  identity: null,         // never persisted
});

Still stuck

Talk to the people who wrote the docs

Security review pack

Threat model, encryption artifacts and subprocessor list — everything procurement asks for, in one place.

view /security →