Web Scraping Layers for AI & Automation

back to blog

How to scrape Indeed, Greenhouse, and Handshake for a recruiting pipeline

ParseBird·11 Sep 2026

Key Takeaways

Why scrape three job boards instead of one? Because they don't overlap the way you'd expect: Indeed is broad but under-discloses salary, Greenhouse is salary-rich but limited to the ~140 companies actually using it as their public board, and Handshake is the only one of the three with meaningful internship and early-career coverage.

Do any of these have an official public API for bulk job search? No. Indeed and Handshake have no public bulk-search API at all; Greenhouse's own API is per-company (you'd need each employer's board token and would still have to query them one at a time), which is exactly the gap a cross-company scraper is built to close.

Is the salary data on these boards reliable, or estimated? It varies by source and by law — Greenhouse's compensation field is parsed directly from the posting text, so it's only populated when the employer actually discloses a range (increasingly required by state pay-transparency laws). Indeed's salary object includes a salarySource field specifically so you can tell an employer-stated figure from an Indeed estimate.

Three Boards, Three Different Jobs

BoardBest forWhat it uniquely covers
IndeedVolume, broad market scanning60+ country editions, company revenue/size/CEO enrichment on every job
GreenhouseTech-sector salary benchmarkingDisclosed compensation ranges at 140+ named tech employers (Anthropic, Stripe, Figma, Coinbase)
HandshakeEarly-career and internshipsThe largest early-career platform in the US, 15M+ students across 1,500+ universities

Indeed: Breadth and Company Enrichment

The Indeed Jobs Scraper reads Indeed's own mobile data source rather than parsing rendered HTML, which is why it returns 40+ fields per job instead of the handful visible on a search results page:

{
  "title": "Senior Data Analyst",
  "companyName": "Acme Analytics",
  "salary": { "salaryMin": 95000, "salaryMax": 130000, "salaryType": "yearly", "salarySource": "employer" },
  "location": { "city": "Austin", "admin1Code": "TX", "latitude": 30.267, "longitude": -97.743 },
  "isRemote": true,
  "companyNumEmployees": "51 to 200",
  "companyIndustry": "Data Analytics"
}

It supports two input modes: paste ready-made Indeed search or company URLs directly, or build a search from query + country (a required 2-letter code covering 60+ editions) plus filters for radius, job type, experience level, and how recently a job was posted. companyNumEmployees, companyRevenue, and companyIndustry come attached to every single job — useful for filtering a candidate pipeline by employer size without a separate enrichment step.

Greenhouse: Salary Transparency at Named Tech Companies

Greenhouse has no single search endpoint spanning every company that uses it — each employer runs an isolated board. The Greenhouse Jobs Scraper maintains a curated list of 140+ active boards (Anthropic, Stripe, Databricks, Cloudflare, Roblox, SpaceX, and more) and queries all of them in one call:

{
  "title": "Senior Software Engineer, Infrastructure",
  "normalized_title": "Senior Software Engineer",
  "company": { "name": "Stripe", "board_token": "stripe" },
  "compensation": { "min": 190000, "max": 250000, "currency": "USD", "period": "year" },
  "workplace_type": "remote",
  "experience_level": "senior"
}

compensation is null when an employer doesn't disclose a range — it's parsed directly from the posting text, never estimated. min_salary_usd/max_salary_usd filters let you skip straight to jobs with a disclosed floor above whatever your benchmark is, and employment_type/workplace_type/experience_level are all classified by transparent keyword rules against the title and posting, not a black-box model — so a wrong tag is traceable back to the specific keyword that caused it.

Handshake: Early-Career and Internships

Neither of the above is built for internship or new-grad search. The Handshake Jobs Scraper covers Handshake's 50,000+ active postings — the largest early-career job network in the US:

{
  "job_title": "Marketing Intern",
  "company_name": "Acme Corp",
  "location": "Remote",
  "is_remote": true,
  "job_type": "Internship",
  "salary_min": 20,
  "salary_max": 25,
  "salary_period": "hourly",
  "date": "2026-09-15"
}

datePosted filters to today, 3days, week, or month — for a recurring recruiting feed, today on a daily schedule catches new postings the same day they go live, which matters more here than on Indeed given how quickly early-career roles fill.

Merging Into One Feed

function normalizeJob(source, job) {
  const map = {
    indeed: () => ({ title: job.title, company: job.companyName, salaryMin: job.salary?.salaryMin, salaryMax: job.salary?.salaryMax, remote: job.isRemote, url: job.jobUrl }),
    greenhouse: () => ({ title: job.title, company: job.company.name, salaryMin: job.compensation?.min, salaryMax: job.compensation?.max, remote: job.workplace_type === "remote", url: job.listing_url }),
    handshake: () => ({ title: job.job_title, company: job.company_name, salaryMin: job.salary_min, salaryMax: job.salary_max, remote: job.is_remote, url: job.URL }),
  };
  return { source, ...map[source]() };
}

Once normalized, run the merged list through the Data Deduplicator on title + company — the same postings occasionally get cross-posted to Indeed by companies that also run a Greenhouse board.

FAQ

Which one should you scrape first if you can only pick one? Indeed, for breadth — it's the only one of the three that isn't scoped to a specific company list or a single early-career segment.

Do these actors need a login or API key for the underlying site? No — all three read public search and job-detail pages directly. None require an Indeed, Greenhouse, or Handshake account.

For scraping European job boards specifically, see Scraping European Job Boards for Multi-Market Recruiting, or Aggregating Job Boards Into One Recruiting Feed for the general dedup pattern.