ATS Jobs Scraper — Workday, Greenhouse, BambooHR | $1.49/1k avatar

ATS Jobs Scraper — Workday, Greenhouse, BambooHR | $1.49/1k

Pricing

from $1.49 / 1,000 job postings

Go to Apify Store
ATS Jobs Scraper — Workday, Greenhouse, BambooHR | $1.49/1k

ATS Jobs Scraper — Workday, Greenhouse, BambooHR | $1.49/1k

Open jobs from company career boards on Workday, Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Personio, Teamtailor, BambooHR and Breezy. Give board URLs or company names; get one normalized row per job: title, location, remote, salary, dates, advert text. No API keys.

Pricing

from $1.49 / 1,000 job postings

Rating

0.0

(0)

Developer

Raffy

Raffy

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

0

Monthly active users

17 days ago

Last modified

Share

What does ATS Jobs Scraper do?

ATS Jobs Scraper reads the public job boards that companies host on eleven applicant-tracking systems (ATS) - Workday, Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Personio, Teamtailor, BambooHR and Breezy HR - and returns one normalized row per open job. You give it a list of companies; it gives you every posting they have open right now, in the same columns whichever system the company uses: title, department, location, remote flag, employment type, salary range where the ATS publishes one, dates, posting and apply links, and the full advert as plain text.

It works as a Workday API, Greenhouse API, Lever API and Ashby API alternative in one call: no API key, no account on any of the systems, no browser and no proxy. Each company is read with plain HTTP requests to the endpoint the company's own careers page uses, so a run over dozens of companies finishes in seconds and the data is first-hand - never a repost from an aggregator. Workday covers large enterprises (NVIDIA, Salesforce and many other big employers run their career sites on it); the other ten systems cover startups and the mid-market.

It does not log in, does not read private applicant data and does not return recruiter or hiring-manager names, e-mail addresses or phone numbers.

Use cases for this job board API

  • Hiring-signal lead generation - feed in your target-account list and see which accounts are hiring, for which teams and where. Ten open "Data Engineer" roles is a buying signal for data tooling; a first "Head of Security" role is one for security vendors. Filter with titleKeywords and you pay only for the roles that matter to you.
  • Job board and newsletter aggregation - pull the openings of the companies you cover straight from the source, with stable jobIds for de-duplication and direct applyUrls.
  • Recruiting and talent-market research - track which departments grow at competitors, how much of their hiring is remote, and what pay ranges they publish.
  • Sales and investor research - headcount plans show up on a careers page long before they show up anywhere else.
  • Automation - schedule a daily run and send new rows to Google Sheets, Airtable, Supabase or a CRM through Apify integrations, webhooks, n8n, Make or Zapier. Diff on ats + companySlug + jobId to get "new jobs today".

Supported applicant-tracking systems

ATSExample entryAlso acceptedNotes
Greenhousehttps://boards.greenhouse.io/stripehttps://job-boards.greenhouse.io/stripe, greenhouse:stripePay range when the company publishes pay transparency ranges.
Leverhttps://jobs.lever.co/palantirlever:palantir, https://jobs.eu.lever.co/<slug>, lever-eu:<slug>Workplace type, team and country on every posting.
Ashbyhttps://jobs.ashbyhq.com/rampashby:rampStructured salary (min, max, currency, period) when the company shows compensation.
SmartRecruitershttps://jobs.smartrecruiters.com/BoschGroupsmartrecruiters:BoschGroupNeeds the company id from the board URL. The advert text takes one extra request per job.
Workablehttps://apply.workable.com/bluegroundworkable:bluegroundCompany boards on apply.workable.com.
Recruiteehttps://bunq.recruitee.comrecruitee:bunqUse the <company>.recruitee.com address, not the company's own careers domain.
Personiohttps://holidu.jobs.personio.depersonio:holidu, .jobs.personio.comRead in English; monthly or yearly salary when published.
Teamtailorhttps://career.teamtailor.comteamtailor:career.teamtailor.comHost-based: give the address of the careers site.
Workdayhttps://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSiteany page under that career site (with or without /en-US/), workday:nvidia/wd5/NVIDIAExternalCareerSiteThe address needs the tenant, the data centre (wd1, wd5, wd12, ...) and the site name, so copy it from the career site. Title keywords also go to Workday's own search. The advert text, every location, the posting date and the time type take one extra request per job.
BambooHRhttps://particle.bamboohr.com/careersbamboohr:particleThe advert text and the posting date take one extra request per job.
Breezy HRhttps://brilliant.breezy.hrbreezy:brilliantThe advert text and the structured pay range come from the posting page: one extra request per job.

