💼 ATS Job Scraper: Workday, Greenhouse, Lever, Ashby + more avatar

💼 ATS Job Scraper: Workday, Greenhouse, Lever, Ashby + more

Pricing

from $2.10 / 1,000 job records

Go to Apify Store
💼 ATS Job Scraper: Workday, Greenhouse, Lever, Ashby + more

💼 ATS Job Scraper: Workday, Greenhouse, Lever, Ashby + more

One job feed from Workday, Greenhouse, Lever, Ashby and SmartRecruiters career sites. Title, location, pay, posted date, description and apply link in one clean format. Full Workday boards past the 2,000 cap. Only-new mode for monitoring. No login, no proxies.

Pricing

from $2.10 / 1,000 job records

Rating

5.0

(1)

Developer

Fin Data

Fin Data

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

a day ago

Last modified

Categories

Security

Limited permissions

Share

Workday, Greenhouse, Lever, Ashby and SmartRecruiters Job Scraper

One clean job feed across the five applicant tracking systems that power most corporate career sites. Paste career-site URLs, get every open role as a normalized record: title, department, location, remote flag, employment type, salary, posting date, description and apply link. The same schema whether the company runs Workday or Greenhouse.

Built for recruiters, staffing firms, job boards, sales teams that use hiring as a buying signal, and AI agents that need job data without parsing five different APIs.

Why these five, in this order

Workday runs the career sites of most large enterprises, and it is the hardest of the five to scrape well: its job API is undocumented, pages 20 at a time, and stops at 2,000 results. This actor handles all of that and returns the whole board. Greenhouse, Lever and Ashby cover most technology and growth companies and publish clean public APIs, so descriptions and (for Lever and Ashby) structured salary ranges come free. SmartRecruiters rounds out the large-employer coverage, especially outside the US.

Together they cover the majority of employers that post jobs on their own career site rather than only on job boards.

What you get

  • Five ATS vendors, one output schema. The vendor is detected from the URL; you never say which ATS a company uses.
  • Whole Workday boards, including sites over the 2,000-job cap (split by facet automatically and de-duplicated).
  • Only-new mode. Run it on a schedule and get only postings that were not on the board last time. You pay only for jobs that are output.
  • Filters that run before billing: title keywords, exclude words, locations, departments, remote only, posted within N days.
  • Salary parsing. Structured ranges when the ATS provides them (Lever, Ashby), parsed from text when it does not (Workday pay ranges, Greenhouse descriptions).
  • Fail soft. One moved or broken board is logged and skipped; the rest of the run completes.

Supported career sites

ATSURL patternDescription in list callNotes
Workday{tenant}.{wd1..wd5}.myworkdayjobs.com/{site}With Fetch detailsAny language path works; the actor strips it. Boards over 2,000 jobs are split by facet
Greenhouseboards.greenhouse.io/{company}, job-boards.greenhouse.io/{company}YesEU-hosted boards detected automatically
Leverjobs.lever.co/{company}, jobs.eu.lever.co/{company}YesStructured salary ranges when published
Ashbyjobs.ashbyhq.com/{company}YesStructured compensation when published
SmartRecruiterscareers.smartrecruiters.com/{Company}, jobs.smartrecruiters.com/{Company}With Fetch detailsCompany identifier is case-sensitive

You can also write sources as vendor:identifier, for example workday:workday.wd5/Workday or greenhouse:stripe.

Input

{
"sources": [
"https://workday.wd5.myworkdayjobs.com/en-US/Workday",
"https://boards.greenhouse.io/stripe",
"https://jobs.lever.co/palantir",
"https://jobs.ashbyhq.com/notion",
"https://careers.smartrecruiters.com/BoschGroup"
],
"keywords": ["accountant", "financial analyst", "controller"],
"locations": ["Atlanta", "Georgia", "GA", "Remote"],
"postedWithinDays": 14,
"onlyNew": true,
"fetchDetails": false
}
FieldTypeDefaultWhat it does
presetstringcustomEmployer list: custom (default), sample, georgia-sc. custom runs the sites you list and falls back to the sample boards when the list is empty
sourcesstring[][]Career-site URLs or vendor:identifier strings
keywordsstring[][]Keep jobs whose title contains any of these (case-insensitive)
excludeKeywordsstring[][]Drop jobs whose title contains any of these
locationsstring[][]Keep jobs whose location text contains any of these
departmentsstring[][]Keep jobs whose department or team contains any of these
remoteOnlybooleanfalseKeep only jobs flagged remote
postedWithinDaysinteger0Keep only jobs posted or updated in the last N days; 0 disables
fetchDetailsbooleanfalseOne extra request per job for Workday and SmartRecruiters to get description, exact dates, additional locations. Only jobs that pass the title and location filters are fetched, so a 2,000-job board filtered to one city costs a few dozen requests
includeDescriptionHtmlbooleanfalseAdd the raw HTML description next to the plain text
onlyNewbooleanfalseOutput only jobs not seen on a previous run of this source
seenStoreNamestringats-jobs-feed-seenKey-value store that remembers seen job IDs; use one name per monitor
maxJobsPerSourceinteger0Stop after N jobs per source; 0 means all
maxConcurrencyinteger5Career sites pulled in parallel

