BIO.RE
Platform

Get Platform Stats

Public aggregate counters — active creators, total messages, total earned. CDN- and Redis-cached for 1 hour. Render in trust badges, homepage hero, footer counters.

GET /api/v1/platform/stats — 🌐 Public · Rate limit: 60 req / minute

Returns three aggregate counters across the entire platform plus a cachedAt timestamp. Used for marketing surfaces (trust badges, "X creators · Y messages" hero text, social proof footers). Cached on two layers — Redis (server-side, 1 hour) and Cache-Control: public, s-maxage=3600 (CDN edge, 1 hour) — so you can hammer this endpoint freely from a static site or RSC layout without warming concerns.

Two cache layers, both 1 hour. The server caches the computed snapshot in Redis under key platform:stats for 3600s, AND emits Cache-Control: public, s-maxage=3600 so the CDN holds the response for the same window. A change in creator/message counts can take up to ~1 hour to surface here — this is a marketing counter, not a real-time dashboard. If you need fresh numbers, render server-side and skip the CDN, but you'll still hit the Redis layer.

totalMessagesSent counts ALL messages, every status. The query is a bare prisma.message.count() with no status filter — so PENDING, ESCROWED, DELIVERED, REPLIED, REFUNDED, EXPIRED rows are all included. This is intentional ("messages sent" = anything that entered the system) but be careful if you label this as "delivered" or "answered" in the UI.

totalEarned is a string, not a number. It's a Decimal(12, 2) in Postgres serialized via .toString(). Parse with Number(totalEarned) or a decimal lib, and never display the raw string with currency formatting — it has no thousands separators or symbol.

Request

No body, no params, no headers required. Bearer token is allowed but ignored.

Response

200 OKApiResponseOf<PlatformStatsDto>

{
  "success": true,
  "data": {
    "totalCreators": 1250,
    "totalMessagesSent": 48320,
    "totalEarned": "125400.00",
    "cachedAt": "2026-04-29T12:00:00.000Z"
  }
}

Fields

FieldTypeNotes
totalCreatorsnumberCount of CreatorProfile rows where user.status = ACTIVE AND deletedAt IS NULL. Excludes suspended/deleted/inactive accounts.
totalMessagesSentnumberCount of ALL Message rows — every status, every type. No filter.
totalEarnedstringSum of CreatorProfile.totalEarnings for non-deleted creators, serialized as a Decimal(12, 2) string (e.g. "125400.00"). Currency: USD. Includes earnings whether or not they've been paid out.
cachedAtstring (ISO 8601)When this snapshot was computed and stored in Redis. Use it to render "Last updated X minutes ago" if you want.

Errors

HTTPReason
429Rate limit exceeded (60 req/min — but you should rarely hit this since the response is CDN-cached).

No documented error responses beyond infrastructure failures.

Side effects

  1. Read Redis cache at key platform:stats. If hit → return immediately.
  2. On miss, run three parallel Prisma queries:
    • prisma.creatorProfile.count({ where: { user: { status: 'ACTIVE' }, deletedAt: null } })
    • prisma.message.count()
    • prisma.creatorProfile.aggregate({ _sum: { totalEarnings: true }, where: { deletedAt: null } })
  3. Build the snapshot — totalEarnings is coerced from Decimal to string (?.toString() ?? '0'); cachedAt is new Date().toISOString().
  4. redis.cacheSet('platform:stats', snapshot, 3600) — non-blocking write-back.
  5. Return the snapshot wrapped manually as { success: true, data } (the controller hand-rolls the envelope rather than relying on the response interceptor — same shape on the wire).

Code samples

curl https://api.bio.re/api/v1/platform/stats
type PlatformStats = {
  totalCreators: number;
  totalMessagesSent: number;
  totalEarned: string; // Decimal(12,2) as string
  cachedAt: string;    // ISO 8601
};

async function getPlatformStats(): Promise<PlatformStats> {
  const res = await fetch('https://api.bio.re/api/v1/platform/stats');
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'Stats fetch failed'), {
      code: json?.error?.code,
    });
  }
  return json.data;
}

// Usage in a hero section:
const stats = await getPlatformStats();
const earnedUsd = Number(stats.totalEarned).toLocaleString('en-US', {
  style: 'currency',
  currency: 'USD',
  maximumFractionDigits: 0,
});
// → "$125,400"
import { useQuery } from '@tanstack/react-query';

export const platformKeys = {
  stats: () => ['platform', 'stats'] as const,
};

export function usePlatformStats() {
  return useQuery({
    queryKey: platformKeys.stats(),
    queryFn: async () => {
      const res = await fetch('/api/v1/platform/stats');
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? 'Stats fetch failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
      return json.data as PlatformStats;
    },
    // CDN + Redis already cache for 1 hour — match the server-side TTL.
    staleTime: 60 * 60_000,
    gcTime: 2 * 60 * 60_000,
  });
}

Try it

GET
/api/v1/platform/stats

Response Body

application/json

curl -X GET "https://loading/api/v1/platform/stats"
{
  "success": true,
  "data": {
    "totalCreators": 1250,
    "totalMessagesSent": 48320,
    "totalEarned": "125400.00",
    "cachedAt": "2026-04-23T12:00:00.000Z"
  }
}

Source

SourcePathLines
Controllerapps/api-core/src/modules/platform/platform.controller.ts13–25 (class), 17–24 (getStats)
DTO (response)apps/api-core/src/modules/platform/dto/platform-response.dto.ts5–26 (PlatformStatsDto)
Serviceapps/api-core/src/modules/platform/platform.service.ts27–41 (getStats — Redis read-through), 43–70 (computeStats — 3 parallel queries)
Cache configapps/api-core/src/modules/platform/platform.service.ts5–6 (CACHE_KEY = 'platform:stats', CACHE_TTL_SECONDS = 3600)
Prisma modelpackages/prisma/prisma/schema.prismaCreatorProfile.totalEarnings (line 331, Decimal(12,2)), CreatorProfile.deletedAt, User.status, Message (counted unfiltered)

On this page