Greenhouse, Lever, Ashby & Workable Jobs Scraper avatar

Greenhouse, Lever, Ashby & Workable Jobs Scraper

Pricing

from $0.90 / 1,000 jobs

Go to Apify Store
Greenhouse, Lever, Ashby & Workable Jobs Scraper

Greenhouse, Lever, Ashby & Workable Jobs Scraper

Give it company websites like stripe.com, company names, or career page and job board URLs. It looks up the Greenhouse, Lever, Ashby, Workable, SmartRecruiters or Recruitee board and returns every open job with title, locations, remote flag, salary, description. $1.50 per 1,000 jobs, failures free.

Pricing

from $0.90 / 1,000 jobs

Rating

0.0

(0)

Developer

Gautham Krishna

Gautham Krishna

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

Get every open job from a company's own career site, straight from its applicant tracking system. Paste company websites (stripe.com), company names (Scale AI), career page URLs, job board URLs or board names, and get one row per job with title, department, locations, remote flag, employment type, seniority level, dates, apply link, salary when published, and the full description. For a website or a name the scraper finds the company's board itself, and every row says which board it used.

It reads Greenhouse, Lever, Ashby, Workable, SmartRecruiters and Recruitee boards. These systems publish a public JSON feed for each company board, so the scraper needs no login, no browser and no proxy.

You pay $1.50 per 1,000 jobs ($0.0015 each), and less on Apify Store discount tiers (Bronze, Silver, Gold), down to $0.90 per 1,000 on Gold. Boards that don't exist, have no open jobs or fail are never charged, and finding a company's board from its website or name costs nothing.

At a glance

TopicDetails
InputMany companies per run: company websites, company names, career page URLs, job board URLs or board names on Greenhouse, Lever, Ashby, Workable, SmartRecruiters and Recruitee. Filters: keywords, excluded words, locations, remote only, posted within N days, salary listed, minimum salary, seniority (intern, entry level and up)
OutputOne JSON row per open job: title, department, locations, remote flag, employment type, seniority level, dates, apply link, salary when published and the full description
Price$0.0015 per job ($1.50 per 1,000), or $1.35 per 1,000 on Bronze, $1.10 on Silver and $0.90 on Gold. No start fee. Boards that don't exist, have no open jobs or fail are free, and so are jobs your filters drop
TestedBoard URLs: 61 of 61 real boards across all six systems returned jobs, 7,898 in total, in about 3 minutes. Websites and names: 238 real companies in three runs, see Company websites and names

What you get per job

FieldWhat it holds
statusok for a job. Other rows say why a board gave nothing, see below
company, ats, boardSlugCompany name, which ATS hosts the board, and the board name on that ATS
boardUrl, resolvedBy, resolvedFromOnly for inputs the scraper resolved (websites, names, career pages, bare board names). The board used, how it was found, and the page that linked to it. Absent for board URLs and ats:name pairs
jobId, titleThe ATS's own job ID and the job title
department, teamAs the company labels them. Not every ATS has both
location, locationsFirst location, and every location the job lists
countryCountry, when the ATS gives one
isRemote, workplaceTypetrue for remote jobs. workplaceType is remote, hybrid, onsite or empty
employmentTypeFull-time, Part-time, Contract, Internship, Temporary, or the company's own label
postedAt, updatedAtISO dates in UTC. updatedAt only on Greenhouse and Recruitee
url, applyUrlThe job page and the application form
salary{ min, max, currency, period, text } when the company publishes pay, otherwise empty
salaryZonesGreenhouse only, on jobs with pay. Every pay zone the company lists, with its own title, range, currency and period
descriptionText, descriptionHtmlThe full job description as clean text and as the original HTML
seniorityintern, entry, mid, senior, lead, executive, or empty when the job gives no clear sign. Always the last field, see Seniority filter

Input

