ATS Jobs Scraper - Career Sites, Greenhouse, Lever, Workday avatar

ATS Jobs Scraper - Career Sites, Greenhouse, Lever, Workday

Pricing

Pay per event

Go to Apify Store
ATS Jobs Scraper - Career Sites, Greenhouse, Lever, Workday

ATS Jobs Scraper - Career Sites, Greenhouse, Lever, Workday

Live jobs from company career sites and careers pages: paste career pages, domains or board URLs. Detects 17 ATSs incl. Greenhouse, Lever, Ashby, Workday, SmartRecruiters. New-job alerts. $1/1k jobs.

Pricing

Pay per event

Rating

0.0

(0)

Developer

Yukai Lin

Yukai Lin

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

25 minutes ago

Last modified

Share

What does ATS Jobs Scraper do?

It returns the open jobs of any list of companies, live from their own career sites. Paste career pages, company websites or job board URLs, in any mix: the Actor finds which applicant tracking system (ATS) each company uses and reads that ATS's public job board feed β€” the JSON, XML and RSS feeds these vendors serve so companies can show their jobs on their own websites. Every job comes back in one normalised format, whatever the ATS.

  • 🏒 17 ATSs, one schema: Greenhouse, Lever (US and EU), Ashby, Workday, SmartRecruiters, Workable, Recruitee, Personio, Breezy HR, Teamtailor, BambooHR, Rippling, Pinpoint, Gem, JazzHR, Homerun and Freshteam
  • πŸ”Ž ATS auto-detection: give it stripe.com or huggingface.co/jobs and it finds the board β€” from links and embeds on the page, the site's careers links, a real browser for JavaScript career pages, and finally verified guesses (stripe.com β†’ greenhouse:stripe, accepted only when the board's company name or job texts match the domain)
  • 🧹 Clean, comparable fields: location (text) plus locations (list), ISO country, workplaceType and remote, employmentType (full-time, part-time, contract, internship…), postedAt in ISO format, jobUrl, applyUrl, and the real company name (read from the board page when the ATS feed has none)
  • πŸ’° Salary columns: salaryMin, salaryMax, salaryCurrency, salaryPeriod from the ATS (Greenhouse pay-transparency ranges included, with every zone in payRanges), or read from the pay-transparency text of the posting (salarySource says which)
  • ⚑ Live, not a database copy: every run reads the boards at that moment, so a job posted an hour ago is there, and jobs that closed are gone
  • πŸ†• Only new jobs since the last run: for daily schedules. Unchanged jobs are not output and not charged; closed jobs get a free closed row
  • 🎯 Filters: title keywords (and exclusions), location, department, remote only, posted in the last N days. Filtered-out jobs are free. Title keywords match whole words and their common forms (engineer β†’ Engineering; excluding intern drops Internship but keeps International Sales); use * for part of a word (*entwickler)
  • 🌍 Optional remote-job sources: add the Remote OK feed and the Hacker News "Who is hiring?" thread, with attribution and links back to each listing
  • 🧾 ATS detection mode: one row per company with its ATS, board URL, number of open jobs, remote jobs, top departments and locations, newest posting date. It also names ATSs it can recognise but not read (iCIMS, Taleo, SuccessFactors, Jobvite, Oracle, UKG, Paylocity…)
  • 🧩 Maps back to your input: every row has input and inputIndex; companies without a readable board get an error row saying why (not charged). The same job found on two boards is returned once
  • πŸ’΅ $1 per 1,000 jobs, down to $0.80 on Apify's Business plan and higher. No start fee

Supported job boards

