ATS & Career Page Jobs Scraper (Greenhouse, Lever, Workday) avatar

ATS & Career Page Jobs Scraper (Greenhouse, Lever, Workday)

Pricing

$0.001 / job posting

Go to Apify Store
ATS & Career Page Jobs Scraper (Greenhouse, Lever, Workday)

ATS & Career Page Jobs Scraper (Greenhouse, Lever, Workday)

Give it a company domain, get every open job from the ATS behind its career page — Greenhouse, Lever, Ashby, Workday and SmartRecruiters in one schema.

Pricing

$0.001 / job posting

Rating

0.0

(0)

Developer

ScrapeBench

ScrapeBench

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

6 hours ago

Last modified

Share

Pain points

  • Several job-board Actors are published one per applicant-tracking system, so a mixed list of employers means running two or three of them and reconciling different output shapes.
  • Most want the ATS board slug rather than the company, which you have to look up per employer before you can start.
  • Career pages increasingly render the job list in JavaScript, so a generic page crawler comes back empty or partial.
  • Boards get large — a single Workday tenant tested held 2,000 open roles — so a per-company cap and a visible total matter more than they sound.

What we solve

  • One input — a company domain, a careers URL, or an explicit board like 'greenhouse:stripe' — resolves to the right ATS automatically.
  • One output schema across all five systems, so Greenhouse and Workday rows sort and filter together.
  • Hundreds of companies in a single run, with each company's outcome written to the run log.
  • A 'posted on or after' filter turns the same run into a weekly new-openings feed.
  • A company we cannot resolve returns a free, unbilled row naming the exact input that would work — never a silent zero.

Summary

Submit a company domain — acme.com — and this Actor works out which applicant-tracking system runs that company's career page, then returns every open job from it. Greenhouse, Lever, Ashby, Workday and SmartRecruiters all land in one schema: title, department, team, location, remote flag, employment type, posting date, requisition ID and the apply link. Submit hundreds of domains in a single run. Every row says how the board was matched to the company (detected_by) and cites the job URL the board itself served, so a result is auditable rather than assembled. Built for recruiters sourcing roles, sales teams treating a new posting as a hiring signal, and job-board aggregators who would otherwise run one Actor per ATS and merge the output by hand.

Who it's for

  • Recruiters and sourcers tracking open roles across a target list of employers
  • Sales and RevOps teams using new job postings as a hiring/expansion signal
  • Job-board and aggregator operators ingesting employer boards at scale
  • Talent-market and compensation analysts measuring hiring by department and location

How to use

Set the input, run the actor, and collect results from the run's dataset (export to JSON/CSV/Excel, or pull via the Apify API). Example input:

{
"companies": [
"stripe.com",
"notion.so",
"lever:gopuff"
],
"maxResults": 100,
"postedAfter": "2026-08-01"
}

See Inputs below for every available field.

What you get

One row per record:

FieldDescription
companyThe value you submitted — domain, careers URL or explicit board
company_nameEmployer name as the board states it (Greenhouse and SmartRecruiters publish it; Ashby, Lever and Workday do not, so it is null there)
atsgreenhouse, lever, ashby, smartrecruiters or workday
ats_slugThe board identifier on that ATS
detected_byHow the board was matched: explicit, careers_page, or slug_match
job_idThe board's own job identifier — stable across runs, so it de-duplicates
titlePosted job title
department / teamDepartment and team or job function, where the board publishes them
location / locationsPrimary location plus any additional locations on the same posting
remoteThe board's own remote flag where it publishes one (Ashby, SmartRecruiters); otherwise read from the board's own location wording, e.g. 'Remote - US' or 'Hybrid - London'. Null when the board says nothing either way
employment_typeFull time, contract, intern and so on, where published
posted_atPublish timestamp — Greenhouse, Lever, Ashby and SmartRecruiters
posted_textThe board's own wording when it gives no date, e.g. Workday's 'Posted Today'
updated_atLast-modified timestamp, where published
apply_urlWhere a candidate applies
requisition_idThe employer's internal requisition reference, where published
descriptionFull job text — only when 'Include the full job description' is on
source_urlThe job page as the board serves it

Sample:

