Career Site Job Board Scraper: Greenhouse, Lever, Ashby +4 ATS avatar

Career Site Job Board Scraper: Greenhouse, Lever, Ashby +4 ATS

Pricing

from $5.20 / 1,000 company summary returneds

Go to Apify Store
Career Site Job Board Scraper: Greenhouse, Lever, Ashby +4 ATS

Career Site Job Board Scraper: Greenhouse, Lever, Ashby +4 ATS

Live jobs scraper for 7 public ATS and career-site boards: Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Personio and Recruitee. Paste company slugs or careers URLs and get every open role with title, location, remote, department, salary, apply URL and posting age, plus hiring momentum.

Pricing

from $5.20 / 1,000 company summary returneds

Rating

0.0

(0)

Developer

daehwan kim

daehwan kim

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

13 hours ago

Last modified

Share

Career Site Job Board Scraper — Greenhouse, Lever, Ashby & 4 more ATS

Paste a list of companies. Get back every open role on their job board, read live at the moment you run it, with a judged one-line verdict on each posting.

No database of yesterday's jobs. No 5,000-job ceiling. No API key.

in["https://boards.greenhouse.io/dropbox", "https://jobs.lever.co/binance"]
out → one row per open role + one hiring-momentum summary per company

Why this one

Most ATS scrapers on the store hand you a cached copy of someone else's crawl, capped at a few thousand rows per run, and then point you at a paid API when you need more. This Actor calls the company's own job board at run time, so:

This Actor
FreshnessRead live, per run. A role posted an hour ago is in your first run.
Per-run job limitNone. A 824-posting board comes back whole.
CoverageSeven boards — Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Personio and Recruitee — auto-detected from a slug or URL.
JudgementEvery row is scored, not just copied — see below.
MovementHand it the dataset of an earlier run and each row says whether it is new, each company says how its open-role count moved and which roles are gone.

The judgement layer

Other scrapers give you fields. This one gives you a verdict:

  • headline — the row in one line: Senior Backend Engineer — Remote (United States), posted 3d ago, $180k–220k
  • freshnessfresh (≤30d), aging (≤90d), stale (older)
  • postedAgeDays — how long this role has actually been open
  • ghostJobRisknone / low / medium / high, with the reasons in ghostJobFlags: open over 90 or 180 days, never updated, or a duplicate title sitting on the same board
  • seniorityintern / junior / senior / principal / manager / director / executive, read from the title
  • salarymin, max, currency parsed out of whatever the board published
  • remote and workplaceType — normalised across four boards that each say it differently
  • keywordHits — which of your keywords this role matched

And once per company, a company-summary row: department and seniority mix, remote share, average posting age, top locations, and a hiring-momentum verdict — hiring hard, steady, slowing or stalled, based on how many roles opened in the last 30 days.


Input

FieldTypeDescription
companiesarrayCompany slugs or careers-page URLs. All seven boards are detected automatically.
companystringAlternative to the list — a single company.
keywordsarrayKeep only roles whose title contains one of these words. Empty = every role.
locationsarrayKeep only roles whose location mentions one of these (Remote works). Empty = everywhere.
postedWithinDaysintegerKeep only roles first published within N days. 0 = no date filter.
maxJobsPerCompanyintegerCeiling per company. 0 = unlimited, which is the default.
includeDescriptionbooleanAdd the full job description as plain text. Off by default — descriptions are large.
monitorModebooleanReturn only what changed since your previous run (default false). See Monitor mode below.
monitorKeystringOptional name for the watch, e.g. competitor-hiring-weekly. Empty = derived from your input.
includeUnchangedbooleanMonitor mode only — also return roles that are still open and unchanged (default false).
previousDatasetIdsarrayDataset IDs of earlier runs, used as the baseline for the change fields. Empty = comparisonStatus: no_baseline.
disclaimerAckbooleanAcknowledges how this Actor sources data (see Data sources & responsibility).

Accepted address formats:

