User API
User, Profile, and Permission Surface
Manage profile data, language, visibility preferences, and permission bootstrap
These routes back the signed-in user settings area and the helper APIs the frontend uses to hydrate the current session. They do not include admin-only user management.
Source
- Profile controller:
backend-nestjs/src/controllers/user-profile.controller.ts - Language controller:
backend-nestjs/src/controllers/user-language.controller.ts - Permissions controller:
backend-nestjs/src/controllers/user-permissions.controller.ts - Users controller:
backend-nestjs/src/users/users.controller.ts - User groups controller:
backend-nestjs/src/user-groups/user-groups.controller.ts - DTOs:
backend-nestjs/src/dto/user-profile.dto.ts,backend-nestjs/src/users/dto/list-users.query.dto.ts,backend-nestjs/src/user-groups/dto/user-group.dto.ts
Authentication and Permissions
- All routes on this page require authentication.
- Routes using
JwtAuthGuardaccept bearer JWT and, where supported, user API keys. POST /api/user/profile-pictureis marked with@AllowIncompleteOnboarding(), so it can be used before onboarding is complete.- Profile writes are scoped to the authenticated user only.
- User-group membership writes require group owner or admin role. Deleting a group requires the owner.
- Group invite creation returns a generic response and does not reveal whether the invited email maps to an existing PrimeCal user.
Endpoint Reference
Profile and Settings
| Method | Path | Purpose | Request or query | Auth | Source |
|---|---|---|---|---|---|
GET | /api/user/profile | Read the current user profile and settings. | None | JWT or user API key | controllers/user-profile.controller.ts |
POST | /api/user/profile-picture | Upload and set a profile picture. | Multipart field: file | JWT or user API key | controllers/user-profile.controller.ts |
PATCH | /api/user/profile | Update profile fields and UI preferences. | Body: profile fields | JWT or user API key | controllers/user-profile.controller.ts |
DELETE | /api/user/event-labels/:label | Remove one saved event label and strip it from the user's events. | Path: label | JWT or user API key | controllers/user-profile.controller.ts |
PATCH | /api/user/theme | Update theme color only. | Body: themeColor | JWT or user API key | controllers/user-profile.controller.ts |
PATCH | /api/user/password | Change the current user's password. | Body: currentPassword,newPassword | JWT or user API key | controllers/user-profile.controller.ts |
PATCH | /api/users/me/language | Update the preferred UI language. | Body: preferredLanguage | JWT or user API key | controllers/user-language.controller.ts |
Session Bootstrap and Sharing Helpers
| Method | Path | Purpose | Request or query | Auth | Source |
|---|---|---|---|---|---|
GET | /api/users/me | Read the current user entity from the users service. | None | JWT or user API key | users/users.controller.ts |
GET | /api/users | Search users for sharing flows. | Query: search | JWT or user API key | users/users.controller.ts |
GET | /api/user-permissions | Get the current permission snapshot. | None | JWT or user API key | controllers/user-permissions.controller.ts |
GET | /api/user-permissions/accessible-organizations | List organizations accessible to the current user. | None | JWT or user API key | controllers/user-permissions.controller.ts |
GET | /api/user-permissions/accessible-reservation-calendars | List reservation calendars accessible to the current user. | None | JWT or user API key | controllers/user-permissions.controller.ts |
People Groups
| Method | Path | Purpose | Request or query | Auth | Source |
|---|---|---|---|---|---|
POST | /api/user-groups | Create a people group for sharing. | Body: name,slug,description,kind | JWT or user API key | user-groups/user-groups.controller.ts |
GET | /api/user-groups | List groups the current user owns or belongs to. | None | JWT or user API key | user-groups/user-groups.controller.ts |
GET | /api/user-groups/:id | Get group details and members. | Path: id | JWT or user API key | user-groups/user-groups.controller.ts |
PATCH | /api/user-groups/:id | Update group metadata. | Path: id, body: partial group fields | JWT or user API key | user-groups/user-groups.controller.ts |
DELETE | /api/user-groups/:id | Delete a group. | Path: id | JWT or user API key | user-groups/user-groups.controller.ts |
GET | /api/user-groups/:id/members | List group members. | Path: id | JWT or user API key | user-groups/user-groups.controller.ts |
POST | /api/user-groups/:id/members | Add or update a member. | Path: id, body: userId,role | JWT or user API key | user-groups/user-groups.controller.ts |
PATCH | /api/user-groups/:id/members/:userId | Change a member role. | Path: id,userId, body: role | JWT or user API key | user-groups/user-groups.controller.ts |
DELETE | /api/user-groups/:id/members/:userId | Remove a member. | Path: id,userId | JWT or user API key | user-groups/user-groups.controller.ts |
POST | /api/user-groups/:id/invites | Create an email invite record. | Path: id, body: email,message | JWT or user API key | user-groups/user-groups.controller.ts |
GET | /api/user-groups/:id/invites | List group invites. | Path: id | JWT or user API key | user-groups/user-groups.controller.ts |
POST | /api/user-groups/invites/:token/accept | Accept an invite by secure token. | Path: token | JWT or user API key | user-groups/user-groups.controller.ts |
POST | /api/user-groups/invites/:token/decline | Decline an invite by secure token. | Path: token | JWT or user API key | user-groups/user-groups.controller.ts |
Request Shapes
Update profile
UpdateProfileDto in backend-nestjs/src/dto/user-profile.dto.ts
username: optional, minimum 3 charsemail: optional, valid emailfirstName: optional stringlastName: optional stringprofilePictureUrl: optional URL, max 2048 charsweekStartDay: optional integer0..6defaultCalendarView: optionalmonth|weektimezone: optional stringtimeFormat: optional12h|24hlanguage: optional enumen|hu|de|frpreferredLanguage: optional enumen|hu|de|frhideReservationsTab: optional booleanhiddenResourceIds: optional number arrayvisibleCalendarIds: optional number arrayvisibleResourceTypeIds: optional number arrayhiddenFromLiveFocusTags: optional string array, max 64 chars eacheventLabels: optional string array, max 64 chars eachcalendarLayoutMode: optional'boxed' | 'full'calendarSidebarCollapsed: optional booleancalendarSidebarWidth: optional integer, clamped server-side to200..420defaultTasksCalendarId: optional number ornull
Implementation behavior from the controller:
- Username and email uniqueness are rechecked only if those fields actually changed.
hiddenFromLiveFocusTagsandeventLabelsare normalized, deduplicated, trimmed, and capped to 100 items.defaultTasksCalendarIdcan be cleared withnull.- Changing
defaultTasksCalendarIdcan trigger task-to-calendar resyncs for tasks with due dates.
Profile picture upload
Rules enforced in backend-nestjs/src/controllers/user-profile.controller.ts
- field name:
file - allowed MIME types:
image/jpeg,image/png,image/gif,image/webp - max file size:
2MB
Theme and password
UpdateThemeDto.themeColor: optional hex string#rgbor#rrggbbChangePasswordDto.currentPassword: required stringChangePasswordDto.newPassword: required, minimum 6 chars
Language
UpdateLanguagePreferenceDto.preferredLanguage: required enumen|hu|de|fr
User search
ListUsersQueryDto.search: optional safe text, max 80 chars
People groups and invites
CreateUserGroupDto, AddUserGroupMemberDto, and CreateUserGroupInviteDto in backend-nestjs/src/user-groups/dto/user-group.dto.ts
name: required on create, 2 to 200 charsslug: optional stable key, max 120 charsdescription: optional string, max 500 charskind: optional enumfamily|friends|team|resource|customrole: member role enumowner|admin|member; API callers can assignadminormemberemail: required valid email for invites, normalized server-sidemessage: optional invite message, max 500 chars
Invite storage includes token, expiresAt, emailSentAt, and emailSendAttempts so outbound SMTP can be added later without changing the API contract.
Example Calls
Update profile preferences
curl -X PATCH "$PRIMECAL_API/api/user/profile" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"timezone": "Europe/Budapest",
"timeFormat": "24h",
"weekStartDay": 1,
"visibleCalendarIds": [2, 3, 5],
"hiddenFromLiveFocusTags": ["no_focus", "private"],
"defaultTasksCalendarId": 7
}'
Upload a profile picture
curl -X POST "$PRIMECAL_API/api/user/profile-picture" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@C:/tmp/avatar.webp"
Search users for sharing
curl "$PRIMECAL_API/api/users?search=justin" \
-H "Authorization: Bearer $TOKEN"
Create a people group invite
curl -X POST "$PRIMECAL_API/api/user-groups/9/invites" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "teammate@example.com",
"message": "Join the planning calendars."
}'
Bootstrap permission-aware UI
curl "$PRIMECAL_API/api/user-permissions" \
-H "Authorization: Bearer $TOKEN"
Response Notes
GET /api/user/profilereturns the richest user-settings payload, including visibility preferences, live-focus hidden tags, event labels, onboarding state, and privacy policy acceptance info.GET /api/users/meis a lighter current-user lookup from the users service.PATCH /api/user/passwordreturns a simple success message after validating the current password.DELETE /api/user/event-labels/:labelreturns the removed label, remaining labels, and the number of events affected.POST /api/user-groups/:id/invitesreturns generic invite metadata only. Clients should not branch UI behavior based on whether the email already belongs to a user.
Workspace layout example
GET /api/user/profile
{
"id": 42,
"username": "demo_user",
"calendarLayoutMode": "full",
"calendarSidebarCollapsed": false,
"calendarSidebarWidth": 320,
"timeFormat": "24h",
"timezone": "Europe/Budapest"
}
PATCH /api/user/profile
{
"calendarLayoutMode": "boxed",
"calendarSidebarCollapsed": true,
"calendarSidebarWidth": 240
}
The controller validates the incoming DTO, ignores missing fields, and clamps the sidebar width server-side before saving.
Best Practices
- Use
GET /api/user/profileas the primary settings bootstrap route. - Use
GET /api/user-permissionsbefore rendering reservations, organization settings, or role-sensitive UI. - Send only changed fields in
PATCH /api/user/profile; the controller intentionally performs narrow uniqueness checks. - Keep
eventLabelsandhiddenFromLiveFocusTagsnormalized on the client too, so UI state matches the backend normalization rules. - Use
Personal Logs APIfor audit history rather than overloading these settings endpoints with activity concerns.