Like Groups API
| Status | Implementation |
|---|---|
| ✅ Shipped | src/feats/groups/ |
A like group is how users apply to attend an event. The group is
formed by one user (the creator) plus optional friends. Once enough
event authors approve the group, the group is approved and a private
chat is created for the participants.
See also: Approval algorithm, Like-groups model.
All endpoints require Authorization: Bearer <supabase-access-token>.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /groups | List my groups (as creator or member) |
| GET | /groups/:id | Get a group |
| POST | /groups/:id/members | Add a friend to the group |
| DELETE | /groups/:id/members/:userId | Remove a member |
| GET | /groups/:id/approvals | List approvals for the group |
| POST | /groups/:id/approve | Approve the group (event author only) |
| POST | /groups/:id/reject | Reject the group (event author only) |
Group creation happens via POST /events/:id/like.
Get group
GET /groups/:id
Authorization: Bearer <token>
{
"id": "uuid",
"eventId": "uuid",
"creatorId": "uuid",
"status": "pending",
"memberIds": ["uuid", "uuid"],
"createdAt": "2026-04-01T00:00:00Z"
}
Visibility: members of the group, the event creator, and event authors.
Manage members
POST /groups/:id/members
{ "userId": "friend-uuid" }
Rules:
- Caller must be the group creator.
- The user being added must be a friend of the group creator.
- The user must not already be in another
pendinggroup for the same event. - Group is in
pending(cannot mutate afterapproved/rejected).
| Limit | Value |
|---|---|
| Members per group | 10 (including creator) |
DELETE /groups/:id/members/:userId
Removing members before approval recalculates the approval threshold; existing approvals still count. See Approval algorithm → edge cases.
Approvals
GET /groups/:id/approvals
{
"data": [
{
"id": "uuid",
"groupId": "uuid",
"authorId": "uuid",
"decision": "consent",
"createdAt": "2026-04-01T00:00:00Z"
}
]
}
The decision field is either consent (approve) or refuse (reject).
POST /groups/:id/approve
POST /groups/:id/reject
Authorization: caller must be a current author of the group's event. Each author can only vote once per group.
Approve — consensus algorithm
The threshold formula:
effectiveT = min(consentThreshold, eligible_organizers)
Where eligible_organizers = event authors who haven't refused yet.
Status transitions:
- approved:
consents >= effectiveT - rejected:
eligible - refuses < effectiveT(threshold can no longer be reached) - pending: otherwise
When approved:
- Group
status → approved. - A
chatof typeapproved_groupis created with all members + all authors as participants. - Response includes the new
chatId.
{
"id": "group-uuid",
"status": "approved",
"approved": true,
"chatId": "new-chat-uuid"
}
Reject
When a single author refuses and the threshold becomes unreachable,
status → rejected. No chat is created.
Errors
| Status | Reason |
|---|---|
400 | Group not in pending state |
403 | Caller is not group creator / event author / member |
404 | Group not found |
409 | Member already in another pending group for this event |
Constants
| Constant | Value |
|---|---|
MAX_MEMBERS | 10 (including creator) |
EXPIRATION_HOURS | 24 |
DEFAULT_PAGE_SIZE | 20 |
MAX_PAGE_SIZE | 100 |
Implementation notes
Source:
src/feats/groups/.
- The consensus algorithm uses
min(consentThreshold, eligible_organizers); see Approval algorithm for the rationale. memberKeyis a canonical sorted string of member IDs, ensuring unique pending group compositions per event via a partial unique index.- Group lifecycle and chat creation are wrapped in a single Drizzle transaction so partial state cannot escape.
- Approval decisions are recorded in an append-only
like_group_audittable.