dropbox (bare slug — probed across all seven boards)
https://boards.greenhouse.io/dropbox
https://job-boards.greenhouse.io/stripe/jobs/12345
https://jobs.lever.co/binance
https://jobs.ashbyhq.com/linear
https://apply.workable.com/blueground
https://jobs.smartrecruiters.com/Bosch
https://teeturtle.recruitee.com
https://personio.jobs.personio.de

A URL is always faster and unambiguous. A bare slug costs up to seven probe requests.

Example

{
"companies": [
"https://boards.greenhouse.io/databricks",
"https://jobs.ashbyhq.com/linear"
],
"keywords": ["engineer", "designer"],
"locations": ["Remote", "United States"],
"postedWithinDays": 30,
"maxJobsPerCompany": 0,
"includeDescription": false,
"disclaimerAck": true
}

Output

One row per open role (rowType: "job"):

FieldDescription
headlineThe one-line verdict
company, companySlug, inputTargetWho this role belongs to, and what you typed
ats, atsLabelgreenhouse / lever / ashby / workable
jobTitleRole title
location, locationsPrimary location and every listed location
remote, workplaceTypetrue/false/null, and remote / hybrid / onsite
department, departmentsTeam and department names
employmentTypeFull-time, contract, intern, … where the board publishes it
postedAt, updatedAtISO dates from the board
postedAgeDays, updatedAgeDaysAge in days
freshnessfresh / aging / stale / unknown
ghostJobRisk, ghostJobFlagsRisk band and the reasons behind it
seniorityInferred from the title
salary{ min, max, currency, raw } when the posting publishes pay
keywordHitsYour keywords that matched
applyUrlDirect application link
jobId, requisitionIdBoard identifiers
jobDescriptionPlain text, when includeDescription is on
sourceUrl, scrapedAtExactly which endpoint was read, and when
changeType, changeDetailMonitor mode only — new / changed / removed / unchanged, and what moved

One row per company (rowType: "company-summary"): hiringMomentum, hiringMomentumReason, totalJobsOnBoard, jobsMatchingFilters, jobsDelivered, remoteJobs, remoteSharePercent, newLast30DaysPercent, averagePostingAgeDays, jobsWithPublishedSalary, departmentDistribution, seniorityDistribution, topLocations.

Rows with rowType: "notice" are status messages — an address with no board behind it, a company with no open roles, a limit reached. Notice rows are never charged.

Invalid input ends the run as SUCCEEDED with a notice row.


Monitor mode (weekly change alerts)

Hiring is a moving target. Turn on Monitor mode and the Actor compares this run with your previous run and returns only what moved on those boards:

changeTypeMeaning
newA role that was not on the board the last time you ran this — the one recruiters and job-seekers actually want
changedThe title or location moved; changeDetail shows what it said before
removedNo longer listed: filled, withdrawn or expired
unchangedStill open, still identical — withheld by default, set includeUnchanged to receive it

Every run also returns one free change-report row, e.g. 12 new job postings, 3 gone, 1 changed since 2026-08-19, with newCount, removedCount, changedCount and unchangedCount. A quiet week returns just that row and no job rows.

The company summary row still describes the whole board (hiring momentum, department mix, remote share) so the change rows always have context.

Put it on a weekly schedule (3 steps)

  1. Run the Actor once with Monitor mode on — the first run saves a baseline and returns every open role.
  2. On the run's page click Actions ▸ Schedule Actor, and pick Every week.
  3. Add an integration on the schedule (Slack, email, webhook) so new roles at the companies you watch reach you without opening Apify.

Runs are matched by monitor key. Leave monitorKey empty and it is derived from your input, so one schedule keeps one history automatically; set it (e.g. competitor-hiring-weekly) to keep several independent watches. State is kept in a named key-value store holding only job identifiers and a short title/location label — never descriptions.

A board that could not be read this run is left untouched: its roles are never reported as "removed" because of a network error.


Compare against an earlier run

Monitor mode keeps its own history. previousDatasetIds does the opposite: you choose the baseline, by pasting the dataset ID of any earlier run of this Actor. Nothing is stored between runs and nothing extra is charged — the comparison arrives as extra fields on rows you were already getting.