ATSPaste any of theseFeed readDescriptionPosted dateSalary
Greenhousejob-boards.greenhouse.io/<slug>, boards.greenhouse.io/<slug>, embed URLs, greenhouse:<slug>Job Board APIβœ…βœ… first publishedstructured pay ranges when published, else from the posting text
Leverjobs.lever.co/<slug>, jobs.eu.lever.co/<slug>, lever:<slug>Postings APIβœ… incl. listsβœ…βœ… when published
Ashbyjobs.ashbyhq.com/<slug>, ashby:<slug>Job Posting APIβœ…βœ…βœ… when published
Workday<tenant>.wd5.myworkdayjobs.com/<site>, wd1.myworkdaysite.com/.../recruiting/<tenant>/<site>the career site's JSONβœ… (1 extra request per job)βœ… start dateβ€”
SmartRecruiterscareers.smartrecruiters.com/<Company>, smartrecruiters:<Company>Posting APIβœ… (1 extra request per job)βœ…β€”
Workableapply.workable.com/<slug>, <slug>.workable.com, workable:<slug>widget APIβœ…βœ…β€”
Recruitee<slug>.recruitee.com, recruitee:<slug>Careers Site APIβœ…βœ…βœ… when published
Personio<slug>.jobs.personio.de / .com, personio:<slug>XML feedβœ…βœ…β€”
Breezy HR<slug>.breezy.hr, breezy:<slug>/json feedβ€” (the feed has none)βœ…text
Teamtailor<slug>.teamtailor.com, <slug>.na.teamtailor.com, a Teamtailor careers site on your own domain, teamtailor:<slug>RSS feed (/jobs.rss)βœ…βœ…β€”
BambooHR<slug>.bamboohr.com/careers, bamboohr:<slug>careers-page JSONβœ… (1 extra request per job)βœ… with detailstext when published
Ripplingats.rippling.com/<slug>/jobs, rippling:<slug>the hosted board's JSONβœ… (1 extra request per job)βœ… with detailsβœ… with details
Pinpoint<slug>.pinpointhq.com, pinpoint:<slug>postings.json + RSSβœ…βœ…βœ… when published
Gemjobs.gem.com/<slug>, gem:<slug>Job Board APIβœ…βœ…from the posting text
JazzHR<slug>.applytojob.com, jazzhr:<slug>XML job feedβœ…βœ… creation timeβ€”
Homerun<slug>.homerun.co, homerun:<slug>Atom job feedβœ…β€” (last update only)text when published
Freshteam<slug>.freshteam.com/jobs, freshteam:<slug>careers widget JSONβœ…βœ…β€”

A company website or career page (e.g. linear.app, webull.com, functionhealth.com) works too: the board is detected. For any ATS, the salary columns are also filled from the posting's own pay-range text when the ATS has no salary fields (salarySource: "description"). Greenhouse boards that publish pay ranges (pay transparency) fill the salary columns from those structured ranges instead (salarySource: "ats", salarySourceDetail: "greenhouse_pay_transparency", every range in payRanges).

Extra sources (option Extra sources, same price per job, same filters):

SourceWhat you getTerms we follow
Remote OKthe latest remote jobs of the public Remote OK API (99 jobs on 30 September 2026)Remote OK asks for a link back to the listing and to name Remote OK as the source: every row has jobUrl on remoteok.com and an attribution line
Hacker News "Who is hiring?"top-level posts of the latest monthly thread, via the official HN API (300 posts in the September 2026 thread)jobUrl links to the HN comment; email addresses in posts are removed

Who is it for?

  • Job boards and aggregators: fill a niche board with fresh jobs from a list of companies every day, with only-new-jobs monitoring
  • Recruiters and talent teams: watch competitors' and target companies' openings
  • Sales and market research: hiring signals β€” who is hiring for which teams and where, which ATS a company uses
  • Compensation research: published pay ranges by title and company, in separate min/max/currency/period columns
  • Job seekers and career coaches: one daily list of matching jobs from dream companies
  • AI agents: "find open ML engineer jobs in Berlin at these 50 startups"

How much does it cost?

Price per 1,000 by Apify plan:

EventFreeStarterScaleBusiness and higher
Job$1.00$1.00$0.90$0.80
Company ATS detected (detection mode only)$2.00$2.00$1.80$1.60

No start fee. Not charged: jobs removed by your filters, unchanged jobs with Only new jobs, duplicate jobs found on a second board, closed rows, companies without a readable job board, invalid input lines.

For comparison (checked September 2026, Apify Store prices on the Free plan): other ATS job scrapers charge $0.0015 per job (bovi/greenhouse-lever-ashby-job-scraper), $0.002 (fantastic-jobs Greenhouse, Ashby and Workday APIs), $0.004 (jobo.world ATS scrapers; memo23/career-site-ats-jobs-api) and $0.012 (fantastic-jobs/career-site-job-listing-api, a database of jobs collected in advance rather than a live read of your companies).

