Me namespace
Reference for the signed-in user, candidate profile, employer tools, alerts, applications, messaging, and access.
A
Jboard.me contains the candidate and employer operations performed as the current board user. Token-authorized exceptions (confirmEmailChange, notification unsubscribe) do not require a bearer; every other method does. Work-email consume is board.auth.verifyWorkEmail, not a board.me method.
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.
The undefined preserves the empty query slot in methods shaped as (query?, options?). Methods without a query slot accept options directly.
Identity
| Method and signature | Returns | Behavior |
|---|---|---|
board.me.retrieve(query?: Record<string, never>, options?: FetchOptions) | Promise<BoardUser> | Returns the authenticated user’s board identity, including hasPassword. |
board.me.delete(options?: FetchOptions) | Promise<void> | Permanently deletes the account and its dependent profile, collections, saved jobs, alerts, avatar, and resume data. |
board.me.updatePassword(body: UpdatePasswordBody, options?: FetchOptions) | Promise<BoardAuthSession> | Changes the password and persists the returned bearer pair. Passwordless accounts return no_password. |
board.me.requestEmailChange(body: RequestEmailChangeBody, options?: FetchOptions) | Promise<void> | Mails a verification link to the new address. The email swaps only after confirm. |
board.me.confirmEmailChange(body: ConfirmEmailChangeBody, options?: FetchOptions) | Promise<void> | Consumes the email-change token. No session required. |
board.me.acceptInvite(body: AcceptCompanyMemberInviteBody, options?: FetchOptions) | Promise<CompanyMemberInviteAcceptance> | Accepts a company member invite for the signed-in board user. Session-gated. |
BoardUser.hasPassword is false for magic-link and OAuth-only accounts. Offer change-password only when it is true. Passwordless accounts should call board.auth.forgotPassword (the existing reset email) rather than updatePassword, which returns no_password.
Account deletion is synchronous, irreversible, and resolves after a 204 response. Do not present it as a reversible sign-out action. updatePassword keeps the caller signed in; every other session dies. Email change is verify-then-switch: requestEmailChange mails the new address and can return same_email or email_taken; confirmEmailChange consumes the token with no session.
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 signature | Returns |
|---|---|
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.retrieveCustomFields(options?: FetchOptions) | Promise<ProfileFieldValues> |
board.me.profile.updateCustomFields(body: UpdateProfileFieldValuesBody, options?: FetchOptions) | Promise<ProfileFieldValues> |
board.me.profile.retrieveObjectReferences(options?: FetchOptions) | Promise<ProfileObjectReferences> |
board.me.profile.updateObjectReferences(body: ReplaceProfileObjectReferencesBody, options?: FetchOptions) | Promise<ProfileObjectReferences> |
board.me.profile.listObjectReferenceChoices(fieldKey: string, query?: ProfileChoiceQuery, options?: FetchOptions) | Promise<ProfileChoiceList> |
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.
retrieveCustomFields returns the profile field definitions with the signed-in candidate's current values. updateCustomFields additively updates owner-editable values, so omitted fields remain unchanged. retrieveObjectReferences returns the editable collection definitions and resolved selections. listObjectReferenceChoices searches and pages the active records for one owner-editable collection field, including a private field; use its returned IDs when building a selection. updateObjectReferences replaces the complete editable selection set with body.selections; send an empty array to clear all editable selections.
Home location and commute distance
Job matches use where the candidate lives and how far they will commute. Pick the place with board.locations.search and send its id as locationId; the server looks up the place and its coordinates. The candidate's country is their home place's country: while a place is stored or sent, countryCode is that place's country and a countryCode you send is ignored (no error), so hide your country field then. countryCode only applies when there is no home place. Removing the place (locationId: null) keeps the last country. The profile returns:
| Field | Meaning |
|---|---|
locationPlace | The picked place: id, name, countryCode, region, city, and placeType (country, region, city, or locality). null when the candidate only has a free-text location. |
commuteRadiusKm | The candidate's commute distance in km, or null for the default. It applies only when placeType is city or locality. |
commuteRadiusDefaultKm | The distance used when commuteRadiusKm is null: 40 km (25 mi) for a US, GB, LR, or MM home, 50 km elsewhere. Use it to prefill the field. |
openToRelocate | Widens on-site and hybrid matches from the commute distance to the candidate's country and their work-permit countries. |
update accepts:
locationId: anidfromboard.locations.search. It also setslocationto the place name unless you sendlocationtoo. Sending the stored id again keeps the place; sendingnullremoves the place and keepslocation. ChanginglocationwithoutlocationIdremoves the place.commuteRadiusKm: 1–250 km, stored to one decimal place.nullresets it to the default.
An unknown locationId throws locations_invalid_id (400). locations_unavailable (503) means the lookup is temporarily down; keep the form values and let the candidate retry.
Hide the commute field when placeType is region or country: a region has no single point to measure from, so it matches on-site jobs anywhere in its country.
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 signature | Returns | Authentication |
|---|---|---|
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.
Channels are messageEmails, applicationEmails, and recommendedJobEmails. The first two default subscribed when no row exists. recommendedJobEmails is explicit opt-in: a missing row means unsubscribed. Each item includes subscribed and additive waitlisted. subscribed: true means they asked, including while queued; they receive mail only when subscribed is true and waitlisted is false. PUT at the Email subscribers cap waitlists instead of failing.
Marketing consent
Marketing consent is a property of the signed-in board user, separate from job alerts and notification preferences. The wording someone agrees to is yours — render your own disclosure copy beside any control that calls these methods; the API records the decision, never the prose. These helpers derive the person from the verified bearer and cannot target another email.
| Method and signature | Returns | Behavior |
|---|---|---|
board.me.marketingConsent.retrieve(options?: FetchOptions) | Promise<MarketingConsent | null> | Reads the current decision; null means none was ever recorded. |
board.me.marketingConsent.grant(options?: FetchOptions) | Promise<MarketingConsent> | Records consent. Call only from a surface that displayed your disclosure wording. Idempotent. |
board.me.marketingConsent.withdraw(options?: FetchOptions) | Promise<MarketingConsent> | Withdraws immediately and idempotently; repeated calls do not add another revision. |
Leave any checkbox unticked by default and call nothing when it stays unticked — absence of a record means no consent, never a default. Withdrawal is at least as direct as grant and stays effective while any downstream webhook delivery is pending.
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 signature | Returns |
|---|---|
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.retrieve(slug: string, options?: FetchOptions) | Promise<EmployerCompany> |
board.me.companies.update(slug: string, body: UpdateEmployerCompanyBody, options?: FetchOptions) | Promise<EmployerCompany> |
board.me.companies.retrieveCustomFields(slug: string, options?: FetchOptions) | Promise<ProfileFieldValues> |
board.me.companies.updateCustomFields(slug: string, body: UpdateProfileFieldValuesBody, options?: FetchOptions) | Promise<ProfileFieldValues> |
board.me.companies.retrieveObjectReferences(slug: string, options?: FetchOptions) | Promise<ProfileObjectReferences> |
board.me.companies.updateObjectReferences(slug: string, body: ReplaceProfileObjectReferencesBody, options?: FetchOptions) | Promise<ProfileObjectReferences> |
board.me.companies.listObjectReferenceChoices(slug: string, fieldKey: string, query?: ProfileChoiceQuery, options?: FetchOptions) | Promise<ProfileChoiceList> |
board.me.companies.delete(slug: string, options?: FetchOptions) | Promise<void> |
board.me.companies.listMembers(slug: string, options?: FetchOptions) | Promise<ListEnvelope<CompanyMember>> |
board.me.companies.updateMemberRole(slug: string, memberId: string, body: UpdateCompanyMemberRoleBody, options?: FetchOptions) | Promise<void> |
board.me.companies.removeMember(slug: string, memberId: string, options?: FetchOptions) | Promise<void> |
board.me.companies.leave(slug: string, options?: FetchOptions) | Promise<void> |
board.me.companies.startMembershipCheckout(slug: string, body: MembershipCheckoutBody, options?: FetchOptions) | Promise<MembershipCheckoutSession> |
board.me.companies.retrieveMembershipCheckout(slug: string, sessionId: string, options?: FetchOptions) | Promise<MembershipCheckoutSessionState> |
board.me.companies.listInvites(slug: string, options?: FetchOptions) | Promise<ListEnvelope<CompanyMemberInvite>> |
board.me.companies.createInvite(slug: string, body: CreateCompanyMemberInviteBody, options?: FetchOptions) | Promise<CompanyMemberInvite> |
board.me.companies.revokeInvite(slug: string, inviteId: string, options?: FetchOptions) | Promise<void> |
board.me.companies.uploadLogo(slug: string, file: Blob, 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> (deprecated — prefer board.auth.verifyWorkEmail; slug is ignored) |
board.me.talentAccess.retrieve(options?: FetchOptions) | Promise<TalentAccess> |
board.me.talentAccess.retrieveCandidate(candidateId: string, options?: FetchOptions) | Promise<TalentCandidateAccess> |
board.me.talentAccess.checkout(body: TalentAccessCheckoutBody, options?: FetchOptions) | Promise<TalentAccessCheckoutSession> |
board.me.talentAccess.claim(body: TalentAccessClaimBody, options?: FetchOptions) | Promise<TalentAccessClaim> |
board.me.talentAccess.retrieveCheckout(sessionId: string, options?: FetchOptions) | Promise<TalentAccessCheckoutSessionState> |
board.me.talentAccess.unlock(body: TalentUnlockBody, options?: FetchOptions) | Promise<TalentUnlock> |
board.me.talentAccess.upgrade(body: TalentAccessUpgradeBody, options?: FetchOptions) | Promise<TalentAccessUpgrade> |
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.
companies.retrieve returns my full editable company profile (summary, socials, logo, description) — the read half of update, for pre-populating an edit form. companies.uploadLogo uploads and attaches a company logo (JPEG/PNG/WebP/GIF, ≤2 MB) in a single multipart POST and returns the updated company with its new logoUrl. Both require an approved membership.
companies.retrieveCustomFields and companies.retrieveObjectReferences return the definitions and current values or resolved selections for the named company. companies.listObjectReferenceChoices searches and pages active records for one owner-editable collection field, including a private field. Custom-field updates are additive, while companies.updateObjectReferences replaces the complete editable selection set. These methods require an approved company membership and only accept fields the company owner can edit.
companies.delete is admin-gated and returns company_deletion_disabled when the board operator has turned employer company deletion off. listMembers is visible to any approved member and includes role (admin or member) plus approvedBy join provenance (owner_creation, domain_match, work_email_verification, admin, or invite). updateMemberRole and removeMember are admin-only; demoting or removing the last admin returns last_admin. leave deletes the caller’s own membership; leaving as the last admin also returns last_admin. A company always has at least one admin. listInvites is visible to any approved member. createInvite and revokeInvite are admin-only; invites expire after 7 days; duplicate members or pending invites return already_member / already_invited. acceptInvite requires a bearer session whose email matches the invite (invite_email_mismatch otherwise) and an employer-role account (candidate_role for candidates).
workEmail.verify sends a 24-hour verification link for a pending claim. Consume that token with board.auth.verifyWorkEmail({ token }) — see Employer work-email verification. workEmail.confirm(slug, body) is the deprecated predecessor; it still works, but slug is ignored.
talentAccess.retrieve answers "does this viewer currently have talent access?" for talent-surface CTAs. An approved employer with an active talent subscription or credit pack (or any approved employer on a board without a talent paywall) gets hasTalentAccess: true; candidates get false. It also returns the charging model and remaining company-scoped unlock/message credits. Never cache it in a shared cache. retrieveCandidate is the per-candidate gate for the opaque /p/{id} route. checkout returns the same connected-account mount kit as candidate-access checkout (origin: talent_access). claim grants a public free talent-access plan without Stripe and is idempotent for the same company and plan. unlock is idempotent (alreadyUnlocked: true with no decrement). upgrade swaps the company's primary talent_access subscription in place; never start a second checkout. companyId is required on writes when the viewer has more than one approved membership (company_required). Message credits spend inline on conversations.start. This is a different buyer from board.me.access.
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. jobs.list returns every status: draft, published, expired, archived, and pending_approval. A pending_approval job is waiting for the board operator to approve it; the employer cannot publish it.
| Method and signature | Returns |
|---|---|
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>> |
board.me.companies.billingPortal.create(slug: string, body?: CompanyBillingPortalBody, options?: FetchOptions) | Promise<CompanyBillingPortalSession> |
board.me.companies.jobStats.retrieve(slug: string, options?: FetchOptions) | Promise<ListEnvelope<EmployerJobStat>> |
board.me.companies.jobStats.timeseries(slug: string, query?: EmployerJobStatsTimeseriesQuery, options?: FetchOptions) | Promise<ListEnvelope<EmployerJobStatsPoint>> |
board.me.companies.profileStats.retrieve(slug: string, options?: FetchOptions) | Promise<EmployerProfileStats> |
board.me.companies.profileStats.timeseries(slug: string, query?: EmployerProfileViewsTimeseriesQuery, options?: FetchOptions) | Promise<ListEnvelope<EmployerProfileViewsPoint>> |
checkout can resolve with status: 'checkout' and a hosted payment URL, status: 'published' when a free, bundle, subscription, or member-credit option publishes immediately, status: 'pending_approval' when a free post (or invoice publish-on-issue) is held for operator review, or status: 'invoice_sent'. Branch on the returned status instead of assuming every call needs a redirect, or that a 200 means the job is live.
billingPortal.create mints a Stripe Customer Portal session for the company. Job-posting and talent-access subscriptions share one Stripe customer, so this is the manage-billing surface for both. There is no board.me.talentAccess.portal.
jobs.create requires remoteOption. A salaryMin or salaryMax also needs salaryCurrency and salaryTimeframe. jobs.update cannot clear remoteOption.
jobStats.retrieve returns one EmployerJobStat per company job — views, applyClicks, and applications (null for an external-apply job, which has no native pipeline) — over an all-time window, the same funnel the hosted employer jobs table shows. jobStats.timeseries returns daily EmployerJobStatsPoint buckets (date, views, applyClicks) aggregated across the company's jobs to drive a chart; the optional since/until window defaults to the last 30 days. Both require an approved membership and are never cached. Analytics degrade to zeroes (or an empty series) rather than failing when the upstream analytics source is unavailable, so a 0 can mean either "no activity" or "analytics briefly unavailable."
profileStats.retrieve returns the company-level profileViews total — views of the company profile page itself (/companies/{slug}), with its tab subpaths (/jobs, /salaries) and bots excluded — over an all-time window, for a dashboard "profile views" metric. profileStats.timeseries returns daily EmployerProfileViewsPoint buckets (date, views) for a sparkline; the optional since/until window defaults to the last 30 days. Both require an approved membership, are never cached, and degrade to 0 / an empty series on an analytics outage.
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 signature | Returns |
|---|---|
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.
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.
Recommended talent
Candidates who fit a job this company posted and who have not applied to it, best match first. Ranking is computed server-side from the job (skills, title) against the candidate corpus and improves over time without any contract change.
| Method and signature | Returns |
|---|---|
board.me.companies.recommendedTalent.list(slug: string, query: RecommendedTalentListQuery, options?: FetchOptions) | Promise<ListEnvelope<RecommendedTalent>> |
query.job is required and must name a job belonging to :slug; a job that is still a draft raises job_not_published rather than returning an empty list, because only a published job carries the vectors a match needs. Requires an approved membership. Never cached.
This is a sourcing surface, and that shapes the contract. Applicants are excluded — they belong to the pipeline, and ranking them would be evaluation rather than discovery. Order is the ranking: no rank, score, band, or ranker identity appears in the response, now or later. The item wrapper carries only candidate today so that typed reasons and a coarse strength band can be added as minor releases. Do not build an automated reject, advance, or filter on this output.
Each item embeds the same talent_directory_entry the public directory returns, so directory rendering works unchanged. jobSearchStatus is populated here even for candidates who scoped it to employers only — the caller is a verified employer, which is what that setting is for. Only candidates who chose to be discoverable appear, and one whose status is not_looking never does. The list is a bounded top slice rather than everyone who matched, so expect it to be short — and note that low relevance alone does not empty it: v0 still returns its top slice on a weak board. An empty data means no candidate on that page survived — either nothing was ranked at all, or the entries for that page were dropped after ranking (profile deleted or hidden, or the candidate opted out since the last sync). It is not a relevance signal, and it can arrive mid-pagination, so follow hasMore / nextCursor rather than treating the first empty page as the end. Render it as "no candidates yet" rather than as an error.
Talent lists and sourced candidates
Two company-private records, one picker. A talent list is a named, company-owned predicate over the public directory (optional jobId binds it to one job). A sourced candidate is per-job membership on an unstaged rail. Saving a candidate always writes sourced membership; a bound list is one-click, an unbound list needs a job picker. Approved company members share both. Requires an approved membership. Never cached.
| Method and signature | Returns |
|---|---|
board.me.companies.talentLists.list(slug: string, options?: FetchOptions) | Promise<ListEnvelope<TalentList>> |
board.me.companies.talentLists.create(slug: string, body: CreateTalentListBody, options?: FetchOptions) | Promise<TalentList> |
board.me.companies.talentLists.update(slug: string, listId: string, body: UpdateTalentListBody, options?: FetchOptions) | Promise<TalentList> |
board.me.companies.talentLists.remove(slug: string, listId: string, options?: FetchOptions) | Promise<void> |
board.me.companies.sourcedCandidates.list(slug: string, query: { job: string }, options?: FetchOptions) | Promise<ListEnvelope<SourcedCandidate>> |
board.me.companies.sourcedCandidates.add(slug: string, body: AddSourcedCandidateBody, options?: FetchOptions) | Promise<{ id: string; object: 'sourced_candidate'; created: boolean }> |
board.me.companies.sourcedCandidates.remove(slug: string, sourcedId: string, options?: FetchOptions) | Promise<void> |
board.me.companies.sourcedCandidates.convert(slug: string, sourcedId: string, body: ConvertSourcedCandidateBody, options?: FetchOptions) | Promise<{ id: string; object: 'application'; created: boolean }> |
create accepts { name, filters?, job? }. update is a merge patch; pass job: null to unbind. List filters reuse the frozen /talent query (q, skill, jobSearchStatus, languages, openToRelocate, sort); place, seniority, permitCountry, and interestedRole are accepted no-ops until later. Deleting a job unbinds lists that pointed at it and deletes that job’s sourced rows.
sourcedCandidates.list requires { job } and omits anyone who already has a pipeline application for that job — convert-on-drop is not a second pipeline. add is idempotent (created: false when the pair already exists). convert writes a jobApplications row with source sourced: hidden from the candidate’s /me/applications, apply emails, and funnel counts until a native or guest apply flips the source. Pass a visible pipeline stage; applied aliases to the system review stage. Convert never raises employer_company_name_taken. A board without a native pipeline raises sourced_convert_unavailable (409). Missing rows raise talent_list_not_found or sourced_candidate_not_found.
Drop onto the job kanban converts; do not keep a sourced stage on the pipeline rail.
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 signature | Returns |
|---|---|
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.
Recommended jobs
Personalized jobs for the signed-in candidate, best match first. Ranking is computed server-side from the candidate's profile (skills, roles, seniority, work authorization) and improves over time without any contract change — the response never exposes scores or ranker internals.
On-site and hybrid jobs are limited to the candidate's commute distance around a city or locality locationPlace, or to their country when openToRelocate is on or the home place is a region or country. Remote jobs are not limited by distance. See Home location and commute distance.
| Method and signature | Returns |
|---|---|
board.me.recommendedJobs.list(query?: RecommendedJobsListQuery, options?: FetchOptions) | Promise<ListEnvelope<RecommendedJob>> |
Each item wraps the same slim job_card the jobs list returns. Jobs the candidate already applied to are excluded; saved jobs are not. An empty list means the profile has no usable matching signal yet — read board.me.profile (parseStatus, skills) to decide whether to prompt for a resume upload rather than showing an empty rail.
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 signature | Returns |
|---|---|
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.
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 signature | Returns |
|---|---|
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. It accepts the recipient as exactly one of candidateBoardUserId or candidateHandle (the public handle from the talent surfaces — resolved server-side through the same gates). 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.
Blocks
| Method and signature | Returns |
|---|---|
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 signature | Returns |
|---|---|
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.
candidate_profile_requiredis the 403 for a candidate profile write (anything butdisplayNameonprofile.update, experience and education create or update,updateSkills,updateLanguages) from an account with no candidate profile, such as an employer. Render candidate screens only whenme.role === 'candidate'. - 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, candidate_profile_required, applications_not_found, messaging_cold_rule, messaging_edit_window_expired, employer_not_member, employer_payment_required, employer_jobs_quota_exceeded, no_password, email_taken, last_admin, already_invited, invite_email_mismatch, company_deletion_disabled, 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.