On every job row:

FieldMeaning
comparisonStatuscompared, compared_without_closures, baseline_unreadable, or no_baseline when you gave none
isNewSinceBaselineThe role was not in the baseline. null without a baseline.
previouslySeenAtWhen the baseline last saw this role
postedAtChangedSinceBaselineThe board restamped the posting date — often a repost

On every company-summary row:

FieldMeaning
rolesOpenNow / rolesOpenInBaseline / rolesOpenChangeOpen roles then, now, and the difference
newRolesSinceBaselineHow many of today's roles are new
closedSinceBaseline / closedRolesHow many are gone, and the first 25 of them with title, location and last-seen date
hiringChangeHeadlineThe same thing in one line

Two rules keep this honest. Without a baseline there is no trend: comparisonStatus is no_baseline and every change field is null — today's role count is never dressed up as movement. And closures are only claimed when the run read whole boards: if keywords, locations, postedWithinDays or maxJobsPerCompany were set, a role missing from this run may simply have been filtered out, so the status is compared_without_closures and the closure fields stay null.

Pricing

Pay per event:

EventPriceWhen
run-started$0.005Once per run, when your company list is accepted and the boards are read.
job-listed$0.003Once per job posting written to the dataset, with its full judged row.
company-summary$0.01Once per company, for the summary row. Only written when at least one job row was delivered for that company.

A 40-role company therefore costs 0.005 + 40 × 0.003 + 0.01 = $0.135.

Notice rows — unreadable board, no open roles, no matching filters, limit reached, and the monitor change report — are not charged.

In Monitor mode the same job-listed price applies to each delivered change row, removed rows included: a role that has just been filled is exactly the answer you scheduled the run for. A week with no movement delivers no job rows and therefore costs only the run-start fee.


Data sources & responsibility

This Actor reads the public, unauthenticated job-board endpoints that each applicant tracking system serves to a company's own careers page:

Greenhouse https://boards-api.greenhouse.io/v1/boards/<slug>/jobs?content=true
Lever https://api.lever.co/v0/postings/<slug>?mode=json
Ashby https://api.ashbyhq.com/posting-api/job-board/<slug>?includeCompensation=true
Workable https://apply.workable.com/api/v1/widget/accounts/<slug>?details=true
SmartRecruiters https://api.smartrecruiters.com/v1/companies/<slug>/postings?limit=100
Recruitee https://<slug>.recruitee.com/api/offers/
Personio https://<slug>.jobs.personio.de/xml
  • One request per company on five of the seven boards. SmartRecruiters pages its feed at 100 postings, so a larger board costs one request per page. SmartRecruiters and Personio omit the job description from the list feed, so turning on includeDescription adds one read of the public posting per role on those two boards only — leave it off and they cost one request like the rest.
  • Two companies at a time, with an honest bot User-Agent (ntriq-ats-jobs/1.0).
  • No login, no captcha, no anti-bot measure is bypassed. Nothing behind an authentication wall is read.
  • Only job-posting data is returned. No recruiter names, no applicant data, no personal contact details.

This Actor is unofficial and is not affiliated with, endorsed by, or sponsored by Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Personio, Recruitee, or any employer whose board you read. Those names appear only to say which public boards it reads. You are responsible for complying with the terms of service of each applicant tracking system and each employer, and for using the results lawfully. You are the data controller for everything you collect with it, including any obligations under GDPR or comparable law.

ghostJobRisk is a heuristic built from posting age, update dates and duplicate titles. It is a signal to investigate, not a statement about any employer's hiring intentions.


Notes

  • Boards that publish no posting date are still returned; their freshness is unknown and postedWithinDays never filters them out.
  • Ashby and Lever publish pay ranges more often than Greenhouse and Workable, so salary is populated unevenly. It is parsed from what the board actually published — nothing is estimated.
  • A company whose board exists but has no open roles returns a free no-jobs notice, not an error.

Works well with

Every run of this Actor also returns these suggestions in the nextSteps field of its summary row.