Jobs Scraper for Workday, Greenhouse, Lever & Ashby avatar

Jobs Scraper for Workday, Greenhouse, Lever & Ashby

Pricing

from $3.50 / 1,000 job posting storeds

Go to Apify Store
Jobs Scraper for Workday, Greenhouse, Lever & Ashby

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

Ebblys Hernandez sierra

Maintained by Community

Actor 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.

ATSTypical career page URL (example verified with live data)boards form
Greenhousehttps://boards.greenhouse.io/stripe or https://job-boards.greenhouse.io/stripegreenhouse:stripe
Leverhttps://jobs.lever.co/palantir (EU-hosted boards are detected automatically)lever:palantir
Ashbyhttps://jobs.ashbyhq.com/openaiashby:openai
Recruiteehttps://vandebron.recruitee.comrecruitee:vandebron
Teamtailorhttps://anyfin.teamtailor.com/jobsteamtailor:anyfin
Personiohttps://personio.jobs.personio.de (or .jobs.personio.com)personio:personio (or personio:<company>@personio.com)
Workdayhttps://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSiteworkday: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:

EventPriceWhen
job$0.005Each job posting stored in the dataset (NEW or UPDATED in change-feed mode)
actor-start$0.005Once 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

FieldDefaultMeaning
startUrlsPalantir (Lever), Anyfin (Teamtailor), Vandebron (Recruitee)Career page URLs (ATS detected automatically)
boards[]Boards as ats:slug, merged with startUrls
includeDescriptiontrueAdd descriptionText. Workday needs one extra request per job for it; switching it off makes Workday boards much faster
maxJobsPerCompany0 (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)
changeFeedfalseReport only NEW / UPDATED / EXPIRED jobs compared with the previous run
stateStoreNamecompany-career-pages-jobs-stateNamed key-value store that keeps the change-feed snapshots; use one per schedule
allowRobotsDisallowedfalseSome 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
maxConcurrency5Career 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

FieldTypeNotes
jobIdstringStable and globally unique: <ats>:<company slug>:<native id>
title, company, atsTypestringcompany 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)
legalEntitystring | nullWorkday 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
locationsstring[]All locations the posting lists, as written by the company
isRemotebooleanFrom 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
departmentstring | nullDepartment, 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
employmentTypeenum | nullfull_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, updatedAtISO 8601 | nullupdatedAt 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, salaryPeriodnumber / string | nullStructured 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
descriptionTextstringPlain text with line breaks and bullets; contact e-mails and phone numbers removed; "" when includeDescription is false
applyUrl, sourceUrlstringApplication form and public job page
scrapedAtISO 8601Run time
changeStatusenumOnly 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; Lever api.lever.co/v0/postings/{site}?mode=json (paginated with skip/limit, falling back to api.eu.lever.co only on a 404); Ashby api.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); Workday POST /wday/cxs/{tenant}/{site}/jobs plus 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-start once and job per 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-jobs in the repository, built by Apify from GitHub on every merge to main.