Skip to main content

Recruitment API

GraphQL operations for the recruitment domain -- talents, talent pools, CVs, candidates, jobs, pipelines, and match scores. All operations require JWT authentication unless marked as Public.

Talent Operations

Queries

OperationAuthReturnsDescription
talentsJWT[Talent!]List all talents visible to the user
talent(id)JWTTalent!Get a single talent by ID
talentByToken(token)PublicTalent!Get a talent via external share token
talentByUserAndCompany(userId, companyId)JWTTalentLook up a talent by user and company
getTalentsJWTGetTalentsResult!Paginated talent listing
myTalentsJWT[Talent!]Talents belonging to the current user
companyTalents(companyId)JWT[Talent!]All talents in a company
talentsByPool(talentPoolId)JWT[Talent!]Talents in a specific pool
highMatchTalentsForJob(jobId, minScore)JWT[Talent!]Talents above a match threshold for a job

Mutations

OperationDescription
addTalentCreate a new talent record
updateTalentUpdate talent fields
removeTalent / deleteTalentSoft-delete or hard-delete a talent
shareTalent / shareTalentWithUserShare a talent with a company or user
bulkShareTalents / bulkShareTalentsWithUserBatch share multiple talents
createTalentExternalShareGenerate external share link with token
revokeTalentExternalShareRevoke an external share link

Example: Add a Talent

mutation AddTalent($input: AddTalentInput!) {
addTalent(input: $input) {
id
userId
talentPoolId
createdAt
}
}

Talent Pool Operations

Queries

OperationAuthReturnsDescription
talentPoolsJWT[TalentPool!]All talent pools for the user
talentPool(id)JWTTalentPool!Single pool by ID
talentPoolByToken(token)PublicTalentPool!Pool via external share token
talentPoolsByCompany(companyId)JWT[TalentPool!]Pools belonging to a company
talentPoolsSharedWithMeJWT[TalentPoolUserShare!]Pools shared with the current user
recommendedTalents(talentPoolId, limit)JWT[RecommendedTalent!]AI-recommended talents for a pool

Mutations

OperationDescription
createTalentPoolCreate a new talent pool
updateTalentPoolUpdate pool name or settings
removeTalentPoolDelete a talent pool
createTalentPoolCompanyShareShare pool with another company
createTalentPoolExternalShareGenerate external share link

CV Operations

Queries

OperationAuthReturnsDescription
cvsJWT[CVType!]All CVs visible to the user
cv(id)JWTCVType!Single CV by ID
myCvsJWT[CVType!]CVs belonging to the current user
cvSkillsByCv(cvId)JWT[CVSkill!]Skills extracted from a CV

Mutations

OperationDescription
createCv / createMyCvUpload a new CV (company-scoped or personal)
updateCv / updateMyCvUpdate CV metadata
removeCv / deleteMyCvDelete a CV
setMyActiveCvSet the active CV for the current user's profile
info

CV parsing is handled by the background-tasks service. After uploading a CV document to S3, subscribe to the cvExtraction subscription on the AI service endpoint to receive parsed fields in real time.

Structured CV Mutations

Typed mutations for editing CV data directly on the 11 structured tables. These replace the deprecated updateCvSchema / updateMyCvSchema raw JSON mutations. All mutations require JWT authentication. Job seeker mutations verify CV ownership; company-scoped mutations verify write access via TalentAuthorizationService.

Profile & Job Preference (1:1 upsert)

OperationReturnsDescription
updateMyCvProfile(cvId, input)CvProfile!Upsert personal info, links, and bio
updateMyCvJobPreference(cvId, input)CvJobPreference!Upsert salary, job types, work site preference
deleteMyCvJobPreference(cvId)SuccessResponse!Remove job preferences from a CV

Work Experience (1:N CRUD)

OperationReturnsDescription
addMyCvWorkExperience(cvId, input)CvWorkExperience!Add a work experience entry
updateMyCvWorkExperience(id, input)CvWorkExperience!Update an existing work experience
removeMyCvWorkExperience(id)SuccessResponse!Remove a work experience entry

Education (1:N CRUD)

OperationReturnsDescription
addMyCvEducation(cvId, input)CvEducation!Add an education entry
updateMyCvEducation(id, input)CvEducation!Update an existing education
removeMyCvEducation(id)SuccessResponse!Remove an education entry

