logo
EndpointsGET /me

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

AttributeTypeDescription
firstNamestringFirst name
lastNamestringLast name
fullNamestringComputed "firstName lastName"
avatarUrlstring | nullCloudinary URL for the user's avatar
headlinestring | nullShort bio / tagline
linkedinUrlstring | nullLinkedIn profile URL
createdAtISO 8601Account creation timestamp
updatedAtISO 8601Last profile update timestamp
profileSettingsobject{ portfolioVisible, isPublicProfile }

Owner-Only (visible only to the authenticated user)

AttributeTypeDescription
emailstringAccount email
phoneNumberstring | nullPhone number on file
referralCodestringUser's referral code for sharing
profileTypestring"individual", "entity", etc.
accreditationConfirmedAtISO 8601 | nullWhen accreditation was verified
onboardingCompletedAtISO 8601 | nullWhen onboarding was completed (null = still in progress)
otpEnabledbooleanWhether 2FA is enabled
otpRequiredForAdminbooleanWhether admin access requires 2FA setup
investmentsStatusstring | nullSelf-reported experience level from questionnaire
unconfirmedEmailstring | nullPending email change (awaiting confirmation)
pendingEmailChangebooleanWhether 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
RelationshipTypeDescription
profileprofileExtended profile data
rolesrole[]User's platform roles (owner-only). Supports nested: roles.permissions to include each role's permissions
permissionspermission[]Flattened distinct permissions from the user's global roles (owner-only)
resourceRoleGrantsresourceRoleGrant[]Per-resource role grants, e.g. "founder on Deal X" (owner-only). Each grant includes the associated role and exposes resourceType / resourceId as attributes
resourcePermissionGrantsresourcePermissionGrant[]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
defaultInvestmentProfileinvestmentProfileThe user's default investment profile (owner-only)
highestInvestmentAchievementpersonAchievementTop achievement badge. Supports nested: highestInvestmentAchievement.achievement
communitiescommunity[]Communities the user belongs to
investmentsinvestment[]User's investments
questionnairequestionnaireOnboarding questionnaire answers
signupsignupSignup 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
}