# Architecture
Version: 1.1.1
Last Updated: January 23, 2026
Identity & Data Model
- Canonical identity:
website_userstable (email unique viaidx_website_users_email_lower). - Transitional identity:
userstable (CLI legacy). Bridge by email; auto-createwebsite_userson claim if absent. - Sessions:
auth_sessionsstores refresh token hashes, device_id, expiry. - Installations:
installations(optional) tracks devices; used for device approval visibility.
User States
- Unregistered: No account, limited core features.
- Registered Free: Account + login, core features enabled.
- Licensed:
renpy_free,pro,team,enterprise(and Basic in legacy docs) with entitlements.
Tokens
- Access Token: JWT, 15 min, memory only, RS256, aud
branchpy-api. - Refresh Token: Opaque, 30–90 days, stored in
auth.json, hashed in DB. - License Token (Entitlements JWT): 30–90 days, offline verification via embedded public key, includes plan, features,
max_offline_days(default 30).
Core Flows
- Login:
POST /v1/auth/login→ access + refresh + license tokens → store refresh/license on disk; access in memory. - Refresh:
POST /v1/auth/refresh→ new access + license tokens; revoke on invalid/expired. - Whoami:
GET /v1/auth/me→ profile, plan, expiry (debug/validation). - Entitlements:
GET /v1/license/meandGET /v1/license/entitlements→ plan, features, expiry, max_offline_days. - Claim Ren’Py License:
POST /v1/claim/renpy(idempotent) → always 200 with token; sets planrenpy_free. - Device Flow:
POST /v1/auth/device/start→device_code+user_code; poll/v1/auth/device/polluntil approved; then issue tokens; sync toauth.json. - Offline Mode: Allowed until
max_offline_daysand JWTexprespected; prompt refresh when exceeded.
Endpoint Contract (Auth/Licensing)
/v1/auth/login(email + password or device approval); returns access/refresh/license tokens./v1/auth/refresh(refresh token) → access + license tokens./v1/auth/me→ user info (id, email, plan, expiry)./v1/auth/device/start→{ device_code, user_code, verification_uri, interval }./v1/auth/device/poll→ exchanges approved device_code for tokens./v1/license/me→ entitlements snapshot./v1/license/entitlements→ entitlements (alias; retained for compatibility)./v1/claim/renpy→ issues/refreshesrenpy_freelicense (idempotent).
Identity Bridge (Dual Auth Systems)
- Preferred: website auth (website_users).
- Bridge logic (claim endpoint): try
website_usersbysub→ fallback byemail→ auto-createwebsite_usersif missing → proceed with claim. - Migration 024 enforces case-insensitive unique email to support bridging.
Governance & Logging
- Emit
auth.login_success|failed,auth.refresh_*,auth.logout,auth.token_revoked,license.entitlement_check,license.claim,installation.device_approved. - Schemas: see
Technical/events/catalog.md(Auth and License topics). Do not duplicate event field definitions here.
Security Notes
- RS256 signatures; no PII in license tokens (user_id only).
- Refresh tokens hashed in DB; store on disk with user-only permissions.
- Rate limiting recommended on
/auth/login,/auth/device/start,/auth/device/poll. - Ren’Py claim intentionally low-friction; honor system with optional evidence.
Evolution Notes
- Legacy endpoints
/v1/entitlements,/v1/license/statusremoved (Auth System Closure). - Device approval UX fixes pending (polling diagnostics in extension, see troubleshooting).
- Future: consolidate to single auth system, add SSO, deprecate
userstable.
Source References (v1.1.0)
licensing/License_Architecture_v1.0_CANONICAL.mdlicensing/AUTH_FLOW.mdlicensing/BACKEND_API.mdauth-system-closure/SPECIFICATION.mdauth-licensing-sso/AUTHENTICATION_FIXES_2026-01-22.md