You can also type just the company name (stripe, ramp). The Actor then tries ten of the systems - all but Workday, whose address cannot be guessed from a name - in the order of the table above until one lists jobs for that name, and records which one matched in the ats column. That costs up to 10 small requests per spelling of the name, so a board URL or ats:slug is faster and exact. When no system knows the name you get one free not_found row for that company - an entry never disappears silently.

What data can ATS Jobs Scraper extract?

Every ok row has the same columns. A value the ATS does not publish is null; nothing is guessed from the advert text.

FieldTypeDescription
statusstringok, not_found or error (see below)
companystringCompany display name as the ATS publishes it; derived from the slug for Lever, Ashby and BambooHR, and from the tenant name for Workday (Nvidia), which publish none
companySlugstringThe company's identifier on the ATS (the careers host name for Teamtailor; tenant/wdN/site for Workday, the same form workday: accepts)
atsstringgreenhouse, lever, ashby, smartrecruiters, workable, recruitee, personio, teamtailor, workday, bamboohr or breezy
jobIdstringThe posting's id on that ATS; unique together with ats and companySlug
titlestringJob title
departmentstringDepartment, when published
teamstringTeam within the department (Lever, Ashby)
locationstringPrimary location as displayed on the board
locationsarrayEvery location listed for the posting, primary first
countrystringISO code (Lever, SmartRecruiters, Recruitee, Workable, Teamtailor, Workday, Breezy) or country name (Ashby, BambooHR); null for Greenhouse and Personio
workplaceTypestringremote, hybrid or onsite, only when the ATS has an explicit workplace field (Lever, Ashby, SmartRecruiters, Recruitee, Breezy, and Workday tenants whose remote type names one)
isRemotebooleanThe ATS's remote flag; for Greenhouse, Personio, Teamtailor and Workday tenants without a remote type, whether the location text says Remote
employmentTypestringFull-time, Part-time, Contract, Temporary, Internship, Permanent, or the ATS's own wording
salaryMin, salaryMaxnumberPay range from the ATS's structured pay fields only
salaryCurrencystringISO 4217 code, e.g. USD
salaryPeriodstringyear, month, week, day or hour, when stated
postedAtstringWhen the posting was published, ISO 8601 UTC (Workday and BambooHR state it on the posting's own page, so it is filled when includeDescription is on)
updatedAtstringWhen it was last updated (Greenhouse, Recruitee)
urlstringPublic posting URL
applyUrlstringDirect link to the application form, when published
descriptionTextstringFull advert as plain text; null when includeDescription is off
sourceEndpointstringThe public ATS endpoint the row was read from
scrapedAtstringISO 8601 time of extraction
errorstringReason, on not_found and error rows only

SmartRecruiters, Workday, BambooHR and Breezy rows can also carry descriptionError: the reason why the extra request for the advert text failed. The rest of that row is still there.

Result status (tri-state output)

statusMeaningBilled?
okOne open job posting.Yes
not_foundThe company has no board on the checked system(s), its board lists no open jobs, or no job matched your filters. One row per company, with the reason in error.No
errorThe board could not be read after retries (network error, block, or a changed response format). error says why.No

A run whose companies all come back not_found (none is hiring, nothing matched your filters, or no system knows the name) is an answer, not a failure: it ends as Succeeded with those free rows and the status message "No results for this input". A run ends as Failed only when a board could not be read (error rows) or nothing was written at all.

How to scrape Workday, Greenhouse, Lever and other ATS job boards

  1. Open the Actor in Apify Console and click Try for free.
  2. Put one company per line into Companies or job board URLs: a board URL, an ats:slug pair, or a company name.
  3. Optionally narrow the run with Title keywords, Location contains, Remote jobs only or Posted within (days). Filters run before a row is written, so you only pay for matches.
  4. Set Maximum results (the default 20 keeps a first run cheap) and, if one large employer should not use up the run, Maximum results per company.
  5. Click Start. Records appear in the dataset in real time; open the Output tab or Export them as JSON, CSV, Excel, XML or HTML.

To automate it, use the API tab (Node.js, Python, curl examples) or add a Schedule.

Input

See the Input tab for the full schema. The main options:

FieldTypeDefaultDescription
companiesarray of stringsthree sample boardsBoard URLs (a Workday career-site URL included), ats:slug pairs or company names, one per entry
titleKeywordsarray of strings-Keep a job when its title contains any of these (case-insensitive); on Workday also sent to Workday's own search
locationContainsstring-Keep a job when its location, extra locations or country contains this text
remoteOnlybooleanfalseKeep only remote postings
postedWithinDaysinteger-Keep only jobs published in the last N days (falls back to the updated date)
maxItemsinteger20Stop after this many rows
maxItemsPerCompanyinteger- (prefilled 7)Cap per company
includeDescriptionbooleantrueAdd descriptionText; turn off for lighter, faster runs
startUrlsarray of stringsthe same three boardsAlias of companies for tools that always send startUrls
proxyConfigurationobjectApify Proxy offNot needed; the boards answer datacenter IPs

Example input (the prefilled default):

{
"companies": ["greenhouse:duolingo", "ashby:linear", "lever:spotify"],
"maxItems": 20,
"maxItemsPerCompany": 7,
"includeDescription": true
}

A hiring-signal run over your own account list, enterprise Workday boards included:

{
"companies": [
"https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
"workday:salesforce/wd12/External_Career_Site",
"https://boards.greenhouse.io/stripe",
"https://jobs.ashbyhq.com/ramp",
"palantir",
"smartrecruiters:BoschGroup",
"bamboohr:particle"
],
"titleKeywords": ["data engineer", "analytics", "machine learning"],
"postedWithinDays": 30,
"includeDescription": false,
"maxItems": 500
}

Output

You can download the dataset in various formats such as JSON, HTML, CSV or Excel. Real rows from a run with the default input on 2026-09-20 and a real Workday row from a run on 2026-09-27 (descriptionText shortened here), followed by the real not_found row a run gave for a company name that no system knows:

[
{
"status": "ok",
"company": "Duolingo",
"companySlug": "duolingo",
"ats": "greenhouse",
"jobId": "8705196002",
"title": "Ad Sales Lead - West",
"department": "Business",
"team": null,
"location": "Remote - California",
"locations": ["Remote - California"],
"country": null,
"workplaceType": null,
"isRemote": true,
"employmentType": null,
"salaryMin": 152000,
"salaryMax": 228000,
"salaryCurrency": "USD",
"salaryPeriod": null,
"postedAt": "2026-08-13T13:22:34.000Z",
"updatedAt": "2026-09-03T16:23:10.000Z",
"url": "https://careers.duolingo.com/jobs/8705196002?gh_jid=8705196002",
"applyUrl": "https://careers.duolingo.com/jobs/8705196002?gh_jid=8705196002",
"descriptionText": "Our mission at Duolingo is to develop the best education in the world and make it universally available. ...",
"sourceEndpoint": "https://boards-api.greenhouse.io/v1/boards/duolingo/jobs?content=true&pay_transparency=true",
"scrapedAt": "2026-09-20T19:16:13.558Z"
},
{
"status": "ok",
"company": "Spotify",
"companySlug": "spotify",
"ats": "lever",
"jobId": "2193db3f-77c5-43b8-b030-8f92c9882bf1",
"title": "Android Engineer - Experience",
"department": "Engineering",
"team": "Experience",
"location": "London",
"locations": ["London", "Stockholm"],
"country": "GB",
"workplaceType": "hybrid",
"isRemote": false,
"employmentType": "Permanent",
"salaryMin": null,
"salaryMax": null,
"salaryCurrency": null,
"salaryPeriod": null,
"postedAt": "2026-06-23T11:29:45.805Z",
"updatedAt": null,
"url": "https://jobs.lever.co/spotify/2193db3f-77c5-43b8-b030-8f92c9882bf1",
"applyUrl": "https://jobs.lever.co/spotify/2193db3f-77c5-43b8-b030-8f92c9882bf1/apply",
"descriptionText": "We design Spotify’s consumer experience—end to end, moment to moment, across every screen, platform, and partner integration. ...",
"sourceEndpoint": "https://api.lever.co/v0/postings/spotify?mode=json",
"scrapedAt": "2026-09-20T19:16:16.121Z"
},
{
"status": "ok",
"company": "Salesforce",
"companySlug": "salesforce/wd12/External_Career_Site",
"ats": "workday",
"jobId": "SMTS--Zuora-Revenue-System-Engineer_JR335227",
"title": "Zuora Revenue System -Senior Analyst",
"department": null,
"team": null,
"location": "India - Hyderabad",
"locations": ["India - Hyderabad"],
"country": "IN",
"workplaceType": null,
"isRemote": false,
"employmentType": "Full-time",
"salaryMin": null,
"salaryMax": null,
"salaryCurrency": null,
"salaryPeriod": null,
"postedAt": "2026-09-26T00:00:00.000Z",
"updatedAt": null,
"url": "https://salesforce.wd12.myworkdayjobs.com/External_Career_Site/job/India---Hyderabad/SMTS--Zuora-Revenue-System-Engineer_JR335227",
"applyUrl": null,
"descriptionText": "To get the best candidate experience, please consider applying for a maximum of 3 roles within 12 months to ensure you are not duplicating efforts. ...",
"sourceEndpoint": "https://salesforce.wd12.myworkdayjobs.com/wday/cxs/salesforce/External_Career_Site/job/India---Hyderabad/SMTS--Zuora-Revenue-System-Engineer_JR335227",
"scrapedAt": "2026-09-26T22:49:24.261Z"
},
{
"url": "https://apify.com/glasswing/ats-jobs-scraper",
"status": "not_found",
"error": "No public job board was found for \"zzqq no such company 123\" on any of the systems a company name is tried on (greenhouse, lever, ashby, smartrecruiters, workable, recruitee, personio, teamtailor, bamboohr, breezy; a Workday board needs its career-site URL). Give the board URL or \"ats:slug\" (for example greenhouse:stripe) if you know where the company posts its jobs.",
"scrapedAt": "2026-09-26T22:50:43.124Z",
"company": "zzqq no such company 123",
"companySlug": "zzqqnosuchcompany123",
"ats": null,
"sourceEndpoint": "https://api.smartrecruiters.com/v1/companies/zzqqnosuchcompany123/postings?limit=1"
}
]

How much does it cost to scrape ATS job boards?

This Actor uses pay-per-event pricing:

EventPriceCharged when
Actor start$0.005 per runOnce when the run starts. The platform charges one event per GB of memory and the Actor runs on 1 GB, so this is one event.
Job posting$0.00149 per jobOne job posting with status: ok was saved to the dataset.

1,000 jobs cost about $1.50 ($0.005 to start plus 1,000 x $0.00149 = $1.495); 100 jobs cost about $0.15 ($0.154), and the default 20-job run about $0.03 ($0.0348). You are never charged for a row with status not_found or error, never for a job your filters excluded, and never twice for the same posting in one run. The free Apify plan's $5 of monthly credit covers about 3,350 jobs in a single run.

You can cap spending per run with Maximum results and with the run's Max total charge option; the Actor stops gracefully when either limit is reached and keeps everything it has already saved.

Integrations, exports and API access

Results export to CSV, JSON, Excel, XML and HTML from the Output tab or the dataset API. From there, Apify integrations and webhooks push the same rows into Google Sheets, Airtable or a CRM, plus Slack, Amazon S3 or your own endpoint - directly, or through Zapier, Make and n8n.

The Actor can also be called from the Apify API (Node.js, Python and curl snippets are on the API tab), from the Apify MCP server, or by any AI agent that can call a tool: inputs are plain strings, numbers and booleans, a call with no arguments runs the three sample boards, and every row carries status and error, so an agent can tell "this company is not hiring" apart from "this board could not be read" without parsing prose.

Tips

  • Prefer a board URL or ats:slug over a bare name: it is exact and saves the auto-detection requests.
  • Put all your companies in one run instead of many single-company runs; you pay the start fee once and the boards are read in parallel.
  • Set includeDescription to false when you only need the hiring signal (company, title, department, location). Greenhouse and Workable responses get much smaller, SmartRecruiters, Workday, BambooHR and Breezy need no per-job request, and the price per row is unchanged.
  • On big Workday boards, add titleKeywords: they go to Workday's own search, so the Actor pages through the matching jobs only instead of the whole board (on NVIDIA's board, data engineer + marketing manager took 4 requests instead of 100).
  • Use maxItemsPerCompany when one employer in the list has thousands of openings.
  • Schedule a daily run with postedWithinDays: 1 for a "new jobs today" feed.

