Get User Profile
Read the calling user's full profile — identity fields + flags (twoFactorEnabled, isCreator, emailVerified) + linked OAuth accounts + attribution data.
GET /api/v1/users/profile — 🔑 Bearer
Returns the full profile for the user resolved from the bearer token: identity fields, account flags (twoFactorEnabled, isCreator, emailVerified), linked OAuth accounts, and attribution metadata captured at registration.
This is heavier than GET /auth/me. /auth/me returns the lightweight identity slice for nav-bar / session checks; /users/profile returns the full row with social accounts and attribution. Use /auth/me for frequent polling, /users/profile for the account / settings screens.
Request
No body, no params.
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer <accessToken> | ✓ | JWT from POST /auth/login |
Response
200 OK — ApiResponseOf<UserProfileDto>
{
"success": true,
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "[email protected]",
"username": "johndoe",
"displayName": "John Doe",
"avatarUrl": "https://cdn.bio.re/avatars/abc.jpg",
"bio": "Software engineer and creator.",
"status": "ACTIVE",
"emailVerified": true,
"locale": "en",
"lastLoginAt": "2026-04-29T19:00:00.000Z",
"twoFactorEnabled": false,
"createdAt": "2025-12-01T08:30:00.000Z",
"isCreator": false,
"socialAccounts": [
{
"platform": "INSTAGRAM",
"platformUsername": "johndoe",
"connectedAt": "2026-01-15T10:00:00.000Z"
}
],
"referralCode": "REF123",
"firstReferrerUrl": "https://example.com/ref",
"firstLandingPage": "/landing",
"utmSource": "google",
"utmMedium": "cpc",
"utmCampaign": "spring_promo",
"registrationDevice": "mobile",
"registrationCountry": "TR"
}
}Fields
| Field | Type | Notes |
|---|---|---|
id | string (UUID) | Account id |
email | string | Account email |
username | string | null | null until first set via PATCH /users/username |
displayName | string | null | Free-form display name |
avatarUrl | string | null | CDN URL of the current avatar |
bio | string | null | Free-form bio (max 500 chars) |
status | enum | ACTIVE / SUSPENDED / BANNED / DEACTIVATED / DELETED |
emailVerified | boolean | True after POST /auth/verify-email |
locale | string | null | Preferred locale code (e.g. en, tr) |
lastLoginAt | string (ISO 8601) | null | Most recent login timestamp |
twoFactorEnabled | boolean | Mirrors User.twoFactorEnabled |
createdAt | string (ISO 8601) | Account creation timestamp |
isCreator | boolean | true if a CreatorProfile row exists for this user |
socialAccounts | array | Linked OAuth accounts — { platform, platformUsername, connectedAt } |
referralCode | string | null | This user's own referral code |
firstReferrerUrl / firstLandingPage / utm* / registrationDevice / registrationCountry | string | null | Attribution snapshot captured at registration |
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
401 | (guard) | Missing / invalid bearer token |
404 | error.user.not_found | Token decoded but user row missing (deleted account) |
Side effects
- Single
prisma.user.findUnique()with selected fields + relations (creatorProfile.id,socialAccounts). isCreatorderived from whethercreatorProfileis non-null (the row itself isn't returned).- No mutations — pure read.
Code samples
curl https://api.bio.re/api/v1/users/profile \
-H "Authorization: Bearer $ACCESS_TOKEN"type SocialAccount = {
platform: string;
platformUsername: string | null;
connectedAt: string;
};
type UserProfile = {
id: string;
email: string;
username: string | null;
displayName: string | null;
avatarUrl: string | null;
bio: string | null;
status: 'ACTIVE' | 'SUSPENDED' | 'BANNED' | 'DEACTIVATED' | 'DELETED';
emailVerified: boolean;
locale: string | null;
lastLoginAt: string | null;
twoFactorEnabled: boolean;
createdAt: string;
isCreator: boolean;
socialAccounts: SocialAccount[];
referralCode: string | null;
firstReferrerUrl: string | null;
firstLandingPage: string | null;
utmSource: string | null;
utmMedium: string | null;
utmCampaign: string | null;
registrationDevice: string | null;
registrationCountry: string | null;
};
async function getProfile(accessToken: string): Promise<UserProfile> {
const res = await fetch('https://api.bio.re/api/v1/users/profile', {
headers: { Authorization: `Bearer ${accessToken}` },
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Failed to load profile'), {
code: json?.error?.code,
});
}
return json.data;
}import { useQuery } from '@tanstack/react-query';
export const userKeys = {
profile: () => ['users', 'profile'] as const,
};
export function useProfile() {
return useQuery({
queryKey: userKeys.profile(),
queryFn: async () => {
const res = await fetch('/api/v1/users/profile');
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Failed to load profile'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
return json.data as UserProfile;
},
staleTime: 60_000, // 1 min — profile changes are user-triggered
});
}Try it
Authorization
bearer In: header
Response Body
application/json
application/json
application/json
curl -X GET "https://loading/api/v1/users/profile"{
"success": true,
"data": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"email": "[email protected]",
"username": "johndoe",
"displayName": "John Doe",
"avatarUrl": "https://cdn.bio.re/avatars/abc.jpg",
"bio": "Software engineer and creator.",
"status": "ACTIVE",
"emailVerified": true,
"locale": "en",
"lastLoginAt": "2019-08-24T14:15:22Z",
"twoFactorEnabled": false,
"createdAt": "2019-08-24T14:15:22Z",
"isCreator": false,
"socialAccounts": [
{
"platform": "INSTAGRAM",
"platformUsername": "johndoe",
"connectedAt": "2019-08-24T14:15:22Z"
}
],
"referralCode": "REF123",
"firstReferrerUrl": "https://example.com/ref",
"firstLandingPage": "/landing",
"utmSource": "google",
"utmMedium": "cpc",
"utmCampaign": "spring_promo",
"registrationDevice": "mobile",
"registrationCountry": "TR"
}
}{
"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 | 53–60 (getProfile) |
| DTO (response) | apps/api-core/src/modules/user/dto/user-client-response.dto.ts | 18–84 (UserProfileDto), 5–14 (SocialAccountDto) |
| Service | apps/api-core/src/modules/user/user.service.ts | 38–59 (getProfile) |
| Prisma model | packages/prisma/prisma/schema.prisma | User, CreatorProfile, SocialAccount |
Get Presigned Upload URL
Two-step upload flow — server returns a signed PUT URL; client uploads file bytes directly to object storage. Three types (avatar / media / document) with admin-managed MIME and size allowlists.
Update User Profile
Patch displayName, bio, or avatarUrl. Only fields present in the body are updated. Returns the fresh updated profile slice.