GET /me
Retrieve the authenticated user's profile, relationships, and account state.
The /me endpoint returns the currently authenticated user's profile. It's the first call most clients make after obtaining an access token to bootstrap the UI — identity, settings, onboarding state, and account flags all come from here.
Request
GET https://{API_HOST}/api/v1/me
Authorization: Bearer {ACCESS_TOKEN}
No request body. No query parameters required (though include and fields are supported — see below).
Response
The response follows JSON:API format. All attribute keys are camelCase.
{
"data": {
"id": "42",
"type": "person",
"attributes": {
"firstName": "Jane",
"lastName": "Doe",
"email": "[email protected]",
"headline": "Angel investor & operator",
"linkedinUrl": "https://linkedin.com/in/janedoe",
"phoneNumber": "+15551234567",
"avatarUrl": "https://res.cloudinary.com/.../avatar.jpg",
"fullName": "Jane Doe",
"referralCode": "JANE42",
"profileType": "individual",
"accreditationConfirmedAt": "2025-08-15T10:30:00Z",
"onboardingCompletedAt": "2025-08-15T11:00:00Z",
"otpEnabled": true,
"otpRequiredForAdmin": false,
"investmentsStatus": "experienced",
"unconfirmedEmail": null,
"pendingEmailChange": false,
"profileSettings": {
"portfolioVisible": true,
"isPublicProfile": true
},
"createdAt": "2025-08-01T09:00:00Z",
"updatedAt": "2026-03-20T14:22:00Z"
},
"relationships": {
"profile": { "data": { "id": "1", "type": "profile" } },
"roles": { "data": [{ "id": "5", "type": "role" }] },
"permissions": { "data": [{ "id": "12", "type": "permission" }] },
"resourceRoleGrants": { "data": [{ "id": "3", "type": "resourceRoleGrant" }] },
"resourcePermissionGrants": { "data": [{ "id": "8", "type": "resourcePermissionGrant" }] },
"defaultInvestmentProfile": { "data": { "id": "7", "type": "investmentProfile" } }
}
},
"meta": {
"currentPersonContext": {},
"auth": {}
}
}
Attributes
Since this is the authenticated user's own profile, the response includes owner-only fields that are hidden on other people's profiles.
Always Returned
| Attribute | Type | Description |
|---|---|---|
firstName | string | First name |
lastName | string | Last name |
fullName | string | Computed "firstName lastName" |
avatarUrl | string | null | Cloudinary URL for the user's avatar |
headline | string | null | Short bio / tagline |
linkedinUrl | string | null | LinkedIn profile URL |
createdAt | ISO 8601 | Account creation timestamp |
updatedAt | ISO 8601 | Last profile update timestamp |
profileSettings | object | { portfolioVisible, isPublicProfile } |
Owner-Only (visible only to the authenticated user)
| Attribute | Type | Description |
|---|---|---|
email | string | Account email |
phoneNumber | string | null | Phone number on file |
referralCode | string | User's referral code for sharing |
profileType | string | "individual", "entity", etc. |
accreditationConfirmedAt | ISO 8601 | null | When accreditation was verified |
onboardingCompletedAt | ISO 8601 | null | When onboarding was completed (null = still in progress) |
otpEnabled | boolean | Whether 2FA is enabled |
otpRequiredForAdmin | boolean | Whether admin access requires 2FA setup |
investmentsStatus | string | null | Self-reported experience level from questionnaire |
unconfirmedEmail | string | null | Pending email change (awaiting confirmation) |
pendingEmailChange | boolean | Whether an email change is pending |
Sparse Fieldsets
Limit which attributes are returned using JSON:API sparse fieldsets:
GET /api/v1/me?fields[people]=firstName,lastName,email,avatarUrl
This returns only the specified attributes, reducing payload size.
Including Relationships
Use the include parameter to sideload related resources:
GET /api/v1/me?include=profile,roles,defaultInvestmentProfile,highestInvestmentAchievement
| Relationship | Type | Description |
|---|---|---|
profile | profile | Extended profile data |
roles | role[] | User's platform roles (owner-only). Supports nested: roles.permissions to include each role's permissions |
permissions | permission[] | Flattened distinct permissions from the user's global roles (owner-only) |
resourceRoleGrants | resourceRoleGrant[] | Per-resource role grants, e.g. "founder on Deal X" (owner-only). Each grant includes the associated role and exposes resourceType / resourceId as attributes |
resourcePermissionGrants | resourcePermissionGrant[] | Per-resource permission grants, e.g. "deal:manage on Deal X" (owner-only). Each grant includes the associated permission and exposes resourceType / resourceId as attributes |
defaultInvestmentProfile | investmentProfile | The user's default investment profile (owner-only) |
highestInvestmentAchievement | personAchievement | Top achievement badge. Supports nested: highestInvestmentAchievement.achievement |
communities | community[] | Communities the user belongs to |
investments | investment[] | User's investments |
questionnaire | questionnaire | Onboarding questionnaire answers |
signup | signup | Signup metadata (owner-only) |
Nested includes use dot notation: include=highestInvestmentAchievement.achievement,highestInvestmentAchievement.context
GET /api/v1/me?include=roles.permissions,resourceRoleGrants,resourcePermissionGrants
Authentication Errors
If the token is missing, expired, or invalid:
{
"errors": [{
"code": "InvalidToken",
"detail": "The provided API token is invalid or expired. Please log in again.",
"requiresLogin": true
}]
}
Status: 401 Unauthorized
When you see requiresLogin: true, redirect the user through the OAuth authorization flow.
Typical Usage
Bootstrap on App Load
After completing the OAuth flow and obtaining tokens, /me is the first call to hydrate the user session:
async function bootstrapUser(accessToken: string) {
const response = await fetch(
`${API_HOST}/api/v1/me?include=profile,roles.permissions,resourceRoleGrants,resourcePermissionGrants,defaultInvestmentProfile`,
{
headers: { Authorization: `Bearer ${accessToken}` },
}
);
if (response.status === 401) {
// Token expired or invalid — trigger re-auth
redirectToOAuth();
return null;
}
const { data } = await response.json();
return {
id: data.id,
name: data.attributes.fullName,
email: data.attributes.email,
avatar: data.attributes.avatarUrl,
onboarded: !!data.attributes.onboardingCompletedAt,
accredited: !!data.attributes.accreditationConfirmedAt,
twoFactorEnabled: data.attributes.otpEnabled,
};
}
Checking Onboarding State
Use onboardingCompletedAt to determine whether the user needs to complete onboarding before accessing the main app:
if (!user.onboardingCompletedAt) {
router.push("/onboarding");
}
Checking Accreditation
accreditationConfirmedAt being null means the user hasn't been verified as an accredited investor yet:
if (!user.accreditationConfirmedAt) {
// Show restricted experience or prompt for verification
}
Last updated Mar 31, 2026
Built with Documentation.AI