{
"companies": [
"stripe.com",
"Scale AI",
"https://job-boards.greenhouse.io/discord",
"https://jobs.lever.co/spotify",
"https://www.notion.so/careers",
"ramp",
"workable:skroutz",
"https://careers.smartrecruiters.com/Wix2"
],
"keywords": ["engineer", "data"],
"locations": ["London", "remote"],
"postedWithinDays": 30
}

Each line in companies can be any of these.

You passExample
A company websitestripe.com, launchdarkly.com, nubank.com.br
A company nameScale AI, Hims & Hers, Lucid Software
A job board URLboards.greenhouse.io/figma, jobs.lever.co/palantir, jobs.ashbyhq.com/openai, apply.workable.com/huggingface, careers.smartrecruiters.com/Canva, bunq.recruitee.com
Any job URL on a boardjobs.lever.co/spotify/0eebb462-... reads the whole Spotify board
A company career pagenotion.so/careers, figma.com/careers, palantir.com/careers
A board namestripe. The scraper checks every ATS for a board with that name, see below
A board name with the ATSgreenhouse:stripe, lever:zoox, smartrecruiters:ServiceNow. Faster and never ambiguous

Board URLs and ats:name pairs are read exactly as given. Everything else goes through the steps in the next section.

Company websites and names

For a website or career page URL the scraper tries, in order:

  1. Links to a supported board on the company's own pages: the page you gave, its careers links, and the usual /careers, /jobs, careers. and jobs. addresses. A page that links many companies' boards, like a job list, counts only for a board with the company's name.
  2. A board named after the domain on each of the six systems, kept only when the company name the ATS reports matches (stripe.com and "Stripe"). When two such boards exist, the one whose jobs mention the domain wins.
  3. The name the site gives itself, when it differs from the domain (wikimediafoundation.org calls itself "Wikimedia Foundation", whose board is wikimedia).
  4. Rarer board names: doordashusa on Greenhouse, Wix2 on SmartRecruiters.

For a company name it tries spellings of the name on each system (scaleai, scale-ai, scale), then the same page search on <name>.com, then the rarer board names. A board counts only when the company name the ATS reports matches yours, ignoring case, spaces and words like Inc, Ltd or Labs.

A bare word such as stripe is first read as a board name. A board whose company has that name wins. Otherwise the first board with jobs in the order Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee is used, as before, even when its company has another name, so check company on the rows.

When several boards match, the one with at least twice the jobs of the next is used. When two are closer than that, you get a free ambiguous row that lists up to 5 of them with their board URLs, and no jobs, so you can pass the one you want. Workable, SmartRecruiters and Recruitee have free accounts, and many sit dormant under big names, such as a SmartRecruiters board "Apple2" whose one job dates from 2016. A board on those three whose newest job is over a year old counts as empty.

What happened, measured on 238 real companies in three runs, given by website and by name, after the last change to how boards are found:

RunCompaniesJobs returnedRight boardWrong boardBoard exists, not foundBoard exists, no open jobsUnsupported systemCould not tell
2xhrfhszjWElHXgtD87575704, one of them ambiguous1223
IbqbOw4k0nf9ZiwQM7950500201413
emHhoiwCAXiPXAJfF7238371601414

Of the 157 companies whose board on one of the six systems we confirmed, the scraper found the right one for 144 (92%), and 93%, 96% and 84% per run. "Unsupported system" counts companies that hire through Workday (16), Phenom (9), iCIMS (5), Eightfold (4), SuccessFactors (3) and others. The scraper named the system in a free unsupported_ats row for 30 of those 50. For the other 20 the system shows only after the page's JavaScript runs, or the company runs its own job site. One more unsupported_ats row was wrong: remote.com's job list links another company's Personio board. "Could not tell" counts companies where we found no board on any system by hand either. The misses were boards only JavaScript reveals (digitalocean98 and ada18 on Greenhouse, an Ashby board named by a UUID), companies renamed or acquired (Superhuman, Auth0, Weights & Biases), boards named differently from the company (oscar for Oscar Health, montecarlodata for Monte Carlo), and one board named careers. The wrong board was Hetzner, a bare word, which matched a Recruitee board named hetzner that belongs to another company, shown as company: xneelo.

