Skip to main content
Was this helpful?

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.

JWT or user API keyMultipart uploadProfile preferencesPermission bootstrap

Authentication and Permissions​

  • All routes on this page require authentication.
  • Routes using JwtAuthGuard accept bearer JWT and, where supported, user API keys.
  • POST /api/user/profile-picture is 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​

MethodPathPurposeRequest or queryAuth
GET/api/user/profileRead the current user profile and settings.NoneJWT or user API key
POST/api/user/profile-pictureUpload and set a profile picture.Multipart field: fileJWT or user API key
PATCH/api/user/profileUpdate profile fields and UI preferences.Body: profile fieldsJWT or user API key
DELETE/api/user/event-labels/:labelRemove one saved event label and strip it from the user's events.Path: labelJWT or user API key
PATCH/api/user/themeUpdate theme color only.Body: themeColorJWT or user API key
PATCH/api/user/passwordChange the current user's password.Body: currentPassword,newPasswordJWT or user API key
PATCH/api/users/me/languageUpdate the preferred UI language.Body: preferredLanguageJWT or user API key
GET/api/user/experience-modeRead the experience mode, skin and Kid Mode PIN status.NoneJWT or user API key
PATCH/api/user/experience-modeSwitch experience mode or skin, or set the Kid Mode PIN.Body: mode, skinId, PIN fieldsJWT or user API key

Session Bootstrap and Sharing Helpers​

MethodPathPurposeRequest or queryAuth
GET/api/users/meRead the current user entity from the users service.NoneJWT or user API key
GET/api/usersSearch users for sharing flows.Query: searchJWT or user API key
GET/api/user-permissionsGet the current permission snapshot.NoneJWT or user API key
GET/api/user-permissions/accessible-organizationsList organizations accessible to the current user.NoneJWT or user API key
GET/api/user-permissions/accessible-reservation-calendarsList reservation calendars accessible to the current user.NoneJWT or user API key

People Groups​

MethodPathPurposeRequest or queryAuth
POST/api/user-groupsCreate a people group for sharing.Body: name,slug,description,kindJWT or user API key
GET/api/user-groupsList groups the current user owns or belongs to.NoneJWT or user API key
GET/api/user-groups/:idGet group details and members.Path: idJWT or user API key
PATCH/api/user-groups/:idUpdate group metadata.Path: id, body: partial group fieldsJWT or user API key
DELETE/api/user-groups/:idDelete a group.Path: idJWT or user API key
GET/api/user-groups/:id/membersList group members.Path: idJWT or user API key
POST/api/user-groups/:id/membersAdd or update a member.Path: id, body: userId,roleJWT or user API key
PATCH/api/user-groups/:id/members/:userIdChange a member role.Path: id,userId, body: roleJWT or user API key
DELETE/api/user-groups/:id/members/:userIdRemove a member.Path: id,userIdJWT or user API key
POST/api/user-groups/:id/invitesCreate an email invite record.Path: id, body: email,messageJWT or user API key
GET/api/user-groups/:id/invitesList group invites.Path: idJWT or user API key
POST/api/user-groups/invites/:token/acceptAccept an invite by secure token.Path: tokenJWT or user API key
POST/api/user-groups/invites/:token/declineDecline an invite by secure token.Path: tokenJWT or user API key

Request Shapes​

Update profile​