Certification (1:N CRUD)

OperationReturnsDescription
addMyCvCertification(cvId, input)CvCertification!Add a certification entry
updateMyCvCertification(id, input)CvCertification!Update an existing certification
removeMyCvCertification(id)SuccessResponse!Remove a certification entry

Language (1:N CRUD)

OperationReturnsDescription
addMyCvLanguage(cvId, input)CvLanguage!Add a language entry
updateMyCvLanguage(id, input)CvLanguage!Update an existing language
removeMyCvLanguage(id)SuccessResponse!Remove a language entry

Project (1:N CRUD)

OperationReturnsDescription
addMyCvProject(cvId, input)CvProject!Add a project entry
updateMyCvProject(id, input)CvProject!Update an existing project
removeMyCvProject(id)SuccessResponse!Remove a project entry

Publication (1:N CRUD)

OperationReturnsDescription
addMyCvPublication(cvId, input)CvPublication!Add a publication entry
updateMyCvPublication(id, input)CvPublication!Update an existing publication
removeMyCvPublication(id)SuccessResponse!Remove a publication entry

Award (1:N CRUD)

OperationReturnsDescription
addMyCvAward(cvId, input)CvAward!Add an award entry
updateMyCvAward(id, input)CvAward!Update an existing award
removeMyCvAward(id)SuccessResponse!Remove an award entry

Volunteer (1:N CRUD)

OperationReturnsDescription
addMyCvVolunteer(cvId, input)CvVolunteer!Add a volunteer entry
updateMyCvVolunteer(id, input)CvVolunteer!Update an existing volunteer entry
removeMyCvVolunteer(id)SuccessResponse!Remove a volunteer entry

CV Skill — Job Seeker (ownership check)

OperationReturnsDescription
addMyCvSkill(cvId, input)CVSkill!Add a skill (finds or creates Skill record by name, case-insensitive)
updateMyCvSkill(id, input)CVSkill!Update proficiency or years of experience
removeMyCvSkill(id)SuccessResponse!Remove a skill from the CV

CV Skill — Company-Scoped (write access check)

OperationReturnsDescription
addCvSkill(cvId, input)CVSkill!Add a skill to any CV the user has company write access to
updateCvSkill(id, input)CVSkill!Update proficiency or years (company access)
removeCvSkill(id)SuccessResponse!Remove a skill (company access)

Input types:

input CreateCvSkillInput {
skillName: String! # Backend finds or creates the Skill record (case-insensitive)
proficiency: String # NONE | BASIC | INTERMEDIATE | ADVANCED | EXPERT
yearsExperience: Int
}

input UpdateCvSkillInput {
proficiency: String
yearsExperience: Int
}

Company-Scoped CV Mutations (All Sections)

Company-scoped mutations for all CV sections, not just skills. These verify write access via TalentAuthorizationService.checkWriteAccessToCV() — the user must own the CV or have write access to the talent (via company talent pool membership or sharing with write permission).

Authorization

Company-scoped mutations use the same input types and logic as the "My" variants. The only difference is the access control check — ownership vs. company write access.

Profile & Job Preference — Company-Scoped

OperationReturnsDescription
updateCvProfile(cvId, input)CvProfile!Upsert profile (company access)
updateCvJobPreference(cvId, input)CvJobPreference!Upsert job preferences (company access)
deleteCvJobPreference(cvId)SuccessResponse!Remove job preferences (company access)

Work Experience — Company-Scoped

OperationReturnsDescription
addCvWorkExperience(cvId, input)CvWorkExperience!Add work experience (company access)
updateCvWorkExperience(id, input)CvWorkExperience!Update work experience (company access)
removeCvWorkExperience(id)SuccessResponse!Remove work experience (company access)

Education — Company-Scoped

OperationReturnsDescription
addCvEducation(cvId, input)CvEducation!Add education entry (company access)
updateCvEducation(id, input)CvEducation!Update education entry (company access)
removeCvEducation(id)SuccessResponse!Remove education entry (company access)

Certification — Company-Scoped

OperationReturnsDescription
addCvCertification(cvId, input)CvCertification!Add certification (company access)
updateCvCertification(id, input)CvCertification!Update certification (company access)
removeCvCertification(id)SuccessResponse!Remove certification (company access)