Filters

FilterWhat it does
keywordsKeep jobs whose title, department or team contains any of these
excludeKeywordsDrop jobs whose title contains any of these, for example senior or intern
locationsKeep jobs whose location or country contains any of these. remote matches every remote job
remoteOnlyKeep only remote jobs
postedWithinDaysKeep only jobs posted in the last N days
hasSalary, minSalaryKeep only jobs that list pay, or pay that reaches a minimum, see Salary filter
seniorityKeep only jobs at these levels, such as intern and entry, see Seniority filter
onlyNewSinceLastRunKeep only jobs this scraper has not returned to you before, see below
maxJobsPerCompanyStop after this many matching jobs per board
includeDescriptionTurn off for a smaller dataset. SmartRecruiters boards also run faster without it

Filters run before charging, so jobs they drop cost nothing.

Salary filter

Both salary filters are off by default and read the salary the board itself sends.

InputWhat it does
hasSalaryKeep only jobs that list a pay amount
minSalaryKeep jobs whose listed pay reaches this amount. The top of the range counts, so a job paying 120,000 to 160,000 passes a minimum of 150,000
salaryCurrencyThe currency of minSalary, USD unless you set it
salaryPeriodYEAR, MONTH, WEEK, DAY or HOUR, YEAR unless you set it
keepJobsWithoutSalaryWith minSalary, also keep jobs that list no pay in that currency and period

Amounts are never converted. With minSalary of 150,000 USD per year, a job paid in CAD or GBP, or per hour, counts as having no salary. Jobs with no salary are dropped, unless you turn on keepJobsWithoutSalary. A board where no job passes gives a free no_jobs row that says so.

How much pay the boards list, measured on 61 real boards with 7,935 open jobs (run D84UCSK0PMuLLr1gX, no filter):

SystemJobsList a pay amount
Ashby1,9561,203
Lever1,012307
SmartRecruiters3,158423
Greenhouse1,600919
Workable1870
Recruitee220

Workable boards send no pay, so the salary filter drops all of their jobs. Many companies write pay into the description instead, such as Notion on Ashby and Brex on Greenhouse, and the filter does not read descriptions.

Greenhouse pay zones

Greenhouse lists pay per zone, such as one range for New York and a lower one for Denver. salary spans every zone in the first zone's currency, from the lowest minimum to the highest maximum, and text holds the zone titles. salaryZones lists every zone as Greenhouse sends it. Greenhouse has no period field, so period is set only when the zone titles name exactly one period, as in "Annual Base Salary" or "Base Pay Range (hourly)". Otherwise it is empty, and minSalary treats the job as having no salary, because its period is unknown. The text around a range is not read for this, because on 88 boards its period words were mostly about bonuses and stock grants.

Measured on 88 Greenhouse boards with 10,225 open jobs (run qulMDQ0C9d9IkHB0b), 2,435 jobs on 36 boards list pay. Their titles name the year for 449, the hour for 136 and the month for 1, and no period for 1,849. So hasSalary kept 2,435 jobs (run nUOBNbtUFCxljij4o), and minSalary: 150000 kept 348 (run 8xcK72NJIR43cpfNg). We checked 20 of those jobs against their Greenhouse job pages by hand, and every zone range matched the page. A few companies label a range wrongly, such as an "Annual Base Salary" of $35 to $58, and the rows keep what the company sent.

This is real output from run qulMDQ0C9d9IkHB0b, shortened.

