Users & Profiles
apps/api/src/router/userRoutes.ts and followRoutes.ts. Every endpoint requires authentication.
Current user
| Method | Path | Purpose |
|---|---|---|
GET | /user | The authenticated user |
GET | /me | Alias for /user |
PUT | /user | Update profile fields |
POST | /user/avatar | Upload avatar (multipart/form-data) |
POST | /user/banner | Upload banner (multipart/form-data) |
Both aliases exist because the mobile app and the web app were written against different names. Prefer /me.
Profiles
| Method | Path | Purpose |
|---|---|---|
GET | /profile | Your own full profile |
GET | /profile/:id | Another user's profile |
PUT | /profile/username | Change username |
PUT | /profile/display-name | Change display name |
PUT | /profile/bio | Change bio |
PUT | /profile/price | Change monthly subscription price (cents) |
Each profile field has its own endpoint rather than one PATCH, which mirrors the mobile app's one-field-per-screen editing flow (app/(app)/(tabs)/me/edit/).
PUT /profile/price sets User.monthlyPrice, the amount fans pay for a creator subscription and the figure anansi multiplies by the active subscriber count at payout time.
Following
| Method | Path | Purpose |
|---|---|---|
POST | /user/:id/follow | Follow a user |
POST | /user/:id/unfollow | Unfollow |
Follow has @@unique([followerId, followingId]), so a duplicate follow cannot create a second row. Following triggers a NEW_FOLLOWER notification.
Security & sessions
| Method | Path | Purpose |
|---|---|---|
GET | /user/sessions | Active login sessions |
GET | /user/login-history | Historical logins |
POST | /auth/logout-all | Revoke every token |
PUT | /auth/change-password | Change password |
DELETE | /user/account | Delete the account |
/user/sessions and /user/login-history both read AuthSession rows — device name, IP, user agent, timestamp. Remember that deactivating a session row does not revoke anything on its own; only /auth/logout-all does. See Authentication.
DELETE /user/account has to unwind relations explicitly, because most foreign keys restrict rather than cascade. Verify what it actually removes before treating it as a GDPR erasure path.
Blocking
| Method | Path | Purpose |
|---|---|---|
GET | /user/blocked | List blocked users |
POST | /user/block/:userId | Block |
DELETE | /user/block/:userId | Unblock |
Blocks are stored in the Block model with a unique constraint on the pair. Feed, search, and messaging queries are expected to filter against it — check the specific query if you are relying on that.
Notification preferences
| Method | Path | Purpose |
|---|---|---|
GET | /user/notifications | Read preferences |
PUT | /user/notifications | Update preferences |
Note the collision with the notification inbox at /notifications — /user/notifications is preferences, /notifications is the list. Easy to confuse. Fields are documented in Notifications.
Push tokens
| Method | Path | Purpose |
|---|---|---|
POST | /user/push-token | Register an Expo push token |
DELETE | /user/push-token | Remove one |
Tokens are validated with Expo.isExpoPushToken before use, and automatically removed when Expo reports DeviceNotRegistered.
Games
| Method | Path | Purpose |
|---|---|---|
GET | /games | The game catalog |
PUT | /user/games | Set favourite games |
The catalog is populated by igdb-heartbeat. Favourites are chosen during onboarding and feed into discovery.
Verification
| Method | Path | Purpose |
|---|---|---|
POST | /verification-asset | Upload an identity document |
POST | /verify-identity | Submit for review |
Part of creator onboarding.