Control your cost

  • What is charged: each job returned (jobs mode) or each company whose ATS was identified (detection mode).
  • Max jobs caps the whole run (default 10,000 when you call the API without it: at most $10 on the Free plan). Max jobs per company caps each company.
  • Filters are applied before charging: title keywords, location, department, remote only and posted-in-the-last-N-days all reduce what you pay.
  • Only new jobs makes daily schedules cheap: after the first run, you pay only for jobs that appeared since the previous run.
  • At the start, the run logs its worst case (e.g. Plan: 400 jobs Γ— $0.001 = at most $0.4) and warns when that is more than your maximum charge per run.
  • When the maximum charge per run is reached, the run stops, keeps everything found so far, and says so. The SUMMARY record then has status: "LIMIT_REACHED" and notProcessed (up to 100 companies not processed or not finished). With Only new jobs, jobs that were not delivered because of a limit are reported as new again on the next run.

Worked example (real runs, 30 September 2026, Free plan prices):

RunSettingJobs returnedCost
Engineering jobs at 20 AI startups (OpenAI, Anthropic, Perplexity, Cohere…)titles engineer, developer, scientist; max 20 per company; only new jobs, first run329$0.33
Same monitor, run again 10 minutes lateronly jobs posted since the previous run0$0
Remote jobs posted in the last 24 hours at 15 Greenhouse companiesremote only, posted in the last 1 day35$0.035
Which ATS do 17 companies use?detection mode16 detected, 1 not found (free)$0.032

Without the title filter and caps, the same 20 AI startups had about 3,900 open jobs (about $3.90 for a full first run), so the filter and Max jobs per company are what keep the first run at $0.33. After that you pay only for new postings.

How to use it

  1. Enter your companies, one per line: job board URLs, career pages, websites or ats:slug.
  2. Optional: set filters (title, location, department, remote, posted in the last N days) and Max jobs per company.
  3. Optional: add Extra sources (Remote OK, Hacker News "Who is hiring?").
  4. Optional: turn on Only new jobs since the last run and schedule the Actor daily.
  5. Click Start and export as CSV, Excel or JSON, or call it from the API. The Jobs (overview) table has the columns most people need; Jobs (all fields) has everything.

Input example

{
"companies": [
"https://job-boards.greenhouse.io/airbnb",
"jobs.lever.co/palantir",
"https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
"https://career.teamtailor.com",
"ats.rippling.com/chess/jobs",
"linear.app",
"smartrecruiters:BoschGroup"
],
"titleKeywords": ["engineer", "data scientist"],
"excludeTitleKeywords": ["intern"],
"locationKeywords": ["US", "Remote"],
"postedWithinDays": 14,
"maxJobsPerCompany": 100,
"descriptionFormat": "text"
}

Output example (jobs mode)

A real result from greenhouse:reddit (description shortened), captured before structured pay was read. The salary was read from the posting's pay-range text; Reddit now publishes Greenhouse pay ranges for most jobs, so such jobs come with salarySource: "ats", salarySourceDetail: "greenhouse_pay_transparency" and payRanges:

{
"input": "greenhouse:reddit",
"inputIndex": 5,
"success": true,
"jobKey": "greenhouse:reddit:8147559",
"jobId": "8147559",
"title": "Front End Software Engineer, Consumer Engineering",
"company": "Reddit",
"department": "Consumer",
"team": null,
"location": "Remote - United States",
"locations": ["Remote - United States"],
"country": "US",
"workplaceType": "remote",
"remote": true,
"employmentType": null,
"employmentTypeText": null,
"salaryMin": 164200,
"salaryMax": 229900,
"salaryCurrency": "USD",
"salaryPeriod": "year",
"salaryText": "$164,200β€”$229,900 USD",
"salarySource": "description",
"postedAt": "2026-09-17T17:07:34.000Z",
"postedAtApproximate": false,
"updatedAt": "2026-09-17T17:07:34.000Z",
"jobUrl": "https://job-boards.greenhouse.io/reddit/jobs/8147559",
"applyUrl": "https://job-boards.greenhouse.io/reddit/jobs/8147559",
"requisitionId": "Pooled",
"descriptionText": "Reddit is a community of communities. It’s built on shared interests, passion, and trust, and is home to the most open and authentic conversations on …",
"source": "ats",
"ats": "greenhouse",
"atsSlug": "reddit",
"boardUrl": "https://job-boards.greenhouse.io/reddit",
"detectionMethod": "input",
"charged": true,
"scrapedAt": "2026-09-30T04:52:29.128Z"
}

