User
Get Attribution
Read the immutable registration attribution snapshot — UTM params, first referrer, landing page, device, country, referral chain. Captured once at signup and never changes.
GET /api/v1/users/attribution — 🔑 Bearer
Returns the attribution snapshot captured at registration time. All fields are read-only after signup — there is no patch endpoint. Useful for analytics views, support tooling, and creator-attribution flows.
The same fields are also embedded in GET /users/profile. This endpoint exists for consumers who want only the attribution slice without paying the cost of fetching the full profile (and the social-accounts join).
Request
No body, no params.
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer <accessToken> | ✓ | JWT from POST /auth/login |
Response
200 OK — ApiResponseOf<UserAttributionDto>
{
"success": true,
"data": {
"referredBy": "c1d2e3f4-a5b6-7890-1234-56789abcdef0",
"referralCode": "REF123",
"firstReferrerUrl": "https://example.com/ref",
"firstLandingPage": "/landing",
"acquiredViaCreatorId": "d2e3f4a5-b6c7-8901-2345-6789abcdef01",
"utmSource": "google",
"utmMedium": "cpc",
"utmCampaign": "spring_promo",
"utmTerm": "creator-tools",
"utmContent": "banner-top",
"registrationDevice": "mobile",
"registrationCountry": "TR",
"createdAt": "2025-12-01T08:30:00.000Z"
}
}| Field | Type | Notes |
|---|---|---|
referredBy | string (UUID) | null | The user id of whoever referred this account |
referralCode | string | null | The referral code that was used at signup (e.g. another user's User.referralCode) |
firstReferrerUrl | string | null | The HTTP Referer captured on the first landing |
firstLandingPage | string | null | Path of the first page hit (e.g. /landing, /creators/alice) |
acquiredViaCreatorId | string (UUID) | null | If the user signed up through a creator's profile / link, the creator's id |
utmSource / utmMedium / utmCampaign / utmTerm / utmContent | string | null | UTM query params captured at first landing |
registrationDevice | string | null | Device classification (e.g. mobile, desktop, tablet) |
registrationCountry | string | null | ISO country code from the IP geo-lookup at signup time |
createdAt | string (ISO 8601) | When the user record was created — also when this snapshot was captured |
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
401 | (guard) | Missing / invalid bearer token |
404 | error.user.not_found | Token decoded but user row missing |
Side effects
prisma.user.findUnique({ where: { id: userId }, select: { ...all 13 attribution fields } }).- Throw
not_foundif missing. - Return the row. No mutations — attribution is immutable post-signup; mutating it would break analytics retention.
Code samples
curl https://api.bio.re/api/v1/users/attribution \
-H "Authorization: Bearer $ACCESS_TOKEN"type UserAttribution = {
referredBy: string | null;
referralCode: string | null;
firstReferrerUrl: string | null;
firstLandingPage: string | null;
acquiredViaCreatorId: string | null;
utmSource: string | null;
utmMedium: string | null;
utmCampaign: string | null;
utmTerm: string | null;
utmContent: string | null;
registrationDevice: string | null;
registrationCountry: string | null;
createdAt: string;
};
async function getAttribution(accessToken: string): Promise<UserAttribution> {
const res = await fetch('https://api.bio.re/api/v1/users/attribution', {
headers: { Authorization: `Bearer ${accessToken}` },
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Attribution fetch failed'), {
code: json?.error?.code,
});
}
return json.data;
}import { useQuery } from '@tanstack/react-query';
export const userKeys = {
attribution: () => ['users', 'attribution'] as const,
};
export function useAttribution() {
return useQuery({
queryKey: userKeys.attribution(),
queryFn: async () => {
const res = await fetch('/api/v1/users/attribution');
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Attribution fetch failed'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
return json.data as UserAttribution;
},
// Attribution never changes after signup — cache aggressively
staleTime: Infinity,
gcTime: 60 * 60_000, // 1 hour
});
}Try it
Authorization
bearer AuthorizationBearer <token>
In: header
Response Body
application/json
application/json
application/json
curl -X GET "https://loading/api/v1/users/attribution"{
"success": true,
"data": {
"referredBy": "e0a7d368-2b46-4066-b62f-ae5e58fbcfd3",
"referralCode": "REF123",
"firstReferrerUrl": "https://example.com/ref",
"firstLandingPage": "/landing",
"acquiredViaCreatorId": "401c31a6-4d54-4edd-bc46-ae5b8136ef92",
"utmSource": "google",
"utmMedium": "cpc",
"utmCampaign": "spring_promo",
"utmTerm": "keyword",
"utmContent": "banner",
"registrationDevice": "mobile",
"registrationCountry": "TR",
"createdAt": "2019-08-24T14:15:22Z"
}
}{
"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
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/user/user.controller.ts | 318–325 (getAttribution) |
| DTO (response) | apps/api-core/src/modules/user/dto/user-client-response.dto.ts | 231–270 (UserAttributionDto) |
| Service | apps/api-core/src/modules/user/user.service.ts | 818–839 (getAttribution) |
| Prisma model | packages/prisma/prisma/schema.prisma | User.referredBy, User.referralCode, User.firstReferrerUrl, User.firstLandingPage, User.acquiredViaCreatorId, User.utm*, User.registrationDevice, User.registrationCountry |