Limitations

  • It needs the company list. The Actor reads the boards you name; it is not a keyword search across all companies on an ATS. Finding out which ATS a company uses is on you, or on the auto-detection, which only tries the company name as a slug.
  • Auto-detection is a best effort. It matches a name only when the company's slug on the ATS is the name itself (stripe) or its compact or hyphenated form (bosch group -> boschgroup, bosch-group), and a different company can own the same slug. At most 100 bare names are auto-detected per run. When it matters, give the board URL.
  • Salary only where the ATS publishes structured pay. Ashby compensation, Greenhouse pay ranges, and the salary fields of Lever, Recruitee, Personio and Teamtailor are read; pay that an employer only mentions inside the advert text stays in descriptionText. Workable and SmartRecruiters publish no structured pay.
  • Fields differ by system. department, team, country, workplaceType, employmentType and updatedAt are null where the ATS has no such field; the field table above says which. Lever and Ashby publish no company display name, so company is derived from the slug there.
  • SmartRecruiters: about 5,000 postings per company per run at most, and the advert text costs one extra request per job.
  • Workday serves at most 2,000 postings per search, so a board with more open jobs is read up to its first 2,000 (use titleKeywords to reach the others). Its job list shows a job with several locations as "5 Locations" and gives no date, so such a job gets its locations, and every Workday job its postedAt, from the per-job request (with includeDescription on). With locationContains or remoteOnly on, a job listed as "5 Locations" is first checked on its own page (one extra request) and written only if one of its locations matches. postedWithinDays works from Workday's "Posted N Days Ago". Workday publishes no department, pay or apply link in this data (applyUrl is null; the Apply button is on the posting page), and the company name is taken from the tenant (Nvidia). Title keywords go to Workday's own search (several keywords as one OR query, a phrase as a phrase), which matches words rather than any part of the title: a keyword that is only part of a word (eng), or a phrase that the title continues (data engineer in "Data Engineering Lead"), may find fewer Workday jobs than it would on the other systems. A keyword with punctuation (c++, r&d) is not sent to that search; the whole board is read and filtered instead. Workday sites on other domains (myworkdaysite.com, a company's own domain) are not supported yet.
  • BambooHR publishes no structured pay (pay mentioned in the advert stays in descriptionText) and gives the country as a name.
  • Breezy HR is read from the public <company>.breezy.hr feed; the advert text and the pay range come from each posting page, so with includeDescription off Breezy rows have no salary.
  • Personio is read in English (language=en); a tenant that writes its adverts only in another language can come back with an empty descriptionText.
  • Recruitee boards must be given as <company>.recruitee.com; a company's own careers domain does not serve the public endpoint.
  • A board URL with a typo gives a not_found row that carries the endpoint URL and HTTP 404 rather than the company columns.
  • Only published, public postings are visible. If a system changes its response format, rows come back as error rather than silently wrong; report it in the Issues tab and the Actor is updated quickly.

FAQ

Do I need an API key for Workday, Greenhouse, Lever, Ashby or the other systems? No. The Actor reads the public, keyless endpoints that the companies' own careers pages use. All you need is an Apify account.

How do I know which ATS a company uses? Open its careers page and look at the address of a job or of the "Apply" button: *.myworkdayjobs.com, boards.greenhouse.io, jobs.lever.co, jobs.ashbyhq.com, jobs.smartrecruiters.com, apply.workable.com, *.recruitee.com, *.jobs.personio.de, *.teamtailor.com, *.bamboohr.com and *.breezy.hr are the giveaways. Paste that URL as it is. Or type the company name and let the Actor try (every system but Workday).

Can it read Workday career sites? Yes, keyless: paste the career-site address (https://<company>.wd5.myworkdayjobs.com/<site>, any page under it) or workday:<tenant>/wd<N>/<site>. The Actor reads the same JSON the site's own search page uses; a wrong site name or data centre gives one free not_found row.

Can I search all companies for a keyword? No. This Actor goes deep on the companies you list rather than wide across a whole system. titleKeywords filters the jobs of those companies.

Does it return recruiter names, e-mails or phone numbers? No. Some of these systems include the posting's creator, owner, a mailbox address or the empty application form in their responses; the Actor never copies those fields, and it masks e-mail addresses and phone numbers that an employer typed into the advert text.

Why is salaryMin empty for most rows? Most employers do not publish pay as a structured field. The Actor does not guess numbers out of prose, so an empty value means "not published in a structured way", never "unknown to us".

Why did I get fewer rows than maxItems? The companies had fewer open jobs, the filters excluded the rest, maxItemsPerCompany capped them, or the run hit your Max total charge limit. Check the run log for "Stopping gracefully".

What happens when a company has no open jobs or nothing matches my filters? You get one free not_found row for that company with the reason, so your pipeline can tell "not hiring" from "not checked". If that is the whole answer of a run, the run still ends as Succeeded.

Can I use this Actor from an AI agent or MCP client? Yes. Inputs are plain values, every row is self-describing (status plus error), and results can be fetched via the dataset API or the Apify MCP server.

This Actor extracts only data that companies publish on their public job boards, and only business data about jobs and hiring companies: job titles, adverts, departments, locations, workplace and employment types, pay ranges, dates, company names and links.

It does not emit recruiter, hiring-manager, creator or owner names, e-mail addresses or phone numbers. Where a system's public response contains such fields the Actor does not copy them, and as an extra safeguard e-mail addresses and phone numbers that an employer typed into the advert text are replaced with [email removed] and [phone removed] before the row is saved. The Actor does not log in, does not solve captchas and does not circumvent access controls.

Job adverts can nevertheless mention individuals. Personal data is protected by the GDPR in the European Union and by other regulations around the world; you should not process personal data without a legitimate reason. If you are unsure whether your reason is legitimate, consult your lawyers. You are responsible for complying with the terms of service of the systems and companies whose boards you read, and with applicable law, when you use the extracted data.

This Actor is an independent tool. It is not affiliated with, endorsed by or sponsored by Workday, Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Personio, Teamtailor, BambooHR, Breezy HR or any company whose jobs it lists. All trademarks belong to their respective owners.