{
"status": "ok",
"input": "greenhouse:robinhood",
"company": "Robinhood",
"ats": "greenhouse",
"boardSlug": "robinhood",
"jobId": "6669758",
"title": "Android Engineer, Government Products",
"department": "ENG Infrastructure",
"team": null,
"location": "New York, NY",
"locations": [
"New York, NY"
],
"country": null,
"isRemote": false,
"workplaceType": null,
"employmentType": null,
"postedAt": "2025-05-08T18:52:01Z",
"updatedAt": "2026-10-02T22:48:15Z",
"url": "https://boards.greenhouse.io/robinhood/jobs/6669758?t=gh_src=&gh_jid=6669758",
"applyUrl": "https://boards.greenhouse.io/robinhood/jobs/6669758?t=gh_src=&gh_jid=6669758",
"salary": {
"min": 130000,
"max": 195000,
"currency": "USD",
"period": null,
"text": "Zone 1 (Menlo Park, CA; New York, NY; Bellevue, WA; Washington, DC); Zone 2 (Denver, CO; Westlake, TX; Chicago, IL); Zone 3 (Lake Mary, FL; Clearwater, FL; Gainesville, FL)"
},
"salaryZones": [
{
"title": "Zone 1 (Menlo Park, CA; New York, NY; Bellevue, WA; Washington, DC)",
"min": 166000,
"max": 195000,
"currency": "USD",
"period": null
},
{
"title": "Zone 2 (Denver, CO; Westlake, TX; Chicago, IL)",
"min": 147000,
"max": 172000,
"currency": "USD",
"period": null
},
{
"title": "Zone 3 (Lake Mary, FL; Clearwater, FL; Gainesville, FL)",
"min": 130000,
"max": 152000,
"currency": "USD",
"period": null
}
],
"descriptionText": "Join us in building the future of finance.\n\nOur mission is to democratize finance for all. An estima...",
"descriptionHtml": "<div class=\"content-intro\"><h2>Join us in building the future of finance.</h2>\n<p>Our mission is to ..."
}

On SmartRecruiters, pay comes from the same extra request per job as the description, so a salary filter makes those boards slower even with includeDescription off.

With minSalary: 150000 (run DWdnqZebGdQH2Hb3J) the scraper kept 1,763 jobs from 23 boards, every one paid in USD per year with a range that reaches 150,000. Before Greenhouse pay was added, we checked 20 kept and 20 dropped jobs on the other systems against their job pages by hand, and all 20 kept jobs showed the same range on their page. Turning the filters off gives the rows the scraper gave before they existed, except that Greenhouse jobs with pay now carry it.

This is real output from run khMgPJqa0fyXPNgfv on Ashby, shortened. Its range starts below 150,000 and reaches above it, so it passes.

{
"status": "ok",
"input": "ramp",
"company": "Ramp",
"ats": "ashby",
"boardSlug": "ramp",
"jobId": "4e64ab86-4e30-403b-b1b9-41dc052570ce",
"title": "Software Engineer, Frontend",
"department": "Engineering",
"team": "Frontend",
"location": "New York, NY (HQ)",
"locations": ["New York, NY (HQ)", "Remote (Canada)", "San Francisco, CA", "Remote (US)", "Miami, FL"],
"country": "USA",
"isRemote": false,
"workplaceType": "hybrid",
"employmentType": "Full-time",
"postedAt": "2023-03-09T17:44:00Z",
"updatedAt": null,
"url": "https://jobs.ashbyhq.com/ramp/4e64ab86-4e30-403b-b1b9-41dc052570ce",
"applyUrl": "https://jobs.ashbyhq.com/ramp/4e64ab86-4e30-403b-b1b9-41dc052570ce/application",
"salary": { "min": 143200, "max": 284000, "currency": "USD", "period": "YEAR", "text": "$143.2K - $284K" },
"descriptionText": "About Ramp\n\nRamp is building the smart infrastructure for finance teams, embedded in the transaction flow of every dolla...",
"descriptionHtml": "<h1><strong>About Ramp</strong></h1><p style=\"min-height:1.5em\">Ramp is building the smart infrastructure for ...",
"boardUrl": "https://jobs.ashbyhq.com/ramp",
"resolvedBy": "board_name",
"resolvedFrom": null
}

A Workable board gave this free row in run DWdnqZebGdQH2Hb3J.

{
"status": "no_jobs",
"input": "https://apply.workable.com/huggingface/",
"ats": "workable",
"boardSlug": "huggingface",
"message": "Board has 6 matching jobs, none with a salary that passes your salary filter. This scraper reads no salary from Workable boards, so none of their jobs lists one"
}

