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 OK — ApiResponseOf<PlatformStatsDto>
{
"success": true,
"data": {
"totalCreators": 1250,
"totalMessagesSent": 48320,
"totalEarned": "125400.00",
"cachedAt": "2026-04-29T12:00:00.000Z"
}
}Fields
| Field | Type | Notes |
|---|---|---|
totalCreators | number | Count of CreatorProfile rows where user.status = ACTIVE AND deletedAt IS NULL. Excludes suspended/deleted/inactive accounts. |
totalMessagesSent | number | Count of ALL Message rows — every status, every type. No filter. |
totalEarned | string | Sum 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. |
cachedAt | string (ISO 8601) | When this snapshot was computed and stored in Redis. Use it to render "Last updated X minutes ago" if you want. |
Errors
| HTTP | Reason |
|---|---|
429 | Rate 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
- Read Redis cache at key
platform:stats. If hit → return immediately. - 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 } })
- Build the snapshot —
totalEarningsis coerced fromDecimalto string (?.toString() ?? '0');cachedAtisnew Date().toISOString(). redis.cacheSet('platform:stats', snapshot, 3600)— non-blocking write-back.- 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/statstype 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
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
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/platform/platform.controller.ts | 13–25 (class), 17–24 (getStats) |
| DTO (response) | apps/api-core/src/modules/platform/dto/platform-response.dto.ts | 5–26 (PlatformStatsDto) |
| Service | apps/api-core/src/modules/platform/platform.service.ts | 27–41 (getStats — Redis read-through), 43–70 (computeStats — 3 parallel queries) |
| Cache config | apps/api-core/src/modules/platform/platform.service.ts | 5–6 (CACHE_KEY = 'platform:stats', CACHE_TTL_SECONDS = 3600) |
| Prisma model | packages/prisma/prisma/schema.prisma | CreatorProfile.totalEarnings (line 331, Decimal(12,2)), CreatorProfile.deletedAt, User.status, Message (counted unfiltered) |
Get Payout Report
Creator-scoped payout history. Optional month filter (YYYY-MM, validated). Returns up to 500 most-recent rows, total amount, and count.
Get Last-Seen + Online Status
REST read of socket-written presence state. Returns isOnline (live socket connection) and lastSeen (ISO-8601 of final disconnect, ~30-day TTL). Soft-degrades to "offline + null" when the session Redis is unreachable.