Skip to content

Levels and security

The server is the only authority. The app never “executes” a decision: it only sends an answer intent, and the server accepts it if it is valid, on time and at the required level.

Decision levels 0 to 4The service asks for a minimum level; the workspace can only raise it. The highest level among the questions applies. Level 0 is a tap, level 1 an unlocked device, level 2 biometrics or a 2FA code (a 2FA session window may cover it), level 3 biometrics or a 2FA code for every action, online and with a short expiry, level 4 is level 3 plus a quorum of people.Service levellevel: 0–4 in ask()(default 1)Workspace minimumcan raise,never lowerEffective levelhighest amongthe questions0 · tapmark as read, view1 · unlocked devicerestart a container2 · biometrics or 2FA2FA window may cover it3 · biometrics or 2FA, every actiononline · short expiry4 · level 3 + quorume.g. 2 of 3 approve (app)
Level Requires Example
0 A tap View a dashboard, mark as read
1 An unlocked device (signed-in session) Restart a container, reply to a lead
2 Biometrics or the device PIN, or the 2FA code An AI agent wants to act, approve a deploy
3 Biometrics or the 2FA code for every action, online and with a short expiry Delete data, grant access, move money
4 Level 3 and a quorum 2 of 3 directors approve

The service sets the level in ask(). The workspace can make it stricter, and the service can never lower it. The highest level among the questions applies to the whole set.

On the native app, the private key is created in the Secure Enclave (iOS) or the Keystore (Android) with the rule “only sign after user authentication”. Without a fingerprint, face or device passcode, the chip itself refuses to sign.

  • The device creates one key per membership/server, so an organization can revoke its key without affecting the others.
  • The signed answer includes the question set id, version, answers, timestamp, nonce and the hash of the content shown.
  • The biometrics prompt shows a summary of what is being signed, so nobody approves blindly.
  • Biometrics is always one prompt per decision.

Not every device has a fingerprint reader, and the browser has no biometrics. For levels 2 and 3, a person can enable two-step verification in Account with any TOTP authenticator app (Google Authenticator, Microsoft Authenticator, 1Password, Authy or the company’s own app) and sign with the 6-digit code.

Signing a decision with the 2FA codeWithout biometrics, a person with 2FA enabled can sign level 2 and 3 decisions with the 6-digit code of an authenticator app. Level 3 always asks for a code. For level 2, if the session is inside the 2FA window the person chose, it is signed without typing a code; otherwise the code is typed, checked by the server (±30 seconds, single use, 5 wrong codes in 15 minutes lock), and a valid code opens the window for that session.Open the decisionno biometrics here(browser, PWA)Level?2 or 3Type the 6-digit codefrom the authenticator appServer checks±30 s · single use5 wrong / 15 min = lock2FA window open?this session,0–240 min you chooseReply acceptedauth_method: "totp"(valid code opens the window)Signed without a codelevel 2 onlylevel 3validlevel 2noyes
  • The code is checked on the server (±30 seconds) and is valid only once. 5 wrong codes in 15 minutes block new attempts.
  • The reply records auth_method: "totp" and the level proven.
  • Level 3 always asks for a code, for every action.
  • Level 4 (quorum) still requires the app with biometrics.
  • Turning 2FA off requires a valid code, so whoever steals a session cannot turn it off.

When 50 approvals are waiting, typing a code for each one is not reasonable. In Account each person chooses a 2FA window: off (always ask), 5, 15, 30, 60, 120 or 240 minutes. After a valid code, that session can sign level 2 decisions without typing the code again until the window ends.

  • The window belongs to the session: other browsers and devices still need their own code.
  • It only covers level 2. Level 3 always asks for a new code.
  • Turning 2FA off clears the window on every session.
  • Each question set declares offline: allowed | denied and an expiry.
  • The server rejects an answer for an old version or an expired question, and the app explains why (“your answer was not accepted because the question expired at 12:30”).
  • Idempotency: the same answer sent twice is executed once.
  • Level 3 or more is never accepted offline.
UPDATE question_sets
SET status = 'answered', answered_by = $3, answered_at = now()
WHERE id = $1 AND version = $2 AND status = 'open';
-- 0 rows = already decided, expired or a different version

The answer and the notice to your system are saved in the same transaction (transactional outbox). A separate worker delivers the signed webhook with automatic retries, so a decision is never lost.

The reply your system receives is signed with the server’s Ed25519 key. The public keys are published at <api>/.well-known/notificme-keys.json, and the SDK verifies them for you. See Webhooks.

  • Revocation of a lost device from the web app (Devices).
  • Audit trail: who, which device, which level, when and the content hash, immutable and exportable.
  • Service tokens have scopes, and tokens for AI agents can require human approval for every write. See AI agents and MCP.