With onlyNewSinceLastRun, the salary filter is part of the filter set, so turning it on starts a new memory for that board. Runs without it keep the memory they had.

Seniority filter

Every job row ends with seniority, one of intern, entry, mid, senior, lead or executive, or empty when the job gives no clear sign. The seniority filter keeps only the levels you list, and it is off by default.

InputWhat it does
seniorityKeep jobs at these levels. ["intern", "entry"] finds internships, new grad and junior roles
keepJobsWithoutSeniorityWith seniority set, also keep jobs whose level is empty

The scraper reads the level in this order, and the first answer wins.

StepWhat it readsExamples
1Level words in the titleIntern, Co-op, Working Student, Stage, New Grad, Graduate, Junior, Entry Level, Senior, Sr., Staff, Principal, Lead, Supervisor, Engineering Manager, Director, VP, Head of, Chief
2The board saying intern or early careerAn Internship employment type, an Internships, Early Career or University department
3The years of experience the description asks for0 or 1 year is entry, 2 to 4 is mid, 5 or more is senior. Also "this internship" (intern) and "new grad" or "recent graduates" (entry)
4A level number or "Associate" in the titleEngineer I is entry, II is mid, III is senior. Sales Associate is entry
5The board's own level fieldSmartRecruiters and Workable experience level, Recruitee experience, Lever level

lead covers staff and principal engineers and people managers. executive covers directors and above. Titles that only look like levels are skipped, such as Account Executive, Member of Technical Staff, Staff Accountant and University Recruiter. A title with a range, such as "Senior / Staff" or "Junior - Mid level", gets the lowest level in it. The board's level field comes last because companies set it loosely: on 8 SmartRecruiters boards, 19 of the 36 "Entry Level" jobs whose title names a level had "Senior" in the title.

Jobs with an empty level are dropped by the filter, unless you turn on keepJobsWithoutSeniority. On 61 real boards with 7,934 open jobs (run P6vp0RLRT5mbKdPoL, no filter), 1,308 jobs (16%) had no clear level. Only 772 were intern or entry, so keeping the empty ones would make most rows of an intern search jobs with no level.

LevelJobs
intern317
entry455
mid1,487
senior2,458
lead1,506
executive403
empty1,308

With seniority: ["intern", "entry"] on 75 real boards across all six systems (run yeKF2Sv0WnKDsVXem), every board was read with no error. The scraper kept 1,927 jobs (444 intern, 1,483 entry) from 58 boards, and the other 17 boards each gave a free no_jobs row. A hand check of 30 kept jobs from 18 boards against their job pages found 28 at the level given and 29 that are intern or entry jobs. One was a fixed-term program for current university students, labelled entry where intern fits better. The other was a yoga instructor role asking for teaching experience, which only the board's own level field called entry.

On SmartRecruiters, the description comes from one extra request per job, so the seniority filter makes those boards slower even with includeDescription off. With the filter off and includeDescription off, SmartRecruiters rows get their level without the description. With onlyNewSinceLastRun, the seniority filter is part of the filter set, so turning it on starts a new memory for that board. Runs without it keep the memory they had.

Monitoring: only new jobs

Turn on onlyNewSinceLastRun and schedule the run, for example daily. The first run returns every matching job and remembers them. Each later run returns only jobs it has not returned to you before for the same board and filters, so you pay only for new postings. A job that a company re-posts under a new ID counts as new. A board with nothing new comes back as a free no_jobs row. The memory is a key-value store named career-site-jobs-scraper-monitor in your own Apify account, one record per board and filter set. Delete that store to start over.

Output example

This is real output for a Lever board URL from run P6vp0RLRT5mbKdPoL, with the description shortened here.

