Auth: all routes use bearer JWT.
- User-scoped routes (
/users/{user_id}/...) require a user JWT wherejwt.sub == user_id. - Admin routes (flat
/accounts/...) require an admin JWT (Actor::AdminUser). Mutating an account (status, external id, details) and soft-delete are admin-only.
Overview
Theaccounts table holds accounts for any holder kind (today: user,
sponsor; extensible). Each row carries a single holder field encoded
as a URN string — user:<12-digit-id> or sponsor:<uuid>. The same
encoding is used everywhere: DB storage, API responses, admin filters,
audit logs.
Accounts are created with status = PENDING by default and soft-deleted
via admin DELETE (status flips to INACTIVE; rows are never removed).
Holder encoding
Validation runs at every entry point — JSON deserialization, DB row
reads, admin query params. A malformed value returns 400 with the parse
error.
Account Types
(holder, account_type) legality is enforced at create time and at
list-filter time. An illegal combination returns 400 (AE_807)
rather than a silent empty list.
Auth Guards by Endpoint
User-scoped routes return 403 if the path
user_id does not match jwt.sub.
Admin routes return 403 if the caller is not an admin.