Language — Company-Scoped

OperationReturnsDescription
addCvLanguage(cvId, input)CvLanguage!Add language (company access)
updateCvLanguage(id, input)CvLanguage!Update language (company access)
removeCvLanguage(id)SuccessResponse!Remove language (company access)

Project — Company-Scoped

OperationReturnsDescription
addCvProject(cvId, input)CvProject!Add project (company access)
updateCvProject(id, input)CvProject!Update project (company access)
removeCvProject(id)SuccessResponse!Remove project (company access)

Publication — Company-Scoped

OperationReturnsDescription
addCvPublication(cvId, input)CvPublication!Add publication (company access)
updateCvPublication(id, input)CvPublication!Update publication (company access)
removeCvPublication(id)SuccessResponse!Remove publication (company access)

Award — Company-Scoped

OperationReturnsDescription
addCvAward(cvId, input)CvAward!Add award (company access)
updateCvAward(id, input)CvAward!Update award (company access)
removeCvAward(id)SuccessResponse!Remove award (company access)

Volunteer — Company-Scoped

OperationReturnsDescription
addCvVolunteer(cvId, input)CvVolunteer!Add volunteer entry (company access)
updateCvVolunteer(id, input)CvVolunteer!Update volunteer entry (company access)
removeCvVolunteer(id)SuccessResponse!Remove volunteer entry (company access)

Naming Convention

ContextPatternExample
Job seeker (owns CV)addMyCv{Section} / updateMyCv{Section} / removeMyCv{Section}addMyCvEducation
Company (write access)addCv{Section} / updateCv{Section} / removeCv{Section}addCvEducation

Example: Company Adds Education to Candidate CV

mutation AddCvEducation($cvId: ID!, $input: CreateCvEducationInput!) {
addCvEducation(cvId: $cvId, input: $input) {
id
degree
institution
startDate
endDate
graduationYear
location
}
}

Frontend Integration

All company-scoped mutation definitions are exported from graphql/operations/cv/structuredMutations.ts:

import {
// Company-scoped (employer editing candidate/talent CVs)
UPDATE_CV_PROFILE,
UPDATE_CV_JOB_PREFERENCE, DELETE_CV_JOB_PREFERENCE,
ADD_CV_WORK_EXPERIENCE, UPDATE_CV_WORK_EXPERIENCE, REMOVE_CV_WORK_EXPERIENCE,
ADD_CV_EDUCATION, UPDATE_CV_EDUCATION, REMOVE_CV_EDUCATION,
ADD_CV_CERTIFICATION, UPDATE_CV_CERTIFICATION, REMOVE_CV_CERTIFICATION,
ADD_CV_LANGUAGE, UPDATE_CV_LANGUAGE, REMOVE_CV_LANGUAGE,
ADD_CV_PROJECT, UPDATE_CV_PROJECT, REMOVE_CV_PROJECT,
ADD_CV_PUBLICATION, UPDATE_CV_PUBLICATION, REMOVE_CV_PUBLICATION,
ADD_CV_AWARD, UPDATE_CV_AWARD, REMOVE_CV_AWARD,
ADD_CV_VOLUNTEER, UPDATE_CV_VOLUNTEER, REMOVE_CV_VOLUNTEER,
ADD_CV_SKILL, UPDATE_CV_SKILL, REMOVE_CV_SKILL,
} from "@graphql/cv/structuredMutations"

Use the "My" variants when the job seeker is editing their own CV. Use the company-scoped variants (no "My" prefix) when an employer is editing a candidate or talent CV they have write access to.

The useStructuredCvMutations hook handles both modes automatically:

import { useStructuredCvMutations } from "@hooks/useStructuredCvMutations"

// Job seeker editing own CV
const { saveStructuredCv, saving } = useStructuredCvMutations()

// Employer editing candidate/talent CV
const { saveStructuredCv, saving } = useStructuredCvMutations({ companyScoped: true })
Events

All structured CV mutations (both job seeker and company-scoped) emit a cv.updated event after the transaction commits, via the @EmitCvUpdatedAfter method decorator on CvStructuredMutationService. This triggers match score invalidation so job-candidate matching scores are recalculated within the next ~5-second worker cycle whenever CV data changes.

Example: Update Profile

