Events API
| Status | Implementation |
|---|---|
| ✅ Shipped | src/feats/events/ |
Events use PostGIS for geo queries. The extension is enabled in migration
0003_overconfident_redwing.sql.
Create and manage events. Events are the primary social object: users create them, invite friends as co-authors, and other users form like groups to apply.
See also: Event lifecycle, Feed algorithm, Events model.
All endpoints require Authorization: Bearer <supabase-access-token>.
Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /events | Create event (in draft) |
| GET | /events | Browse events feed |
| GET | /events/:id | Get event details |
| PATCH | /events/:id | Update event (before start_at) |
| DELETE | /events/:id | Soft-delete |
| POST | /events/:id/restore | Restore a soft-deleted event |
| POST | /events/:id/publish | draft → published |
| POST | /events/:id/cancel | published | started → cancelled |
| POST | /events/:id/authors | Invite a friend as co-author |
| GET | /events/:id/authors | List authors |
| DELETE | /events/:id/authors/:userId | Remove a co-author (creator only) |
| POST | /events/:id/like | Create a solo or group like |
| GET | /events/:id/likes | List like groups for the event |
| GET | /events/mine | List owned and accepted co-organized events |
| PUT | /events/:id/cover | Set an uploaded cover |
| DELETE | /events/:id/cover | Remove the cover |
| POST | /events/:id/authors/accept | Accept a co-author invitation |
| POST | /events/:id/authors/refuse | Refuse a co-author invitation |
Create event
POST /events
Authorization: Bearer <token>
Content-Type: application/json
{
"title": "Saturday Night Party",
"description": "Join us for drinks!",
"type": "party",
"visibility": "public",
"startAt": "2026-05-15T20:00:00Z",
"endAt": "2026-05-16T02:00:00Z",
"latitude": 40.7128,
"longitude": -74.006,
"capacity": 50
}
| Field | Type | Required | Notes |
|---|---|---|---|
title | string | ✅ | Max 100 characters |
description | string | Max 2000 characters | |
type | enum | ✅ | Event taxonomy from OpenAPI; includes many values plus other |
visibility | enum | ✅ | public, private |
startAt | ISO8601 | ✅ | |
endAt | ISO8601 | ||
latitude | number | ✅ | WGS84 |
longitude | number | ✅ | WGS84 |
capacity | int | Default 200, max 10000; creator + authors don't count | |
consentThreshold | int | Deprecated compatibility field; no longer affects approval | |
imageKey | string | Uploaded media key for a cover | |
imageUrl | string | Deprecated external cover URL | |
address, city | string | Display location text |
Response
{
"id": "uuid",
"creatorId": "uuid",
"status": "draft",
"...": "..."
}
Feed
GET /events?lat=40.7128&lng=-74.006&radius=5000
Authorization: Bearer <token>
| Query | Description |
|---|---|
lat, lng | Required when filtering by location |
radius | Metres (100-50000, default 5000) — uses ST_DWithin on geography(Point, 4326) |
type | Filter by event type |
friendsOnly | Only events created by friends |
cursor, limit | Pagination (limit 1-100, default 20) |
startDate, endDate | ISO date range filter |
maleFrom, maleTo | Filter by male attendee count range |
femaleFrom, femaleTo | Filter by female attendee count range |
organizerFrom, organizerTo | Filter by organizer count range |
Visibility and feed rules:
visible = event.visibility = 'public'
The feed excludes the viewer's own events, accepted author events, and friends'
events, and excludes events that have already started. friendsOnly is accepted
for compatibility but currently ignored. Without coordinates it returns the
global feed, sorted by start time. With coordinates, radius is optional and
distance is included in the result. Search supports q, single type, or
comma-separated/repeated types.
Sort order: distance ASC when a point is supplied, then start_at ASC. See
Feed algorithm for the full details and
boost-by-preferences design.
Update / cancel / delete
PATCH /events/:id # only while now() < startAt
POST /events/:id/cancel # any time, by creator or any author
DELETE /events/:id # soft delete (sets deleted_at)
POST /events/:id/restore # before status='finished'
After start_at the event becomes immutable for content fields. See
Event lifecycle for the locked-fields
list and state machine.
Authors
Only friends of the creator can be invited as authors. Invitations have
pending, accepted, and refused states. Only accepted co-authors count as
organizers for quorum, voting, and the organizers' chat. Creating an event also
creates its event_authors chat; this is separate from an approved group's
chat.
POST /events/:id/authors
{ "userId": "friend-uuid" }
| Limit | Value |
|---|---|
| Authors per event | 10 |
Like an event
Solo or group like.
POST /events/:id/like
{ "memberIds": ["friend-1", "friend-2"] } // optional
- Without
memberIds→ solo like (group of size 1). - All
memberIdsmust be friends of the caller. - A user can only be in one
pendinglike group per event.
{
"id": "group-uuid",
"eventId": "event-uuid",
"status": "pending",
"memberIds": ["caller-uuid", "friend-1", "friend-2"]
}
See the Like Groups API for approval, removal and chat-creation side effects.
Implementation notes
- Geo: events store
geography(Point, 4326). PostGIS is required; seeapi/docs/DATABASE.mdfor the existing setup ofgeographycolumns oncitiesandprofiles. - Soft delete:
deleted_atis partial-indexed for fast feed queries (WHERE deleted_at IS NULL). - Realtime: clients can subscribe to
event:{eventId}via Supabase Realtime to receiveevent.updatednotifications. - Module layout will follow the shipped pattern:
src/feats/events/{router,service,schemas,constants}.ts.