Ppoppo Docs

Trust boundary

Most of Ppoppo's design choices follow from one boundary and one principle.

1st-party vs 3rd-party

Ppoppo operates some applications itself (its own chat clients). You — an external developer — build your own application on top of the platform. The line between the two is a trust boundary, not an org chart: a 1st-party app is one Ppoppo runs; a 3rd-party app is one Ppoppo authenticates but does not control. Everything in these docs is the 3rd-party surface.

Authentication is not identity verification

PAS proves that a user controls a Ppoppo account. It does not verify who they are in the real world. If your service needs identity verification (KYC), you must implement it yourself — Ppoppo authentication cannot stand in for it.

Why PKCE-only, with no client_secret for login

For the login flow Ppoppo supports exactly one grant — Authorization Code with PKCE S256 — and issues no client_secret:

Aspectclient_secretPKCE S256
SecurityA leaked secret compromises every loginPer-request cryptographic verification
OperationsRequires secure storage and rotationNothing to store or rotate
Client typesSafe only for server appsSafe for web, mobile, and CLI alike

There is no implicit flow and no password grant — PKCE gives equivalent or better security without the burden of secret management. (The External API is different: it authenticates with a client_credentials secret, because it's a server-to-server call.)

Why gRPC for the External API

AspectREST (JSON / HTTP)gRPC
TypesManual schema validationProtocol Buffers enforce types at compile time
StreamingNeeds WebSocket or SSE on the sideNative server streaming — replaces webhooks, no public endpoint to expose
ClientsHand-written HTTP wrappersAuto-generated, strongly typed
ContractAn OpenAPI spec that tends to drift.proto files are the source of truth

This is what lets the pcs-external crate give you a compile-time-safe send path, and lets event streaming replace webhooks without you running an inbound endpoint.

Who owns which data

PAS owns the user's mutable data — most importantly their email. Cache only the immutable pair ppnum_id + ppnum; fetch email from UserInfo when you actually need it, so you never serve a stale address. See The ppnum model for the identifiers.