Me namespace

Reference for the signed-in user, candidate profile, employer tools, alerts, applications, messaging, and access.

board.me contains the candidate and employer operations performed as the current board user. With two documented token-authorized exceptions, every method requires a bearer access token.

Do not put these responses in a shared public cache. In SSR applications, keep a module-scoped nostore client and pass the current request’s authorization in the method’s final FetchOptions argument.

ts
const privateOptions = {
cache: 'no-store' as const,
headers: { authorization: `Bearer ${accessToken}` },
};
const user = await board.me.retrieve(undefined, privateOptions);

The undefined preserves the empty query slot in methods shaped as (query?, options?). Methods without a query slot accept options directly.

Identity

Method and signatureReturnsBehavior
board.me.retrieve(query?: Record<string, never>, options?: FetchOptions)Promise<BoardUser>Returns the authenticated user’s board identity.
board.me.delete(options?: FetchOptions)Promise<void>Permanently deletes the account and its dependent profile, collections, saved jobs, alerts, avatar, and resume data.

Account deletion is synchronous, irreversible, and resolves after a 204 response. Do not present it as a reversible sign-out action.

Candidate profile

Profile updates use merge-patch semantics: omitted fields remain unchanged. Collection create/update methods return the affected entity; delete methods resolve void.

Method and signatureReturns
board.me.profile.retrieve(query?: Record<string, never>, options?: FetchOptions)Promise<CandidateProfile>
board.me.profile.update(body: UpdateCandidateProfileBody, query?: Record<string, never>, options?: FetchOptions)Promise<CandidateProfile>
board.me.profile.handleAvailable(handle: string, options?: FetchOptions)Promise<HandleAvailability>
board.me.profile.listExperience(options?: FetchOptions)Promise<ListEnvelope<CandidateExperience>>
board.me.profile.createExperience(body: CreateExperienceBody, options?: FetchOptions)Promise<CandidateExperience>
board.me.profile.updateExperience(id: string, body: UpdateExperienceBody, options?: FetchOptions)Promise<CandidateExperience>
board.me.profile.deleteExperience(id: string, options?: FetchOptions)Promise<void>
board.me.profile.listEducation(options?: FetchOptions)Promise<ListEnvelope<CandidateEducation>>
board.me.profile.createEducation(body: CreateEducationBody, options?: FetchOptions)Promise<CandidateEducation>
board.me.profile.updateEducation(id: string, body: UpdateEducationBody, options?: FetchOptions)Promise<CandidateEducation>
board.me.profile.deleteEducation(id: string, options?: FetchOptions)Promise<void>
board.me.profile.listSkills(options?: FetchOptions)Promise<ListEnvelope<CandidateSkill>>
board.me.profile.updateSkills(body: UpdateSkillsBody, options?: FetchOptions)Promise<ListEnvelope<CandidateSkill>>
board.me.profile.listLanguages(options?: FetchOptions)Promise<ListEnvelope<CandidateLanguage>>
board.me.profile.updateLanguages(body: UpdateLanguagesBody, options?: FetchOptions)Promise<ListEnvelope<CandidateLanguage>>
board.me.profile.uploadAvatar(file: Blob, options?: FetchOptions)Promise<CandidateAvatar>

handleAvailable is advisory. A later profile.update rechecks the handle and can still throw candidate_handle_taken after another user claims it.

updateSkills and updateLanguages replace their ordered sets rather than merging individual entries. uploadAvatar accepts JPEG, PNG, or WebP up to 5 MB and builds the multipart body for you.

ts
const profile = await board.me.profile.update(
{ headline: 'Staff engineer' },
undefined,
privateOptions,
);
const experience = await board.me.profile.createExperience(
{
companyName: 'Analytical Engines',
title: 'Programmer',
startDate: '2024-01',
},
privateOptions,
);
await board.me.profile.updateExperience(
experience.id,
{ title: 'Senior programmer' },
privateOptions,
);

Use the installed TypeScript types for the complete optional profile and collection fields; they are generated from the same API schemas as the SDK.

Notification preferences

Method and signatureReturnsAuthentication
board.me.notificationPreferences.retrieve(options?: FetchOptions)Promise<ListEnvelope<NotificationPreference>>Bearer required.
board.me.notificationPreferences.update(body: UpdateNotificationPreferenceBody, options?: FetchOptions)Promise<ListEnvelope<NotificationPreference>>Bearer required; returns the complete updated set.
board.me.notificationPreferences.unsubscribeWithToken(body: UnsubscribeBody, options?: FetchOptions)Promise<void>The signed email token authorizes the request; no session required.

unsubscribeWithToken is intended for one-click email links and resolves after a 204 response.

Employer companies and claims

A board user becomes an employer by opening or receiving a company membership. An approved membership is required for company editing, job management, billing options, and the applicant pipeline.

