Skip to main content

User API

GraphQL API reference for user management -- authentication, profiles, onboarding, company membership, and account deletion.


Authentication

Login & Registration

OperationTypeAuthDescription
signInMutationPublicEmail/password login, returns AuthPayload (token, temporaryToken, message, user)
signUpMutationPublicCreate account (SignUpsInput: email, password, confirmPassword, firstName, lastName)
verifyEmailMutationPublicVerify email via token or OTP

Password Management

OperationTypeAuthDescription
requestPasswordResetMutationPublicSend password reset email (RequestPasswordResetInput). Reset token TTL is 24 hours (SUP-00006, 2026-04-21 — bumped from 1h because users reported links expiring before they opened the email). See EXPIRY_TIMES.PASSWORD_RESET in src/common/constants/time.constants.ts.
resetPasswordMutationPublicReset password with token (24h valid, single-use, deleted after redemption)

Two-Factor Authentication

OperationTypeAuthDescription
enableTwoFactorMutationJWTEnable/disable 2FA
verifyTwoFactorMutationPublicVerify 2FA code during login
info

JWT tokens are issued on login and must be included in the Authorization: Bearer <token> header for all protected operations. Tokens are validated by JwtAuthGuard.


User Profile Queries

whoAmI -- Current User

Returns the authenticated user's full profile with company roles and subscription.

query WhoAmI {
whoAmI {
id
email
firstName
lastName
title
location
biography
profileImageUrl
viewMode
selectedCompanyId
isVerified
isSuperAdmin
twoFactorEnabled
UserCompanyRole {
Role { name }
Company { companyName }
}
}
}

Auth: JWT required. Uses @CurrentUser() decorator to inject the authenticated user.

userByEmail -- Lookup by Email

query GetUserByEmail($email: String!) {
userByEmail(email: $email) {
id
email
firstName
lastName
isOnboarding
isVerified
isActive
UserCompanyRole {
Role { name }
Company { companyName }
}
}
}

Auth: JWT required. Email is normalized to lowercase before lookup.

user -- Lookup by ID

query GetUser($id: ID!) {
user(id: $id) {
id
email
firstName
lastName
title
location
}
}

User Onboarding

createOnboardedUser

Creates a new user with optional company talent pool assignment and CV data. This is the primary endpoint for adding users to the platform programmatically.

mutation CreateOnboardedUser($input: CreateOnboardedUserInput!) {
createOnboardedUser(input: $input) {
id
email
firstName
lastName
isOnboarding
isVerified
UserCompanyRole {
Role { name }
Company { companyName }
}
}
}

Auth: Public endpoint (@SetMetadata('isPublic', true)).

Key input fields:

FieldTypeRequiredDescription
emailStringYesMust be valid email
passwordStringYesMin 8 characters
firstNameStringNoMin 2 characters
lastNameStringNoMin 2 characters
viewModeViewModeNoEMPLOYER or JOB_SEEKER
talentPoolIdStringNoAssign to company talent pool
cvTextStringNoCV content for parsing
sendInvitationBooleanNoSend verification email
desiredJobTitles[String]NoJob seeker preferences
preferredJobTypes[PreferredJobType]NoFULL_TIME, PART_TIME, etc.
expectedSalaryFloatNoExpected compensation

Behavior:

  • If user already exists, adds them to the specified talent pool
  • New users are created with isVerified: false, isActive: false
  • When talentPoolId is provided, creates a Talent record in the company's default pool
  • CV data is structured into 11 CV tables + CVSkill records
  • Email verification is non-blocking (failure does not rollback user creation)

User Updates

updateUser

Update profile fields for a user.

mutation UpdateUser($id: ID!, $input: UpdateUserInput!) {
updateUser(id: $id, input: $input) {
id
firstName
lastName
title
location
biography
viewMode
selectedCompanyId
updatedAt
}
}

Auth: JWT required.

Security

