Skip to content

Discovery & Search

Explore, trending, search, games, sponsors, and creator links. All endpoints require authentication — there is no anonymous browsing.

MethodPathReturns
GET/trending/clipsTrending video clips
GET/trending/playpalzTrending creators
GET/trending/coachesTrending coaches
GET/trending/gamesTrending games
GET/trending/searchesPopular search queries

All five wrap their payload:

json
{ "success": true, "data": [  ] }

Caching

Every trending endpoint caches in Redis for 5 minutes (CACHE_TTL = 60 * 5), keyed per endpoint and, where personalised, per user. Expiry only — there is no invalidation, so a change takes up to five minutes to surface.

PlayPalz vs coaches

Both are creators; the distinction is by badge. The PlayPal badge is assigned automatically to creators, and the coach badge is assigned manually. /trending/coaches filters on the coach badge. See Domain Models.

Backed by the SearchLog model — every query is logged with query and createdAt, both indexed, and aggregated into the popular list.

MethodPathReturns
GET/search?q=<query>Users, clips, and games in one response
GET/search/users?q=<query>Users only
json
GET /search?q=valorant
{
  "success": true,
  "data": { "users": [  ], "clips": [  ], "games": [  ] }
}

An empty or whitespace-only q short-circuits to empty results without touching the database.

Note the inconsistency: /search uses the { success, data } envelope while /search/users returns a bare array.

Discover

MethodPathPurpose
GET/discoverThe discover feed

The discover query is a placeholder

ts
export const fetchDiscoverFeed = async (authenticatedUserId, limit?, cursor?) => {
  return prisma.user.findMany({ where: { NOT: { id: authenticatedUserId } } });
};

It selects every user in the database except you — no ranking, no filtering, no pagination, and the limit and cursor parameters are accepted and ignored. It works at demo scale and will fall over as the user base grows.

A real implementation should filter to creators (userType = "playpal"), exclude blocked users, rank by something meaningful (recency, engagement, shared game interests), and honour the pagination parameters it already accepts. Partial

The /trending/* endpoints are considerably more developed and are what the explore screen actually uses.

Games

MethodPathPurpose
GET/gamesThe catalog
PUT/user/gamesSet your favourite games

Populated by igdb-heartbeat — roughly 300 published games at the time of writing, refreshed daily. Favourites are chosen during onboarding and feed discovery.

Sponsors

MethodPathPurpose
GET/sponsorThe currently active sponsor

Sponsored placements rendered in discovery surfaces.

Creator external links — merch stores, socials — shown on the profile, similar to Instagram's link list.

MethodPathPurpose
GET/linksYour links
GET/links/:linkIdOne link
POST/linksCreate
PUT/links/:linkIdUpdate
DELETE/links/:linkIdDelete

A Link is { name, url } scoped to a user. Zod schemas live in apps/api/src/schemas/link.ts.

Notifications

MethodPathPurpose
GET/notificationsCursor-paginated inbox, ?unreadOnly=true
GET/notifications/unread-countBadge count
POST/notifications/:id/readMark one read
POST/notifications/read-allMark all read

The reference implementation for cursor pagination in this codebase. Details in Notifications.

Internal documentation — PlayPalz platform