ATS Job Scraper: Greenhouse, Lever, Ashby, SmartRecruiters avatar

ATS Job Scraper: Greenhouse, Lever, Ashby, SmartRecruiters

Pricing

from $3.00 / 1,000 job listeds

Go to Apify Store
ATS Job Scraper: Greenhouse, Lever, Ashby, SmartRecruiters

ATS Job Scraper: Greenhouse, Lever, Ashby, SmartRecruiters

Open jobs from company career sites on Greenhouse, Ashby, Lever, SmartRecruiters and Personio. Returns only jobs added since your last run when you want a diff.

Pricing

from $3.00 / 1,000 job listeds

Rating

0.0

(0)

Developer

FAST SEO LIMITED

FAST SEO LIMITED

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

19 days ago

Last modified

Categories

Share

ATS Job Feed

Read open jobs straight from the career sites of the companies you care about, and get one clean table back. Greenhouse, Ashby, Lever, SmartRecruiters and Personio all return different shapes. This returns one.

Every run reads the live job board at the moment you ask. There is no index in between, so nothing is stale and nothing is missing because a crawler has not caught up yet.

Three things it does well

  • You choose the companies. Give it a list of employers and it reads only those boards. No searching a database of millions of postings to find the fifty companies you actually track.
  • It tells you only what is new. Switch on the incremental feed and it remembers the exact job IDs it has already given you, so the next run returns genuinely new postings and nothing else.
  • One schema from every platform. Greenhouse calls it title, Lever calls it text, SmartRecruiters calls it name. You get title from all of them.

Supported platforms

PlatformStatus
GreenhouseVerified against live boards
AshbyVerified against live boards
LeverVerified against live boards
SmartRecruitersVerified against live boards
PersonioVerified against live boards
WorkableImplemented, awaiting a live board to confirm
RecruiteeImplemented, awaiting a live board to confirm

These are each platform's own public endpoints. No login, no cookies, no proxies, nothing that breaks the first time a site changes its markup.

Quick start

Paste any of these into the input and run it.

Every open job at three companies

{ "companies": ["greenhouse:stripe", "ashby:ramp", "lever:gopuff"] }

Remote engineering roles only, posted in the last month

{
"companies": ["greenhouse:stripe", "ashby:ramp"],
"titleIncludes": ["engineer", "developer"],
"remoteOnly": true,
"postedAfter": "30 days"
}

A daily alert feed that never repeats itself

{
"companies": ["greenhouse:monzo", "lever:gopuff"],
"newSinceLastRun": true,
"includeDescription": false
}

Put that last one on a daily schedule and each run returns only the jobs that appeared since the previous run.

Naming a company

Three accepted forms, and you can mix them in one run.

You typeWhat happens
greenhouse:stripeUsed directly. Fastest, and never ambiguous.
https://jobs.ashbyhq.com/rampThe token is read out of the URL.
monzo.comThe careers page is fetched and the board found automatically.

Auto-detection follows links from the company's site to find its job board. It works on most sites. Some marketing sites block automated requests, and when that happens the run names the entry and tells you to pass its board token directly instead of silently returning nothing.

Only what is new

This is the part worth understanding, because it is not the same as filtering by date.

A date filter asks "what was posted in the last 24 hours". If a run fails, or a job is posted twice, or a board backdates a posting, you get duplicates or gaps. Shortening the window loses jobs and widening it repeats them.

The incremental feed instead remembers the exact job IDs it has already handed you. The next run compares against that list and returns the difference. No window to tune, no duplicates, and nothing missed because a run was late.

Three details that matter in practice:

  • History is keyed to your company list, so two schedules watching different companies never interfere. Set State key yourself to override that.
  • Jobs are recorded as seen only after they have actually been delivered, so a run that stops at a spend limit will not hide those postings from the next one.
  • Your history lives in your own account. It is yours, and nobody else's runs can touch it.

Filters

Every filter is optional, and leaving one empty keeps everything.

FilterBehaviour
titleIncludesKeep a job if its title contains any of these. Case-insensitive.
titleExcludesDrop a job if its title contains any of these.
locationIncludesMatch against the job's location text.
remoteOnlyUses the board's own remote flag where it has one, and the location text where it does not.
postedAfterA date, or a relative value such as 7 days.