UpdateProfileDto

  • username: optional, minimum 3 chars
  • email: optional, valid email
  • firstName: optional string
  • lastName: optional string
  • profilePictureUrl: optional URL, max 2048 chars
  • weekStartDay: optional integer 0..6
  • defaultCalendarView: optional month|week
  • timezone: optional string
  • timeFormat: optional 12h|24h
  • taskUiMode: optional simplified|standard — controls Tasks/Focus UI density; standard (default) shows the full task board, filters, and Focus Sessions timer, simplified shows a calm single-task Focus view with no timer. See Focus and Scheduling.
  • language: optional enum en|hu|de|fr
  • preferredLanguage: optional enum en|hu|de|fr
  • hideReservationsTab: optional boolean
  • hiddenResourceIds: optional number array
  • visibleCalendarIds: optional number array
  • visibleResourceTypeIds: optional number array
  • hiddenFromLiveFocusTags: optional string array, max 64 chars each
  • eventLabels: optional string array, max 64 chars each
  • calendarLayoutMode: optional 'boxed' | 'full'
  • calendarSidebarCollapsed: optional boolean
  • calendarSidebarWidth: optional integer, clamped server-side to 200..420
  • defaultTasksCalendarId: optional number or null

Implementation behavior from the controller:

  • Username and email uniqueness are rechecked only if those fields actually changed.
  • hiddenFromLiveFocusTags and eventLabels are normalized, deduplicated, trimmed, and capped to 100 items.
  • defaultTasksCalendarId can be cleared with null.
  • Changing defaultTasksCalendarId can trigger task-to-calendar resyncs for tasks with due dates.

Profile picture upload​

Validation rules:

  • 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 #rgb or #rrggbb
  • ChangePasswordDto.currentPassword: required string
  • ChangePasswordDto.newPassword: required, minimum 6 chars

Language​

  • UpdateLanguagePreferenceDto.preferredLanguage: required enum en|hu|de|fr

Experience mode​

  • UpdateExperienceModeDto.mode: required enum PRODUCTION|FOCUS|PLANNER|FAMILY|SIMPLE|KID. The names in the app are Standard (PRODUCTION), Focus, Planner, Family, Simple and Kid. The value is stored as a plain string, so a new mode needs no migration.
  • UpdateExperienceModeDto.skinId: optional string, at most 64 characters (default, comfort, ...).
  • newPinHash, newPinSalt, newPinIterations: only when activating Kid Mode for the first time or changing its PIN. The PIN is hashed with PBKDF2 in the browser; the server never sees it.
  • pinAttemptHash: required to leave Kid Mode - 401 without it or with a wrong one, 403 once too many attempts have locked it out (1, 2, 4, 8 and 16 seconds, then 5 minutes). Every other switch (Standard, Focus, Planner, Family and Simple to one another, and into Kid) needs no PIN.
  • GET answers { preferredExperienceMode, preferredSkinId, kidModePinIsSet, kidModePinSalt, kidModePinIterations, kidModePinFailedAttempts, kidModePinLockedUntil }; a user who has never chosen is PRODUCTION with the default skin.
  • A mode changes what the client shows, never what the server allows: events, tasks and calendars are untouched by a switch. See Experience Modes.
  • ListUsersQueryDto.search: optional safe text, max 80 chars

People groups and invites​

CreateUserGroupDto, AddUserGroupMemberDto, and CreateUserGroupInviteDto

  • name: required on create, 2 to 200 chars
  • slug: optional stable key, max 120 chars
  • description: optional string, max 500 chars
  • kind: optional enum family|friends|team|resource|custom
  • role: member role enum owner|admin|member; API callers can assign admin or member
  • email: required valid email for invites, normalized server-side
  • message: 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/profile returns the richest user-settings payload, including visibility preferences, live-focus hidden tags, event labels, onboarding state, and privacy policy acceptance info.
  • GET /api/users/me is a lighter current-user lookup from the users service.
  • PATCH /api/user/password returns a simple success message after validating the current password.
  • DELETE /api/user/event-labels/:label returns the removed label, remaining labels, and the number of events affected.
  • POST /api/user-groups/:id/invites returns 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",
"taskUiMode": "standard",
"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/profile as the primary settings bootstrap route.
  • Use GET /api/user-permissions before 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 eventLabels and hiddenFromLiveFocusTags normalized on the client too, so UI state matches the backend normalization rules.
  • Use Personal Logs API for audit history rather than overloading these settings endpoints with activity concerns.