Skip to main content

Friends API

Manage friend requests and friendships. Friendships are bidirectional — once accepted, both parties can see each other for purposes of event co-authoring and like-group formation.

See also: Friendship algorithm, friendships table.

All endpoints require Authorization: Bearer <supabase-access-token>. The acting user is resolved from the JWT (no userId query param).

Status

EndpointStatus
GET /friends✅ Shipped
POST /friends/requests✅ Shipped
POST /friends/requests/:id/accept✅ Shipped
POST /friends/requests/:id/reject✅ Shipped
POST /friends/:userId/block✅ Shipped
DELETE /friends/:userId/block✅ Shipped
DELETE /friends/:userId✅ Shipped

Source: src/feats/friends/.

Endpoints

MethodPathDescription
GET/friendsList friendships of the current user
POST/friends/requestsSend a friend request
POST/friends/requests/:id/acceptAccept a request
POST/friends/requests/:id/rejectReject a request
POST/friends/:userId/blockBlock another user
DELETE/friends/:userId/blockUnblock a user
DELETE/friends/:userIdUnfriend

List friends

GET /friends?status=accepted
Authorization: Bearer <token>
QueryTypeDescription
statusenumpending | accepted | rejected | blocked
cursorstringPagination cursor
limitnumberPage size (default 20)
{
"data": [
{
"id": "uuid",
"requesterId": "uuid",
"addresseeId": "uuid",
"status": "accepted",
"createdAt": "2026-04-01T00:00:00Z"
}
],
"nextCursor": null
}

Send friend request

POST /friends/requests
Authorization: Bearer <token>
Content-Type: application/json

{ "addresseeId": "uuid" }
StatusReason
201Created
400Cannot request yourself
409Already friends, already pending, or you are blocked

Accept / reject

POST /friends/requests/:id/accept
POST /friends/requests/:id/reject

Only the addressee of a pending row may call these. Returns the updated friendship.

Block

POST /friends/:userId/block

Blocking is a unilateral action: it sets the row to blocked regardless of who initiated it, removes any existing pending state, and prevents future requests from the blocked user.

The response includes a blockedBy field indicating who initiated the block.

Unblock

DELETE /friends/:userId/block

Only the user who initiated the block can unblock. Unblocking deletes the friendship row entirely, allowing either party to send a new friend request.

StatusReason
200Success
403Only the blocker can unblock
404Block not found

Unfriend

DELETE /friends/:userId

Removes the row entirely. Either side can re-request later.

Limits

LimitValue
Pending outgoing requests per user50
Friends per userNo limit

Implementation

Source: src/feats/friends/.

src/feats/friends/
├── friends.router.ts # POST/GET/DELETE handlers, auth + zod middleware
├── friends.service.ts # Business rules: bidirectional check, block, etc.
├── friends.schemas.ts # Zod request/response schemas
└── friends.constants.ts # Status enum, limits

Key service functions

FunctionDescription
areFriends(a, b)Check if two users are friends (status = accepted)
getFriendship(a, b)Get the friendship row between two users
listFriendships(userId, query)List with status filter and cursor pagination
sendFriendRequest(from, to)Create pending request
acceptFriendRequest(id, userId)Accept (addressee only)
rejectFriendRequest(id, userId)Reject (addressee only)
blockUser(blockerId, blockedId)Block a user
unblockUser(userId, blockedId)Unblock (blocker only)
unfriend(userId, otherId)Remove friendship

Cross-user reads run as service_role (bypass RLS) and are scoped in code by requesterId = auth.uid() OR addresseeId = auth.uid().