The isSuperAdmin field is automatically stripped from update inputs to prevent privilege escalation. Setting email checks uniqueness against existing users.

Update behavior:

  • undefined fields are ignored (not updated)
  • null values explicitly clear a field
  • Emits user.profile.updated domain event on success

Account Deletion

deleteMyAccount -- Self-Deletion

Any authenticated user can soft-delete their own account.

mutation DeleteMyAccount($input: DeleteMyAccountInput!) {
deleteMyAccount(input: $input) {
success
message
}
}
FieldTypeRequiredDescription
confirmDeletionBooleanYesMust be true
reasonStringNoOptional reason for audit log

Behavior:

  • Always soft delete (isActive = false), data preserved
  • SuperAdmin users cannot self-delete via this mutation
  • Emits user.account.deleted event

Admin Deletion

Use the Admin APIadminDeleteUser and adminBulkDeleteUsers (both @RequireSuperAdmin) — for staff-driven deletion. They support soft delete (isActive = false, data preserved) and hard delete (transactional cascade with S3 cleanup).

Removed mutation (SUP-00164)

A consumer-facing removeUser(id: ID!) mutation used to live on this page. It was guarded only by JwtAuthGuard — any authenticated user could pass any other user's id and trigger the full hard-delete cascade (CVs + S3 files, candidates, meetings, UCRs, notifications, the User row; FK-nulls on Talent / Invoice / InterviewBookingAttendee). The frontend never actually called it, so it was deleted from the schema rather than locked down — adminDeleteUser already covers the staff path and deleteMyAccount covers self-deletion.

Calling mutation { removeUser(id: "...") } against api.aiqlick.com now returns Cannot query field "removeUser" on type "Mutation". Same change of class as removeUserCompanyRole (SUP-00163), which was hardened rather than removed because it has live callers.


Company Membership

Company Talent Queries

query GetCompanyTalents($companyId: String!) {
companyTalents(companyId: $companyId) {
id
user {
id email firstName lastName
title expectedSalary
}
talentPool { id name }
createdAt
}
}
query GetCompanyTalentPools($companyId: String!) {
talentPoolsByCompany(companyId: $companyId) {
id
name
description
talents {
id
user { firstName lastName email }
}
}
}

Profile Sharing

External profile sharing uses UUID-based tokens with configurable expiration (default 7 days).

OperationTypeDescription
createUserProfileExternalShareMutationCreate a public shareable link for a user profile
createUserProfileCompanyShareMutationShare a user profile with a specific company
createUserProfileUserShareMutationShare a user profile with a specific user
getUserProfileByPublicToken(token: String!)QueryAccess shared profile via public token
removeUserProfileExternalShare(id: String!)MutationRemove an external share link
removeUserProfileCompanyShare(id: String!)MutationRemove a company share
removeUserProfileUserShare(id: String!)MutationRemove a user share

Share tokens are double-UUID format, track access count, and can be deactivated without deletion.


Error Handling

All user operations return structured GraphQL errors:

{
"message": "User not found",
"extensions": {
"code": "USER_NOT_FOUND",
"statusCode": 404,
"correlationId": "uuid",
"timestamp": "2026-01-15T10:30:00Z"
}
}
Error CodeHTTPWhen
USER_NOT_FOUND404User ID does not exist
USER_ALREADY_EXISTS409Duplicate email on create/update
INVALID_CREDENTIALS401Wrong password on login
EMAIL_NOT_VERIFIED403Login before email verification
UNAUTHENTICATED401Missing or invalid JWT

Events Emitted

User operations emit domain events consumed by notification listeners and activity loggers:

EventTrigger
user.profile.updatedProfile fields changed
user.account.deletedAccount soft/hard deleted
user.email.verifiedEmail verification completed
user.password.reset.requestedPassword reset initiated
user.2fa.enabledTwo-factor authentication toggled
user.activation.invitation.sentVerification email sent