@scalemule/jobs
v0.0.1
Published
Official ScaleMule Jobs family SDK (employers, openings, candidates, applications)
Maintainers
Readme
@scalemule/jobs
Official ScaleMule SDK for the Jobs family: employers, openings, postings, candidates, and applications (including saved jobs and invitations).
Works in browsers, Node.js 18+, and edge runtimes with standard fetch.
Install
npm install @scalemule/jobsQuick Start
import { createJobsClient } from '@scalemule/jobs'
const jobs = createJobsClient({
apiKey: process.env.NEXT_PUBLIC_SCALEMULE_API_KEY!,
environment: 'prod',
})
// Public posting discovery (no session required; x-api-key + x-app-id only)
const postings = await jobs.postings.searchPublic({ q: 'rust' })Session-Scoped Usage
Jobs APIs use your application API key plus a member or end-user bearer token
for anything behind /applications/{app_id}/....
const jobs = createJobsClient({
apiKey: process.env.NEXT_PUBLIC_SCALEMULE_API_KEY!,
environment: 'prod',
})
const authedJobs = jobs.withAccessToken(accessToken)
// End-user flows
const me = await authedJobs.candidates.getMe(appId)
const mine = await authedJobs.applications.listMine(appId)
await authedJobs.applications.submit(appId, {
posting_id,
resume_id,
answers: [],
})
// Employer/member flows
await authedJobs.openings.create(appId, {
employer_id,
title: 'Senior Rust Engineer',
employment_type: 'full_time',
})Surface Area
Employers
await jobs.employers.create(appId, { company_name: 'Acme' })
await jobs.employers.get(appId, employerId)
await jobs.employers.update(appId, employerId, { website: 'https://acme.test' })
await jobs.employers.updateBranding(appId, employerId, { brand_primary: '#000' })
await jobs.employers.addLocation(appId, employerId, {
city: 'Remote',
country_code: 'US',
})
await jobs.employers.submitVerification(appId, employerId, {
kind: 'domain',
evidence_ref: 'dns:acme.test',
})
await jobs.employers.getPublicBySlug('acme')Openings + postings
await jobs.openings.create(appId, {
employer_id,
title: 'Senior Rust Engineer',
employment_type: 'full_time',
})
await jobs.openings.list(appId, { employer_id, status: 'open' })
await jobs.openings.submitForApproval(appId, openingId)
await jobs.openings.approve(appId, openingId)
await jobs.openings.addQuestion(appId, openingId, {
question_md: 'Why Rust?',
kind: 'text',
})
await jobs.openings.addRequirement(appId, openingId, {
kind: 'skill',
ref_key: 'rust',
})
await jobs.postings.create(appId, openingId, { channel: 'site', body_md: '...' })
await jobs.postings.publish(appId, postingId)
await jobs.postings.searchPublic({ q: 'rust' })Candidates
await jobs.candidates.upsertMe(appId, { display_name: 'Jane' })
await jobs.candidates.getMe(appId)
await jobs.candidates.addSkill(appId, { skill_key: 'rust', proficiency: 'advanced' })
await jobs.candidates.addExperience(appId, {
title: 'Senior Engineer',
employer_name: 'Acme',
start_date: '2024-01-01',
})
await jobs.candidates.upsertPreferences(appId, { willing_relocate: true })
await jobs.candidates.uploadResume(appId, {
storage_ref: 'storage/resumes/r.pdf',
filename: 'resume.pdf',
mime: 'application/pdf',
size_bytes: 12345,
})
await jobs.candidates.createAlert(appId, {
name: 'Remote Rust roles',
criteria_json: { tags: ['rust', 'remote'] },
})Applications + saved jobs + invitations
// candidate side
await jobs.applications.submit(appId, {
posting_id,
resume_id,
answers: [{ question_id, answer_text: '...' }],
})
await jobs.applications.listMine(appId)
await jobs.applications.withdraw(appId, applicationId)
// employer pipeline
await jobs.applications.listPipeline(appId, { opening_id })
await jobs.applications.advance(appId, applicationId, { to_status: 'screen' })
await jobs.applications.reject(appId, applicationId, {
disposition_code: 'not_a_fit',
})
await jobs.applications.hire(appId, applicationId, { disposition_code: 'hired' })
// wishlist
await jobs.savedJobs.save(appId, { posting_id })
await jobs.savedJobs.list(appId)
await jobs.savedJobs.unsave(appId, posting_id)
// employer outreach
await jobs.invitations.send(appId, { posting_id, candidate_id })
await jobs.invitations.respond(appId, invitationId, { response: 'accepted' })Error Handling
Requests throw JobsHttpError on HTTP failures or success: false envelopes.
import { JobsHttpError } from '@scalemule/jobs'
try {
await jobs.applications.submit(appId, { posting_id, resume_id })
} catch (error) {
if (error instanceof JobsHttpError) {
console.error(error.status, error.detail?.code, error.detail?.message)
}
}React
The package ships a React entrypoint with hooks for the common flows:
import {
JobsProvider,
useJobs,
useMyCandidateProfile,
useMyApplications,
useEmployerOpenings,
useOpeningPipeline,
usePublicPostingSearch,
usePublicPosting,
useApplyToJob,
useAdvanceApplication,
useRejectApplication,
useWithdrawApplication,
useSaveJob,
useUnsaveJob,
useCreateOpening,
usePublishPosting,
useCreateEmployer,
useUpsertCandidate,
useUploadResume,
useCreateAlert,
} from '@scalemule/jobs/react'
function App({ apiKey }: { apiKey: string }) {
return (
<JobsProvider config={{ apiKey, environment: 'prod' }}>
<SearchJobs />
</JobsProvider>
)
}
function SearchJobs() {
const search = usePublicPostingSearch({ q: 'rust', limit: 20 })
if (search.isLoading) return null
return <pre>{JSON.stringify(search.data, null, 2)}</pre>
}Use @scalemule/nextjs if you want the higher-level ScaleMule server/client
integration for session handling.
