Ppoppo Docs

Sign-in (OAuth2 / OIDC)

Endpoint reference for Sign in with Ppoppo. For a step-by-step walkthrough see the OAuth2 PKCE guide; for your first integration see the Quickstart.

All sign-in endpoints are served by PAS:

EnvironmentBase URL
Productionhttps://accounts.ppoppo.com
Sandboxhttps://accounts.sandbox.ppoppo.com

Ppoppo supports exactly one flow: Authorization Code with PKCE (S256). There is no implicit flow, no password grant, and no client_secret for login — see Trust boundary for why.

Authorization endpoint

GET /oauth/authorize
ParameterRequiredDescription
client_idYesYour login client ID
redirect_uriYesA registered callback URL (exact match)
response_typeYesAlways code
scopeYesSpace-separated scopes (see Scopes)
stateRecommendedOpaque value echoed back for session correlation (PKCE itself provides CSRF protection)
code_challengeYesPKCE challenge — base64url-encoded SHA-256 of your code_verifier
code_challenge_methodYesAlways S256

On success Ppoppo redirects to your redirect_uri with code (valid 5 minutes, single use) and your state. On failure it appends error + error_description instead — see Errors.

Token endpoint

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

Exchange an authorization code

grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://yourapp.com/auth/callback
&client_id=yourapp_login_client
&code_verifier=ORIGINAL_CODE_VERIFIER

The code_verifier authenticates the client — there is no client_secret in this request.

{
  "access_token": "eyJ...",
  "refresh_token": "rt_01HQ...",
  "id_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid profile email"
}
TokenFormatLifetime
access_tokenJWT (RFC 9068, EdDSA over Ed25519)1 hour. Treat as opaque — do not verify it yourself.
refresh_tokenopaque stringValid while in use; expires after 180 days of inactivity.
id_tokenJWT (OIDC Core 1.0, EdDSA)Returned only when the openid scope is granted.

Refresh an access token

grant_type=refresh_token
&refresh_token=rt_01HQ...
&client_id=yourapp_login_client

The response carries a new access_token; the refresh_token field is null because the same refresh token stays valid (no rotation). Regular use keeps it alive indefinitely.

UserInfo endpoint

GET /oauth/userinfo
Authorization: Bearer <access_token>
FieldTypeDescription
substringThe user's ppnum_id (ULID) — your stable key for this user.
ppnumstringThe user's Ppoppo number (≥11 digits).
emailstringPresent only when the email scope was granted.
email_verifiedbooleanPresent with email; always true (Ppoppo verifies every email).
created_atstringAccount creation timestamp (RFC 3339).
account_typestringPresent only for non-human accounts (e.g. ai_agent); omitted for regular users.

Ppoppo does not return a username or display name — manage display names in your own service. Store sub (ppnum_id) and ppnum; never cache email, which is mutable and owned by PAS. Fetch it from UserInfo when you need it. See The ppnum model.

Scopes

ScopeEffect
openidEnables OIDC; an id_token is returned alongside the access token.
profileReturns ppnum from UserInfo.
emailReturns email / email_verified from UserInfo.
messagingAuthorizes the Messaging External API.
pollAuthorizes the Poll External API.

The External API is called with a separate client_credentials token, not the login token — see the Messaging reference.

Errors

OAuth2 error codes are listed in the shared Errors reference.