Method and signatureReturns
board.me.companies.search(query: EmployerCompanySearchQuery, options?: FetchOptions)Promise<ListEnvelope<ClaimableCompany>>
board.me.companies.list(options?: FetchOptions)Promise<ListEnvelope<CompanyMembership>>
board.me.companies.create(body: CreateCompanyBody, options?: FetchOptions)Promise<CompanyMembership>
board.me.companies.claim(slug: string, options?: FetchOptions)Promise<CompanyMembership>
board.me.companies.cancelClaim(slug: string, options?: FetchOptions)Promise<void>
board.me.companies.update(slug: string, body: UpdateEmployerCompanyBody, options?: FetchOptions)Promise<EmployerCompany>
board.me.companies.workEmail.verify(slug: string, body: SendWorkEmailBody, options?: FetchOptions)Promise<CompanyMembership>
board.me.companies.workEmail.confirm(slug: string, body: ConfirmWorkEmailBody, options?: FetchOptions)Promise<CompanyMembership>

companies.search requires { q } and accepts limit from 1–50. create can adopt an existing same-domain company. claim can return an approved membership immediately on an email-domain match; otherwise it remains pending.

workEmail.verify sends a 24-hour verification link for a pending claim. workEmail.confirm uses the token in that link as authorization and does not require a bearer session.

Employer jobs and billing

Every job method is scoped by company slug. New employer jobs are held drafts. publish only republishes a job still inside its paid window; a held or expired draft can require checkout.

Method and signatureReturns
board.me.companies.jobs.list(slug: string, query?: EmployerJobsListQuery, options?: FetchOptions)Promise<ListEnvelope<EmployerJobSummary>>
board.me.companies.jobs.retrieve(slug: string, id: string, options?: FetchOptions)Promise<EmployerJob>
board.me.companies.jobs.create(slug: string, body: CreateEmployerJobBody, options?: FetchOptions)Promise<EmployerJob>
board.me.companies.jobs.update(slug: string, id: string, body: UpdateEmployerJobBody, options?: FetchOptions)Promise<EmployerJob>
board.me.companies.jobs.delete(slug: string, id: string, options?: FetchOptions)Promise<void>
board.me.companies.jobs.publish(slug: string, id: string, options?: FetchOptions)Promise<EmployerJob>
board.me.companies.jobs.unpublish(slug: string, id: string, options?: FetchOptions)Promise<EmployerJob>
board.me.companies.jobs.checkout(slug: string, id: string, body: EmployerCheckoutBody, options?: FetchOptions)Promise<EmployerCheckout>
board.me.companies.billingOptions.list(slug: string, options?: FetchOptions)Promise<ListEnvelope<EmployerBillingOption>>

checkout can resolve with status: 'checkout' and a hosted payment URL, status: 'published' when a free/bundle/subscription option publishes immediately, or status: 'invoice_sent'. Branch on the returned status instead of assuming every call needs a redirect.

ts
const draft = await board.me.companies.jobs.create(
'analytical-engines',
{
title: 'Staff engineer',
description: 'Build systems for a growing team.',
applicationUrl: 'https://example.com/apply',
},
privateOptions,
);
const result = await board.me.companies.jobs.checkout(
'analytical-engines',
draft.id,
{ billing: { type: 'new', planId } },
privateOptions,
);
if (result.status === 'checkout' && result.checkoutUrl) {
window.location.assign(result.checkoutUrl);
}

Applicants and pipeline stages

The applicant read returns the job header, stage rail, applicants, signed resume links, and activity timelines in one EmployerPipeline. Pipeline mutations resolve void; re-read applicants.list for fresh state.

Method and signatureReturns
board.me.companies.applicants.list(slug: string, query: EmployerPipelineQuery, options?: FetchOptions)Promise<EmployerPipeline>
board.me.companies.applicants.move(slug: string, applicationId: string, body: MoveApplicantStageBody, options?: FetchOptions)Promise<void>
board.me.companies.applicants.bulkMove(slug: string, body: BulkMoveApplicantsBody, options?: FetchOptions)Promise<void>
board.me.companies.applicants.bulkReject(slug: string, body: BulkRejectApplicantsBody, options?: FetchOptions)Promise<void>
board.me.companies.applicants.addNote(slug: string, applicationId: string, body: AddApplicantNoteBody, options?: FetchOptions)Promise<void>
board.me.companies.pipelineStages.create(slug: string, body: CreatePipelineStageBody, options?: FetchOptions)Promise<void>
board.me.companies.pipelineStages.update(slug: string, stageId: string, body: UpdatePipelineStageBody, options?: FetchOptions)Promise<void>
board.me.companies.pipelineStages.remove(slug: string, stageId: string, options?: FetchOptions)Promise<void>
board.me.companies.pipelineStages.reorder(slug: string, body: ReorderPipelineStagesBody, options?: FetchOptions)Promise<void>

