Encryption
/e2e is how an integration takes part in a tenant's end-to-end encryption: the team key to seal records for, its own key, and its side of the emoji comparison with a super admin. The steps and the wire format are in End-to-end encryption.
GET /e2e works with any token. The other endpoints are for service accounts only (403 for a personal access token), and a read-only token may use them: a key changes nothing of the tenant's.
The encryption state model
- Name
phase- Type
- string
- Description
off,running(the team's browser is encrypting the stored records) oron.
- Name
encrypting- Type
- boolean
- Description
Whether writes must arrive sealed:
truefromrunningon.
- Name
tenant- Type
- object
- Description
The tenant, with
uuidandname.
- Name
team- Type
- object
- Description
The team key every record key is sealed for:
keyId,encPub(X25519) andsignPub(Ed25519), base64url. Trust it only as the emoji comparison vouched for it.
- Name
fingerprint- Type
- string
- Description
The fingerprint of the team signing key, as the admin and the tenant's emergency kit show it.
- Name
statement, statementSig- Type
- object, string
- Description
What the team declared when it switched the encryption on, and its Ed25519 signature over the canonical JSON. See The tenant statement.
- Name
sealedFields- Type
- object
- Description
The sealed fields per record type, e.g.
{"participant": ["firstname", "lastname"]}. A write carries an envelope for every one of them.
- Name
key- Type
- object
- Description
The service account's own key:
keyId,encPub,signPub,fingerprint,registeredAt, andstatus,pendinguntil the comparison, thenconfirmedwith itsscope:{"kind": "team"}for every participant, or{"kind": "host", "hostUuid", "name"}for the children of one host. Missing without a key.
- Name
request- Type
- object
- Description
The comparison of the key:
status(pending,running,completed,cancelled,expired),txn,scope, the admin's key inverifier(withuser), the team key ingroup,teamSignPub, and the messages so far inexchange(start,accept,keyA,keyB,macA,macB,cancel). It stays after the comparison so you can checkmacA.
/e2eRetrieve the encryption state
Returns the state of the tenant's encryption. For a personal access token, key and request are missing. Poll it during a comparison to see the admin's messages.
curl https://example.fepli.eu/api/e2e \
-H "Authorization: Bearer $FEPLI_TOKEN"
Response (excerpt)
{
"phase": "on",
"encrypting": true,
"tenant": {
"uuid": "0191c7a0-1b2c-7d3e-8f40-5a6b7c8d9e01",
"name": "Ferienpass Elmsbrück"
},
"team": {
"keyId": "9e2b4c6d-8f01-4a23-b456-7c8d9e0f1a2b",
"encPub": "…",
"signPub": "…"
},
"fingerprint": "4F2A 9C1E 77B0 D3A5 0E6C 18F9",
"sealedFields": {
"participant": ["firstname", "lastname"]
},
"key": {
"keyId": "3f0c2c1e-8a4b-4c5d-9e6f-7a8b9c0d1e2f",
"encPub": "ER0ZP0Nvhdmt1HnwTD5pDCPBpt_YMHXfiQyQWTuB8zs",
"signPub": "yumW6j3VfgKQ42w3izlE0nIs9Wwp9i6h3zZsjCswOdM",
"fingerprint": "F6F0 F48E D485 AF87 5737 9FBE",
"status": "confirmed",
"scope": { "kind": "team" },
"registeredAt": "2026-10-26T10:05:00+01:00"
}
}
/e2e/keyRegister a key
Registers the public halves of the service account's key pairs. The key then waits for a super admin's emoji comparison; nothing is sealed for it before. A key still waiting is replaced. While the service account holds a confirmed key, a new one is refused (409) until a super admin revokes the old one. Without encryption at the tenant, the answer is 409 as well.
Required attributes
- Name
keyId- Type
- string
- Description
A version 4 UUID you choose for the key.
- Name
encPub- Type
- string
- Description
The X25519 public key records are sealed for: 32 bytes, base64url.
- Name
signPub- Type
- string
- Description
The Ed25519 public key: 32 bytes, base64url.
curl -X POST https://example.fepli.eu/api/e2e/key \
-H "Authorization: Bearer $FEPLI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"keyId": "3f0c2c1e-8a4b-4c5d-9e6f-7a8b9c0d1e2f",
"encPub": "ER0ZP0Nvhdmt1HnwTD5pDCPBpt_YMHXfiQyQWTuB8zs",
"signPub": "yumW6j3VfgKQ42w3izlE0nIs9Wwp9i6h3zZsjCswOdM"
}'
The response (200 OK) is the encryption state, with key.status pending.
/e2e/key/acceptAccept a comparison
Answers the comparison a super admin started (request.status running, request.exchange.start present) with your commitment to an ephemeral X25519 key you make for this comparison only.
Required attributes
- Name
commit- Type
- string
- Description
base64url(SHA-256(ephemeral public key ‖ canonical JSON of
request.exchange.start)).
curl -X POST https://example.fepli.eu/api/e2e/key/accept \
-H "Authorization: Bearer $FEPLI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"commit": "shmSD_06nVsrjXtdH0TeEgrfbIlA5k_nfKahJmAfdvc"}'
/e2e/key/keyBSend the ephemeral key
Sends your ephemeral X25519 public key, once the admin's is in request.exchange.keyA. Both sides then compute the 7 emojis.
Required attributes
- Name
pub- Type
- string
- Description
The ephemeral X25519 public key you committed to, base64url.
curl -X POST https://example.fepli.eu/api/e2e/key/keyB \
-H "Authorization: Bearer $FEPLI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"pub": "iPdR1AZknGX95mTA3b7GloMdLVXgwEYCPm0Mslh1p34"}'
/e2e/key/macBConfirm the emojis
Confirms that the emojis matched, with your MAC over your key. The admin confirms on their side; their browser then signs your key into the tenant's directory, and key.status becomes confirmed. Check the admin's macA before you trust the team key.
Required attributes
- Name
mac- Type
- string
- Description
HMAC-SHA256 over the canonical JSON of
{"key": {"keyId", "encPub", "signPub", "userUuid"}}, base64url. See The emoji comparison.
curl -X POST https://example.fepli.eu/api/e2e/key/macB \
-H "Authorization: Bearer $FEPLI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mac": "VUhqONFtbXZXxeTg49Vt-9mlbfTsd5Ulw03VTtSxS6k"}'
/e2e/key/cancelCancel a comparison
Calls the comparison off. Nothing is shared; the admin can start a new one.
Required attributes
- Name
reason- Type
- string
- Description
mismatch(the emojis differ),cancelled,commitment,mac(a MAC did not check) orkey.
curl -X POST https://example.fepli.eu/api/e2e/key/cancel \
-H "Authorization: Bearer $FEPLI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"reason": "mismatch"}'