ATS Job Scraper: Greenhouse, Lever, Ashby, SmartRecruiters
Pricing
from $3.00 / 1,000 job listeds
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
Maintained by CommunityActor 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 ittext, SmartRecruiters calls itname. You gettitlefrom all of them.
Supported platforms
| Platform | Status |
|---|---|
| Greenhouse | Verified against live boards |
| Ashby | Verified against live boards |
| Lever | Verified against live boards |
| SmartRecruiters | Verified against live boards |
| Personio | Verified against live boards |
| Workable | Implemented, awaiting a live board to confirm |
| Recruitee | Implemented, 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 type | What happens |
|---|---|
greenhouse:stripe | Used directly. Fastest, and never ambiguous. |
https://jobs.ashbyhq.com/ramp | The token is read out of the URL. |
monzo.com | The 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.
| Filter | Behaviour |
|---|---|
titleIncludes | Keep a job if its title contains any of these. Case-insensitive. |
titleExcludes | Drop a job if its title contains any of these. |
locationIncludes | Match against the job's location text. |
remoteOnly | Uses the board's own remote flag where it has one, and the location text where it does not. |
postedAfter | A 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.
| Field | Notes |
|---|---|
jobId, source, companyToken, companyName | source names the platform the job came from |
title, department, team, employmentType | |
location, locations, isRemote, workplaceType | locations lists every location when a job has several |
salaryMin, salaryMax, salaryCurrency, salaryText | Parsed into numbers you can sort on, including European formats |
postedAt, updatedAt | Always UTC ISO 8601, whatever the board sent |
jobUrl, applyUrl | |
descriptionHtml, descriptionText | Both 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.