mutation UpdateMyCvProfile($cvId: ID!, $input: UpdateCvProfileInput!) {
updateMyCvProfile(cvId: $cvId, input: $input) {
id
firstName
lastName
email
phone
linkedinUrl
biography
currentRole
}
}

Example: Add Work Experience

mutation AddMyCvWorkExperience($cvId: ID!, $input: CreateCvWorkExperienceInput!) {
addMyCvWorkExperience(cvId: $cvId, input: $input) {
id
jobTitle
companyName
startDate
endDate
isCurrent
technologies
achievements
}
}
tip

Each typed mutation writes directly to the normalized structured CV tables. Prefer these typed mutations over the deprecated updateCvSchema / updateMyCvSchema raw JSON mutations.

Candidate Operations

Candidates represent a talent applied or added to a specific job. They are tracked through a pipeline with statuses: NEW -> IN_REVIEW -> INTERVIEW -> APPROVED / REJECTED / WITHDRAWN.

Queries

OperationAuthReturnsDescription
candidatesJWT[Candidate!]All candidates
candidate(id)JWTCandidate!Single candidate
candidateDetailed(id)JWTCandidate!Candidate with expanded relations
companyCandidates(companyId)JWTCompanyCandidatesResponse!All candidates for a company
myApplicationsJWT[Candidate!]Current user's job applications
candidateNotesByCandidate(candidateId)JWT[CandidateNote!]Notes on a candidate
candidateDetailed — snapshot-only read path (SUP-00049)

When building the employer-facing candidate view, fetch CV/preference data from candidate.cv (the application snapshot). Do not add talent.activeCV { ... } or the live preference fields on user (expectedSalary, salaryCurrency, salaryFrequency, preferredJobTypes, preferredWorkSite, preferredLocation, desiredJobTitles, willingToRelocate, availableIn) — those bleed the seeker's live edits through the frontend fallback chains, which is exactly what the snapshot is meant to prevent. See Candidate Management — Frontend read path for the full rule and rationale.

Mutations

OperationDescription
createCandidateAdd a candidate to a job
addTalentAsCandidateConvert a talent into a candidate for a job
applyAsCandidateJob seeker self-application
updateCandidateUpdate candidate status or fields
withdrawCandidateWithdraw a candidate from a job
notifyCandidateManually send notification/email to any candidate in a pipeline
createCandidateNoteAdd a note to a candidate
exportCompanyCandidatesExport candidates as a file

Candidate Fields

The Candidate type includes a convenience responsibleUser field that resolves the responsible user from the associated job:

type Candidate {
id: ID!
userId: ID!
jobId: ID!
source: CandidateSource!
status: CandidateStatus!
talentId: ID!
cvId: ID
# ... other fields
responsibleUser: User # Resolved from job.responsibleUser
user: User
job: Job
talent: Talent
cv: CV
matchScore: Float # Numeric score (0-100)
matchReport: MatchScore # Full match report with details JSON
interviews: [Interview]
notes: [CandidateNote]
}
tip

responsibleUser is a convenience field that reads from candidate.job.responsibleUser. It avoids the need to traverse the job relation in frontend queries.

Example: Add Talent as Candidate

mutation AddTalentAsCandidate($input: AddTalentAsCandidateInput!) {
addTalentAsCandidate(input: $input) {
id
talent { id firstName lastName }
job { id title }
responsibleUser { id firstName lastName }
status
createdAt
}
}

Job Operations

Queries

OperationAuthReturnsDescription
opportunities / allJobsJWT[Job!]List jobs
job(id)JWTJob!Single job by ID
publicJob(id) / publicJobsPublicJob! / [Job!]Public job listings — hide flags applied by the public serializer (SUP-00121/122/125/126/106)
contactsByLabel(label)JWT[Contact!]!Contacts on the caller's company filtered by a ContactType label (e.g. "Client", "Partner"). Used by the Job form's Hiring Company and Supplied By dropdowns (SUP-00121/122).
getJobMatchScores(jobId)JWT[UserJobMatchScore!]Match scores for all candidates on a job
getUserJobMatchScore(jobId, userId)JWTUserJobMatchScoreMatch score for a specific user-job pair

Mutations