Output

One record per job. Fields are identical across vendors; vendor-specific extras sit under extra.

{
"vendor": "workday",
"source": "https://workday.wd5.myworkdayjobs.com/en-US/Workday",
"company": "Workday, Inc.",
"jobId": "JR-0110787",
"title": "Senior Product Manager, Technical",
"department": null,
"team": null,
"location": "USA, CA, Pleasanton",
"locations": ["USA, CA, Pleasanton"],
"country": "US",
"remote": null,
"employmentType": "Full Time",
"postedAt": "2026-11-04T00:00:00.000Z",
"updatedAt": null,
"url": "https://workday.wd5.myworkdayjobs.com/Workday/job/USA-CA-Pleasanton/Senior-Product-Manager--Technical_JR-0110787",
"applyUrl": "https://workday.wd5.myworkdayjobs.com/Workday/job/USA-CA-Pleasanton/Senior-Product-Manager--Technical_JR-0110787",
"descriptionText": "Your work days are brighter here...",
"salaryMin": 168000,
"salaryMax": 252000,
"salaryCurrency": "USD",
"salaryPeriod": "year",
"salaryText": "Primary Location Base Pay Range: $168,000 USD - $252,000 USD",
"extra": { "postedOnText": "Posted Today", "remoteType": null, "bulletFields": ["JR-0110787"], "endDate": null, "externalPath": "/job/USA-CA-Pleasanton/Senior-Product-Manager--Technical_JR-0110787" },
"scrapedAt": "2026-11-05T01:47:38.912Z"
}
FieldMeaning
vendorworkday, greenhouse, lever, ashby or smartrecruiters
companyCompany name as the ATS reports it, or the board identifier when the ATS does not expose a name. Workday reports the hiring organization name when Fetch details is on, otherwise the tenant
jobIdStable ID within the vendor (Workday requisition ID, Greenhouse numeric ID, Lever and Ashby UUIDs, SmartRecruiters posting ID). Use vendor + jobId as a global key
location, locationsPrimary location text and every location listed, primary first
remotetrue when the ATS flags remote or the location says so; false when the ATS says on-site; null when unknown
postedAt, updatedAtISO 8601. Workday list pages give relative dates ("Posted 3 Days Ago"); these are converted, and "30+ Days Ago" becomes null unless Fetch details is on
salaryMin, salaryMax, salaryCurrency, salaryPeriodNumbers when the ATS publishes a range or the description states one; otherwise null
descriptionTextPlain text. Available in the list call for Greenhouse, Lever and Ashby; needs Fetch details for Workday and SmartRecruiters

A RUN_SUMMARY record in the default key-value store reports sources processed, jobs fetched, filtered, de-duplicated, skipped as already seen, and any source failures with the error text.

Pricing

Pay per event. You are charged per job record written to the dataset, plus a small actor-start event. Filtered-out jobs and already-seen jobs in only-new mode are not charged. No proxies are needed, so platform usage is minimal: a run returning 186 jobs from three boards used under one cent of platform resources.

Monitoring new postings

  1. Set onlyNew to true and give seenStoreName a name for this monitor.
  2. Add your filters.
  3. Schedule the actor (daily works well; hourly for high-volume boards).
  4. Connect the dataset to Google Sheets, Slack, Make, Zapier, or a webhook from the Integrations tab.

The first run outputs everything currently on the boards and remembers it. Each later run outputs only what is new. Jobs that leave a board and come back are treated as new.

Finding career-site URLs

Open a company's careers page and click any job. The address bar shows the ATS: myworkdayjobs.com, boards.greenhouse.io, jobs.lever.co, jobs.ashbyhq.com, or smartrecruiters.com. Paste that URL as a source; the actor trims it to the board.

Limits and notes

  • Public job-board endpoints only. No login, no proxies, no private data.
  • Workday returns at most 2,000 jobs per query. Boards above that are split by facet; a handful of very large boards with coarse facets may still miss a few postings. Tested on a 2,763-job board.
  • Workday company is the tenant name unless Fetch details is on, which reads the hiring organization name.
  • Greenhouse and Lever boards that are closed or renamed return an error for that source only; the run continues.
  • Descriptions are plain text by default. Turn on includeDescriptionHtml for the original markup.

For AI agents

Every field is optional: an empty input {} runs the sample boards and returns jobs, so a first call never fails. Pick "preset": "sample" to say so explicitly, or "preset": "georgia-sc" for the curated Georgia and upstate SC employer list. Minimal call: { "sources": ["https://boards.greenhouse.io/stripe"] }. Pass any careers URL from the five supported systems; the vendor is detected. Add "keywords" and "locations" arrays to narrow, "fetchDetails": true for Workday and SmartRecruiters descriptions. Every record has the same fields; vendor + jobId is the stable key.

Same engine, same output fields, so records from any of them can be combined.

Using it from code

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('findata/ats-jobs-feed').call({
sources: ['https://workday.wd5.myworkdayjobs.com/en-US/Workday'],
keywords: ['engineer'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Works the same through the Apify API, MCP server and the Python client.