Jobs Scraper for Workday, Greenhouse, Lever & Ashby
Pricing
from $3.50 / 1,000 job posting storeds
Jobs Scraper for Workday, Greenhouse, Lever & Ashby
Every open job from the career pages you follow (Greenhouse, Lever, Ashby, Workday, Recruitee, Teamtailor, Personio) as clean JSON: title, locations, remote, salary, department, apply link. Optional NEW/UPDATED/EXPIRED change feed. About $5 per 1,000 jobs.
Pricing
from $3.50 / 1,000 job posting storeds
Rating
0.0
(0)
Developer
Ebblys Hernandez sierra
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
9 minutes ago
Last modified
Categories
Share
Give it the career pages of the companies you follow and get every open job as clean, normalized JSON, straight from the public endpoints of the applicant tracking systems those pages run on: Greenhouse, Lever, Ashby, Recruitee, Teamtailor, Personio and Workday.
- Every posting, one format. Title, locations, remote flag, department, employment type, posting date, salary (min/max/currency/period) when the company publishes it, full plain-text description, apply URL, job page URL. The same fields whatever the ATS.
- Only what changed, if you want. Turn on the change feed and each run returns only NEW, UPDATED and EXPIRED jobs compared with the previous run of the same list, ready for alerts or a job board that stays fresh.
- Fast, cheap, no browser. Pure HTTP against the JSON/XML feeds the career pages themselves use, with retries and back-off. $0.005 per job — about $5 per 1,000 jobs — nothing for expired jobs, no other charge.
Company and job data only. The actor does not extract recruiter names, e-mails, phone numbers or any other personal data; contact e-mails and phone numbers that a company pastes into a job description are removed from descriptionText before it is stored.
Example output
One record per job posting, the same fields whatever the ATS. These three come from the actor's scheduled test runs of 27–29 September 2026 (nine runs, nine successes, seven career pages, up to 100 jobs each in that test), shown as the current version formats them; descriptions are cut short here, the dataset has them in full.
A Workday job (NVIDIA publishes under many legal entities; company is the brand, legalEntity the hiring entity):
{"jobId": "workday:nvidia/nvidiaexternalcareersite:JR2023975","title": "Senior Mask Layout Design Engineer","company": "NVIDIA","legalEntity": "TW07 NVIDIA Singapore Development PTE. Ltd. Taiwan Branch","atsType": "workday","locations": ["Taiwan, Hsinchu","Taiwan, Taipei"],"isRemote": false,"department": null,"employmentType": "full_time","postedAt": "2026-09-29T00:00:00.000Z","updatedAt": null,"salaryMin": null,"salaryMax": null,"salaryCurrency": null,"salaryPeriod": null,"descriptionText": "We are looking for a Senior Mask Layout Design Engineer – someone who is excited to join a growing group of diverse individuals responsible for handling challenging high-speed mixed-signal circuit designs.\nNVIDIA has con …","applyUrl": "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite/job/Taiwan-Hsinchu/Senior-Mask-Layout-Design-Engineer_JR2023975/apply","sourceUrl": "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite/job/Taiwan-Hsinchu/Senior-Mask-Layout-Design-Engineer_JR2023975","scrapedAt": "2026-09-29T06:30:28.191Z","changeStatus": "NEW"}
An Ashby job with the published pay range (OpenAI is derived from the board slug: Ashby exposes no board name):
{"jobId": "ashby:openai:6c6c1ace-12d6-48f0-88f0-488ff8521571","title": "IT Solutions Engineer","company": "OpenAI","legalEntity": null,"atsType": "ashby","locations": ["San Francisco"],"isRemote": false,"department": "IT","employmentType": "full_time","postedAt": "2026-03-06T01:23:02.602Z","updatedAt": null,"salaryMin": 187000,"salaryMax": 266000,"salaryCurrency": "USD","salaryPeriod": "year","descriptionText": "About the Team\n\nIT Systems Operations serves as the operational control layer connecting Security, Engineering, and Employee Technology platforms. The team ensures employee-facing systems, identity wo …","applyUrl": "https://jobs.ashbyhq.com/openai/6c6c1ace-12d6-48f0-88f0-488ff8521571/application","sourceUrl": "https://jobs.ashbyhq.com/openai/6c6c1ace-12d6-48f0-88f0-488ff8521571","scrapedAt": "2026-09-28T18:30:14.833Z","changeStatus": "NEW"}
A Recruitee job in another currency and period (monthly EUR, fixed-term contract):
{"jobId": "recruitee:vandebron:2757993","title": "Business Analyst Portfolio Management & Trading","company": "Vandebron","legalEntity": null,"atsType": "recruitee","locations": ["Amsterdam, Noord-Holland, Nederland","Vandebron HQ"],"isRemote": false,"department": "Producer","employmentType": "contract","postedAt": "2026-09-28T07:13:32.000Z","updatedAt": "2026-09-28T11:51:44.000Z","salaryMin": 3000,"salaryMax": 3700,"salaryCurrency": "EUR","salaryPeriod": "month","descriptionText": "Wij zijn Vandebron\nOnze reis begon in 2014 met Hoofdstuk 1 van de energietransitie, waarin we de markt transparanter wilden maken door consumenten rechtstreeks te verbinden met lokale, groene energiep …","applyUrl": "https://werkenbij.vandebron.nl/o/business-analyst-portfolio-management-trading/c/new","sourceUrl": "https://werkenbij.vandebron.nl/o/business-analyst-portfolio-management-trading","scrapedAt": "2026-09-28T12:30:12.678Z","changeStatus": "NEW"}
changeStatus appears only with the change feed on (these runs had it on): NEW, UPDATED or EXPIRED; an EXPIRED record keeps the last known title, company, locations and URLs plus firstSeenAt / lastSeenAt.
Export the dataset as JSON, CSV or Excel from the Apify Console, or read it through the API.
Supported ATS and how to find the URL
Open the company's "Careers" page and look at the address bar (or the address the job listings load from). Paste that URL into Career page URLs; the ATS is detected automatically. You can also list boards as ats:slug in Boards.
| ATS | Typical career page URL (example verified with live data) | boards form |
|---|---|---|
| Greenhouse | https://boards.greenhouse.io/stripe or https://job-boards.greenhouse.io/stripe | greenhouse:stripe |
| Lever | https://jobs.lever.co/palantir (EU-hosted boards are detected automatically) | lever:palantir |
| Ashby | https://jobs.ashbyhq.com/openai | ashby:openai |
| Recruitee | https://vandebron.recruitee.com | recruitee:vandebron |
| Teamtailor | https://anyfin.teamtailor.com/jobs | teamtailor:anyfin |
| Personio | https://personio.jobs.personio.de (or .jobs.personio.com) | personio:personio (or personio:<company>@personio.com) |
| Workday | https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite | workday:nvidia.wd5/NVIDIAExternalCareerSite |
Tips: a career site on the company's own domain (e.g. careers.example.com) is usually one of these in disguise; open a job and look at where the "Apply" button goes, or view the page source for greenhouse, lever.co, ashbyhq, recruitee, teamtailor, personio or myworkdayjobs. Workday always needs the tenant, the wd<N> host and the site name, all of them in the career page URL. Career pages on other systems (iCIMS, Taleo, SuccessFactors, BambooHR, Jobvite…) are not supported yet; tell us in the Issues tab which one you need.
What you can build with it
- Recruiting and sourcing agencies: watch the career pages of target accounts and know the same day when they open roles you can fill.
- Job boards and aggregators: feed a niche board (by industry, region or role) with structured, deduplicated postings that include apply links and, where published, salary, and let the change feed expire them when they close.
- Labour-market and competitive intelligence: track hiring volume, locations, remote share and published salary ranges across hundreds of companies over time.
- Job alerts: schedule the actor with the change feed on and push NEW jobs to Slack, e-mail or a webhook through Apify integrations.
- Sales and investment signals: hiring bursts in engineering, sales or a new country say a lot about a company's plans.
Pricing
Pay per event. Two events, nothing else:
| Event | Price | When |
|---|---|---|
job | $0.005 | Each job posting stored in the dataset (NEW or UPDATED in change-feed mode) |
actor-start | $0.005 | Once per run |
Examples:
- 1 company, 40 jobs → 40 × $0.005 + $0.005 = $0.21
- 25 companies, 1,000 jobs in total → $5.01
- Daily change feed over 100 companies that together post ~60 new or changed jobs a day → about $0.31 per day; expired jobs are free.
The price already includes the compute the actor uses; there is no extra usage charge. Set Maximum cost per run in the run options to cap what a run may spend: the actor stops storing jobs when the cap is reached and says so in the log. Free-plan credits work as usual.
Input
| Field | Default | Meaning |
|---|---|---|
startUrls | Palantir (Lever), Anyfin (Teamtailor), Vandebron (Recruitee) | Career page URLs (ATS detected automatically) |
boards | [] | Boards as ats:slug, merged with startUrls |
includeDescription | true | Add descriptionText. Workday needs one extra request per job for it; switching it off makes Workday boards much faster |
maxJobsPerCompany | 0 (all) | Stop after this many jobs per career page |
onlyNewSince | – | ISO date; drops jobs posted before it (jobs whose posting date the ATS hides are kept) |
changeFeed | false | Report only NEW / UPDATED / EXPIRED jobs compared with the previous run |
stateStoreName | company-career-pages-jobs-state | Named key-value store that keeps the change-feed snapshots; use one per schedule |
allowRobotsDisallowed | false | Some ATS APIs (currently SmartRecruiters) publish a robots.txt that disallows automated access. Their career pages are skipped with a warning unless you enable this, at your own responsibility |
maxConcurrency | 5 | Career pages processed in parallel |
Change feed
With changeFeed: true the actor keeps one snapshot per career page in the named key-value store (stateStoreName), created by the actor itself on the first run, so it works with the default limited permissions. Each later run compares against it:
- NEW – job id not seen in the previous run (charged as
job); - UPDATED – title, locations, salary, employment type, dates or description changed (charged as
job); - EXPIRED – present before, gone now; emitted from the snapshot with
firstSeenAt/lastSeenAt(free); - unchanged jobs are not emitted.
The first run reports everything as NEW. Use a different stateStoreName for each list of companies you schedule, otherwise the lists would expire each other's jobs. If the store cannot be opened the run still completes: everything is reported as NEW and a warning explains why.
Output fields
| Field | Type | Notes |
|---|---|---|
jobId | string | Stable and globally unique: <ats>:<company slug>:<native id> |
title, company, atsType | string | company is the brand name: the board name when the ATS exposes it (Greenhouse, Recruitee, Teamtailor, Personio, SmartRecruiters), otherwise derived from the board slug (openai → OpenAI, acme-labs → Acme Labs); for Workday it comes from the tenant, taking the brand's own capitalisation from the career-site name when it has it (nvidia + NVIDIAExternalCareerSite → NVIDIA) |
legalEntity | string | null | Workday only, with includeDescription on (it comes from the job detail): the hiring organization exactly as the tenant publishes it (2100 NVIDIA USA, IL00 Mellanox Technologies…), useful for group companies; null on the other ATSs |
locations | string[] | All locations the posting lists, as written by the company |
isRemote | boolean | From the ATS flag when it has one (Lever and Ashby workplaceType, Recruitee remote / hybrid / on_site, Teamtailor TELECOMMUTE, Workday remoteType); otherwise the words remote / work from home in title or locations |
department | string | null | Department, team or function, when the ATS publishes it (Greenhouse, Lever, Ashby, Recruitee, Teamtailor, Personio, SmartRecruiters). The Workday endpoints we use expose no department, so it is null there |
employmentType | enum | null | full_time, part_time, contract, internship, temporary, freelance, other — when the ATS publishes it: Ashby employmentType, Lever categories.commitment, Recruitee employment_type_code, Personio employmentType + schedule + seniority, Teamtailor schema.org employmentType, Workday timeType. Greenhouse's public API has no such field (only a custom metadata entry on some boards), so it is usually null there |
postedAt, updatedAt | ISO 8601 | null | updatedAt only where the ATS provides it (Greenhouse, Lever, Recruitee, Teamtailor, SmartRecruiters); Ashby, Personio and Workday publish no update date, so it is null there — we never invent one |
salaryMin, salaryMax, salaryCurrency, salaryPeriod | number / string | null | Structured pay data first (Ashby compensation, Lever salaryRange, Recruitee salary, Greenhouse pay-transparency ranges, Personio salaryInformation, Teamtailor baseSalary). Text is only a fallback under strict rules: an explicit range with a currency symbol or code glued to the figures, never followed by "years"/"experience", within plausible bounds per period; a single figure is never a salary. Empty beats wrong. salaryPeriod ∈ year, month, biweekly, week, day, hour |
descriptionText | string | Plain text with line breaks and bullets; contact e-mails and phone numbers removed; "" when includeDescription is false |
applyUrl, sourceUrl | string | Application form and public job page |
scrapedAt | ISO 8601 | Run time |
changeStatus | enum | Only in change-feed mode |
The run's key-value store also gets a SUMMARY record: companies processed and failed (with the error), jobs fetched, records stored, change-feed counts.
FAQ and limits
A company failed, does the run stop? No. A career page that cannot be read (wrong URL, board removed, ATS outage, response that is not JSON) is logged as a warning with the reason and the other companies continue. The run fails only if every career page failed. Requests are retried with exponential back-off on 429 / 5xx and network errors.
Are jobs deduplicated? Yes, by jobId within a run.
Change feed and maxJobsPerCompany. Keep maxJobsPerCompany at 0 (all) when the change feed is on: with a cap, jobs that fall outside the first N of a career page are reported as EXPIRED although they are still open (the run logs a warning when both are set). Use onlyNewSince instead to limit what a run returns.
Why is postedAt missing on some Workday jobs? Workday's list only says "Posted 30+ Days Ago". With includeDescription on (the default) the exact date comes from the job detail; with it off, jobs older than 30 days have postedAt: null.
How long does a run take? Greenhouse, Lever, Ashby, Recruitee, Teamtailor and Personio return a whole board in one or a few requests (Stripe's 702 jobs take seconds). Workday returns 20 jobs per page and needs one request per job for the description, so a 2,000-job Workday tenant takes a few minutes.
Does it work with limited permissions? Yes. The actor only uses its own storages and a key-value store it creates itself for the change feed.
Very large Workday tenants. Some Workday sites cap a search at 2,000 results. Jobs beyond that are not returned in this version.
Endpoint status. Greenhouse (Stripe), Lever (Palantir), Ashby (OpenAI), Recruitee (Vandebron), Teamtailor (Anyfin), Personio (Personio) and Workday (NVIDIA, nine legal entities) are verified against live data in scheduled runs; report anything odd in Issues.
Technical details
- TypeScript on Node 20 with the Apify SDK; native
fetch, no browser, no third-party scraping libraries. - Endpoints: Greenhouse
boards-api.greenhouse.io/v1/boards/{token}/jobs?content=true&pay_transparency=true; Leverapi.lever.co/v0/postings/{site}?mode=json(paginated withskip/limit, falling back toapi.eu.lever.coonly on a 404); Ashbyapi.ashbyhq.com/posting-api/job-board/{board}?includeCompensation=true; Recruitee{company}.recruitee.com/api/offers/; Teamtailor{company}.teamtailor.com/jobs.json(JSON Feed 1.1 with an embedded schema.org_jobposting); Personio{company}.jobs.personio.de/xml(XML: name, office, department, employmentType, schedule, seniority, createdAt, jobDescriptions); WorkdayPOST /wday/cxs/{tenant}/{site}/jobsplus one detail request per job. - Sources are processed with a small concurrency pool (
maxConcurrency); Workday detail requests run 4 at a time per company. - HTTP: 30 s timeout per request, up to 4 retries with exponential back-off honouring
Retry-After; a 2xx whose body is not JSON is retried and then treated as a failure; other 4xx fail immediately. - Budget: on pay-per-event runs the actor charges
actor-startonce andjobper stored posting through the SDK, which trims a batch to what the run's maximum cost allows and stops scraping further companies once the cap is reached. Runs without pay-per-event pricing (free trials, local runs) charge nothing and say so once in the log. - Snapshots (change feed) are JSON records named
snapshot-<ats>-<slug>in the state store; each holds a fingerprint per job plus the fields needed to describe an expiry. - Memory: 512 MB is plenty; a run's peak is a few tens of MB per large Workday tenant.
- Source:
actors/ats-jobsin the repository, built by Apify from GitHub on every merge tomain.