Greenhouse, Lever, Ashby & Workable Jobs Scraper (Any Company)
Pricing
from $1.00 / 1,000 jobs
Greenhouse, Lever, Ashby & Workable Jobs Scraper (Any Company)
Pull every open job from company career pages on Greenhouse, Lever, Ashby and Workable into one clean schema. Paste company URLs or slugs, filter by keyword, location, remote or posting date, and pay only per job returned.
Pricing
from $1.00 / 1,000 jobs
Rating
0.0
(0)
Developer
Nekruz Kasimov
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Get every open job from company career pages in one normalized dataset. Give it a list of companies, get back title, location, remote flag, department, employment type, salary where published, posting date, apply link, and optionally the full description. Works for any company whose careers page runs on Greenhouse, Lever, Ashby or Workable, which together host the hiring pages of tens of thousands of companies, from startups to Stripe, Palantir and OpenAI.
No browser, no proxies, no CAPTCHAs: it reads the official public job-board endpoints, so runs are fast, cheap and stable.
What you can do with it
- Build a job board or newsletter for a niche, a city or an industry, refreshed daily.
- Track hiring at competitors, customers or target accounts for sales and investment research.
- Feed AI agents and RAG pipelines with structured job data. The Actor is available over API and MCP.
- Monitor remote roles or specific titles across hundreds of companies with keyword and location filters.
- Source candidates' market data for recruiting and salary research.
Input
| Field | Description |
|---|---|
companies | One entry per company. Accepts a careers URL, a platform:slug entry, or a bare slug. Examples: https://boards.greenhouse.io/stripe, https://jobs.lever.co/palantir, https://jobs.ashbyhq.com/openai, https://apply.workable.com/careers, ashby:openai, stripe. A bare slug is tried on all four platforms. |
includeDescription | Add the full job description as plain text and HTML. Off by default to keep output small. |
keywords | Keep only jobs whose title contains one of these words, for example ["engineer", "designer"]. |
locations | Keep only jobs whose location contains one of these strings, for example ["Remote", "Berlin"]. |
remoteOnly | Keep only remote jobs. |
postedAfter | ISO date. Keep only jobs posted or updated on or after it. Use it for daily incremental runs. |
maxJobsPerCompany | Cap per company. 0 means no cap. |
Example:
{"companies": ["https://boards.greenhouse.io/stripe", "lever:palantir", "ashby:openai"],"keywords": ["engineer"],"remoteOnly": true,"postedAfter": "2026-10-01"}
Output
One dataset item per job:
{"platform": "greenhouse","company": "Stripe","companySlug": "stripe","jobId": "8172503","title": "Abuse Research Engineer","url": "https://stripe.com/jobs/search?gh_jid=8172503","applyUrl": "https://stripe.com/jobs/search?gh_jid=8172503","location": "Remote from the US","locations": ["US"],"country": null,"isRemote": true,"workplaceType": null,"department": "Security Analytics","team": null,"employmentType": null,"salary": null,"publishedAt": "2026-09-09T10:52:09-04:00","updatedAt": "2026-10-07T19:02:27-04:00","descriptionPlain": "Who we are ...","descriptionHtml": "<h2>Who we are</h2> ...","scrapedAt": "2026-10-12T20:08:49.690Z"}
isRemote comes from the platform's workplace type where one exists (remote vs hybrid vs on-site), otherwise from its remote flag, otherwise it is inferred from the location text. salary is filled when the board publishes a range (Lever and Ashby support this). Fields that a platform does not provide are null.
The run's key-value store also gets an OUTPUT record with a per-board summary: jobs found on the board, jobs after filters, jobs pushed, and any boards that were not found or failed. It contains:
boards: one entry per board or input entry, withinput,platform,slug,status,jobsOnBoard(jobs on the board),jobsFetched(jobs downloaded; on Workable this can be fewer when paging stops early at your cap),jobsAfterFilters,jobsPushed, plusdescriptionFailures,descriptionsStopped,errororduplicateOfwhen they apply.descriptionsStoppedistruewhen a Workable board's description requests were given up (see Notes). Aduplicateentry carries onlyinput,status,duplicateOfand, where the entry names a board,platformandslug.jobsPushed: jobs saved to the dataset.jobsCharged: jobs billed under pay-per-event pricing, otherwisenull.filteredOut: jobs dropped by each filter (keywords,locations,remoteOnly,postedAfter); a job is counted under the first filter it failed.descriptionFailures: jobs saved without a description (see Pricing).descriptionsRecovered: descriptions that failed once and succeeded on the automatic retry.chargeLimitReached:trueif the run stopped at your maximum charge per run.
Each board's status is one of:
ok: the board was read.partial: a later page of a Workable board failed. Jobs from the earlier pages are kept anderrorsays which page failed.not_found: the careers URL orplatform:slugentry points to no board.not_found_on_any_platform: a bare company name matched no board on Greenhouse, Lever, Ashby or Workable.unrecognized: the entry is not a supported careers URL or slug.error: the board request failed after retries;errorhas the reason.duplicate: the entry is the same board as an earlier one (see Notes), so it was not requested and not charged;duplicateOfis the first input for it. A bare company name that an earlier entry already looked up and did not find is reported the same way.
With includeDescription, a Workable job whose description could not be fetched is still returned, with null descriptions and a descriptionError field (for example HTTP 503, or not requested: 3 descriptions in a row failed (HTTP 503) when the Actor stopped requesting that board's descriptions, see Notes).
Pricing
You pay per job returned. Filtered-out jobs are free, so narrow filters keep costs low. Set a maximum charge per run in the run options if you want a hard cap; the Actor stops cleanly when it is reached (chargeLimitReached is true in OUTPUT).
A board entered more than once is requested and charged once: repeat entries show as duplicate in OUTPUT and cost nothing.
A Workable job whose description could not be fetched is not charged. Failed description requests are retried (rate limits and server errors get one more try after a short pause); if the description is still missing, the job is returned with descriptionError set and counted in descriptionFailures. If the Actor stops requesting a board's descriptions (see Notes), the jobs it skipped are saved the same way, free, with descriptionError: "not requested: ...". jobsCharged shows what was actually billed. Such a job still counts toward maxJobsPerCompany.
Limits and notes
- Only public, listed jobs are returned. Internal or unlisted postings are not visible on these endpoints.
- A company that moved from one ATS to another may still have an old, empty board on the previous platform. The
OUTPUTsummary tells you which board returned how many jobs. - Workable descriptions require one extra request per job, so
includeDescriptionis slower for Workable boards. - Workable description requests stop on a board after 3 failures in a row (rate limits, server errors or network errors, after their own retries). The remaining jobs wait for the retry after 5 s. If it works, the skipped jobs are fetched; if it fails, the board's descriptions are given up: those jobs are saved free with
descriptionError: "not requested: ...", the board'sOUTPUTentry getsdescriptionsStopped: true, and the final status message adds ", description requests stopped as Workable kept failing". - If a later page of a Workable board fails after retries, the board is marked
partialinOUTPUTand a warning is logged. The jobs already fetched are kept, and the final status message adds "(1 incomplete, see OUTPUT)", for exampleDone: 10 jobs from 1 boards (1 incomplete, see OUTPUT). CheckOUTPUTforpartialboards. - Boards are compared by platform and slug. Greenhouse and Ashby ignore case, so
greenhouse:Stripe,https://boards.greenhouse.io/stripe/andboards.greenhouse.io/STRIPEare one board. Lever and Workable slugs are case-sensitive, so another case there is another board. A repeat makes no request and is not charged. - A bare company name is probed across the four platforms only once; entering it again, or entering the board it matched as
platform:slugor as a URL, is a repeat. Known limit:greenhouse:xfollowed by a barexstill probes the other three platforms. A repeat of a board that failed is not retried. - Careers URLs without
https://(for examplejobs.lever.co/palantir) are read as URLs, not as bare slugs. - On Workable, a job that appears on two list pages (the board changed while it was being read) is kept once, so
jobsFetchedandmaxJobsPerCompanycount it once. - A run that saves no jobs says why in its status message and in a warning, for example
Done: 0 jobs. 1033 jobs found but all filtered out (1033 by keywords) (see OUTPUT). Other reasons it can give: charge limit reached, board empty, not found, not recognized, failed, or only partly fetched. - Rate limits (HTTP 429), server errors (5xx) and network errors are tried up to 3 times. The Actor waits for the
Retry-Afterthe server sends (at most 60 s), otherwise 1.5 s, then 3 s. - Found a company whose careers page runs on a different ATS? Open an issue on the Actor page and it may be added.
Integrations
Run it on a schedule, then push results to Google Sheets, Airtable, Slack, a webhook or your own database with Apify integrations. Call it from code with the Apify API clients for JavaScript and Python, or let an AI agent call it as a tool through the Apify MCP server.