{
"source_url": "https://job-boards.greenhouse.io/vercel/jobs/6136160004",
"company": "vercel.com",
"company_name": "Vercel",
"ats": "greenhouse",
"ats_slug": "vercel",
"detected_by": "slug_match",
"job_id": "6136160004",
"title": "Account Executive, Commercial",
"department": "Account Executive",
"team": null,
"location": "Hybrid - London",
"locations": [
"Office - London"
],
"remote": null,
"employment_type": null,
"posted_at": "2026-08-06T12:50:10-04:00",
"posted_text": null,
"updated_at": "2026-08-18T18:06:19-04:00",
"apply_url": "https://job-boards.greenhouse.io/vercel/jobs/6136160004",
"requisition_id": null,
"description": null
}

Inputs

FieldRequiredTypeDefaultDescription
companynostring"stripe.com"A company domain (acme.com), a careers URL, or an explicit board such as 'greenhouse:stripe' or 'lever:gopuff'. The Actor works out which applicant-tracking system the company uses. Kept forever alongside 'companies' — saved tasks and API callers send it.
companiesnoarray["stripe.com", "notion.so", "greenhouse:figma"]Look up many companies in ONE run instead of starting a run per company. Takes the same values as 'Company'. Combined with 'Company' if you fill both, and duplicates are removed. Billing is unchanged: you are charged per job returned, not per company submitted.
maxResultsnointeger1000Cap on jobs returned PER COMPANY, not per run — so a batch cannot silently return only the first rows overall and read as 'these companies aren't hiring'. When a board holds more, the run log states the board's own total next to what was returned.
postedAfternostringKeep only jobs published on or after this date (YYYY-MM-DD) — this is what turns the Actor into a weekly new-openings feed. Jobs whose board states no publish date (Workday says 'Posted Today' rather than a date) are KEPT, because dropping them would hide every Workday job and read as 'not hiring'.
includeDescriptionnobooleanfalseOff by default: descriptions are the bulk of the payload (one large board is ~16 KB per job) and most buyers filter on title, team and location first. Turn it on when you need the text.
proxyConfigurationnoobject{"useApifyProxy": false}Optional. These are public vendor APIs and the plain path is the fastest one, so a proxy is genuinely not needed for the boards themselves. Enable it only if a company's own careers page blocks the detection step.

Pricing (Pay Per Event)

You pay per result (job-posting) — no charge for empty runs. Example: 1000 job postings at $0.00125/result$1.25.

Scheduled price change: $0.001 per job ($1.00 per 1,000) until 21 September 2026, $0.00125 ($1.25 per 1,000) from that date. You are charged per job returned, not per company submitted — 1,000 jobs cost the same whether they come from one company or fifty. Marker rows explaining an unresolved company are free. Apify platform usage (compute) is billed separately per your plan.

Use cases

  • Hiring-signal alerts — schedule a daily run over your account list with 'Posted on or after' set to yesterday, and get only the roles opened since the last run.
  • Sourcing a target list — submit 200 employer domains and get every open engineering role across all of them in one dataset.
  • Aggregator ingestion — pull whole boards by ATS and reconcile them on the stable per-board job ID rather than on titles.
  • Market research — count openings by department and location across a peer group to see where a sector is hiring.

Why this actor

  • Takes a company domain, so you do not have to look up an ATS board slug per employer first.
  • Includes Workday, which several ATS Actors leave out and which holds some of the largest boards — one tenant tested held 2,000 open roles.
  • Refuses to guess: a board is only used when you named it, when the company's own site links it, or when the vendor states a company name that matches the domain — so you are never handed another company's jobs.
  • Every row carries the board's own job URL, job ID and how the board was matched (detected_by), so a result is auditable and de-duplicates cleanly across runs.
  • $1.25 per 1,000 jobs from 21 September 2026 ($1 until then), charged per job returned rather than per company submitted — below both comparable Actors benchmarked on 2026-09-06, at $1.50 and ~$2 per 1,000.

Limitations & updates

Covers the five applicant-tracking systems listed; a company on any other system (or hosting its own board) will not resolve, and returns a free marker row saying so. Automatic detection from a domain worked for 11 of 14 companies tested on 2026-09-06 — the misses were career pages rendered entirely in JavaScript, which expose no ATS link to read; those still work if you pass the board or careers URL directly. Workday in particular needs its careers URL rather than a bare domain, because its tenant, pod and site name cannot be derived from a company domain. Fields are returned only where the board publishes them: employer name comes from Greenhouse and SmartRecruiters but not Ashby, Lever or Workday; posting dates come from all but Workday. Data is whatever the employer's board shows at the moment of the run — this Actor adds no candidate data, no salary estimates and no personal information.

