BIO.RE
Creator

List Subscribers (Creator)

Owner-only paginated list of confirmed, active mailing-list subscribers. Filter is confirmed=true AND unsubscribedAt=null.

GET /api/v1/creators/subscribers — 🔑 Bearer

Lists the calling creator's mailing-list subscribers — only those who confirmed their subscription AND have not unsubscribed. Owner-only: the creator's bio page is resolved server-side from the bearer's user id; you don't pass a bioPageId. Pagination via ?page / ?limit, server-clamped to [1, 100] per page.

Sensitive field exposure (known issue). Each item currently includes confirmToken — a server-side comment in the DTO marks this as a TODO to omit/mask in a future iteration. Until then, do not log the response in any client-side observability tool, and treat the field as if it weren't there. It will likely become null on confirmed subscribers anyway (the confirm endpoint sets confirmToken: null), but unconfirmed-then-unsubscribed edge cases may surface real tokens.

Request

Query parameters

ParamTypeDefaultValidationNotes
pagenumber1ParseIntPipe, server-clamped to >= 1Page index (1-based)
limitnumber50ParseIntPipe, server-clamped to [1, 100]Items per page
HeaderRequiredNotes
Authorization: Bearer <accessToken>JWT from POST /auth/login

Response

200 OKApiResponseOf<SubscribersResponseDto>

{
  "success": true,
  "data": {
    "items": [
      {
        "id": "s1u2b3c4-d5e6-7890-abcd-ef1234567890",
        "bioPageId": "b1a2b3c4-d5e6-7890-abcd-ef1234567890",
        "email": "[email protected]",
        "name": "Fan Doe",
        "subscribedAt": "2026-04-29T20:00:00.000Z",
        "unsubscribedAt": null,
        "confirmed": true,
        "confirmToken": null,
        "source": "bio_page",
        "createdAt": "2026-04-29T20:00:00.000Z"
      }
    ],
    "total": 42
  }
}

Item fields

FieldTypeNotes
idstring (UUID)BioEmailSubscriber.id — pass to the unsubscribe link
bioPageIdstring (UUID)The bio page this subscription belongs to
emailstringLowercased email
namestring | nullHTML-stripped at write time
subscribedAtstring (ISO 8601)When the row was created (subscription start)
unsubscribedAtstring (ISO 8601) | nullAlways null here (filter excludes non-null)
confirmedbooleanAlways true here (filter requires confirmation)
confirmTokenstring | nullSensitive — see callout above. Typically null for confirmed subscribers.
sourcestring | nullAcquisition source tag (e.g. bio_page)
createdAtstring (ISO 8601)DB row creation timestamp

Top-level fields

FieldTypeNotes
itemsarrayUp to limit items, ordered by subscribedAt DESC
totalnumberTotal count of confirmed + active subscribers (matching the same filter)

Errors

HTTPcode / i18nKeyReason
400(validation)page / limit not parseable as int
401(guard)Missing / invalid bearer token
404creator.bio.not_foundNo BioPage for this creator

Side effects

  1. Resolve creatorId from the bearer's userId via prisma.creatorProfile.findUnique({ where: { userId }, select: { id: true } }).
  2. Lookup BioPage by creatorId; throw not_found if missing.
  3. Clamp pagination: safePage = max(page, 1), safeLimit = min(max(limit, 1), 100).
  4. Two queries in parallel:
    • findMany({ where: { bioPageId, confirmed: true, unsubscribedAt: null }, skip, take, orderBy: { subscribedAt: 'desc' } }).
    • count({ where: <same filter> }).
  5. Return { items, total }. No mutations.

Code samples

curl 'https://api.bio.re/api/v1/creators/subscribers?page=1&limit=50' \
  -H "Authorization: Bearer $ACCESS_TOKEN"
type SubscriberItem = {
  id: string;
  bioPageId: string;
  email: string;
  name: string | null;
  subscribedAt: string;
  unsubscribedAt: string | null;
  confirmed: boolean;
  confirmToken: string | null;
  source: string | null;
  createdAt: string;
};

type SubscribersResponse = {
  items: SubscriberItem[];
  total: number;
};

async function getSubscribers(accessToken: string, page = 1, limit = 50): Promise<SubscribersResponse> {
  const url = new URL('https://api.bio.re/api/v1/creators/subscribers');
  url.searchParams.set('page', String(page));
  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 ?? 'Subscribers fetch failed'), {
      code: json?.error?.code,
    });
  }
  return json.data;
}
import { useQuery } from '@tanstack/react-query';

export const creatorKeys = {
  subscribers: (page: number, limit: number) => ['creators', 'subscribers', page, limit] as const,
};

export function useSubscribers(page = 1, limit = 50) {
  return useQuery({
    queryKey: creatorKeys.subscribers(page, limit),
    queryFn: async () => {
      const url = new URL('/api/v1/creators/subscribers', window.location.origin);
      url.searchParams.set('page', String(page));
      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 ?? 'Subscribers fetch failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
      return json.data as SubscribersResponse;
    },
    staleTime: 30_000,
  });
}

Try it

GET
/api/v1/creators/subscribers
AuthorizationBearer <token>

In: header

Query Parameters

page*number
limit*number

Response Body

application/json

application/json

application/json

curl -X GET "https://loading/api/v1/creators/subscribers?page=0&limit=0"
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
        "bioPageId": "19ae7b68-8677-4862-a3d1-274c4a95a121",
        "email": "[email protected]",
        "name": "string",
        "subscribedAt": "2019-08-24T14:15:22Z",
        "unsubscribedAt": "2019-08-24T14:15:22Z",
        "confirmed": true,
        "confirmToken": "string",
        "source": "bio_page",
        "createdAt": "2019-08-24T14:15:22Z"
      }
    ],
    "total": 42
  }
}
{
  "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"
  }
}
{
  "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

SourcePathLines
Controllerapps/api-core/src/modules/creator/creator.controller.ts351–361 (getSubscribers)
DTO (response)apps/api-core/src/modules/creator/dto/creator-client-response.dto.ts595–601 (SubscribersResponseDto), 562–593 (SubscriberItemDto)
Serviceapps/api-core/src/modules/creator/creator.service.ts731–744 (getSubscribers)
Prisma modelpackages/prisma/prisma/schema.prismaBioEmailSubscriber (filtered confirmed = true AND unsubscribedAt = null)

On this page