OperationDescription
createJobCreate a new job posting. dueDate rejects past dates via @IsFutureDate (SUP-00051). Accepts hideHiringCompany/hideSuppliedBy/hideRateOut Boolean flags (SUP-00121/122/125/126/106).
updateJobUpdate job fields or status. Rejects IN_PROGRESS → NEW when dueDate is already past (SUP-00129).
removeJobSoft-archive a job — sets Job.archivedAt instead of hard-deleting; emits job.archived event (SUP-00083). Pipeline items / candidates / skill requirements are preserved.
duplicateJobClone an existing job
updateJobInterviewSettingsConfigure interview preferences for a job

Example: Create a Job

mutation CreateJob($input: CreateJobInput!) {
createJob(input: $input) {
id
title
status
# SUP-00125/126/106 — public-view visibility toggles
hideHiringCompany
hideSuppliedBy
hideRateOut
archivedAt # null unless removeJob was called (SUP-00083)
company { id name }
createdAt
}
}
Public view masking (SUP-00121/122/125/126/106)

When a job is fetched via the public jobUnrestricted / allJobs resolvers, a public-job.serializer.ts applies the hide* flags:

  • hideHiringCompany=truehiringCompanyContactId is nulled and hiringCompanyId is rewritten to the posting company's id (external viewers see "Posted by <your company>")
  • hideSuppliedBy=truesuppliedById / suppliedByContactId are nulled
  • hideRateOut=truerateOut / rateOutFrequency / rateOutCurrency are nulled

The underlying Job row is never mutated — authenticated company queries (opportunities, job(id)) still return the complete record.

Pipeline Operations

Pipelines are Kanban-style workflows for tracking candidates through recruitment stages.

Queries

OperationAuthReturnsDescription
pipelines / companyPipelinesJWT[Pipeline!]List pipelines
pipeline(id)JWTPipeline!Single pipeline
pipelineItemsByPipeline(pipelineId)JWT[PipelineItem!]Items in a pipeline
pipelineItemsByJob(jobId)JWT[PipelineItem!]Pipeline items for a job
pipelineStatusTransitions(currentStatus)JWTPipelineStatusTransitionInfo!Valid next statuses
getPipelineByPublicToken(token)PublicPipelineExternalShare!Pipeline via share token

Mutations

OperationDescription
createPipelineCreate a new pipeline
updatePipelineUpdate pipeline settings
removePipelineDelete a pipeline
createPipelineItem / updatePipelineItemAdd or move an item in a pipeline
addJobToPipeline / addJobToMultiplePipelinesLink a job to one or more pipelines
sharePipelineWithCollaboratorShare a pipeline with a collaborator

Talent Notes

Internal notes on talent profiles, independent of job candidacy. See Talent Notes for full details.

Queries

OperationAuthReturnsDescription
talentNotes(talentId)JWT[TalentNote!]All notes for a talent, ordered by createdAt DESC

Mutations

OperationDescription
createTalentNote(input: { talentId, title?, content })Create a note on a talent profile
updateTalentNote(input: { id, title?, content? })Update a note (author only)
deleteTalentNote(id)Delete a note (author only)

Match Score Queries

Match scores are computed by the background-tasks matching engine and stored for lookup.

OperationAuthReturnsDescription
matchScore(id)JWTMatchScore!Single match score
matchScoresByJob(jobId)JWT[MatchScore!]All match scores for a job
matchScoresByCv(cvId)JWT[MatchScore!]All match scores for a CV

The MatchScore type includes a details JSON field containing the full AI-generated report:

type MatchScore {
id: ID!
jobId: ID!
cvId: ID!
score: Float! # 0-100
details: JSONObject # { basicScores, detailedReport, timestamp, version }
createdAt: DateTime!
job: Job
cv: CV
}

The details.detailedReport object contains: strengths[], gaps[], recommendations[], scoring_breakdown, skill_analysis (matched/missing/transferable skills), experience_analysis, fit_assessment (hiring recommendation, technical readiness, growth potential).

tip

Use candidateDetailed(id).matchReport to fetch the full MatchScore object (including details) directly from a candidate, instead of looking up the score separately by job + CV.

warning

Match scores are generated asynchronously by background-tasks. They may not exist immediately after creating a candidate. Use the userJobMatching or candidateJobMatching subscriptions on the AI service endpoint to trigger and monitor matching in real time.