Filters run before the per-company cap and before descriptions are fetched. So asking for five software roles at a company with 4,836 openings searches the whole board, returns five, and fetches five descriptions rather than 4,836.

Output

One row per job, identical in shape whichever platform it came from.

FieldNotes
jobId, source, companyToken, companyNamesource names the platform the job came from
title, department, team, employmentType
location, locations, isRemote, workplaceTypelocations lists every location when a job has several
salaryMin, salaryMax, salaryCurrency, salaryTextParsed into numbers you can sort on, including European formats
postedAt, updatedAtAlways UTC ISO 8601, whatever the board sent
jobUrl, applyUrl
descriptionHtml, descriptionTextBoth provided. Turn them off to make large boards much faster
scrapedAt

Fields a platform does not publish come back as null rather than being left out, so the shape never changes between rows.

{
"jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
"source": "ashby",
"companyToken": "ramp",
"companyName": "Ramp",
"title": "Security Engineer, Cloud",
"department": "Engineering",
"team": "Backend",
"employmentType": "FullTime",
"location": "New York, NY (HQ)",
"locations": ["New York, NY (HQ)", "Remote (US)", "Miami, FL"],
"isRemote": true,
"workplaceType": "Hybrid",
"salaryText": "$211.4K - $290.6K",
"salaryMin": 211400,
"salaryMax": 290600,
"salaryCurrency": "USD",
"postedAt": "2026-04-07T17:12:35.753Z",
"jobUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
"applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application"
}

What people use it for

  • Niche job boards that list roles from a curated set of employers and need the board to stay current without manual work.
  • Recruiters and sourcers watching a target account list for the moment a relevant role opens.
  • Sales teams treating a new engineering hire as a buying signal, since a company staffing up a data team is a company about to buy data tooling.
  • AI agents that need live hiring data rather than a model's stale memory.
  • Market and salary research, using the parsed salary fields across a defined peer group.

How this compares

Being straight with you, because it will save you time.

If you need to search across every company at once, or you want millions of postings from 150 platforms, the large aggregators are a better fit. They run enormous indexes and this does not try to.

This is the right tool when you know which employers matter. It reads their boards live rather than serving you a cached copy, you pay only for the jobs at the companies you asked for rather than querying a vast database, and the incremental feed gives exact-once delivery rather than a time window you have to tune.

Calling it from your own code

Small runs can use the synchronous endpoint, which returns the rows directly in the response body.

curl -X POST "https://api.apify.com/v2/acts/seotrader~ats-job-scraper-greenhouse-lever-ashby-smartrecruiters/run-sync-get-dataset-items?token=YOUR_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"companies": ["greenhouse:stripe", "ashby:ramp"],
"titleIncludes": ["engineer"],
"includeDescription": false
}'

That endpoint gives up after 300 seconds, so for long company lists start the run asynchronously and read the dataset when it finishes.

Frequently asked questions

How current is the data? It is read from the career site during your run. There is no index and no refresh delay.

Does it find jobs that are not on a company's board? No. It reads the board, so you get what the employer has actually published, with nothing inferred and nothing duplicated from an aggregator.

What if a company changes platform? Supply the company as a bare domain and detection finds the new board on the next run.

Why did a small max-jobs value return nothing? It should not. Filters run before the cap, so a cap of five returns five matches if five exist. If you see otherwise, that is a bug worth reporting.

Can I get only new jobs without duplicates? Yes, that is what newSinceLastRun does, and it is tracked by job ID rather than by a date window.

Which platforms are coming next? Workable and Recruitee are written and waiting on a live board to verify against. Tell me which company you need and it moves to the front.

Speed and cost

  • Turning descriptions off is the single biggest speed win, and on SmartRecruiters it removes one request per job.
  • Max jobs per company counts matching jobs, not jobs looked at, so a small cap with a narrow filter still searches the whole board.
  • Max concurrent requests applies per platform rather than globally, so one slow career site cannot hold up the others. Lower it if you see rate limiting.