applicants.list requires { job: jobId } and accepts an optional stage filter. Bulk moves are atomic for applicants of one job. Company-private notes appear in the applicant timeline.

ts
const pipeline = await board.me.companies.applicants.list(
'analytical-engines',
{ job: jobId },
privateOptions,
);
const firstApplicant = pipeline.applicants[0];
if (firstApplicant) {
await board.me.companies.applicants.move(
'analytical-engines',
firstApplicant.id,
{ stageId: interviewStageId },
privateOptions,
);
}

Custom stages are limited to ten per job. Protected system stages cannot be removed, and a reorder must include each stage exactly once with the protected terminal order preserved.

Signed-in job alerts

Authenticated alerts are active immediately and do not use the anonymous double-opt-in flow. A user can have up to ten; removing an alert is the only pause mechanism.

Method and signatureReturns
board.me.alerts.list(options?: FetchOptions)Promise<ListEnvelope<Alert>>
board.me.alerts.create(body: AlertBody, options?: FetchOptions)Promise<Alert>
board.me.alerts.retrieve(alertId: string, options?: FetchOptions)Promise<Alert>
board.me.alerts.update(alertId: string, body: AlertBody, options?: FetchOptions)Promise<Alert>
board.me.alerts.remove(alertId: string, options?: FetchOptions)Promise<void>

update replaces the alert’s filters and frequency in full. Check board.context().features.jobAlerts before exposing the route, while still handling job_alerts_disabled from the API.

Applications, resume, and saved jobs

Applications are keyed by application ID. Applying to a job itself lives at board.jobs.apply; this namespace manages applications already submitted by the current candidate.

Method and signatureReturns
board.me.applications.list(query?: ApplicationsListQuery, options?: FetchOptions)Promise<ListEnvelope<Application>>
board.me.applications.retrieve(applicationId: string, options?: FetchOptions)Promise<Application>
board.me.applications.updateFacts(applicationId: string, body: UpdateApplicationFactsBody, options?: FetchOptions)Promise<Application>
board.me.applications.withdraw(applicationId: string, options?: FetchOptions)Promise<void>
board.me.resume.upload(file: Blob, opts?: ResumeUploadOptions, options?: FetchOptions)Promise<Resume>
board.me.resume.retrieve(options?: FetchOptions)Promise<Resume>
board.me.resume.delete(options?: FetchOptions)Promise<void>
board.me.savedJobs.list(query?: SavedJobsListQuery, options?: FetchOptions)Promise<ListEnvelope<SavedJob>>
board.me.savedJobs.save(body: SaveJobBody, query?: Record<string, never>, options?: FetchOptions)Promise<SavedJob>
board.me.savedJobs.unsave(jobId: string, query?: Record<string, never>, options?: FetchOptions)Promise<void>

updateFacts is a merge-patch and is accepted only while the application is editable. withdraw permanently removes the application.

Resume upload creates multipart data internally and starts asynchronous parsing. opts accepts keepResumeOnFile, importMode: 'append_only' | 'replace_all', and confirmReplaceAll. Poll resume.retrieve until parseStatus is parsed or failed, then re-read the profile collections. Deleting the stored resume does not remove fields already imported into the profile.

ts
const uploaded = await board.me.resume.upload(
file,
{ keepResumeOnFile: true, importMode: 'append_only' },
privateOptions,
);
async function waitForResume() {
for (let attempt = 0; attempt < 30; attempt += 1) {
const resume = await board.me.resume.retrieve(privateOptions);
if (resume.parseStatus !== 'parsing') return resume;
await new Promise((resolve) => setTimeout(resolve, 2_000));
}
throw new Error('Resume parsing did not finish within one minute');
}
const parsed =
uploaded.parseStatus === 'parsing' ? await waitForResume() : uploaded;
await board.me.savedJobs.save(
{ jobId },
undefined,
privateOptions,
);
console.log(parsed.parseStatus);

Saving an already-saved job converges on the existing row. Unsaving an unknown job ID still resolves 204.

Conversations and messages

Messaging is polled REST, not a realtime transport. Poll the conversation list, unread count, or current thread at a bounded interval—the reference cadence is 3–5 seconds—and cancel polling when the view is inactive.

