Skip to main content

Chat API

StatusImplementation
✅ Shipped (read-only)src/feats/chats/

In-app messaging for approved groups. Chats are auto-created when a like group is approved — all group members plus all event authors become participants.

Message Sending

Message sending is done via Supabase Realtime, not through this REST API. The API provides read-only access to chat history.

See also: Chats model.

All HTTP endpoints require Authorization: Bearer <supabase-access-token>.

Endpoints

MethodPathStatusDescription
GET/chats✅ ShippedList my chats with last message preview
GET/chats/:id✅ ShippedGet chat details with participants
GET/chats/:id/messages✅ ShippedPaginate messages (newest-first)
POST/chats🚧 PlannedCreate a direct chat
POST/chats/:id/messagesUse Supabase Realtime
PATCH/chats/:id/messages/:msgId🚧 PlannedEdit a message
DELETE/chats/:id/messages/:msgId🚧 PlannedSoft-delete a message

Chat types

TypeCreated byStatus
approved_groupAuto-created on group approval✅ Shipped
directPOST /chats (planned)🚧 Planned

List chats

GET /chats?cursor=xxx&limit=20
Authorization: Bearer <token>
QueryTypeDefaultDescription
cursorstringPagination cursor
limitnumber20Page size (1-100)
{
"data": [
{
"id": "uuid",
"type": "approved_group",
"createdAt": "2026-04-01T00:00:00Z",
"lastMessageAt": "2026-04-01T12:30:00Z",
"lastMessageContent": "See you there!",
"lastMessageSenderId": "uuid"
}
],
"nextCursor": null
}

Get chat

GET /chats/:id
Authorization: Bearer <token>
{
"id": "uuid",
"type": "approved_group",
"createdAt": "2026-04-01T00:00:00Z",
"participantIds": ["uuid", "uuid", "..."]
}

Visibility: only chat participants can view.

List messages

GET /chats/:id/messages?cursor=xxx&limit=30
Authorization: Bearer <token>
QueryTypeDefaultDescription
cursorstringPagination cursor
limitnumber30Page size (1-100)

Returns messages newest-first (descending createdAt).

{
"data": [
{
"id": "uuid",
"chatId": "uuid",
"senderId": "uuid",
"content": "Hello!",
"createdAt": "2026-04-01T12:30:00Z",
"updatedAt": "2026-04-01T12:30:00Z",
"deletedAt": null
}
],
"nextCursor": "base64-cursor"
}

Errors

StatusReason
401Missing / invalid bearer token
403Not a participant of the chat
404Chat not found

Realtime — Supabase Realtime

Message sending and real-time updates use Supabase Realtime channels. Clients subscribe via @supabase/supabase-js:

const channel = supabase
.channel(`chat:${chatId}`)
.on(
"postgres_changes",
{ event: "*", schema: "public", table: "messages", filter: `chat_id=eq.${chatId}` },
(payload) => {
switch (payload.eventType) {
case "INSERT": /* message.created */ break;
case "UPDATE": /* message.updated */ break;
case "DELETE": /* message.deleted */ break;
}
},
)
.subscribe();

Row-Level Security guarantees a user only receives messages for chats they are a participant of, even on the realtime stream.

Implementation notes

Source: src/feats/chats/.

src/feats/chats/
├── chats.router.ts # GET handlers, auth middleware
├── chats.service.ts # Business logic, pagination
├── chats.schemas.ts # Zod request/response schemas
└── chats.constants.ts # Page size limits
  • The API bypasses RLS (uses service_role) — authorization is done in code by checking chat participation.
  • Cursor-based pagination uses base64-encoded {createdAt, id} tuples.
  • Default page size: 30 messages, max: 100.