fepli
DevelopersAPI resources

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) or on.

  • Name
    encrypting
    Type
    boolean
    Description

    Whether writes must arrive sealed: true from running on.

  • Name
    tenant
    Type
    object
    Description

    The tenant, with uuid and name.

  • Name
    team
    Type
    object
    Description

    The team key every record key is sealed for: keyId, encPub (X25519) and signPub (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, and status, pending until the comparison, then confirmed with its scope: {"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 in verifier (with user), the team key in group, teamSignPub, and the messages so far in exchange (start, accept, keyA, keyB, macA, macB, cancel). It stays after the comparison so you can check macA.


GET/e2e

Retrieve 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.

GET/e2e
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"
  }
}

POST/e2e/key

Register 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.

POST/e2e/key
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.


POST/e2e/key/accept

Accept 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)).

POST/e2e/key/accept
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"}'

POST/e2e/key/keyB

Send 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.

POST/e2e/key/keyB
curl -X POST https://example.fepli.eu/api/e2e/key/keyB \
  -H "Authorization: Bearer $FEPLI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pub": "iPdR1AZknGX95mTA3b7GloMdLVXgwEYCPm0Mslh1p34"}'

POST/e2e/key/macB

Confirm 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.

POST/e2e/key/macB
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"}'

POST/e2e/key/cancel

Cancel 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) or key.

POST/e2e/key/cancel
curl -X POST https://example.fepli.eu/api/e2e/key/cancel \
  -H "Authorization: Bearer $FEPLI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason": "mismatch"}'

Was this page helpful?