Skip to main content

Keys and Encryption API

Machine-readable spec: openapi/keys-and-encryption.yaml (generated from the live FastAPI app — covers keys.router, wrapped_key.router, user_keys.router).

These endpoints manage the ECDH public keys and wrapped master keys used for E2E encryption. The server stores only public keys, signed credentials, and opaque encrypted blobs — it cannot decrypt message content or master keys. See mobile-app/sapot-mobile-app/docs/ARCHITECTURE.md and the crypto-architecture skill for the client-side design.


Endpoints at a glance

MethodPathAuthSummary
POST/keys/registerJWT Bearer (rate-limited 3/min)Register/replace the current user's ECDH public key; server signs it and returns a SignedCredential.
GET/keys/server-public-keyNoneRetrieve the server's Ed25519 verify key (used to check credential signatures).
POST/keys/contacts/{peer_id}JWT Bearer (rate-limited 30/min)Upsert a backup of a guest peer's ECDH public key, pre-encrypted client-side under the caller's master key.
GET/keys/contactsJWT Bearer (rate-limited 10/min)List all backed-up contact key blobs for the current user (opaque to the server).
GET/keys/{peer_id}/typeJWT BearerReturns {"is_guest": bool} — whether peer_id has a server-registered PeerKey.
GET/keys/{peer_id}JWT BearerRetrieve a peer's signed public-key credential. 404 if the peer has no registered key.
POST/users/wrapped-keyJWT Bearer (rate-limited 3/min)Store the current user's wrapped master key (201; replaces any existing row).
GET/users/wrapped-keyJWT Bearer (rate-limited 10/min)Retrieve the current user's wrapped master key. 404 if none exists.
PUT/users/wrapped-keyJWT Bearer (rate-limited 5/min)Update the current user's wrapped master key blob. 404 if none exists.
POST/users/recovery-setupJWT Bearer (rate-limited 3/min)Configure one or more recovery-method copies of the wrapped master key (201).
GET/users/recovery-keyRecovery session token (recovery_token + method query params, rate-limited 5/min)Retrieve a recovery copy of the wrapped master key during the account recovery flow.
PUT/users/recovery-keysJWT Bearer (rate-limited 5/min)Bulk-replace recovery-method blobs (same body as /users/recovery-setup).

POST /keys/register

Registers/replaces the current user's ECDH public key (SignedCredential request/response schema in the YAML). Any existing key for the user is deleted and replaced. Credentials expire 365 days after issuance — this business rule isn't visible in the schema.


POST /keys/contacts/{peer_id}

Used for guest peers who are not server-registered and whose keys are otherwise only available from a local TCP handshake. Upserts on conflict (owner_id, peer_id) — also not visible in the schema.


Wrapped master key

The wrapped_blob field on WrappedKeyRequest is a single opaque string (the client is responsible for embedding any salt/nonce it needs inside it) — the server does not parse or validate its contents.

Recovery blobs

GET /users/recovery-key requires a recovery_token obtained from one of the flows in auth-and-recovery.md, plus the method to fetch.


See keys-and-encryption.yaml for exact field-level request/response schemas (SignedCredential, WrappedKeyRequest, RecoverySetupRequest, RecoveryBlobItem), or the live server's /docs / /openapi.json.