# Greenhouse, Lever & Ashby Jobs Scraper | No Slugs + Salaries (`corvuslab/greenhouse-jobs-scraper`) Actor

Search Greenhouse jobs without knowing a board slug: a built-in directory of 2,800+ US and EU employers, filtered by title, location, country, remote/hybrid, department, posting date or published salary. Or paste a slug, board or job URL, careers page or company name. 55 fields incl. salary min/max.

- **URL**: https://apify.com/corvuslab/greenhouse-jobs-scraper.md
- **Developed by:** [Corvuslab](https://apify.com/corvuslab) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What does Greenhouse, Lever & Ashby Jobs Scraper do?

Greenhouse, Lever & Ashby Jobs Scraper pulls open jobs from company career boards on **six applicant-tracking systems: Greenhouse, Lever, Ashby, SmartRecruiters, Personio and Recruitee**, all in one shared output. You don't need a single board slug: a **built-in directory of 11,000+ live employers** lets you search them all at once by job title, location, country, workplace type, department, posting date, or **only jobs with a published salary**. Every job comes back as a typed record with **63 structured fields**, including the employer's official pay range (`salaryMin` / `salaryMax` / `salaryCurrency` / `salaryPeriod`), the posting split into `qualifications`, `responsibilities` and `benefits`, and a `skills` list. No code needed: export to JSON, CSV or Excel, use the API, or hand it to an AI agent.

Leave **Companies** empty to search the whole directory, or list the employers you care about in whatever form you have: a board URL from any of the six systems, a job link, a slug, the company's own careers page, or just its name. The system is detected automatically.

> New to Apify? Sign up free and use the included monthly platform credit to try this Actor — no credit card needed.

**Why this scraper**

- 🧩 **Six job-board systems, one schema**: Greenhouse, Lever, Ashby, SmartRecruiters, Personio and Recruitee in a single run.
- 🗂️ **No slugs needed**: search 11,000+ employers by keyword, country, system and salary.
- 💰 **Structured official salary** and **requirements, benefits and skills** split out of every posting.
- ♻️ **Cheap to monitor**: incremental mode returns only new, changed and closed jobs, with alerts to Telegram, Slack, Discord, WhatsApp or any webhook.

***

### What data can you extract from Greenhouse, Lever & Ashby?

Every record carries **63 fields** grouped like this:

| Group | What you get |
|---|---|
| **Job & links** | Job ID, job-board system, title, company name and slug, board URL, job URL, direct apply URL, requisition ID, internal job ID, language, employer custom fields |
| **Location & workplace** | Raw location, city, region, country, ISO country code(s), offices, workplace type (remote / hybrid / on-site), remote flag |
| **Salary** | `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, readable `salaryText`, every published pay zone in `salaryRanges`, a `hasSalary` flag |
| **Team & role** | Department, all departments, team, seniority, experience level, employment type, industry, posted and last-updated dates |
| **Requirements & skills** | `qualifications`, `responsibilities` and `benefits` as text, plus a `skills` list (Python, AWS, Salesforce …) |
| **Contacts & application** | Emails, phones, URLs and social profiles found in each posting, plus the application form's questions |
| **Description** | Full text as plain text, HTML and/or Markdown, with an optional length cap |
| **Change tracking** | `changeType` (NEW / UPDATED / UNCHANGED / REAPPEARED / EXPIRED), first/last seen, expiry and repost detection |

***

### Key features

- 🧩 **Six systems in one run**: mix Greenhouse, Lever, Ashby, SmartRecruiters, Personio and Recruitee boards freely; `ats` tells you where each job came from, and every field means the same thing whichever system it came from.
- 🗂️ **Search without board slugs**: a bundled directory of 11,000+ live employers across all six systems, indexed by the countries they hire in, so a country filter only reads the boards actually hiring there. Narrow it to one system with **Job-board systems**.
- 🔗 **Any input form**: a board or job URL from any system, a slug or `lever:spotify`, the company's own careers page (it finds the board behind it), or the company's name. EU-hosted Greenhouse and Lever boards work like the rest.
- 💰 **Official structured salary**: min/max/currency/period from the employer's own published pay ranges, every zone kept in `salaryRanges`. Nothing is guessed, and **Only jobs with a published salary** filters to them in one click.
- 🧠 **Requirements, benefits and skills**: each posting is split into `qualifications`, `responsibilities` and `benefits`, with a `skills` list mined from the requirements. Experience level and industry come through where the system publishes them.
- 🎚️ **Exact filters**: whole-word title keywords (`engineer OR developer`), exclusions (`senior` also drops "Sr."), description keywords, locations with spelling aliases (NYC/New York, Bangalore/Bengaluru), 35 countries, remote / hybrid / on-site, departments, posted-within-days and salary-only. Only jobs that pass are returned and billed.
- 📇 **Contacts & application questions**: emails, phones, URLs and social profiles from each description, a `requireContact` filter for lead lists, and the application form's questions on Greenhouse and Recruitee boards.
- ♻️ **Incremental change feed & alerts**: only NEW / UPDATED / REAPPEARED jobs, plus **EXPIRED** records when a role closes and optional repost skipping, pushed to Telegram, Slack, Discord, WhatsApp or a webhook. Compact and drop-empty modes keep payloads lean for LLMs and MCP.

***

### 🚀 How to scrape Greenhouse, Lever & Ashby jobs

1. Open the Actor. Leave **Companies** empty to search the whole directory, or list the companies you want (board URL, job link, slug, careers page or name).
2. Add **Job title keywords** (e.g. `product manager`) and any filters: country, location, workplace type, department, posted within, salary only, job-board system.
3. Set **Max results** and choose whether to **include full job details** and **application questions**.
4. (Optional) Turn on **incremental mode** and a **notification** channel, then **Schedule** it.
5. Click **Start**. Results stream into the Output tab, ready to download as **JSON, CSV or Excel** or to pull from the **API**.

No login, no cookies, no API key: it works as soon as you start it.

#### Quick-start example input

```json
{
  "searchTerms": ["product manager"],
  "countries": ["US"],
  "requireSalary": true,
  "postedWithinDays": 14,
  "maxResults": 100
}
```

This searches every company in the directory, on all six systems, for product-manager roles in the United States posted in the last two weeks **that publish a salary range**, and returns up to 100 jobs spread across many employers: a ready-made pay benchmark without typing a single company name.

***

### Input

Configure everything in the visual editor, or pass JSON via the API. There are **42 inputs** in seven groups.

#### 🏢 Companies

| Field | Type | What it does |
|---|---|---|
| `companies` | array | One per line, in any form below. **Leave empty to search the whole directory.** |
| `searchAllCompanies` | boolean | Search the whole directory *as well as* the companies you listed. Always on when Companies is empty. |
| `platforms` | array | Limit a directory search to some systems: `greenhouse`, `lever`, `ashby`, `smartrecruiters`, `personio`, `recruitee`. Empty = all six. |
| `maxCompanies` | integer | Limit how many directory companies are searched, biggest employers first. 0 = all. |

What you can paste into **Companies**:

| System | Board URL example | Also accepted |
|---|---|---|
| Greenhouse | `https://job-boards.greenhouse.io/stripe` (or `job-boards.eu.greenhouse.io/...`) | `stripe`, a job link, an embed link |
| Lever | `https://jobs.lever.co/spotify` | `lever:spotify`, a job link, `jobs.eu.lever.co/...` |
| Ashby | `https://jobs.ashbyhq.com/ramp` | `ashby:ramp`, a job link |
| SmartRecruiters | `https://jobs.smartrecruiters.com/BoschGroup` | `smartrecruiters:BoschGroup`, a job link |
| Personio | `https://1komma5grad.jobs.personio.de` | `personio:1komma5grad`, a job link |
| Recruitee | `https://channable.recruitee.com` | `recruitee:channable`, a job link |

A bare slug is looked up in the directory, then on each system. A careers page (`stripe.com/jobs`) or a company name (`Figma`) works too.

#### 🔎 Filters

Every filter is exact. Several values inside one filter widen it (OR); different filters narrow it (AND).

| Field | Type | What it does |
|---|---|---|
| `searchTerms` | array | Keep jobs whose title contains any of these words or phrases (whole words, case-insensitive). `engineer OR developer` in one line works too. |
| `excludeKeywords` | array | Drop jobs whose title contains any of these, e.g. `senior`, `intern`, `manager`. `senior` also drops "Sr." titles. |
| `searchInDescription` | boolean | Match keywords against the full description too, not just the title. |
| `locations` | array | Keep jobs whose location contains any of these, e.g. `London`, `Berlin`, `New York`, `Remote`. Spelling aliases match both ways. |
| `countries` | array (35 options) | US, GB, CA, DE, IE, FR, NL, ES, IN, AU, SG, JP, BR, MX, PL, PT, SE, CH, IL, AE, IT, DK, NO, FI, BE, AT, CZ, RO, AR, CO, NZ, HK, KR, PH, ZA. Also narrows a directory search to companies hiring there. |
| `workplaceTypes` | array | `remote`, `hybrid`, `onsite`. |
| `departments` | array | Keep departments or teams whose name contains any of these, e.g. `Engineering`, `Sales`, `Data`. |
| `postedWithinDays` | integer | Keep jobs first published in the last N days. 0 = any time. |
| `requireSalary` | boolean | Keep only jobs where the employer publishes a pay range. |

#### 📏 Limits

| Field | Type | What it does |
|---|---|---|
| `maxResults` | integer | Maximum jobs to return (default 50). 0 = every matching job. |
| `maxJobsPerCompany` | integer | Cap per company. 0 = no cap for companies you list, and 10 per company in a directory search so results span many employers. |
| `ignoreUrlFailures` | boolean | Skip a careers page with no supported job board instead of failing the run (on by default). Unknown slugs and names are always skipped with a warning. |

#### 📄 Output

| Field | Type | What it does |
|---|---|---|
| `includeDetails` | boolean | Add the description, its qualifications / responsibilities / benefits, skills and contacts (on by default). Salary, location and dates are always included. |
| `includeApplicationQuestions` | boolean | Add each job's application form questions (label, required, answer type) on Greenhouse and Recruitee boards. |
| `descriptionFormat` | enum | `all`, `text`, `html` or `markdown`. |
| `descriptionMaxLength` | integer | Trim each description to this many characters. 0 = full length. |
| `requireContact` | enum | `off`, `email`, `phone`, `either` or `both`: keep only jobs whose description carries that contact. |
| `compact` | boolean | Core fields only. Ideal for AI agents and MCP clients. |
| `excludeEmptyFields` | boolean | Drop null, empty-string and empty-array fields. |

#### ♻️ Incremental monitoring

| Field | Type | What it does |
|---|---|---|
| `incrementalMode` | boolean | Track jobs between runs and tag each with a `changeType`. Unchanged jobs are skipped and cost nothing. |
| `stateKey` | string | Stable name for the tracked search (auto-derived from your settings if blank). |
| `emitUnchanged` | boolean | Also emit jobs that haven't changed since the last run. |
| `emitExpired` | boolean | Emit a record for each job that was open last run and is gone now (needs a run that reads every matching job). |
| `skipReposts` | boolean | Drop a new job that repeats one already tracked under a new ID. |

#### 🔔 Notifications

Credentials are secret inputs: encrypted at rest, masked in the UI and never written to the run log.

| Field | Type | What it does |
|---|---|---|
| `telegramToken` + `telegramChatId` | string | Telegram bot token + chat or channel ID. |
| `slackWebhookUrl` / `discordWebhookUrl` | string | Incoming webhook URL. |
| `whatsappPhoneNumberId` + `whatsappAccessToken` + `whatsappTo` | string | WhatsApp Cloud API sender, token and recipient. |
| `webhookUrl` + `webhookHeaders` | string / object | POST the run's job count and first jobs as JSON anywhere: n8n, Make, Zapier. |
| `notificationLimit` | integer | How many jobs to include in each message (1–20, default 5). |
| `notifyOnlyChanges` | boolean | Keep unchanged jobs out of alerts even when they are emitted. |

#### ⚙️ Advanced

| Field | Type | What it does |
|---|---|---|
| `maxConcurrency` | integer | How many company boards to read at once (default 20, up to 24). |
| `maxRequestRetries` | integer | Retries per failed request (default 3). |
| `proxyConfiguration` | object | Optional and not needed for normal runs. |

#### More example inputs

Boards from all six systems in one run, five jobs each:

```json
{
  "companies": [
    "https://job-boards.greenhouse.io/stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp",
    "https://jobs.smartrecruiters.com/BoschGroup",
    "https://1komma5grad.jobs.personio.de",
    "https://channable.recruitee.com"
  ],
  "maxJobsPerCompany": 5,
  "maxResults": 30
}
```

Remote or hybrid data jobs in the UK and Germany across the directory, seniors excluded:

```json
{
  "searchTerms": ["data engineer", "data scientist", "analytics"],
  "excludeKeywords": ["senior", "staff", "principal"],
  "countries": ["GB", "DE"],
  "workplaceTypes": ["remote", "hybrid"],
  "maxResults": 200
}
```

A daily change feed for a watch-list of companies, posted to Slack:

```json
{
  "companies": ["stripe", "https://jobs.ashbyhq.com/ramp", "https://jobs.lever.co/zoox"],
  "searchTerms": ["engineer"],
  "incrementalMode": true,
  "emitExpired": true,
  "maxResults": 0,
  "slackWebhookUrl": "https://hooks.slack.com/services/XXX/YYY/ZZZ"
}
```

***

### Output

Each dataset item is one job. Here is a real Ashby record, trimmed for length (descriptions also come as `descriptionHtml` and `descriptionMarkdown`):

```json
{
  "id": "ashby:ramp:34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "ats": "ashby",
  "title": "Security Engineer, Cloud",
  "company": "Ramp",
  "companySlug": "ramp",
  "boardUrl": "https://jobs.ashbyhq.com/ramp",
  "url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
  "location": "New York, NY (HQ)",
  "city": "New York City",
  "region": "NY",
  "country": "United States",
  "countryCode": "US",
  "countryCodes": [
    "US",
    "CA"
  ],
  "offices": [
    "New York, NY (HQ)",
    "Remote (Canada)",
    "Remote (US)",
    "Miami, FL"
  ],
  "workplaceType": "hybrid",
  "isRemote": false,
  "department": "Engineering",
  "departments": [
    "Engineering"
  ],
  "team": "Backend",
  "employmentType": "FULL_TIME",
  "postedAt": "2026-04-07T17:12:35.753000+00:00",
  "salaryMin": 211400,
  "salaryMax": 290600,
  "salaryCurrency": "USD",
  "salaryPeriod": "YEAR",
  "salaryText": "211,400 – 290,600 USD per year",
  "salaryRanges": [
    {
      "min": 211400,
      "max": 290600,
      "currency": "USD",
      "period": "YEAR",
      "label": null
    }
  ],
  "hasSalary": true,
  "description": "About Ramp\nRamp is building the smart infrastructure for finance teams, embedded in the transaction flow of every dollar a business spends. We automate how …",
  "qualifications": "• Minimum 5 years of experience building software\n• Minimum 3 years of experience building in AWS (with Terraform)\n• A strong sense of ownership: you need …",
  "responsibilities": "The Security Engineering team helps make Ramp the most secure place for our customers to collect, manage, and put to work their business’ financial …",
  "benefits": "• Flexible PTO\n• Centralized home-office equipment ordering\n• Health and wellness stipend\n• Budget for intra-office travel\n• Weekly coffee stipend",
  "skills": [
    "AWS",
    "Terraform",
    "Python",
    "Flask",
    "Azure",
    "GCP"
  ],
  "extractedUrls": [
    "https://engineering.ramp.com/",
    "http://Ramp.com",
    "https://ramp.com/legal/applicant-privacy-notice"
  ],
  "source": "ashbyhq.com",
  "scrapedAt": "2026-10-20T11:18:40.862777+00:00",
  "detailFetched": true
}
```

#### Data fields

- **Core**: `id`, `jobId`, `ats`, `title`, `company`, `companySlug`, `boardUrl`, `url`, `applyUrl`, `requisitionId`, `internalJobId`, `language`, `customFields`.
- **Location**: `location`, `city`, `region`, `country`, `countryCode`, `countryCodes`, `offices`, `workplaceType`, `isRemote`.
- **Salary**: `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryText`, `salaryRanges`, `hasSalary`.
- **Role**: `department`, `departments`, `team`, `seniority`, `experienceLevel`, `employmentType`, `industry`, `postedAt`, `updatedAt`.
- **Details** (with `includeDetails`): `description`, `descriptionHtml`, `descriptionMarkdown`, `qualifications`, `responsibilities`, `benefits`, `skills`, plus `applicationQuestions` when that option is on.
- **Contacts & signals** (only when the posting has them): `extractedEmails`, `extractedPhones`, `extractedUrls`, `socialProfiles`.
- **Tracking**: `source`, `searchKeyword`, `scrapedAt`, `detailFetched`, `contentHash`, plus, in incremental mode, `changeType`, `firstSeenAt`, `lastSeenAt`, `previousSeenAt`, `expiredAt`, `isRepost`, `repostOfId`, `repostDetectedAt`.

The core, location, salary and role fields are on every record (`null` when the employer or the system leaves them blank); details, contacts and tracking fields appear when the run's options produce them. **Compact mode** returns the core fields only, and **`excludeEmptyFields`** drops empty values entirely. The Output tab ships two ready-made views: **Overview** (title, company, job board, location, workplace, salary, department, posted date, link) and **Salaries** (title, company, location, min, max, currency, period, seniority, link).

***

### ♻️ How to monitor Greenhouse, Lever & Ashby jobs with incremental mode

Schedule the Actor and turn on **incremental mode**. Each run compares against the previous one and emits only **NEW / UPDATED / REAPPEARED** jobs, plus **EXPIRED** records for roles that closed if you ask for them. Unchanged jobs are recognised *before* their details are fetched, so you skip both the work and the cost.

| | First run (baseline) | Next run (nothing changed) |
|---|---|---|
| Jobs found | all | all |
| Job details fetched | all | **none** |
| Records emitted & billed | all | **none** |

After the baseline, your cost depends on how much the boards you watch change, not on how many jobs you watch:

| Daily churn | of 1,000 tracked | billable records | you save |
|---|---|---|---|
| 5 % | 1,000 | 50 | **95 %** |
| 15 % | 1,000 | 150 | **85 %** |
| 30 % | 1,000 | 300 | **70 %** |

#### Setting up scheduled monitoring

1. Configure your companies (or a directory search) and filters, and turn on **Incremental mode**.
2. For EXPIRED records, set **Max results** to 0 and turn on **Include closed jobs**. In a directory search, also raise **Max jobs per company** so no board is cut short.
3. Run it once to seed the baseline, then open the **Schedules** tab and set a recurring run (e.g. hourly or daily).
4. Add a notification channel. In incremental mode it only hears about jobs that changed.

***

### 🔔 How to set up Greenhouse, Lever & Ashby job alerts

Get pinged as soon as a matching job goes live on any of the six systems. Fill in only the channels you want:

| Channel | What to configure |
|---|---|
| ✈️ **Telegram** | `telegramToken` (from @BotFather) + `telegramChatId` |
| 💬 **Slack** | `slackWebhookUrl` (Incoming Webhook URL) |
| 🎮 **Discord** | `discordWebhookUrl` (channel webhook URL) |
| 📱 **WhatsApp** | `whatsappPhoneNumberId` + `whatsappAccessToken` + `whatsappTo` (WhatsApp Cloud API) |
| 🪝 **Webhook** | `webhookUrl` receives the job count and the first jobs as JSON, which suits n8n / Make / Zapier |

Each message lists up to `notificationLimit` jobs with title, company, location, salary and link. Every channel fires independently, so a broken channel can't stop the scrape. Pair alerts with **incremental mode** to hear only about new roles.

***

### 💡 What can you do with job-board data?

#### Benchmark salaries from official pay ranges

Filter to jobs with a published salary and you get the employer's own min/max per role, currency and period. Group by title, country and seniority to see what companies actually pay, not what a survey estimates.

#### Catch new roles straight from the employer

A company's job board is its own job list, so nothing is relayed or delayed. Watch your target employers, or every directory company hiring in your country, and get new roles the day they open.

#### Recruiting, talent and competitive intelligence

See which companies are hiring for which teams, where, and how fast. Departments, teams, skills, remote share and posting dates show where headcount is growing, and closed-job records show which roles have come down.

#### Sales prospecting and lead generation

A company opening a burst of roles in one department is a buying signal. Build account lists from company, department, location and hiring volume, and add the contacts published in postings.

#### Feed AI agents and LLM pipelines

Compact JSON, ready-split requirements and skill lists go straight into an LLM context, an MCP tool or a vector store. `excludeEmptyFields`, compact mode and `descriptionMaxLength` keep token costs low.

***

### 💰 How much does it cost to scrape Greenhouse, Lever & Ashby?

This Actor uses Apify's **pay-per-event** model: a small fee when a run starts, plus a fee for each job record. The current numbers are in the Actor's **Pricing** tab, rendered live so this page never goes stale.

Each board is read in one pass and only matching jobs are enriched, which keeps the fee per job low, and the start fee is small so frequent scheduled runs stay cheap. Two things cut your bill further:

- **Filters**: jobs your filters reject are never returned and never billed. Narrow by country, title and salary first.
- **Incremental mode**: after the baseline you pay only for what changed (see the table above).

***

### 🔌 Integrations & export

Export to **JSON, CSV, Excel** or an HTML table, or pull from the **REST API** and the **JavaScript / Python** clients. Runs can go on a **schedule**, connect to **Google Sheets, Slack, Make, Zapier and n8n**, and work as an **MCP tool** for AI agents, where compact mode keeps token usage small.

***

### 🔗 Using the API

Run this Actor from your own code. Example with the Apify Python client:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")

run_input = {
    "searchTerms": ["software engineer"],
    "countries": ["US", "CA"],
    "workplaceTypes": ["remote"],
    "requireSalary": True,
    "maxResults": 50,
}

run = client.actor("corvuslab/greenhouse-jobs-scraper").call(run_input=run_input)

for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["ats"], job["title"], job["company"], job["salaryMin"], job["salaryMax"], job["url"])
```

***

### ❓ FAQ

**Which job-board systems are covered?** Greenhouse (US and EU), Lever (US and EU), Ashby, SmartRecruiters, Personio and Recruitee. Workday, iCIMS and similar enterprise systems are not covered.

**Does the directory cover every company on these systems?** No. It holds 11,000+ employers with live boards (about 3,000 Ashby, 2,800 Greenhouse, 1,900 Personio, 1,600 Lever, 1,300 SmartRecruiters and 1,100 Recruitee boards), sorted biggest first and refreshed over time. Any company that isn't in it can still be read: paste its board URL, slug or careers page into Companies.

**Why is the salary empty on some jobs?** Salary comes only from the pay range the employer publishes in a structured field; nothing is estimated from text. Ashby and Recruitee boards carry one most often, Greenhouse and Lever boards when the employer fills it in, SmartRecruiters boards rarely. Turn on **Only jobs with a published salary** to keep just those.

**Why does a directory search return only 10 jobs per company?** It's on purpose, so a broad search spans many employers instead of filling up with the biggest board. Set `maxJobsPerCompany` to change it. Companies you list yourself are uncapped by default.

**Why are qualifications, seniority or experience level sometimes null?** Sections are only filled when the posting puts them under a recognisable heading (or the system splits them itself), seniority when the title or the employer states it, experience level and industry only where the system publishes them (SmartRecruiters, Personio, Recruitee). A missing value is left `null` rather than guessed.

**How often will I find contact details?** About 3 in 10 descriptions include an email address (often recruiting or accommodations contacts). Phone numbers are rare. Use `requireContact` to keep only jobs that have them; jobs without one are skipped and not billed.

**What if a careers page isn't recognised?** It finds the board behind most careers pages, including those of every directory employer. A page with no supported board is skipped with a warning, or fails the run if you turn **Skip companies that can't be found** off. Pasting the board URL always works.

**Is a description search slower on some boards?** On SmartRecruiters boards, yes: the job list carries no descriptions, so a description keyword search reads each job's posting. Title searches are just as fast as elsewhere.

***

### ⚖️ Is it legal to scrape Greenhouse, Lever & Ashby?

This Actor collects only **publicly available** job postings that employers publish on their Greenhouse, Lever, Ashby, SmartRecruiters, Personio and Recruitee job boards. You're responsible for how you use the data, in particular any personal information such as recruiter contact details, and for complying with each platform's terms and applicable law (including the GDPR/LGPD where they apply). Not affiliated with, endorsed by or sponsored by Greenhouse Software, Lever, Ashby, SmartRecruiters, Personio, Recruitee or any employer whose jobs it reads.

***

**Keywords:** greenhouse scraper · greenhouse jobs scraper · greenhouse api · lever scraper · lever jobs api · ashby scraper · ashby jobs api · smartrecruiters scraper · personio jobs scraper · recruitee scraper · multi-ats job scraper · ats jobs api · scrape greenhouse jobs · greenhouse salary data · company career page scraper · job postings scraper · salary benchmarking · job monitoring · MCP tool for AI agents

# Changelog

This Actor's version history is a separate document: https://apify.com/corvuslab/greenhouse-jobs-scraper/changelog.md

# Actor input Schema

## `companies` (type: `array`):

One per line. Any of: a board URL from Greenhouse (job-boards.greenhouse.io/stripe), Lever (jobs.lever.co/spotify), Ashby (jobs.ashbyhq.com/ramp), SmartRecruiters (jobs.smartrecruiters.com/BoschGroup), Personio (acme.jobs.personio.de) or Recruitee (acme.recruitee.com); a job URL; a slug (stripe) or 'lever:spotify'; a company's own careers page (stripe.com/jobs); or a company name from the directory (Figma). The system is detected automatically. Leave empty to search the whole directory.

## `searchAllCompanies` (type: `boolean`):

Search every company in the built-in directory as well as the ones you listed. Always on when Companies is empty.

## `platforms` (type: `array`):

Limit a directory search to these systems. Empty = all six. Companies you list are always read.

## `maxCompanies` (type: `integer`):

Limit how many directory companies are searched (biggest employers first). 0 = all of them.

## `searchTerms` (type: `array`):

Keep jobs whose title contains any of these words or phrases (whole words, case-insensitive). "engineer OR developer" in one line works too. Leave empty for all jobs.

## `excludeKeywords` (type: `array`):

Drop jobs whose title contains any of these, e.g. senior, intern, manager. "senior" also drops "Sr." titles, "junior" drops "Jr.".

## `searchInDescription` (type: `boolean`):

Match the keywords against the full job description too, not just the title. Finds more jobs, reads more data.

## `locations` (type: `array`):

Keep jobs whose location contains any of these, e.g. London, Berlin, New York, Remote. Spellings are matched both ways: NYC/New York, Bangalore/Bengaluru, Munich/München.

## `countries` (type: `array`):

Keep jobs located in any of these countries. Also narrows a directory search to companies hiring there.

## `workplaceTypes` (type: `array`):

Keep only remote, hybrid or on-site jobs.

## `departments` (type: `array`):

Keep jobs in departments whose name contains any of these, e.g. Engineering, Sales, Data.

## `postedWithinDays` (type: `integer`):

Keep jobs first published in the last N days. 0 = any time.

## `requireSalary` (type: `boolean`):

Keep only jobs where the employer publishes a pay range.

## `maxResults` (type: `integer`):

Maximum number of jobs to return. 0 = unlimited (every matching job).

## `maxJobsPerCompany` (type: `integer`):

Cap the jobs returned per company. 0 = no cap for the companies you list, and 10 per company when searching the directory so results span many employers.

## `ignoreUrlFailures` (type: `boolean`):

Skip a careers page with no Greenhouse board instead of failing the run. Unknown slugs and names are always skipped with a warning.

## `includeDetails` (type: `boolean`):

Add the job description, contacts, qualifications, responsibilities, benefits and skills. Turn off for the fastest runs (departments and offices may then be missing on some boards).

## `includeApplicationQuestions` (type: `boolean`):

Add the questions each job's application form asks (label, required, answer type). Available on Greenhouse and Recruitee boards.

## `descriptionFormat` (type: `string`):

Which representation(s) of the job description to include.

## `descriptionMaxLength` (type: `integer`):

Trim each description to this many characters. 0 = full length.

## `requireContact` (type: `string`):

Keep only jobs whose description includes a contact. off = keep everything; email / phone = require that channel; either = at least one; both = email and phone.

## `compact` (type: `boolean`):

Emit only the core fields. Ideal for AI agents and MCP clients.

## `excludeEmptyFields` (type: `boolean`):

Remove null, empty-string and empty-array fields from each record.

## `incrementalMode` (type: `boolean`):

Track state between runs and tag every job with a changeType (NEW / UPDATED / UNCHANGED / REAPPEARED / EXPIRED). Unchanged jobs are skipped and cost nothing.

## `stateKey` (type: `string`):

Stable name for the tracked search. Leave empty to derive one automatically from your settings.

## `emitUnchanged` (type: `boolean`):

Also emit jobs that have not changed since the previous run.

## `emitExpired` (type: `boolean`):

Emit a record for each job that was open last run and is gone now. Needs a run that reads every matching job: Max results 0, and in a directory search a Max jobs per company high enough not to cut a board short.

## `skipReposts` (type: `boolean`):

Drop a new job that repeats one already tracked (same title, company and place under a new ID).

## `telegramToken` (type: `string`):

Bot token from @BotFather.

## `telegramChatId` (type: `string`):

Chat or channel ID, e.g. "-100123456789" or "@yourchannel".

## `slackWebhookUrl` (type: `string`):

Slack incoming-webhook URL.

## `discordWebhookUrl` (type: `string`):

Discord incoming-webhook URL.

## `whatsappPhoneNumberId` (type: `string`):

WhatsApp Cloud API phone number ID (Meta Business).

## `whatsappAccessToken` (type: `string`):

WhatsApp Cloud API access token.

## `whatsappTo` (type: `string`):

Recipient phone number in international format, e.g. 447700900123.

## `webhookUrl` (type: `string`):

Any HTTPS endpoint. Receives a JSON POST with the total count and the first jobs (up to Jobs per notification) — works with n8n, Make and Zapier.

## `webhookHeaders` (type: `object`):

Extra headers for the webhook request, e.g. {"Authorization": "Bearer xyz"}.

## `notificationLimit` (type: `integer`):

How many jobs to include in each notification message.

## `notifyOnlyChanges` (type: `boolean`):

When unchanged jobs are emitted too, keep them out of notifications: only new, updated, reappeared and closed jobs are sent.

## `proxyConfiguration` (type: `object`):

Optional. Not needed — Greenhouse job boards are read directly. Turn on only if your runs are refused.

## `maxConcurrency` (type: `integer`):

How many company boards to read at once.

## `maxRequestRetries` (type: `integer`):

How many times to retry a failed request before giving up on it.

## Actor input object example

```json
{
  "companies": [
    "stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp"
  ],
  "searchAllCompanies": false,
  "maxCompanies": 0,
  "searchTerms": [
    "engineer"
  ],
  "searchInDescription": false,
  "postedWithinDays": 0,
  "requireSalary": false,
  "maxResults": 50,
  "maxJobsPerCompany": 0,
  "ignoreUrlFailures": true,
  "includeDetails": true,
  "includeApplicationQuestions": false,
  "descriptionFormat": "all",
  "descriptionMaxLength": 0,
  "requireContact": "off",
  "compact": false,
  "excludeEmptyFields": false,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "skipReposts": false,
  "notificationLimit": 5,
  "notifyOnlyChanges": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "maxConcurrency": 20,
  "maxRequestRetries": 3
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `allItems` (type: `string`):

No description

## `salaries` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "companies": [
        "stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp"
    ],
    "searchTerms": [
        "engineer"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("corvuslab/greenhouse-jobs-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "companies": [
        "stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp",
    ],
    "searchTerms": ["engineer"],
}

# Run the Actor and wait for it to finish
run = client.actor("corvuslab/greenhouse-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "companies": [
    "stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp"
  ],
  "searchTerms": [
    "engineer"
  ]
}' |
apify call corvuslab/greenhouse-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,corvuslab/greenhouse-jobs-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/qXu8Wg8QJWhyy7y4C/builds/PPtqR0R5OVNK63Yuo/openapi.json
