BIO.RE
Creator Analytics

Creator Analytics Overview

Top-line counters for the authenticated creator's bio page — total views, unique visitors (by hashed visitorId), total link clicks, CTR. Scoped to the caller's own bio page; foreign access is impossible.

GET /api/v1/creators/analytics/overview — 🔑 Bearer · Rate limit: 60 req / minute

Four headline metrics for the current creator's bio page over a configurable window. The endpoint resolves the bio page from the JWT subject — there is no creator/userId path or query parameter, so a creator can only ever see their own numbers.

Owner-scoped via JWT, no cross-creator reads. All eight creator-analytics endpoints follow the same pattern: getBioPageId(userId) looks up CreatorProfile then BioPage from the main DB, throws 404 if either is missing. The Analytics DB queries that follow always filter by that resolved bioPageId. There is no way to spoof another creator's analytics through this controller.

uniqueVisitors counts distinct hashed visitor ids. The visitor key was SHA256-hashed at session-create time (see Create Analytics Session) — the count here is groupBy(visitorId) over those hashes, so it's a privacy-preserving distinct-count, not a raw distinct user count.

days is capped at 365. Math.min(parseInt(days ?? '30'), 365) — anything larger is silently clamped. Out-of-range strings (?days=abc) become NaN then default to 30 (since Math.min(NaN, 365) === NaN falls through to default). Prefer to send a clean integer.

Request

Query parameters

ParamTypeDefaultNotes
daysinteger30Lookback window in days. Server caps at 365.

Headers

HeaderRequiredNotes
Authorization: Bearer <accessToken>JwtAuthGuard (explicit on this controller, not just global).

Response

200 OKApiResponseOf<CreatorAnalyticsOverviewDto>

{
  "success": true,
  "data": {
    "totalViews": 4200,
    "uniqueVisitors": 1800,
    "totalClicks": 320,
    "ctr": 7.62,
    "days": 30
  }
}
FieldTypeNotes
totalViewsnumbercount(AnalyticsPageView WHERE bioPageId = ? AND enteredAt >= since). Counts every pageview, including link-click pageviews.
uniqueVisitorsnumberDistinct hashed visitorId over AnalyticsSession rows in the window.
totalClicksnumbercount(AnalyticsPageView WHERE linkClicked IS NOT NULL ...). A view counts as a "click" when the URL navigation came from a bio link.
ctrnumberMath.round((totalClicks / totalViews) * 10000) / 100 — 2-decimal percentage. 0 when there are no views.
daysnumberEcho of the effective window (after defaulting + capping).

Errors

HTTPCode / i18nKeyReason
401(guard)Missing / invalid bearer token.
404error.creator.not_foundThe authenticated user has no CreatorProfile.
404error.creator.bio_not_foundCreator exists but has no BioPage.
429ThrottleOver 60 req/min from this IP.

Side effects

  1. JwtAuthGuard. ThrottleGuard (60 req / 60s).
  2. getBioPageId(userId) — main DB:
    • prisma.creatorProfile.findFirst({ where: { userId }, select: { id } }) → 404 if missing.
    • prisma.bioPage.findFirst({ where: { creatorId }, select: { id } }) → 404 if missing.
  3. Compute since = new Date(Date.now() - d * 86400000).
  4. Three parallel queries against the Analytics DB:
    • analyticsDb.analyticsPageView.count({ where: { bioPageId, enteredAt: { gte: since } } })
    • analyticsDb.analyticsSession.groupBy({ by: ['visitorId'], where: { bioPageId, startedAt: { gte: since } } }).then(r => r.length) — distinct visitors
    • analyticsDb.analyticsPageView.count({ where: { bioPageId, linkClicked: { not: null }, enteredAt: { gte: since } } })
  5. Compute ctr and return. No DB writes.

Code samples

curl 'https://api.bio.re/api/v1/creators/analytics/overview?days=30' \
  -H "Authorization: Bearer $ACCESS_TOKEN"
type CreatorOverview = {
  totalViews: number;
  uniqueVisitors: number;
  totalClicks: number;
  ctr: number;        // 2-decimal percentage
  days: number;
};

async function getOverview(
  accessToken: string,
  days = 30,
): Promise<CreatorOverview> {
  const res = await fetch(
    `https://api.bio.re/api/v1/creators/analytics/overview?days=${days}`,
    { headers: { Authorization: `Bearer ${accessToken}` } },
  );
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'Overview failed'), {
      code: json?.error?.code,
    });
  }
  return json.data;
}
import { useQuery } from '@tanstack/react-query';

export const creatorAnalyticsKeys = {
  overview: (days: number) => ['creator-analytics', 'overview', days] as const,
};

export function useCreatorOverview(days = 30) {
  return useQuery({
    queryKey: creatorAnalyticsKeys.overview(days),
    queryFn: async () => {
      const res = await fetch(`/api/v1/creators/analytics/overview?days=${days}`);
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? 'Overview failed'), {
          code: json?.error?.code,
        });
      }
      return json.data as CreatorOverview;
    },
    staleTime: 5 * 60_000,
  });
}

Try it

GET
/api/v1/creators/analytics/overview
AuthorizationBearer <token>

In: header

Query Parameters

days?string

Response Body

application/json

application/json

application/json

curl -X GET "https://loading/api/v1/creators/analytics/overview"
{
  "success": true,
  "data": {
    "totalViews": 4200,
    "uniqueVisitors": 1800,
    "totalClicks": 320,
    "ctr": 7.62,
    "days": 30
  }
}
{
  "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/analytics/creator-analytics.controller.ts23–28 (class), 45–62 (overview)
Owner-scope helperapps/api-core/src/modules/analytics/creator-analytics.controller.ts30–43 (getBioPageId — main DB lookups)
DTO (response)apps/api-core/src/modules/analytics/dto/analytics-client-response.dto.ts57–72 (CreatorAnalyticsOverviewDto)
Prisma model (main)packages/prisma/prisma/schema.prismaCreatorProfile.userId (315–317), BioPage.creatorId (426–443)
Prisma model (analytics)packages/prisma-analytics/prisma/schema.prismaAnalyticsSession.visitorId (line 48), AnalyticsPageView.bioPageId / linkClicked / enteredAt (102–119)

On this page