Fields are the same for every ATS; a field the ATS does not publish is null. jobKey (ats:slug:jobId) is unique across boards and stable between runs. workplaceType is remote, hybrid, onsite or null; employmentType is the normalised value and employmentTypeText the ATS's own wording. postedAtApproximate is true when the date comes from Workday's "Posted N Days Ago". detectionMethod tells how the board was found: input, career-page, careers-link, career-page-browser, slug-guess or source.

A row from the Hacker News source (other fields as above):

{ "input": "source:hackernews", "jobKey": "hackernews:49524580", "title": "Lead Product Manager, Security", "company": "Wikimedia Foundation", "location": "REMOTE (US + 18 countries)", "workplaceType": "remote", "employmentType": "full-time", "postedAt": "2026-09-01T16:52:15.000Z", "jobUrl": "https://news.ycombinator.com/item?id=49524580", "source": "hackernews", "attribution": "Job post from the Hacker News \"Ask HN: Who is hiring?\" thread (https://news.ycombinator.com), via the official HN API. Link back to jobUrl when you republish it." }

With Only new jobs, rows also have changeType: "new" and firstSeenAt, and jobs that disappeared get a free row with changeType: "closed", firstSeenAt and closedAt.

Output example (ATS detection mode)

Real result for webull.com (lists shortened):

{
"input": "webull.com",
"success": true,
"company": "Webull",
"companyWebsite": "https://webull.com",
"ats": "rippling",
"atsSupported": true,
"atsSlug": "webull",
"boardUrl": "https://ats.rippling.com/webull/jobs",
"jobCount": 29,
"remoteJobs": 0,
"topDepartments": [{ "name": "Finance", "jobs": 8 }, { "name": "Institutional", "jobs": 4 }, { "name": "Clearing Operations", "jobs": 3 }],
"topLocations": [{ "name": "Saint Petersburg, FL", "jobs": 19 }, { "name": "New York, NY", "jobs": 9 }, { "name": "Toronto, Canada", "jobs": 1 }],
"detectionMethod": "careers-link",
"foundOn": "https://www.webull.com/careers",
"charged": true
}

In the same run, stripe.com β†’ greenhouse:stripe (711 jobs), functionhealth.com β†’ gem:function-health (33), ioet.com β†’ teamtailor:ioet.na (5), youlend.com β†’ pinpoint:youlend (25) and getflowpath.com β†’ freshteam:getflowpath (3).

A company without a readable job board

Real output for netflix.com (its career site is not on a supported ATS; pagesTried shortened). Not charged:

{
"input": "netflix.com",
"success": false,
"errorType": "not_found",
"error": "no supported ATS job board found on https://netflix.com or its career links (supported: greenhouse, lever, ashby, workday, smartrecruiters, workable, recruitee, personio, breezy, teamtailor, bamboohr, rippling, pinpoint, gem, jazzhr, homerun, freshteam). Tip: paste the job board URL instead (not charged)",
"pagesTried": [{ "url": "https://netflix.com", "via": "direct" }, { "url": "https://jobs.netflix.com/jobs", "via": "browser" }, { "url": "greenhouse:netflix", "result": "no match" }],
"charged": false
}

errorType is unsupported when the company uses an ATS without a public feed (listed in atsDetected), not_found when no board was found or a board URL does not exist, no_jobs when the board was read but has no open jobs right now (or the company id is wrong: SmartRecruiters ids are case-sensitive), network/timeout/blocked when a page or feed could not be loaded (rate limits and server errors are retried with backoff first), and invalid_input for lines that are not a company.

The SUMMARY record (key-value store) has status (SUCCESS, PARTIAL_RESULTS, FAILED, NO_RESULTS, LIMIT_REACHED), totals (including duplicatesSkipped), and perCompany: ATS, slug, company, jobsFound (open jobs on the board) and jobsOutput (after filters) for every company.