FAQ

Which applicant-tracking systems are supported?

Greenhouse, Lever, Ashby, SmartRecruiters and Workday. Each is read from that vendor's own public job-board endpoint, so there is no login, no API key and no browser rendering involved.

How does it know which ATS a company uses?

Three ways, tried in that order. If you name the board — 'greenhouse:stripe', or a careers URL — that wins. Otherwise it looks for an ATS link on the company's own careers page, which proves the board belongs to them. Failing that it derives a likely board name from the domain and accepts it only if the vendor states a company name that matches. Every row records which of the three applied, in detected_by.

What happens when a company cannot be resolved?

You get one free, unbilled row explaining why and naming the input that would work, for example 'lever:acme'. It is never a silent empty result, because 'we could not find the board' and 'this company is not hiring' are different answers and only one of them is our fault. On a 14-company test on 2026-09-06, 11 resolved from the domain alone and the three that did not were companies whose careers page is entirely JavaScript.

Why is posted_at empty on some rows?

Workday publishes a relative phrase — 'Posted Today' — rather than a date. Rather than invent a timestamp, those rows leave posted_at null and keep the board's own wording in posted_text. The other four systems publish a real timestamp. For the same reason, the 'Posted on or after' filter keeps undated rows instead of dropping them, which would hide every Workday job.

Can I get only jobs posted since my last run?

Yes. Set 'Posted on or after' to a date and schedule the Actor; each run returns the roles opened since then. Rows carry the board's own stable job ID, so de-duplicating across runs is exact rather than title matching.

Do I pay per company or per job?

Per job returned. Submitting 50 companies that collectively have 1,000 open roles costs the same as one company with 1,000 roles. Companies that resolve to nothing, and boards with no open jobs, are free.

How many jobs can one company return?

Whole boards, and they get large — on 2026-09-06 the boards in the example input held 617, 132 and 765 open roles, and one Workday tenant tested held 2,000. Use 'Maximum jobs per company' to cap it; the cap applies per company, not per run, and the run log always states the board's own total next to what was returned so a cap can never read as completeness.

Why is remote sometimes null instead of false?

Because the board did not say. Ashby and SmartRecruiters publish a real remote flag; Greenhouse and Workday publish none, so for those the value is read from the board's own wording — 'Remote - US' gives true, 'Hybrid - London' gives false. A posting that just says 'Dublin' says nothing about remote work, and this Actor returns null there rather than asserting false. Filtering for remote roles gives the same answer either way; the difference only shows when you filter for non-remote, and there null is the honest value.

What is not in the output?

Salary, skills, requirements and seniority. Not an oversight: on 2026-09-06 the three boards checked published pay data on 0 of 617 Greenhouse jobs, 0 of 132 Ashby jobs and 0 of 40 Lever jobs. Those columns would be present and empty, so this Actor does not carry them rather than implying data it cannot get. Everything returned comes from the board; nothing here is inferred by a model.

Is a proxy needed?

No. These are public vendor endpoints and the direct path is the fastest one, so the proxy input is off by default. Turn it on only if a particular company's own careers page blocks the detection step.

Which actor to choose

Other Actors in the portfolio that pair with this one:

  • Business Email & Phone Contact Details Scraper — You have the employers and now need contact details for outreach rather than their open roles.
  • Multi-State Corporate & Business Entity Registry — You want newly formed companies to build the target list this Actor then checks for hiring.

Guides & use cases

Written up on scrapebench.dev — the bench that runs and verifies this actor against the live source every night:

More actors, coverage and nightly verification results: scrapebench.dev

Works with AI assistants (MCP)

Callable as an MCP tool, so Claude, Cursor, VS Code Copilot and other MCP clients can run it directly. Grab the config from the MCP tab on this page — Apify hosts the server and keeps that snippet current, and OAuth signs you in on first connect, so no API token goes in your config file.

Then just ask:

"List every open job at stripe.com and notion.so, with department and location."

Those two resolve straight from the domain. For a company whose careers page is JavaScript-only, name the board instead — 'lever:gopuff' — or paste its careers URL. Runs started this way bill exactly like any other run.