Auth: all routes use bearer JWT.
  • User-scoped routes (/users/{user_id}/...) require a user JWT where jwt.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

The accounts 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.

Account Lifecycle

Soft delete only — rows persist for audit.

Admin list filters


Request / Response Examples


Error Codes