Track Bio Page View
Append a view record. Server parses User-Agent and resolves country from CDN headers when the client doesn't supply them. Creator-side analytics aggregate from this stream.
POST /api/v1/bio/:bioPageId/view — 🌐 Public · Rate limit: 60 req / minute
Appends a BioPageView row for analytics. Public — no auth required (the bio page itself is public). The server always re-derives userAgent from the request header (client-supplied is ignored), and falls back to server-side UA parsing for device / browser and GeoIP + CDN-header lookup for country when the client doesn't supply them.
Server-side override semantics:
userAgent— always from the request header. TheuserAgentfield in the body is read but immediately overwritten.device/browser— body wins when present; otherwise UAParser-derived from the request UA header.country— body wins when present; otherwise GeoIP lookup against the resolved client IP, falling back to the platform CDN's country header (e.g.cf-ipcountrystyle header).
Don't rely on tampered values to be persisted — the server treats the request headers as truth for things it can derive itself.
IP handling is privacy-preserving. The raw client IP is resolved (priority: cf-connecting-ip style CDN header → first entry of x-forwarded-for → req.ip), used only for the GeoIP lookup, and never persisted or logged on failure paths. The BioPageView row stores country (a 2-letter code), not the IP.
Request
Path parameters
| Param | Type | Validation | Notes |
|---|---|---|---|
bioPageId | string (UUID) | ParseUUIDPipe | The page being viewed |
Body — TrackViewDto
All fields optional. Send what your client knows; the server fills the rest.
| Field | Type | Validation | Notes |
|---|---|---|---|
visitorId | string | IsString() | Stable client-generated id (e.g. cookie / localStorage UUID) — used to de-duplicate "unique visitors" |
referrer | string | IsString() | The referring URL the client navigated from |
userAgent | string | IsString() | Ignored — server overwrites from request header |
country | string | IsString() | 2-letter country code; if absent, server resolves via GeoIP / CDN header |
device | string | IsString() | mobile / tablet / desktop; if absent, server derives from UA |
browser | string | IsString() | Browser name; if absent, server derives from UA |
isEmbed | boolean | IsBoolean() | Set to true when fired from inside the iframe embed (so analytics can split embed vs direct) |
No headers required.
Response
200 OK — ApiResponseOf<TrackResponseDto>
{
"success": true,
"data": {
"tracked": true
}
}| Field | Type | Notes |
|---|---|---|
tracked | boolean | Always true on 200 — the row was created |
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
400 | (validation) | bioPageId not a valid UUID; field type mismatches |
429 | (throttle) | Rate limit exceeded (60 req/min) |
Side effects
- Server UA derivation: read
req.headers['user-agent']; parse via UAParser:device = body.device ?? (parsed.device.type === 'mobile' ? 'mobile' : 'tablet' if tablet else 'desktop').browser = body.browser ?? parsed.browser.name.
- Country resolution (when
body.countryabsent):- Resolve client IP: prefer
cf-connecting-ipstyle CDN header, fall back to first entry ofx-forwarded-for, fall back toreq.ip. - GeoIP lookup against the resolved IP (failures swallowed — raw IP never logged).
- Fallback: read the platform CDN's country header (e.g.
cf-ipcountrystyle) when GeoIP returned nothing.
- Resolve client IP: prefer
prisma.bioPageView.create({ id, bioPageId, visitorId, referrer, userAgent, country, device, browser, isEmbed: false })—isEmbeddefaults tofalseif omitted.- Return
{ tracked: true }. No mutations beyond the single insert.
Code samples
curl -X POST 'https://api.bio.re/api/v1/bio/b1a2b3c4-d5e6-7890-abcd-ef1234567890/view' \
-H 'Content-Type: application/json' \
-d '{
"visitorId": "cookie-uuid-12345",
"referrer": "https://twitter.com/johndoe",
"isEmbed": false
}'type TrackViewInput = {
visitorId?: string;
referrer?: string;
country?: string; // server resolves if absent
device?: string; // server derives from UA if absent
browser?: string; // server derives from UA if absent
isEmbed?: boolean;
// userAgent: omit — server always uses request header
};
async function trackBioView(bioPageId: string, input: TrackViewInput = {}): Promise<void> {
const res = await fetch(`https://api.bio.re/api/v1/bio/${bioPageId}/view`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input),
});
// Fire-and-forget pattern is safe — analytics shouldn't block UI
if (!res.ok) {
console.warn('Bio view tracking failed:', res.status);
}
}import { useMutation } from '@tanstack/react-query';
export function useTrackBioView() {
return useMutation({
mutationFn: async (vars: { bioPageId: string; input?: TrackViewInput }) => {
await fetch(`/api/v1/bio/${vars.bioPageId}/view`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(vars.input ?? {}),
});
// Don't throw — analytics failures should not surface to the user
},
retry: false, // Already a fire-and-forget — don't double-count on retry
});
}Try it
Path Parameters
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
curl -X POST "https://loading/api/v1/bio/string/view" \ -H "Content-Type: application/json" \ -d '{}'{
"success": true,
"data": {
"tracked": true
}
}{
"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/creator/bio-analytics.controller.ts | 72–111 (trackView — UA + GeoIP + CDN header logic) |
| DTO (request) | apps/api-core/src/modules/creator/dto/bio-analytics.dto.ts | 4–12 (TrackViewDto) |
| DTO (response) | apps/api-core/src/modules/creator/dto/bio-analytics-response.dto.ts | 214–217 (TrackResponseDto) |
| Service | apps/api-core/src/modules/creator/bio-analytics.service.ts | 180–202 (trackView) |
| GeoIP | packages/geoip/ | GeoIPService.lookup() |
| Prisma model | packages/prisma/prisma/schema.prisma | BioPageView |
Get Embeddable Bio Page
Iframe-friendly variant of the public bio render — same payload, but only succeeds when embedEnabled is true. Sets X-Frame-Options ALLOWALL so partner sites can host the iframe.
Track Bio Link Click
Append a click record + increment BioLink.clickCount. Two writes happen in parallel; a missing link silently no-ops the increment but still records the view.