/** * Lever public job postings API. * * https://github.com/lever/postings-api/blob/master/README.md * GET https://api.lever.co/v0/postings/{company}?mode=json * * No auth. We iterate `leverCompanies` (set in Settings or LEVER_COMPANIES env) * and pull every active posting; downstream filtering by `searchTerms` / * country happens in the pipeline. */ import type { ExtractorManifest, ExtractorRunResult, } from "@shared/types/extractors"; import type { CreateJobInput } from "@shared/types/jobs"; interface LeverCategories { team?: string; department?: string; commitment?: string; location?: string; allLocations?: string[]; } interface LeverPosting { id?: string; text?: string; hostedUrl?: string; applyUrl?: string; description?: string; descriptionPlain?: string; categories?: LeverCategories; createdAt?: number; workplaceType?: string; } function asString(value: unknown): string | undefined { if (typeof value !== "string") return undefined; const trimmed = value.trim(); return trimmed ? trimmed : undefined; } function readCompanies(raw: string | undefined): string[] { if (!raw) return []; try { const parsed = JSON.parse(raw); if (Array.isArray(parsed)) { return parsed .map((entry) => typeof entry === "string" ? entry.trim().toLowerCase() : "", ) .filter((entry) => entry.length > 0); } } catch { // fall through to delimited-list parsing below } return raw .split(/[\n,;|]+/) .map((entry) => entry.trim().toLowerCase()) .filter(Boolean); } function locationFor(posting: LeverPosting): string | undefined { const cats = posting.categories; if (!cats) return undefined; if (Array.isArray(cats.allLocations) && cats.allLocations.length > 0) { return cats.allLocations.filter(Boolean).join("; "); } return asString(cats.location); } function mapPosting( posting: LeverPosting, company: string, ): CreateJobInput | null { const jobUrl = asString(posting.hostedUrl); if (!jobUrl) return null; const employer = company .split("-") .filter(Boolean) .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(" "); return { source: "lever", sourceJobId: asString(posting.id), title: asString(posting.text) ?? "Unknown Title", employer: employer || company, jobUrl, applicationLink: asString(posting.applyUrl) ?? jobUrl, location: locationFor(posting), jobType: asString(posting.categories?.commitment), jobFunction: asString(posting.categories?.team), companyIndustry: asString(posting.categories?.department), isRemote: posting.workplaceType?.toLowerCase() === "remote" ? true : undefined, datePosted: typeof posting.createdAt === "number" ? new Date(posting.createdAt).toISOString() : undefined, jobDescription: asString(posting.descriptionPlain) ?? asString(posting.description), }; } async function fetchCompany(company: string): Promise { const url = `https://api.lever.co/v0/postings/${encodeURIComponent(company)}?mode=json`; const response = await fetch(url, { headers: { Accept: "application/json" }, }); if (response.status === 404) return []; if (!response.ok) { throw new Error( `Lever request for "${company}" failed with status ${response.status}`, ); } const body = (await response.json()) as unknown; return Array.isArray(body) ? (body as LeverPosting[]) : []; } export const manifest: ExtractorManifest = { id: "lever", displayName: "Lever (ATS)", providesSources: ["lever"], async run(context): Promise { if (context.shouldCancel?.()) return { success: true, jobs: [] }; const companies = readCompanies(context.settings.leverCompanies); if (companies.length === 0) { return { success: true, jobs: [], error: "No Lever companies configured. Set LEVER_COMPANIES or the leverCompanies setting (comma- or newline-separated slugs).", }; } const seen = new Set(); const out: CreateJobInput[] = []; try { for (let i = 0; i < companies.length; i += 1) { if (context.shouldCancel?.()) break; const company = companies[i]; context.onProgress?.({ phase: "list", termsProcessed: i, termsTotal: companies.length, currentUrl: company, detail: `Lever: ${company} (${i + 1}/${companies.length})`, }); let added = 0; const postings = await fetchCompany(company); for (const posting of postings) { const mapped = mapPosting(posting, company); if (!mapped) continue; const key = mapped.sourceJobId || mapped.jobUrl; if (seen.has(key)) continue; seen.add(key); out.push(mapped); added += 1; } context.onProgress?.({ phase: "list", termsProcessed: i + 1, termsTotal: companies.length, currentUrl: company, jobPagesProcessed: out.length, detail: `Lever: ${company} → ${added} jobs (${out.length} total)`, }); } } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; return { success: false, jobs: out, error: message }; } return { success: true, jobs: out }; }, }; export default manifest;