User Group API
People Groups and Calendar Sharing
Manage reusable people groups, invites, and group-shared calendars
User groups are people groups, not calendar sidebar groups. They let a user share one or more calendars with a stable set of members while preserving the existing calendar color, icon, visibility, and event behavior.
JWT or user API keyPrivacy-preserving invitesCalendarShare principal: groupOwner/admin controls
Source
- User groups controller:
backend-nestjs/src/user-groups/user-groups.controller.ts - User group calendars controller:
backend-nestjs/src/calendars/user-group-calendars.controller.ts - User groups service:
backend-nestjs/src/user-groups/user-groups.service.ts - Calendar share service:
backend-nestjs/src/calendars/calendar-share.service.ts - DTOs:
backend-nestjs/src/user-groups/dto/user-group.dto.ts - Entities:
backend-nestjs/src/entities/user-group.entity.ts,backend-nestjs/src/entities/calendar.entity.ts
Authentication and Permissions
- All endpoints require authentication.
- Users can list groups they own or belong to.
- Group metadata, membership, invite, and calendar-link writes require owner or admin role.
- Deleting a group requires the group owner.
- Calendar attachment also checks the actor's calendar rights before creating the group share.
- Invite creation is privacy-preserving: the response shape does not reveal whether the email belongs to an existing PrimeCal user.
- Invite accept and decline use an opaque token and authenticated user context. Invalid, expired, and already-used tokens return generic errors.
Endpoint Reference
Groups
| Method | Path | Purpose | Request or query | Auth | Source |
|---|---|---|---|---|---|
GET | /api/user-groups | List groups owned by or shared with the current user. | None | JWT or user API key | user-groups/user-groups.controller.ts |
POST | /api/user-groups | Create a people group. | Body: name,slug,description,kind | JWT or user API key | user-groups/user-groups.controller.ts |
GET | /api/user-groups/:id | Get one group with members and shared calendar details where visible. | 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 people group. | Path: id | JWT or user API key | user-groups/user-groups.controller.ts |
Members
| Method | Path | Purpose | Request or query | Auth | Source |
|---|---|---|---|---|---|
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 directly by user ID. | 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 from the group. | Path: id,userId | JWT or user API key | user-groups/user-groups.controller.ts |
Invites
| Method | Path | Purpose | Request or query | Auth | Source |
|---|---|---|---|---|---|
POST | /api/user-groups/:id/invites | Create an invite by email. | 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 for owner/admin review. | Path: id | JWT or user API key | user-groups/user-groups.controller.ts |
POST | /api/user-groups/invites/:token/accept | Accept an invite by opaque 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 opaque token. | Path: token | JWT or user API key | user-groups/user-groups.controller.ts |
Calendars
| Method | Path | Purpose | Request or query | Auth | Source |
|---|---|---|---|---|---|
POST | /api/user-groups/:groupId/calendars | Attach one or more existing calendars to a people group. | Path: groupId, body: calendarIds,calendarId,permission | JWT or user API key | calendars/user-group-calendars.controller.ts |
POST | /api/user-groups/:groupId/calendars/create | Create a calendar primarily attached to a people group. | Path: groupId, body: calendar fields plus optional groupPermission | JWT or user API key | calendars/user-group-calendars.controller.ts |
DELETE | /api/user-groups/:groupId/calendars | Detach one or more calendars from a people group. | Path: groupId, body: calendarIds or calendarId | JWT or user API key | calendars/user-group-calendars.controller.ts |
DELETE | /api/user-groups/:groupId/calendars/:calendarId | Detach one calendar from a people group. | Path: groupId,calendarId | JWT or user API key | calendars/user-group-calendars.controller.ts |
DTO Shapes and Validation
CreateUserGroupDto
name: required string, 2 to 200 chars.slug: optional stable key, max 120 chars, pattern^[a-z0-9][a-z0-9_-]*$.description: optional string, max 500 chars.kind: optional enumfamily|friends|team|resource|custom.
UpdateUserGroupDto
- Same group metadata fields as create, all optional.
sluganddescriptionmay be cleared withnullwhere service behavior allows it.
AddUserGroupMemberDto and UpdateUserGroupMemberDto
userId: required integer greater than0for direct member additions.role: enumadmin|memberfor API callers.owneris reserved for the group owner record.
CreateUserGroupInviteDto
email: required email string, max 320 chars, trimmed and lowercased server-side.message: optional string, max 500 chars, trimmed server-side.
Invite storage includes token, expiresAt, emailSentAt, and emailSendAttempts for future outbound email support.
GroupCalendarsDto
calendarIds: array of unique integers, 1 to 100 items.calendarId: legacy single-calendar field, accepted whencalendarIdsis absent.permission: optional enumread|write|admin, defaults toreadfor attach operations.
Example Calls
Create a group
curl -X POST "$PRIMECAL_API/api/user-groups" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Family Planning",
"slug": "family-planning",
"description": "Calendars shared with the household",
"kind": "family"
}'
Invite a user by email
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 shared planning calendars."
}'
The response is intentionally generic. Do not use it to infer whether teammate@example.com has a PrimeCal account.
Accept an invite
curl -X POST "$PRIMECAL_API/api/user-groups/invites/$INVITE_TOKEN/accept" \
-H "Authorization: Bearer $TOKEN"
Attach calendars to a group
curl -X POST "$PRIMECAL_API/api/user-groups/9/calendars" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"calendarIds": [5, 7],
"permission": "write"
}'
Response and Behavior Notes
- Group-shared calendars are represented by
CalendarSharerows withprincipalType: "group". - Direct user sharing uses the same
CalendarSharemodel withprincipalType: "user". - Effective calendar access includes owned calendars, direct user shares, and people-group shares.
- If multiple paths grant access to the same calendar, the highest permission applies:
admin>write>read. - Accepted group members see group-shared calendars through
GET /api/calendarswithout calling this API directly. - External email invites are recorded for future email delivery and post-signup mapping, but the current API does not send outbound email.
Best Practices
- Use people groups when several calendars should stay aligned under one member list and permission model.
- Keep direct user shares for one-off access.
- Build invite UIs around generic success states.
- Refresh both
/api/user-groupsand/api/calendarsafter accepting an invite. - Prefer
GET /api/notifications/catalogwhen building rule or preference editors for invite-related events.