Integrations

  • Schedules: run daily with Only new jobs for a new-jobs feed.
  • API and webhooks: start runs and read the dataset from any language; use a webhook to get notified when a run finishes.
  • Exports: CSV, Excel, JSON, XML and HTML from the dataset (the overview view gives a flat table).
  • Apify integrations: send results to Google Sheets, Slack, Zapier, Make, n8n and others from the Integrations tab.
  • AI agents (MCP): connect Apify's MCP server (https://mcp.apify.com?tools=tidytools/ats-career-site-jobs) to Claude, Cursor or any MCP client, then ask e.g. "List open data engineering jobs in Europe at linear.app, notion.so and ramp.com, posted in the last two weeks."
{ "companies": ["linear.app", "notion.so", "ramp.com"], "titleKeywords": ["data"], "locationKeywords": ["Europe", "London", "Berlin"], "postedWithinDays": 14 }

Tips and limitations

  • Pasting the board URL is the most reliable input. Detection from a website works for most startups and scale-ups; big companies often use career sites on ATSs without a public feed (they get an unsupported or not_found row, free).
  • Guessed boards (detectionMethod: "slug-guess") are accepted only when the board has open jobs, its newest job is less than a year old, and its company name or job texts match the domain. Turn Guess the board off in Advanced settings if you only want boards linked from the website.
  • Workday lists at most 2,000 jobs per search. For bigger employers, a single title keyword is sent to Workday as a search, so you still reach the matching jobs.
  • Extra requests per job: Workday, SmartRecruiters, Rippling and BambooHR need one request per job for descriptions; choose Job description: None for faster runs. Rippling and BambooHR then have no posting date (and Rippling no salary) unless Posted in the last N days is set.
  • Homerun feeds have no publication date (only updatedAt), so Posted in the last N days drops Homerun jobs. JazzHR dates are the job's creation time. Freshteam feeds have no employment type.
  • Salary from the posting text is read only when a currency and a plausible range are written (e.g. $164,200β€”$229,900); single amounts and vague text are ignored. In a real run on 92 software engineer jobs from 10 companies, 49 salaries came from the ATS and 35 from the posting text.
  • Greenhouse pay ranges: when a job has several ranges (location zones such as "Zone 1 (New York…)", or two currencies), salaryMin/salaryMax span the ranges in the first range's currency and payRanges lists each one (title, min, max, currency, interval). A range without a stated period is read as yearly from 10,000 and hourly up to 200.
  • Career page requests from: Our second server only sends career pages and job board API requests through our second server (another IP), for boards that refuse data-center networks. Workday searches are POST requests and still go from Apify's network. SUMMARY shows requestsViaSecondServer.
  • Hacker News titles and locations are read from the first line of each post (Company | Role | Location | …); posts that do not follow that pattern use their first line or the first role mentioned. Only the latest monthly thread is read.
  • SmartRecruiters company ids are case-sensitive (BoschGroup); an unknown id returns 0 jobs.
  • Only new jobs remembers jobs per monitor name. Use a different monitor name when you change the company list or filters.
  • Job boards change their feeds from time to time; if a board stops working, open an issue with the URL.

Responsible use

The Actor reads only public job postings from the job feeds that ATS vendors provide for publishing jobs on career sites and job boards, and from public job feeds whose terms allow reuse with attribution. It does not log in, does not collect applicant data and does not extract recruiters' personal contact details (email addresses in Hacker News posts are removed). If you republish jobs, link to the original posting (jobUrl), keep the attribution of Remote OK and Hacker News rows, and follow the rules of the site where you publish them.

FAQ

How fresh are the jobs? As fresh as the company's board: each run reads it live.

Why did I get fewer jobs than the board shows? Check your filters, Max jobs per company and Max jobs, duplicates removed across boards (SUMMARY.duplicatesSkipped), and SUMMARY.perCompany[].jobsFound.

Why is company different from the slug? The name comes from the ATS feed, or from the board page's title when the feed has none (Lever, Ashby, Gem, BambooHR, Freshteam): jobs.lever.co/palantir gives "Palantir Technologies". Workday feeds have no company name, so the tenant name is used ("Nvidia").

Can I run it on a schedule? Yes. Turn on Only new jobs since the last run, set a monitor name, and create a daily schedule in Apify Console; each run returns only the jobs posted since the previous one.

Which ATSs can't it read? iCIMS, Taleo, SuccessFactors, Jobvite, Oracle Recruiting, UKG, Paylocity, ADP, Paycom, Dayforce, Zoho Recruit, HiBob, Comeet, JOIN and a few others are recognised (detection mode tells you which one a company uses) but not read, because they publish no public job feed we can use.

My company's ATS is not supported. Detection mode tells you which ATS it uses; open an issue and it may be added.