Get Recently Viewed
Authenticated. Returns the calling user's recently viewed creators ordered by viewedAt DESC. Active-creator filter — inactive / suspended / vacationing creators are dropped from the read.
GET /api/v1/discover/history — 🔑 Bearer · Kill-switched
Returns the calling user's recently viewed creators, ordered by viewedAt DESC. Active-creator filter is applied on the read — even if the user previously viewed a creator who later went inactive / suspended / vacationing, that row is silently dropped from the response (the underlying RecentlyViewed row stays, but the join filters it out).
The list can be shorter than limit. If, say, you ask for 10 and 3 of them have gone inactive since you viewed them, you'll get 7 cards. There's no automatic top-up — the active filter runs on the join, not on the cursor pull.
Request
Query parameters
| Param | Type | Default | Validation | Notes |
|---|---|---|---|---|
limit | number | discover.recently_viewed_count (admin, default 10) | Min(1), Max(50), @Type(Number) | Server-additionally clamps to min(limit, 50) |
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer <accessToken> | ✓ | JWT from POST /auth/login |
Response
200 OK — ApiResponseOf<CreatorListDto>
{
"success": true,
"data": {
"creators": [
{
"id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
"userId": "u1a2b3c4-d5e6-7890-abcd-ef1234567890",
"username": "recent-creator",
"displayName": "Recent Creator",
"avatarUrl": "https://cdn.bio.re/avatars/abc.webp",
"level": "BRONZE",
"dmActive": true,
"totalFollowers": 1500
}
]
}
}| Field | Type | Notes |
|---|---|---|
creators | CreatorCardDto[] | Up to limit cards, ordered by recency. May be shorter than limit when previously-viewed creators have gone inactive. |
See Search for the full CreatorCardDto field reference.
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
400 | (DTO validation) | limit outside [1, 50] |
401 | (guard) | Missing / invalid bearer token |
503 | features.discover_disabled | Admin kill switch DISCOVER is active |
Side effects
- Resolve
effectiveLimit = min(limit ?? config.discover.recently_viewed_count ?? 10, 50). recentlyViewed.findMany({ where: { userId }, orderBy: viewedAt desc, take: effectiveLimit }).- Empty list → return
{ creators: [] }. creatorProfile.findMany({ where: { id IN recent.ids AND active filter }, include: user })— note this drops inactive rows silently.- Map to cards, return. No mutations.
Code samples
curl https://api.bio.re/api/v1/discover/history \
-H "Authorization: Bearer $ACCESS_TOKEN"
curl 'https://api.bio.re/api/v1/discover/history?limit=20' \
-H "Authorization: Bearer $ACCESS_TOKEN"async function getRecentlyViewed(accessToken: string, limit?: number): Promise<CreatorCard[]> {
const url = new URL('https://api.bio.re/api/v1/discover/history');
if (limit) url.searchParams.set('limit', String(limit));
const res = await fetch(url, {
headers: { Authorization: `Bearer ${accessToken}` },
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'History fetch failed'), {
code: json?.error?.code,
});
}
return json.data.creators;
}import { useQuery } from '@tanstack/react-query';
export const discoverKeys = {
history: (limit?: number) => ['discover', 'history', limit ?? 'default'] as const,
};
export function useRecentlyViewed(limit?: number) {
return useQuery({
queryKey: discoverKeys.history(limit),
queryFn: async () => {
const url = new URL('/api/v1/discover/history', window.location.origin);
if (limit) url.searchParams.set('limit', String(limit));
const res = await fetch(url);
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'History fetch failed'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
return json.data.creators as CreatorCard[];
},
staleTime: 30_000,
});
}Try it
Authorization
bearer In: header
Query Parameters
Number of recent views to return
1 <= value <= 50Response Body
application/json
application/json
curl -X GET "https://loading/api/v1/discover/history"{
"success": true,
"data": {
"creators": [
{
"id": "cuid_creator_123",
"userId": "2c4a230c-5085-4924-a3e1-25fb4fc5965b",
"username": "johndoe",
"displayName": "John Doe",
"avatarUrl": "https://cdn.bio.re/avatars/abc.jpg",
"level": "BRONZE",
"dmActive": false,
"totalFollowers": 0
}
]
}
}{
"success": false,
"error": {
"code": "AUTH_UNAUTHORIZED",
"message": "Invalid credentials",
"i18nKey": "auth.login.invalid_credentials",
"i18nVars": {
"field": "email"
},
"details": [
{
"message": "email must be an email"
}
],
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}Source
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/discover/discover.controller.ts | 90–96 (getHistory) |
| DTO (request) | apps/api-core/src/modules/discover/dto/index.ts | 46–49 (HistoryQueryDto) |
| DTO (response) | apps/api-core/src/modules/discover/dto/discover-response.dto.ts | 52–55 (CreatorListDto) |
| Service | apps/api-core/src/modules/discover/discover.service.ts | 375–387 (getRecentlyViewed) |
| Config | apps/api-core/src/modules/config/config.service.ts | discover.recently_viewed_count (admin-managed default 10) |
| Prisma models | packages/prisma/prisma/schema.prisma | RecentlyViewed, CreatorProfile (active filter) |
Record Profile View
Authenticated. Upserts a RecentlyViewed row for the (user, creator) pair — refreshes viewedAt on subsequent visits. Returns whether the row was recorded.
Clear Recently Viewed
Authenticated. Hard-deletes every RecentlyViewed row for the calling user. Returns the count actually deleted. No undo.