{
"status": "ok",
"input": "https://jobs.lever.co/aircall",
"company": "aircall",
"ats": "lever",
"boardSlug": "aircall",
"jobId": "0fbc9b05-520b-4cbb-a276-5a9a75e8f3bf",
"title": "Regional Marketing Intern - NA Market",
"department": "Marketing",
"team": "11003 - Demand Generation",
"location": "San Francisco Office",
"locations": ["San Francisco Office"],
"country": "US",
"isRemote": false,
"workplaceType": "hybrid",
"employmentType": "Internship",
"postedAt": "2026-01-14T18:14:31Z",
"updatedAt": null,
"url": "https://jobs.lever.co/aircall/0fbc9b05-520b-4cbb-a276-5a9a75e8f3bf",
"applyUrl": "https://jobs.lever.co/aircall/0fbc9b05-520b-4cbb-a276-5a9a75e8f3bf/apply",
"salary": { "min": 20, "max": 22, "currency": "USD", "period": "HOUR", "text": "The hourly rate is not including benefits, including health insurance and public transit + gym reimbursement." },
"descriptionText": "Aircall is a unicorn, AI-powered customer communications platform used by 22,000+ companies worldwide to drive revenue, resolve issues faste...",
"descriptionHtml": "<div><span style=\"font-size: 16px;\">Aircall is a unicorn, AI-powered customer communications platform used by 22,000+ co...",
"seniority": "intern"
}

This is real output for the board name ramp from the same run, shortened the same way. boardUrl, resolvedBy and resolvedFrom say which board the scraper used and how it found it.

{
"status": "ok",
"input": "ramp",
"company": "Ramp",
"ats": "ashby",
"boardSlug": "ramp",
"jobId": "fc971889-db1d-4a20-a25e-f282f9296936",
"title": "University Grad | Customer Experience Associate",
"department": "Customer Experience",
"team": "Customer Experience",
"location": "New York, NY (HQ)",
"locations": ["New York, NY (HQ)", "San Francisco, CA", "Remote (US)"],
"country": "USA",
"isRemote": false,
"workplaceType": "hybrid",
"employmentType": "Full-time",
"postedAt": "2026-09-09T12:19:47Z",
"updatedAt": null,
"url": "https://jobs.ashbyhq.com/ramp/fc971889-db1d-4a20-a25e-f282f9296936",
"applyUrl": "https://jobs.ashbyhq.com/ramp/fc971889-db1d-4a20-a25e-f282f9296936/application",
"salary": { "min": 60000, "max": 70000, "currency": "USD", "period": "YEAR", "text": "$60K - $70K" },
"descriptionText": "About Ramp\n\nRamp is building the smart infrastructure for finance teams, embedded in the transaction flow of every dollar a business spends....",
"descriptionHtml": "<h1><strong>About Ramp</strong></h1><p style=\"min-height:1.5em\">Ramp is building the smart infrastructure for finance te...",
"boardUrl": "https://jobs.ashbyhq.com/ramp",
"resolvedBy": "board_name",
"resolvedFrom": null,
"seniority": "entry"
}

resolvedBy is careers_page_link when a company page linked the board (resolvedFrom is that page), company_name_match when the board's company name matched, or board_name when a bare word matched a board name.

A company on a system this scraper does not read gives one free row, from an earlier test run.

{
"status": "unsupported_ats",
"input": "Nike",
"ats": "workday",
"boardSlug": null,
"message": "'Nike' uses workday, linked as https://nike.wd1.myworkdayjobs.com. This actor reads Greenhouse, Lever, Ashby, Workable, SmartRecruiters and Recruitee boards only.",
"boardUrl": "https://nike.wd1.myworkdayjobs.com"
}

Two close boards give one free ambiguous row, from run 2xhrfhszjWElHXgtD.

{
"status": "ambiguous",
"input": "Doctolib",
"ats": null,
"boardSlug": null,
"message": "Several boards match 'Doctolib' about equally well (none has twice the jobs of the next): Doctolib on Greenhouse, 149 jobs, https://job-boards.greenhouse.io/Doctolib; Doctolib on Ashby, 146 jobs, https://jobs.ashbyhq.com/Doctolib. Pass the board URL you want.",
"candidates": [
{ "ats": "greenhouse", "boardSlug": "Doctolib", "company": "Doctolib", "jobs": 149, "boardUrl": "https://job-boards.greenhouse.io/Doctolib" },
{ "ats": "ashby", "boardSlug": "Doctolib", "company": "Doctolib", "jobs": 146, "boardUrl": "https://jobs.ashbyhq.com/Doctolib" }
]
}

