Candidate accounts and talent
Build candidate profiles and resume import, then expose the permitted talent directory.
A
JBuild two deliberately separate experiences: a candidate’s private profile editor under board.me, and the board’s public or employer-only talent directory under board.talent.
Prerequisites
board.context().features.candidatesistruefor candidate accounts.- A server-owned board-user session for every
board.merequest. talentDirectoryVisibilitychecked before creating a talent route.
The examples expect a stateless server client and pass the access token per call.
Load and update the candidate profile
12345678910111213141516171819202122// src/data/candidate-profile.tsimport type { FetchOptions, UpdateCandidateProfileBody } from '@cavuno/board';import { board } from '../lib/board';function session(accessToken: string): FetchOptions {return {cache: 'no-store',headers: { authorization: `Bearer ${accessToken}` },};}export function loadCandidateProfile(accessToken: string) {return board.me.profile.retrieve(undefined, session(accessToken));}export function updateCandidateProfile(accessToken: string,input: UpdateCandidateProfileBody,) {return board.me.profile.update(input, undefined, session(accessToken));}
update is a merge patch: omitted fields remain unchanged. Check handle availability before submission for responsive feedback, but treat the update response as authoritative because availability can change.
Import a resume
Resume parsing is asynchronous. Upload once, then poll until parseStatus is parsed or failed.
12345678910111213141516171819export async function importResume(accessToken: string, file: Blob) {const options = session(accessToken);let resume = await board.me.resume.upload(file,{ keepResumeOnFile: true, importMode: 'append_only' },options,);while (resume.parseStatus === 'parsing') {await new Promise((resolve) => setTimeout(resolve, 1_500));resume = await board.me.resume.retrieve(options);}if (resume.parseStatus === 'failed') {throw new Error(resume.parseFailureReason ?? 'Resume parsing failed');}return board.me.profile.retrieve(undefined, options);}
Use replace_all only after an explicit confirmation. The SDK requires confirmReplaceAll: true for that destructive import mode.
Load the talent directory
12345678910111213export async function loadTalent(skill: string | undefined,employerAccessToken?: string,) {const context = await board.context();if (context.talentDirectoryVisibility === 'off') return null;return board.talent.list({ skill, limit: 20 },employerAccessToken ? session(employerAccessToken) : undefined,);}
Only public profiles appear. On an employers_only board, anonymous requests throw a 403 talent_directory_restricted; render an employer sign-in or upsell rather than pretending the directory is empty.
Expected result
The private account loader returns the signed-in candidate’s profile and parsed resume state. The talent loader returns only profiles visible to the current viewer, or no route when the directory is off.
Verify the result
- Candidate profile responses never enter a shared cache.
- A profile update returns the edited values from the server.
- Resume import reaches
parsedor shows the returned failure reason. - Hidden and logged-in-only profiles do not appear in anonymous talent reads.
- An employers-only directory distinguishes 403 restricted access from 404 disabled access.
Production cautions
- Stop polling when the request is aborted or the user leaves the page.
- Keep short-lived resume download URLs out of logs and durable caches.
- Do not expose
board.meprofile data through public loaders. - Treat
jobSearchStatus: nullin a talent profile as an intentional visibility outcome.
Next, let candidates act on jobs with Applications.