Method and signatureReturns
board.me.conversations.list(query?: ConversationsListQuery, options?: FetchOptions)Promise<ListEnvelope<Conversation>>
board.me.conversations.unreadCount(options?: FetchOptions)Promise<UnreadCount>
board.me.conversations.retrieve(id: string, options?: FetchOptions)Promise<ConversationDetail>
board.me.conversations.listMessages(id: string, query?: ThreadMessagesQuery, options?: FetchOptions)Promise<ListEnvelope<Message>>
board.me.conversations.start(body: StartConversationBody, options?: FetchOptions)Promise<Message>
board.me.conversations.startAboutApplication(body: StartAboutApplicationBody, options?: FetchOptions)Promise<Message>
board.me.conversations.reply(id: string, body: ReplyBody, options?: FetchOptions)Promise<Message>
board.me.conversations.markRead(id: string, options?: FetchOptions)Promise<ReadReceipt>
board.me.conversations.archive(id: string, options?: FetchOptions)Promise<ConversationArchive>
board.me.conversations.unarchive(id: string, options?: FetchOptions)Promise<ConversationArchive>
board.me.conversations.findExisting(query: FindExistingConversationQuery, options?: FetchOptions)Promise<ConversationRef>
board.me.messages.edit(id: string, body: EditMessageBody, options?: FetchOptions)Promise<Message>
board.me.messages.unsend(id: string, options?: FetchOptions)Promise<Message>
board.me.messages.report(id: string, body: ReportBody, options?: FetchOptions)Promise<ModerationReport>

list({ archived: true }) returns the archived view. Thread messages are oldest-first; an unsent message remains as a tombstone with an empty body and deletedAt set.

start is the employer-to-candidate cold-message flow and converges on an existing thread. startAboutApplication contacts an applicant in application context. Message edit and unsend are limited to the author’s 15-minute window. Reporting a message addressed to the current user also blocks its author.

ts
const { data: messages } = await board.me.conversations.listMessages(
conversationId,
{ limit: 50 },
privateOptions,
);
const sent = await board.me.conversations.reply(
conversationId,
{ body: 'Thanks—let’s arrange a call.' },
privateOptions,
);
console.log(messages.length, sent.id);

Blocks

Method and signatureReturns
board.me.blocks.list(options?: FetchOptions)Promise<ListEnvelope<BlockedUser>>
board.me.blocks.create(body: BlockUserBody, options?: FetchOptions)Promise<Block>
board.me.blocks.remove(boardUserId: string, options?: FetchOptions)Promise<void>
board.me.blocks.status(boardUserId: string, options?: FetchOptions)Promise<BlockStatus>

Creating and removing a block are idempotent. Blocking is silent; it changes the messaging relationship but does not notify the blocked user.

Candidate job access

Enumerate public offers with board.paywall.offers, then use the authenticated board.me.access methods to purchase and confirm an entitlement. Expose this flow only when board.context().features.candidatePaywall is true. Starting checkout also requires a candidate profile.

Method and signatureReturns
board.me.access.checkout(body: AccessCheckoutBody, options?: FetchOptions)Promise<AccessCheckoutSession>
board.me.access.retrieveCheckout(sessionId: string, options?: FetchOptions)Promise<AccessCheckoutSessionState>
board.me.access.grant(options?: FetchOptions)Promise<AccessGrant>
board.me.access.portal(body?: AccessPortalBody, options?: FetchOptions)Promise<AccessPortalSession>

checkout returns the connected-account mount values needed by Stripe.js. It does not load Stripe.js or mount the checkout UI. returnPath is a safe relative path.

Poll retrieveCheckout until the state is complete or expired; an open session can be remounted with its client secret. After completion, call grant again. No entitlement is represented by a successful { hasAccess: false } response, not a 404.

portal is available for a recurring grant and returns the billing-portal URL. Its optional body accepts a return path.

Pagination, features, and errors

List methods return ListEnvelope<T> and accept their documented cursor/limit query where present. Keep all board.me reads and writes out of shared public caches, including lists that appear non-sensitive.

Before rendering optional account routes, check the matching board context feature—especially candidates, employers, jobAlerts, and candidatePaywall. Feature flags improve navigation; the API remains authoritative and can reject stale or unauthorized state.

Every non-2xx API response throws BoardApiError. Common branches include:

  • 401 for a missing or expired board-user token—enter the application’s explicit refresh/sign-in flow.
  • 403 for another user’s resource, an unapproved employer membership, messaging restrictions, or entitlement restrictions.
  • 404 for an ID that does not exist or does not belong to the current user.
  • 409 for state conflicts such as a handle already taken.
  • 429 for rate-limited messaging or other protected writes.

Domain codes give the precise branch: examples include candidate_handle_taken, applications_not_found, messaging_cold_rule, messaging_edit_window_expired, employer_not_member, employer_payment_required, employer_jobs_quota_exceeded, paywall_no_candidate_profile, and paywall_offer_not_found.

Do not turn an exception into an empty list. Log code and requestId without logging authorization, resume URLs, checkout secrets, or candidate data, then preserve unexpected errors for the framework’s error boundary.