Greenhouse Jobs Scraper: All 5 ATS Boards
Pricing
from $0.20 / 1,000 job postings
Greenhouse Jobs Scraper: All 5 ATS Boards
Scrape job postings from Greenhouse, Lever, Ashby, Workable and Recruitee boards into one row shape for a list of companies, at $0.45 per 1,000 ($0.20 on Gold) with the full description. Title, location, department, pay when published, apply link. Get only new jobs, and the ones that closed.
Pricing
from $0.20 / 1,000 job postings
Rating
0.0
(0)
Developer
Pradio Actors
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
10 days ago
Last modified
Categories
Share
What does Greenhouse Jobs Scraper do?
Greenhouse Jobs Scraper returns one row per posting from Greenhouse jobs boards. It covers Lever, Ashby, Workable and Recruitee boards the same way. Give it company names, careers-page URLs or slugs. It finds each board itself. Or start from a preset list. Every posting comes back in the same 36-field shape. Each row carries title, location, department and team. It also carries pay where published, the apply link and the full posting text. $0.45 per 1,000 postings. A board it cannot find or read costs nothing. Its row says why.
On a schedule it returns the postings new since your last run. It adds the ones that closed or changed. No login, no API key, no browser. The boards answer their own public feeds.
The prefilled input reads three companies on three different boards:
{"company": ["https://jobs.ashbyhq.com/ramp","https://boards.greenhouse.io/stripe","https://jobs.lever.co/spotify"]}
Who uses Greenhouse Jobs Scraper
| Who | What they run it for |
|---|---|
| Recruiters and sourcers | A target list's live postings, with apply links and published pay |
| Talent intelligence and job boards | Every open role across a company list, in one shape for five vendors |
| Sales teams on hiring signals | Which roles a watched company opens, where and at what pay |
| Agents and pipelines | Company names in, the same columns out, whichever board answered |
Features
- Five boards, one shape. Workable postings land in the same rows as Greenhouse, Lever, Ashby and Recruitee.
- Board found from a name. A company name or careers-page URL is tried on all five boards until one answers. A guessed board is kept only when the employer name it publishes agrees with your entry.
- Preset company lists. AI startups (21), fintech (12), developer tools (16) and top tech (14), each checked on its vendor's feed.
- Filters you set. Keep only the postings that publish pay. Or drop the roles and places you do not want by title, department, location, remote flag, date or type.
- New and closed postings. A scheduled run can return only what appeared since your last run. It adds the postings that left the board or changed.
- Pay read where published. The vendor's pay fields, or a range the posting text states next to a pay word, with on-target earnings flagged.
- Postings by address. Paste posting page URLs and get one row each. The list filters do not apply to them.
- Clean text beside the original. The posting as published, plus the same text as clean plain text on every vendor.
- Optional English translation. Made inside the run by a bundled model, with no outside service. The posted text is never rewritten.
What you can count on
- An entry it cannot answer still comes back as a row naming what you gave it and the reason.
- A row is charged only after it is written to your dataset.
- A run that finds nothing returns one row saying so, never an empty dataset.
- A spending limit you set on the run stops it cleanly. A last row says how many rows came back and how many did not.
- Every run saves a summary of what it read, returned and charged. A short run is easy to tell from a broken one.
- A board that refuses a read or answers with an error is reported in the log. Its own row says so too. It is never returned as empty rows.
- No value is invented. A field the board does not publish stays null. The tables below say which.
Why this one
| This Actor | Most-used alternative | |
|---|---|---|
| Postings returned | 285 | 10 |
| Price per 1,000 postings | $0.45 | $1.50 |
The most-used alternative on this platform, run on the same three-company input on 2026-10-11. This Actor's run stopped at its default 100-per-company cap on two of the three boards.
- Workable boards in the same rows as Greenhouse, Lever, Ashby and Recruitee. None of this shelf's leading Actors reads Workable.
- A published-pay filter and exclusion lists keep or drop the postings you choose. The leading Actors do not offer them.
- A $0.00005 start charge a run, the smallest on this shelf. Start fees elsewhere run to $0.005.
- The source on every row. Each posting carries its own URL and apply link. You can check any row against the live board.
What data does Greenhouse Jobs Scraper return?
The example run read three boards: Ramp on Ashby, Stripe on Greenhouse, Spotify on Lever. One of its postings, cut to the fields a buyer reads first:
{"platform": "ashby","official_url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245","title": "Security Engineer, Cloud","posted_date": "2026-04-07T17:12:35.753+00:00","location": "New York, NY (HQ)","is_remote": false,"workplace_type": "Hybrid","department": "Engineering","team": "Backend","description": "ABOUT RAMP\n\nRamp is building the smart infrastructure for finance teamsโฆ","salary_minimum": 211400,"salary_maximum": 290600,"salary_currency": "USD","salary_text": "$211.4K - $290.6K","pay_interval": "year","has_equity": true,"company_name": "Ramp","apply_url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application","city": "New York City","employment_type": "full_time"}
Fields on every posting row
Every value is the board's own. A field the board leaves out stays null. A few fields are derived instead, and they say so beside them. They are the company name from the slug, the level read from the title and the cleaned employment type. They also cover the split city, region and country, the remote flag on Greenhouse and the cleaned description text.
| Field | What it carries |
|---|---|
platform | Which board answered: ashby, greenhouse, lever, workable or recruitee |
official_url | The posting's public URL on the board |
title | Job title as published |
posted_date | When the posting was published |
location | Workplace text as published |
is_remote | The vendor's remote mark; Greenhouse infers it from the location wording |
workplace_type | remote, hybrid or onsite as published; null on Greenhouse |
description | The full posting text, exactly as published |
description_text | The same text as clean plain text |
job_type | Employment type in the vendor's own words |
employment_type | Cleaned: full_time, part_time, contract, intern or temporary |
department | Its department; on Ashby and Lever, the team when none is given |
team | Team inside the department; Ashby and Lever only |
job_id | The vendor's own posting identifier |
company_slug | The board slug that answered |
company_name | Employer's display name; the slug in title case on Ashby and Lever |
address | The vendor's structured location, unchanged |
apply_url | Where a person applies; falls back to the posting URL |
seniority | The level the title states, intern to c-level, else null |
city, region, country | The location split out; country as a two-letter code where it resolves |
salary_minimum, salary_maximum | Published pay floor and ceiling in the posting's currency |
salary_currency | ISO currency code of the published pay |
salary_text | The pay as the posting states it; may be on-target earnings |
pay_interval | The period the pay is stated for: year to hour |
has_equity | Equity in the published package; Ashby only |
salary_is_ote | True when the published pay says OTE or on-target earnings |
compensation | The vendor's own pay object, unchanged |
is_repost | Repost mark when Flag reposted postings is on; null otherwise |
sections | Parsed description sections when Split description into sections is on |
translation | English translation when translateTo is en |
translation_language | The detected language of the posting text |
translation_note | Why translation is null, or its machine-translation label |
row_type | ROW on a posting; the status kinds are in the Output table |
status, reason, subject | ok on a posting row; on a status row, which note, why, and what it is about |
rowsFetched, rowsReturned, rowsRemaining | The counts behind a status row |
Which board fills which field
Counts from the example run (100 Ashby rows, 100 Greenhouse, 85 Lever):
| Field group | Ashby | Greenhouse | Lever |
|---|---|---|---|
| Core posting fields: title, URL, date, location, description, apply link, ids | every row | every row | every row |
| department, team | all | department only | all |
| workplace_type | all | null | all |
| job_type, employment_type | all | null | 79 / 78 of 85 |
| is_remote | all | 9 of 100 | all |
| address | all | null | null |
| Pay columns (min, max, currency, text, interval, equity, OTE, raw object) | 97 of 100 | none on this board | none on this board |
| city / region / country | 93 / 88 / 100 | 58 / 10 / 18 | 82 / 30 / 85 |
| seniority | 32 of 100 | none on this board | 54 of 85 |
A lower number means that board published the value less often or not at all. Every row keeps the same shape either way. Workable and Recruitee were not on that input.
How much does it cost?
$0.45 per 1,000 postings ($0.00045 each), plus $0.00005 once a run to start it. The two charges:
| Charge | Price | When |
|---|---|---|
listing-returned | $0.00045 | One posting written to your dataset |
apify-actor-start | $0.00005 | Once per run |
| Postings written | You pay |
|---|---|
| 100 | $0.045 |
| 1,000 | $0.45 |
| 10,000 | $4.50 |
On Apify's paid Store plans the posting price steps down. Bronze is $0.00035 per posting ($0.35 per 1,000). Silver is $0.0003 ($0.30). Gold, Platinum and Diamond are $0.0002 ($0.20). Status rows and dropped duplicates are free. A posting your filters drop is dropped before it is written. Platform compute is billed separately by Apify, the same as for any Actor.
How do I use Greenhouse Jobs Scraper?
- Open the Actor in the Apify Console and press Start. The prefilled input reads Ramp, Stripe and Spotify on three vendors.
- Replace the company list with yours: names, careers-page URLs, slugs or board URLs, one per line. Or pick a preset list, or paste posting page addresses.
- Set any filters you want. For a schedule, turn on Only new since my last run and Report closed postings.
- Press Start. Rows land in your dataset as they are read. The run's summary in the key-value store holds the totals.
Worked examples
Use it to get every open job at companies you know by name:
{"company": ["Hugging Face", "Scale AI", "https://careers.bunq.com"],"maxItemsPerCompany": 50}
Use it to find engineering jobs at AI startups posted this week:
{"preset": "ai-startups","keywords": ["engineer", "researcher", "scientist"],"departments": ["engineering", "research"],"postedWithinDays": 7,"maxItemsPerCompany": 30}
Use it to collect remote jobs from company boards:
{"company": ["ramp", "greenhouse:stripe", "lever:zoox"],"remoteOnly": true,"maxItemsPerCompany": 100}
Use it to track new fintech jobs every day:
{"preset": "fintech","onlyNew": true,"postedWithinDays": 2,"maxItemsPerCompany": 100}
Through the API, post the same JSON to https://api.apify.com/v2/acts/Pradio~greenhouse-lever-ashby-job/runs. Read the dataset back from the run.
Input
| Input | Type | Default | What it does |
|---|---|---|---|
company | list of strings | three board URLs | One entry per company: name, careers-page URL, slug, vendor:slug or board URL |
preset | choice | (none) | A bundled list: ai-startups, fintech, devtools or top-tech |
postingUrls | list of strings | one Ramp posting | Posting page addresses, one row each; the list filters do not apply |
titleKeyword | string | (none) | Keep postings whose title contains this text |
keywords | list of strings | (none) | Keep postings whose title contains any of these |
excludeTitleKeywords | list of strings | (none) | Drop postings whose title contains any of these |
searchDescription | boolean | false | Also match those words in the posting text |
departments | list of strings | (none) | Keep postings whose department contains any of these |
locationKeyword | string | (none) | Keep postings whose location contains this text |
excludeLocationKeywords | list of strings | (none) | Drop postings whose location contains any of these |
remoteOnly | boolean | false | Keep only postings the vendor marks remote |
postedWithinDays | integer | (none) | Keep postings published in the last N days |
employmentTypes | list of choices | (none) | Keep these types: full_time, part_time, contract, intern, temporary |
onlyWithPay | boolean | false | Keep only postings that publish pay |
maxItemsPerCompany | integer | 100 | Most posting rows per company; each board capped on its own |
maxItems | integer | (none) | An overall cap across the whole run |
onlyNew | boolean | false | Skip postings an earlier run already returned |
reportClosed | boolean | false | Add rows for postings that closed or changed since the last run |
memoryName | string | (empty) | A schedule's own memory name; up to 20 letters, digits or hyphens |
includeDescription | boolean | true | Off is a cheaper title-and-link pass on Workable |
detectReposts | boolean | false | Mark reposted postings from a board's second run on |
structuredDescription | boolean | false | Add sections parsed from the posting text |
omitEmptyFields | boolean | false | Drop the fields a row carries null |
translateTo | choice | (none) | en adds an English translation made inside the run |
company
One entry per company, in any of these forms:
- A company name (Hugging Face, Scale AI): the likely slugs are tried on all five boards. A guessed board is kept only when the employer name it publishes agrees with your entry.
- A careers-page URL or domain (careers.bunq.com, stripe.com): the domain's name is tried as a slug on every board. The page itself is not fetched.
- A slug, bare or tagged (ramp, greenhouse:stripe): a bare slug tries each board in turn. vendor:slug pins the vendor when two boards share one. Find a slug in a job's address: jobs.ashbyhq.com/ramp gives ramp.
- A board URL (jobs.ashbyhq.com/ramp, boards.greenhouse.io/stripe, jobs.lever.co/zoox, apply.workable.com/nuvei, bunq.recruitee.com): read directly.
An entry naming a vendor outside the five (a Personio or SmartRecruiters board) gets a status row. So does one pointing at a private hiring API. Neither is fetched.
postingUrls
One posting page address per entry, on the five hosts (jobs.ashbyhq.com, boards.greenhouse.io, jobs.lever.co, apply.workable.com, company.recruitee.com). Each returns the one posting it names, as listed. The list filters do not apply to it. Any other answer comes back as a status row. It says the board does not list that posting now. Or the address is no posting page it reads, or the vendor is not one of the five. Leave Companies and the preset empty to read only the postings you name.
The memory switches
Only new since my last run skips postings an earlier run already returned. Report closed postings watches each board's open postings. It adds a row when one leaves. It also adds a row when a posting's title, department, team, location, remote flag or apply link changes. Both keep their lists in a store under your account. The stores hold posting URLs and those watched fields, never pay or descriptions. Memory name gives each schedule its own pair. Delete the store to start over.
Output
A posting row carries every field above. A value the board leaves out stays null. A duplicate posting URL is dropped before it is written. A company no board answers still gets a row naming it and saying why. An empty dataset never stands in for the answer:
Every word in the status column says what happened to one entry, in one row that names it; row_type says whether the row is a posting (ROW) or a verdict (ITEM_STATUS), and reason says it in words. These are all the words the Actor can write โ no fourth value ships undocumented:
row_type | status | When it appears |
|---|---|---|
ROW | ok | A posting |
ITEM_STATUS | vendor_not_read | A vendor it does not read, a removed board, a robots-disallowed feed |
ITEM_STATUS | read_refused | The board refused the read; not retried that run |
ITEM_STATUS | no_board_found | No vendor has a public board for the entry |
ITEM_STATUS | posting_closed | A posting on the board's last run is no longer listed |
ITEM_STATUS | posting_changed | A watched field on a listed posting moved |
ITEM_STATUS | no_postings | The board was read and had nothing to return |
ITEM_STATUS | posting_not_found | A posting address whose board does not list it now |
ITEM_STATUS | bad_input | An entry that is unusable; the row says why, and it costs nothing |
ITEM_STATUS | duplicate | The same board or posting as an entry above it; that entry's rows are the answer. This row names the repeat, costs nothing and spends no maxItems slot (the cap counts postings); on a run-ending row it counts among the rows pushed before that row, which is what rowsReturned means here |
ITEM_STATUS | not_found | No postings came back for this input; the reason names which (a board that refused, a vendor this Actor does not read, a board with nothing to return, a posting its board does not list) โ a refused read is named, never filed as a no |
ITEM_STATUS | not_reached | An entry the run stopped before reading; nothing was billed for it |
NO_MATCHING_LISTINGS | The same run-ending row as not_found; the two words differ only in row_type | |
STOPPED_EARLY | aborted | Your spending limit ended the run; the counts and the entries it never returned ride on the row |
STOPPED_EARLY | error | The run stopped on a fault; the rows it already returned are kept, and this row names what failed |
| run entry | RUN_SUMMARY | End of every run, in your key-value store, with the totals |
Zero postings is an answer, not silence. The row's reason says the board was empty, the filters dropped everything, or no board answered the entry. A refused read keeps the postings already collected. The run goes on to the next board.
What can you do with the data?
Watch hiring across a list. A recruiting lead schedules a weekly run over its target companies with Only new on. Monday's dataset holds the roles that appeared. Report closed postings shows which ones filled.
Feed a board or a market map. An aggregator sweeps a preset list and keeps the postings that publish pay. Title, pay, level and place are already columns whatever board answered. The rows go straight into a table.
Screen a niche. A sourcing lead mixes the filters. Remote only, posted in the last week, engineering departments, no intern titles. What is left is a short list with apply links and the full posting text.
Use Greenhouse Jobs Scraper with AI agents
claude mcp add --transport http apify "https://mcp.apify.com?tools=Pradio/greenhouse-lever-ashby-job"
Paste that line to give an MCP client this Actor as a tool.
Personal data
- Each row describes a job posting, not a person. No field pulls a name, email or phone out of the posting text. A name an employer wrote stays inside it.
- Every posting row carries
platform,official_urlandapply_urlwhere the vendor gives one. Each row links back to its source posting. - The posting text is the employer's own advert, unchanged. The optional translation is made inside the run by a bundled model. The text is never sent to an outside service, trained on or republished as a corpus.
- The only-new and closed-postings memories hold posting URLs and watched posting fields. They live in a store under your own account. No applicant or person data is stored.
- You are the controller for the rows you keep. Use them for reading and job-market research, with a lawful basis for keeping them.
- To have a row corrected or removed, open an issue on the Actor's Issues tab. A board owner or a person named in a posting uses the same route. A listed board goes on a removal list every run reads first.
Release notes
- 2026-10-11: postings by address (one row each). A repost flag, description sections, drop-empty-fields and an optional English translation made inside the run.
- 2026-09-28: Report closed postings and changed-posting rows, the memory name switch, devtools and top-tech presets, the team column.
- 2026-09-25: SmartRecruiters and Personio left unread (their rules do not allow the read). The five vendors above stay.
See also
- YC Jobs Listings Scraper: roles on Y Combinator's Work at a Startup board.
- Authentic Jobs Scraper: design, development and remote roles on the Authentic Jobs board.
- Companies House New Companies Scraper: this week's new UK incorporations, for hiring-signal users who also watch new companies.
Limits
- Five vendors only. SmartRecruiters, Personio and Workday have no logged-out feed this Actor may read. An entry naming one gets a status row saying so.
- A refused board is not retried. Not in the same run, and not under another identity or proxy. Its entry gets a status row naming it.
- Pay is the employer's choice. On the example's Ashby board nearly every row carried it. The other two boards published none. Null is what the board answered.
- Greenhouse publishes less. No employment type, no team, no workplace word. Its remote flag is inferred from the word Remote in the location.
- Seniority only when the title says it. Most titles state no level. Those rows stay null rather than guess one.
- A company name is a guess. Check
company_nameon the rows. Pin the vendor as vendor:slug when it matters. - Memory starts on the second run. The only-new and closed-postings switches need a first run to learn the board.
- Filters need their field. Posted-within, department, location, remote and type filters drop a posting that has no value to test.
- Workable is read per posting. Its description lives on a separate endpoint, one request per kept posting. With Include the full description off, you get a title-and-link pass.
Troubleshooting
Fewer rows than the board lists? The per-company cap (default 100) stopped it. Raise Max postings per company, or set Max postings in total for the run.
Zero rows? The one status row says which. The board was read and had nothing, your filters dropped everything, or no board answered your entry. An empty board is an answer, not a broken run.
The wrong company answered? Two vendors can share a slug. Check company_name on your rows. Pin the vendor: greenhouse:stripe instead of stripe.
A board refused the read? Its status row names it. The other boards still ran. A refusal is not retried in that run. Take the entry off the list or run again later.
FAQ
Can I run it on a schedule?
Yes. Add a schedule in the Apify Console. A cron such as 0 7 * * * works. With Only new since my last run on, each run returns what appeared since the last.
Do I need a proxy or an API key?
Neither. The boards answer a plain logged-out request over a direct connection. A board that refuses gets a status row naming it.
Can I use integrations with Greenhouse Jobs Scraper?
Yes. Apify's integrations (Slack, email, webhooks, Make and Zapier) fire on a finished run. They read the dataset, so a digest or a sheet update is wiring, not code.
Can I use it with the Apify API?
Yes. The API runs it. It returns the dataset as JSON, CSV, Excel or XML, the same as the Console.
Can I use it through an MCP server?
Yes. The one-line setup in the AI agents section adds it to a Claude or another MCP client as a tool.
How much does it cost to scrape job boards?
It is $0.00045 per posting written. On Gold plans and up it is $0.0002. Add $0.00005 once a run. Status rows and dropped duplicates are free.
What if a company has no open postings?
You get one row saying so. The board was read and had nothing, or nothing passed your filters. An empty dataset never stands in for the answer.
Is it legal to scrape the boards?
It reads the five boards' public logged-out feeds. It honours each host's robots rules. A block stops that board's read. Nothing goes around it. Having the right to read the boards you give it, and how you use the rows, is your responsibility. This page is not legal advice.
Can I export the results to Excel or CSV?
Yes. The dataset exports as JSON, CSV, Excel or XML from the run's Export button, or through the API. The same row schema applies in every format.
How many jobs can I scrape in one run?
Each company keeps up to 100 postings by default. Raise Max postings per company for bigger boards, or cap the whole run with Max postings in total. A company list can hold as many entries as you need.
How accurate is the data?
A row is what the board published when it was read, so the data is as fresh as the last run. When a vendor names its employer, the board must prove it is your company before its rows are kept. Check company_name on the rows to see who answered.
Feedback
Something broken, or a field you need? Open an issue on the Actor's Issues tab. We answer within two days. If this Actor saved you time, a review on its Store page helps other buyers find it.
Not affiliated
Greenhouse Jobs Scraper is an independent tool. It is not affiliated with, endorsed by or maintained by Greenhouse Software, Lever (Employ Inc.), Ashby, Workable or Recruitee. Nor by any employer whose public job board you read through it. Job postings belong to the companies that posted them.