portals.js
"use strict";
/**
* ATS Portal APIs + Company Database
*
* Supported ATS platforms (no browser, pure HTTP):
* Greenhouse — GET /v1/boards/{slug}/jobs
* Ashby — POST GraphQL /api/non-user-graphql
* Lever — GET /v0/postings/{slug}?mode=json
* Workable — GET /api/v3/accounts/{slug}/jobs
*
* Falls back to web search for companies without a supported ATS.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CLICHE_WORDS = exports.REGIONAL_BOARDS = exports.SENIORITY_BOOST = exports.NEGATIVE_KEYWORDS = exports.POSITIVE_KEYWORDS = exports.COMPANY_PORTALS = void 0;
exports.fetchGreenhouse = fetchGreenhouse;
exports.fetchAshby = fetchAshby;
exports.fetchLever = fetchLever;
exports.fetchWorkable = fetchWorkable;
exports.fetchCompanyJobs = fetchCompanyJobs;
exports.filterByTitle = filterByTitle;
exports.detectArchetype = detectArchetype;
exports.assessJobLegitimacy = assessJobLegitimacy;
exports.extractAtsKeywords = extractAtsKeywords;
exports.fetchRemotive = fetchRemotive;
exports.fetchRemoteOK = fetchRemoteOK;
const UA = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
async function fetchJson(url, opts, signal) {
const res = await fetch(url, {
...opts,
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(12_000)]) : AbortSignal.timeout(12_000),
headers: { "User-Agent": UA, "Accept": "application/json", ...(opts.headers ?? {}) },
});
if (!res.ok)
throw new Error(`HTTP ${res.status} from ${url}`);
return res.json();
}
async function fetchGreenhouse(slug, company, signal) {
// EU subdomain for European companies (greenhouse redirects, try eu first then fallback)
const urls = [
`https://job-boards.eu.greenhouse.io/${slug}/jobs`,
`https://job-boards.greenhouse.io/${slug}/jobs`,
`https://boards-api.greenhouse.io/v1/boards/${slug}/jobs`,
];
for (const url of urls) {
try {
const data = await fetchJson(url, {}, signal);
if (data.jobs && data.jobs.length > 0) {
return data.jobs.map((j) => ({
title: j.title ?? "",
url: j.absolute_url ?? "",
company,
location: j.location?.name ?? "",
department: j.departments?.[0]?.name ?? "",
}));
}
}
catch { /* try next URL */ }
}
return [];
}
async function fetchAshby(slug, company, signal) {
const body = JSON.stringify({
operationName: "ApiJobBoardWithTeams",
variables: { organizationHostedJobsPageName: slug },
query: `query ApiJobBoardWithTeams($organizationHostedJobsPageName: String!) {
jobBoard: publishedJobBoard(organizationHostedJobsPageName: $organizationHostedJobsPageName) {
jobPostings { title locationName teamName jobUrl }
}
}`,
});
const data = await fetchJson("https://jobs.ashbyhq.com/api/non-user-graphql", { method: "POST", headers: { "Content-Type": "application/json" }, body }, signal);
return (data.data?.jobBoard?.jobPostings ?? []).map((j) => ({
title: j.title ?? "",
url: j.jobUrl ?? `https://jobs.ashbyhq.com/${slug}`,
company,
location: j.locationName ?? "",
department: j.teamName ?? "",
}));
}
async function fetchLever(slug, company, signal) {
const data = await fetchJson(`https://api.lever.co/v0/postings/${slug}?mode=json`, {}, signal);
return (Array.isArray(data) ? data : []).map((j) => ({
title: j.text ?? "",
url: j.hostedUrl ?? j.applyUrl ?? "",
company,
location: j.categories?.location ?? "",
department: j.categories?.department ?? j.categories?.team ?? "",
}));
}
async function fetchWorkable(slug, company, signal) {
const data = await fetchJson(`https://apply.workable.com/api/v3/accounts/${slug}/jobs`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: "", location: [] }) }, signal);
return (data.jobs ?? []).map((j) => ({
title: j.title ?? "",
url: j.url ?? "",
company,
location: j.location?.location_str ?? "",
department: j.department ?? "",
}));
}
exports.COMPANY_PORTALS = [
// AI Labs & LLM Providers
{ name: "Anthropic", slug: "anthropic", ats: "greenhouse", region: "US", category: "AI Lab" },
{ name: "Cohere", slug: "cohere", ats: "ashby", region: "CA", category: "AI Lab" },
{ name: "Mistral AI", slug: "mistral", ats: "lever", region: "EU", category: "AI Lab" },
{ name: "Perplexity", slug: "perplexity", ats: "ashby", region: "US", category: "AI Lab" },
// Voice & Conversational AI
{ name: "PolyAI", slug: "polyai", ats: "greenhouse", region: "UK", category: "Voice AI" },
{ name: "Parloa", slug: "parloa", ats: "greenhouse", region: "EU", category: "Voice AI" },
{ name: "Intercom", slug: "intercom", ats: "greenhouse", region: "US", category: "CX AI" },
{ name: "Hume AI", slug: "humeai", ats: "greenhouse", region: "US", category: "Voice AI" },
{ name: "ElevenLabs", slug: "elevenlabs", ats: "ashby", region: "US", category: "Voice AI" },
{ name: "Deepgram", slug: "deepgram", ats: "ashby", region: "US", category: "Voice AI" },
{ name: "Vapi", slug: "vapi", ats: "ashby", region: "US", category: "Voice AI" },
{ name: "Bland AI", slug: "bland", ats: "ashby", region: "US", category: "Voice AI" },
// Enterprise AI Platforms
{ name: "Airtable", slug: "airtable", ats: "greenhouse", region: "US", category: "Platform" },
{ name: "Vercel", slug: "vercel", ats: "greenhouse", region: "US", category: "DevTools" },
{ name: "Temporal", slug: "temporal", ats: "greenhouse", region: "US", category: "Platform" },
{ name: "Arize AI", slug: "arizeai", ats: "greenhouse", region: "US", category: "LLMOps" },
{ name: "RunPod", slug: "runpod", ats: "greenhouse", region: "US", category: "AI Infra" },
{ name: "Glean", slug: "gleanwork", ats: "greenhouse", region: "US", category: "Enterprise AI" },
// Contact Center AI
{ name: "Sierra", slug: "sierra", ats: "ashby", region: "US", category: "CX AI" },
{ name: "Decagon", slug: "decagon", ats: "ashby", region: "US", category: "CX AI" },
{ name: "Gong", slug: "gong", ats: "search", region: "US", category: "Revenue AI" },
{ name: "Salesforce", slug: "salesforce", ats: "search", region: "US", category: "Enterprise" },
// Developer Tools
{ name: "Supabase", slug: "supabase", ats: "ashby", region: "US", category: "DevTools" },
{ name: "Resend", slug: "resend", ats: "ashby", region: "US", category: "DevTools" },
{ name: "Clerk", slug: "clerk", ats: "ashby", region: "US", category: "DevTools" },
{ name: "Inngest", slug: "inngest", ats: "ashby", region: "US", category: "DevTools" },
{ name: "PlanetScale", slug: "planetscale", ats: "greenhouse", region: "US", category: "DevTools" },
{ name: "WorkOS", slug: "workos", ats: "ashby", region: "US", category: "DevTools" },
// LLMOps & AI Infra
{ name: "LangChain", slug: "langchain", ats: "ashby", region: "US", category: "LLMOps" },
{ name: "Pinecone", slug: "pinecone", ats: "ashby", region: "US", category: "AI Infra" },
{ name: "Weights & Biases", slug: "wandb", ats: "lever", region: "US", category: "LLMOps" },
{ name: "Runway", slug: "runwayml", ats: "greenhouse", region: "US", category: "Generative AI" },
{ name: "Hightouch", slug: "hightouch", ats: "greenhouse", region: "US", category: "Data" },
{ name: "Clay Labs", slug: "claylabs", ats: "ashby", region: "US", category: "RevOps" },
{ name: "Lindy", slug: "lindy", ats: "ashby", region: "US", category: "Automation" },
// Automation / No-code
{ name: "n8n", slug: "n8n", ats: "ashby", region: "EU", category: "Automation" },
{ name: "Zapier", slug: "zapier", ats: "ashby", region: "US", category: "Automation" },
{ name: "Boomi", slug: "boomilp", ats: "greenhouse", region: "US", category: "Automation" },
// DACH Europe
{ name: "Aleph Alpha", slug: "AlephAlpha", ats: "ashby", region: "DE", category: "AI Lab" },
{ name: "DeepL", slug: "DeepL", ats: "ashby", region: "DE", category: "AI Platform" },
{ name: "Black Forest Labs", slug: "blackforestlabs", ats: "greenhouse", region: "DE", category: "Generative AI" },
{ name: "Helsing", slug: "helsing", ats: "greenhouse", region: "DE", category: "Defence AI" },
{ name: "Celonis", slug: "celonis", ats: "greenhouse", region: "DE", category: "Process AI" },
{ name: "Contentful", slug: "contentful", ats: "greenhouse", region: "DE", category: "CMS" },
{ name: "GetYourGuide", slug: "getyourguide", ats: "greenhouse", region: "DE", category: "Travel" },
{ name: "HelloFresh", slug: "hellofresh", ats: "greenhouse", region: "DE", category: "FoodTech" },
{ name: "N26", slug: "n26", ats: "greenhouse", region: "DE", category: "FinTech" },
{ name: "Trade Republic", slug: "traderepublicbank", ats: "greenhouse", region: "DE", category: "FinTech" },
{ name: "SumUp", slug: "sumup", ats: "greenhouse", region: "DE", category: "FinTech" },
{ name: "Qonto", slug: "qonto", ats: "lever", region: "FR", category: "FinTech" },
// UK & Ireland
{ name: "Wayve", slug: "wayve", ats: "greenhouse", region: "UK", category: "Autonomous AI" },
{ name: "Isomorphic Labs", slug: "isomorphiclabs", ats: "greenhouse", region: "UK", category: "BioAI" },
{ name: "PhysicsX", slug: "physicsx", ats: "greenhouse", region: "UK", category: "Scientific AI" },
{ name: "Stability AI", slug: "stabilityai", ats: "greenhouse", region: "UK", category: "Generative AI" },
{ name: "Synthesia", slug: "synthesia", ats: "ashby", region: "UK", category: "Video AI" },
{ name: "Faculty", slug: "faculty", ats: "ashby", region: "UK", category: "Applied AI" },
{ name: "Causaly", slug: "causaly", ats: "ashby", region: "UK", category: "BioAI" },
{ name: "Speechmatics", slug: "speechmatics", ats: "greenhouse", region: "UK", category: "Voice AI" },
// Nordics
{ name: "Lovable", slug: "lovable", ats: "ashby", region: "SE", category: "DevTools" },
{ name: "Legora", slug: "legora", ats: "ashby", region: "SE", category: "LegalTech" },
{ name: "Spotify", slug: "spotify", ats: "lever", region: "SE", category: "Music Tech" },
{ name: "Vinted", slug: "vinted", ats: "lever", region: "LT", category: "eCommerce" },
// France
{ name: "Hugging Face", slug: "huggingface", ats: "workable", region: "FR", category: "AI Platform" },
{ name: "Photoroom", slug: "photoroom", ats: "ashby", region: "FR", category: "Generative AI" },
{ name: "Pigment", slug: "pigment", ats: "lever", region: "FR", category: "Finance AI" },
// Switzerland / Austria
{ name: "Lakera", slug: "lakera.ai", ats: "ashby", region: "CH", category: "AI Security" },
{ name: "Scandit", slug: "scandit", ats: "greenhouse", region: "CH", category: "Computer Vision" },
{ name: "Cradle", slug: "cradlebio", ats: "ashby", region: "CH", category: "BioAI" },
// Spain
{ name: "Factorial", slug: "factorial", ats: "greenhouse", region: "ES", category: "HR Tech" },
{ name: "Travelperk", slug: "travelperk", ats: "ashby", region: "ES", category: "Travel Tech" },
// Canada
{ name: "Cohere (CA)", slug: "cohere", ats: "ashby", region: "CA", category: "AI Lab" },
{ name: "Klue", slug: "klue", ats: "ashby", region: "CA", category: "Revenue AI" },
// Palantir / Consulting
{ name: "Palantir", slug: "palantir", ats: "lever", region: "US", category: "Data AI" },
// B2B SaaS with strong AI hiring
{ name: "Attio", slug: "attio", ats: "ashby", region: "UK", category: "CRM" },
{ name: "Tinybird", slug: "tinybird", ats: "ashby", region: "ES", category: "Analytics" },
];
// ---------------------------------------------------------------------------
// Dispatcher — calls the right ATS API per portal entry
// ---------------------------------------------------------------------------
async function fetchCompanyJobs(portal, signal) {
try {
switch (portal.ats) {
case "greenhouse": return await fetchGreenhouse(portal.slug, portal.name, signal);
case "ashby": return await fetchAshby(portal.slug, portal.name, signal);
case "lever": return await fetchLever(portal.slug, portal.name, signal);
case "workable": return await fetchWorkable(portal.slug, portal.name, signal);
case "search": return []; // caller handles search fallback
}
}
catch {
return [];
}
}
// ---------------------------------------------------------------------------
// Title filtering (career-ops positive/negative keyword lists)
// ---------------------------------------------------------------------------
exports.POSITIVE_KEYWORDS = [
"AI", "ML", "LLM", "Agent", "Agentic", "GenAI", "Generative", "NLP", "LLMOps", "MLOps",
"Voice AI", "Conversational AI", "Speech", "Platform Engineer", "Solutions Architect",
"Solutions Engineer", "Forward Deployed", "Deployed Engineer", "Customer Engineer",
"Integration Engineer", "Product Manager", "Technical PM", "Automation", "Hyperautomation",
"Low-Code", "No-Code", "GTM Engineer", "RevOps", "Transformation", "KI", "Machine Learning",
"Data Science", "Inference", "Foundation Model", "Retrieval", "RAG", "Embeddings",
];
exports.NEGATIVE_KEYWORDS = [
"Junior", "Intern", ".NET", "Java Developer", "iOS", "Android", "PHP", "Ruby",
"Embedded", "Firmware", "FPGA", "ASIC", "Blockchain", "Web3", "Crypto",
"Salesforce Admin", "SAP", "Oracle EBS", "Mainframe", "COBOL",
];
exports.SENIORITY_BOOST = ["Senior", "Staff", "Principal", "Lead", "Head", "Director"];
function filterByTitle(jobs, roleFilter) {
return jobs.filter((j) => {
const t = j.title;
// Exclude negatives
if (exports.NEGATIVE_KEYWORDS.some((kw) => new RegExp(`\\b${kw}\\b`, "i").test(t)))
return false;
// If a specific role filter is given, use it
if (roleFilter)
return new RegExp(roleFilter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i").test(t);
// Otherwise require at least one positive keyword
return exports.POSITIVE_KEYWORDS.some((kw) => new RegExp(`\\b${kw}\\b`, "i").test(t));
});
}
const ARCHETYPE_SIGNALS = {
llmops: /observabilit|eval|pipeline|monitor|reliabilit|MLOps|LLMOps|inference|deploy.*model|model.*deploy/i,
agentic: /\bagent\b|HITL|orchestrat|workflow.*auto|multi.?agent|human.?in.?the.?loop/i,
pm: /\bPRD\b|roadmap|product.?discover|product.?manager|product.?owner|stakeholder.*product/i,
sa: /solutions.?architect|enterprise.*integrat|system.*design|technical.*architect|presales/i,
fde: /forward.?deployed|field.?engineer|customer.*engineer|deployment.*engineer|prototype.*client|client.*facing.*engineer/i,
transformation: /change.?management|adoption|enablement|transformation|organizational|scaling.*ai|ai.*upskill/i,
other: /.*/,
};
const ARCHETYPE_LABELS = {
llmops: "AI Platform / LLMOps",
agentic: "Agentic / Automation",
pm: "Technical AI PM",
sa: "AI Solutions Architect",
fde: "AI Forward Deployed Engineer",
transformation: "AI Transformation",
other: "Other / General AI",
};
function detectArchetype(title, description) {
const text = `${title} ${description}`;
for (const [type, re] of Object.entries(ARCHETYPE_SIGNALS)) {
if (type === "other")
continue;
if (re.test(text))
return { archetype: type, label: ARCHETYPE_LABELS[type] };
}
return { archetype: "other", label: ARCHETYPE_LABELS.other };
}
function assessJobLegitimacy(jobDescription, postedDate, url) {
const signals = [];
let suspiciousCount = 0;
let cautionCount = 0;
const wordCount = jobDescription.split(/\s+/).length;
if (wordCount < 100) {
signals.push("Very short JD — low detail");
suspiciousCount++;
}
else if (wordCount < 250) {
signals.push("Short JD — limited specificity");
cautionCount++;
}
else {
signals.push(`Detailed JD (${wordCount} words)`);
}
const hasTechTerms = /\b(Python|TypeScript|JavaScript|Go|Kubernetes|AWS|LLM|RAG|API|SQL|React|Node)\b/i.test(jobDescription);
if (!hasTechTerms) {
signals.push("No specific tech mentioned — vague requirements");
cautionCount++;
}
else {
signals.push("Specific technologies mentioned");
}
const hasCompensation = /\$[\d,]+|\d+k\b|salary|OTE|equity|compensation/i.test(jobDescription);
if (hasCompensation)
signals.push("Salary/compensation mentioned");
else {
signals.push("No salary info");
cautionCount++;
}
const hasResponsibilities = /responsibilities|you will|you'll|duties|your role/i.test(jobDescription);
if (!hasResponsibilities) {
signals.push("No clear responsibilities section");
cautionCount++;
}
if (postedDate) {
const ageMs = Date.now() - new Date(postedDate).getTime();
const ageDays = ageMs / 86_400_000;
if (ageDays > 90) {
signals.push(`Posted ${Math.round(ageDays)} days ago — may be expired`);
cautionCount++;
}
else if (ageDays > 30) {
signals.push(`Posted ${Math.round(ageDays)} days ago`);
}
else {
signals.push(`Fresh posting (${Math.round(ageDays)} days old)`);
}
}
if (url) {
const isAts = /greenhouse\.io|lever\.co|ashbyhq\.com|workable\.com|bamboohr\.com/i.test(url);
if (isAts)
signals.push("Posted on known ATS platform");
else
signals.push("Non-ATS URL — verify apply button works");
}
const tier = suspiciousCount >= 2 ? "suspicious"
: cautionCount >= 2 ? "caution"
: "high";
return { tier, signals };
}
// ---------------------------------------------------------------------------
// ATS keyword extractor (for cover letters / resume matching)
// ---------------------------------------------------------------------------
function extractAtsKeywords(jobDescription, count = 20) {
// Extract multi-word and single-word tech/domain terms
const multiWordPatterns = [
/RAG pipeline[s]?/gi, /large language model[s]?/gi, /language model[s]?/gi,
/machine learning/gi, /deep learning/gi, /natural language processing/gi,
/computer vision/gi, /retrieval.augmented generation/gi, /human.in.the.loop/gi,
/multi.?agent/gi, /prompt engineering/gi, /fine.?tuning/gi, /vector database[s]?/gi,
/model evaluation[s]?/gi, /model deployment/gi, /inference pipeline[s]?/gi,
/data pipeline[s]?/gi, /CI\/CD/gi, /system design/gi, /distributed systems/gi,
/technical roadmap/gi, /cross.functional/gi, /go.to.market/gi,
];
const singleTermPattern = /\b(Python|TypeScript|JavaScript|Go|Rust|Java|C\+\+|React|Next\.js|Node\.js|AWS|GCP|Azure|Docker|Kubernetes|PostgreSQL|MySQL|MongoDB|Redis|GraphQL|REST|gRPC|Terraform|Kafka|Spark|PyTorch|TensorFlow|LangChain|LlamaIndex|OpenAI|Anthropic|Hugging\s*Face|FastAPI|LangGraph|Dify|n8n|Zapier|Pinecone|Weaviate|Chroma|FAISS|MLflow|Weights\s*&\s*Biases|Prometheus|Grafana|Datadog|New\s*Relic|Sentry|Airflow|dbt|Snowflake|BigQuery|Redshift|DynamoDB|Elasticsearch|Qdrant|Milvus|Langfuse|Arize|Helicone)\b/gi;
const found = new Set();
for (const re of multiWordPatterns) {
const matches = jobDescription.match(re);
if (matches)
matches.forEach((m) => found.add(m.trim()));
}
const singleMatches = jobDescription.match(singleTermPattern);
if (singleMatches)
singleMatches.forEach((m) => found.add(m.trim()));
return [...found].slice(0, count);
}
async function fetchRemotive(query, limit = 50, signal) {
const url = `https://remotive.com/api/remote-jobs?search=${encodeURIComponent(query)}&limit=${limit}`;
const data = await fetchJson(url, {}, signal);
return (data.jobs ?? []).map((j) => ({
title: j.title,
url: j.url,
company: j.company_name,
location: j.candidate_required_location || "Remote",
department: j.category,
}));
}
async function fetchRemoteOK(query, signal) {
const data = await fetchJson("https://remoteok.com/api", { headers: { "User-Agent": UA } }, signal);
const q = query.toLowerCase();
return data
.filter((j) => !j.legal && j.url) // skip metadata element
.filter((j) => {
const haystack = `${j.position ?? ""} ${(j.tags ?? []).join(" ")} ${j.company ?? ""}`.toLowerCase();
return q.split(/\s+/).every((term) => haystack.includes(term));
})
.map((j) => ({
title: j.position ?? "Unknown",
url: j.url ?? "",
company: j.company ?? "Unknown",
location: j.location || "Remote",
department: (j.tags ?? []).slice(0, 2).join(", "),
}));
}
// ---------------------------------------------------------------------------
// Regional board registry — site: filter strings by region
// ---------------------------------------------------------------------------
exports.REGIONAL_BOARDS = {
india: [
"site:naukri.com",
"site:foundit.in",
"site:instahyre.com",
"site:iimjobs.com",
"site:hirist.tech",
],
europe: [
"site:xing.com/jobs",
"site:stepstone.de",
"site:stepstone.co.uk",
"site:jobteaser.com",
"site:euraxess.net",
],
sea: [
"site:jobstreet.com",
"site:jobsdb.com",
"site:mynavi.jp",
"site:seek.com.au",
],
latam: [
"site:computrabajo.com",
"site:bumeran.com",
"site:getonbrd.com",
"site:vagas.com",
],
me: [
"site:bayt.com",
"site:naukrigulf.com",
"site:gulfjobs.com",
"site:gulftalent.com",
],
africa: [
"site:jobberman.com",
"site:careers24.com",
"site:brightermonday.com",
"site:myjobmag.com",
],
remote: [
"site:weworkremotely.com",
"site:remote.co/remote-jobs",
"site:workingnomads.com/jobs",
"site:remotive.com",
],
startup: [
"site:wellfound.com/jobs",
"site:workatastartup.com",
"site:jobs.lever.co",
"site:boards.greenhouse.io",
],
};
// ---------------------------------------------------------------------------
// Anti-cliché word list (career-ops writing guidelines)
// ---------------------------------------------------------------------------
exports.CLICHE_WORDS = [
"passionate about", "results-oriented", "leveraged", "spearheaded", "facilitated",
"synergies", "robust", "seamless", "cutting-edge", "innovative", "game-changer",
"thought leader", "guru", "ninja", "rockstar", "wizard", "disruptive",
"dynamic", "proactive", "go-getter", "self-starter", "detail-oriented",
"team player", "hard worker", "fast-paced environment",
];