A board that doesn't exist gives one free row too.

{
"status": "not_found",
"input": "greenhouse:thisdoesnotexist999",
"ats": "greenhouse",
"boardSlug": "thisdoesnotexist999",
"message": "Board does not exist (https://boards-api.greenhouse.io/v1/boards/thisdoesnotexist999/jobs answered 404)"
}
StatusMeaningCharged
okOne open jobYes
no_jobsThe board exists but has no open jobs, or none matched your filters, including the salary and seniority filtersNo
not_foundNo such board on that ATSNo
ats_not_detectedNo supported board found for the website, name or page. The message lists the pages read and the board names triedNo
unsupported_atsThe company's pages link to a system this scraper does not read, such as Workday, iCIMS or SuccessFactors. ats names it and boardUrl is the link foundNo
ambiguousSeveral boards match the name about equally well. candidates lists them with their board URLsNo
invalid_inputThe line is not a URL, a name or a board nameNo
errorThe ATS kept failing after retriesNo

Use it from AI agents

The scraper works as a tool through the Apify MCP server in Claude, Cursor, VS Code and other MCP clients. An agent can pass a list of companies and a keyword, and get back structured jobs it can rank, summarise or match to a CV.

You can also call it through the Apify API or the Python and JavaScript clients, and schedule it or connect it to Make, Zapier and n8n. A daily schedule with onlyNewSinceLastRun: true gives you a feed of new jobs at the companies you track.

Pricing

Prices are per 1,000 jobs.

EventNo discountBronzeSilverGold
Job returned$1.50$1.35$1.10$0.90
Board not found, empty, filtered out, undetected, unsupported, ambiguous or failedFreeFreeFreeFree

Bronze, Silver and Gold are Apify Store discount tiers, and Apify's Pricing tab shows them as Starter, Scale and Business. Apify decides which tier an account is on.

Set a maximum cost per run in the run options. The scraper stops once that limit is reached.

FAQ

How do I find a company's board name? You usually don't need to: pass the company's website or name and check boardUrl on the rows. To be certain, open its careers page and click any job. The address shows the ATS and the board name, for example jobs.lever.co/zoox/... means Lever, board zoox. A board URL is always read exactly as given.

Why is a field empty for some jobs? Each ATS publishes different fields, and companies fill in different ones. Greenhouse has no employment type field of its own, so it only appears when the company adds one. Salary appears only when the company publishes it in the board's own pay fields. Workable boards send no pay, so their salary is always empty, even when the description states pay.

How fresh is the data? Every run reads the live board, so you get the jobs open at that moment.

Is a job listed once per location? No. A job with several locations is one row, with all of them in locations.

Limitations

  • Only Greenhouse, Lever, Ashby, Workable, SmartRecruiters and Recruitee boards. Workday, iCIMS, Taleo and custom career sites are not supported.
  • It reads the boards you give it, or the ones it finds for the companies you name. It does not search across all companies.
  • Finding a board from a website or name reads only the HTML the site sends. Boards that only JavaScript reveals, boards with numbered names such as digitalocean98, and companies renamed or acquired are often missed. Pass the board URL for those.
  • Workable boards give a posting date without a time.
  • SmartRecruiters needs one extra request per job for the description, so large SmartRecruiters boards take longer.
  • seniority is worked out by fixed rules from the title, the description and the board's level fields, not read from a field every board has. On the 30 hand-checked jobs it was right for 28, and 16% of jobs get no level.
  • The salary filter reads only the board's pay fields. It never converts currencies or periods, it drops every Workable job, and Greenhouse pay whose zone titles name no period never passes minSalary.