# Greenhouse, Lever & Ashby Jobs Scraper (`piping_satisfaction/jobs-scraper`) Actor

Collect open jobs from any company's public Greenhouse, Lever or Ashby job board, with title and location filters and salaries, in one consistent format.

- **URL**: https://apify.com/piping_satisfaction/jobs-scraper.md
- **Developed by:** [Mohamed-amine Elouardi](https://apify.com/piping_satisfaction) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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** collects every open job from any company whose careers page runs on [Greenhouse](https://www.greenhouse.com/), [Lever](https://www.lever.co/) or [Ashby](https://www.ashbyhq.com/) — the applicant tracking systems behind thousands of startups and tech companies, such as Discord, Spotify, Palantir, Notion, Ramp and Linear.

Give it a list of companies and get back one clean, consistent dataset: job title, location, department, team, workplace type (remote, hybrid, onsite), salary range, posting date, and links to the job and the application form. Download it as JSON, CSV, Excel or HTML, or send it straight to Google Sheets, Slack, a database or your own app.

It reads the official public job-board APIs that power those careers pages, so it is **fast** (a few seconds per run), **reliable** (no browser, no proxies, no fragile page parsing) and **cheap**.

### What data can you get?

| Field | Example |
| --- | --- |
| Job title | Security Engineer, Cloud |
| Company | ramp |
| Location (all offices) | New York, NY (HQ); Remote (Canada); Remote (US); Miami, FL |
| Workplace type | hybrid |
| Department / team | Engineering / Backend |
| Employment type | FullTime |
| Salary range | 211,400 – 290,600 USD per year |
| Country | USA |
| Posted date | 2026-04-07 |
| Job link and apply link | `https://jobs.ashbyhq.com/ramp/…` |
| Full description (optional) | plain text, ready for search or AI analysis |

### Why use this jobs scraper?

- 🧩 **Three platforms, one format.** Greenhouse, Lever and Ashby all structure their data differently. This Actor maps them to the same fields, so you can mix companies from all three in one dataset.
- 💰 **Salaries as numbers.** When a company publishes pay on Lever or Ashby, you get minimum, maximum, currency and interval as separate fields — ready for sorting and charts.
- 🆕 **Only new jobs.** Flip one switch and every run returns only jobs posted since the previous run. Ideal for daily alerts and job feeds.
- 🎯 **Filter while you collect.** Keep only jobs whose title or location matches your terms, for example "engineer" roles that are "Remote". Jobs open in several offices match if any of their offices does.
- 🔗 **Paste names or links.** `discord` and `https://job-boards.greenhouse.io/discord` both work.
- 🛡️ **Keeps going on errors.** A misspelled or closed board is logged and skipped; all other companies are still collected.
- 💸 **Pay only for what you get.** $1.00 per 1,000 jobs, nothing for jobs your filters leave out.

### Who is it for?

- **Job seekers** — get a daily list of new roles at your dream companies, filtered to your skills and location.
- **Job boards and newsletters** — keep a niche board (remote, design, climate, a city…) full of fresh roles automatically.
- **Recruiters and talent teams** — track who is hiring for which roles, and benchmark salaries against competitors.
- **Sales and B2B marketing** — a company hiring 10 salespeople or its first data engineer is a buying signal. Turn new job posts into leads.
- **Investors and analysts** — follow hiring momentum across a portfolio or a market segment.
- **Researchers and AI builders** — build clean, structured datasets of job postings and descriptions.

### How to scrape jobs from Greenhouse, Lever and Ashby

1. **Find each company's board name** in its careers page URL — it's the part right after the domain:

   | Platform | Careers URL looks like | Board name |
   | --- | --- | --- |
   | Greenhouse | `job-boards.greenhouse.io/discord` or `boards.greenhouse.io/discord` | `discord` |
   | Lever | `jobs.lever.co/spotify` | `spotify` |
   | Ashby | `jobs.ashbyhq.com/ramp` | `ramp` |

   Tip: if a company's own careers page (like `company.com/careers`) has "Apply" links pointing to one of these domains, that's the board to use.
2. **Add the names** to **Greenhouse boards**, **Lever companies** and/or **Ashby boards**. You can list many companies in one run.
3. **Optional:** add **Title keywords** (e.g. `engineer`, `sales`) and **Locations** (e.g. `Remote`, `London`).
4. Click **Start**. Most runs finish in seconds.
5. Open the **Output** tab to view the jobs table, or **Export** the results in the format you need.

### Input

| Field | What it does | Default |
| --- | --- | --- |
| Greenhouse boards | Greenhouse board names or URLs | none |
| Lever companies | Lever company names or URLs | none |
| Lever region | `global`, or `eu` for boards hosted at `jobs.eu.lever.co` | `global` |
| Ashby boards | Ashby board names or URLs | none |
| Title keywords | Keep jobs whose title contains any of these words | no filter |
| Locations | Keep jobs whose location or workplace type (remote, hybrid, onsite) contains any of these terms | no filter |
| Include job descriptions | Add the full job description as plain text | off |
| Max results | Stop after this many jobs in total | 1000 |
| Only new jobs | Return only jobs that earlier runs haven't returned yet | off |
| History name | Which memory **Only new jobs** uses | `default` |

Filters are not case-sensitive. At least one board is required.

Example input — remote engineering and data roles at five companies:

```json
{
    "greenhouseBoards": ["discord"],
    "leverCompanies": ["spotify", "palantir"],
    "ashbyBoards": ["ramp", "linear"],
    "keywords": ["engineer", "data"],
    "locations": ["remote"],
    "includeDescription": false,
    "maxResults": 1000
}
```

### Output

Each job is one item in the dataset. Fields a platform doesn't provide are `null`. Example:

```json
{
    "source": "ashby",
    "company": "ramp",
    "companyName": null,
    "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
    "title": "Security Engineer, Cloud",
    "location": "New York, NY (HQ); Remote (Canada); Remote (US); Miami, FL",
    "department": "Engineering",
    "team": "Backend",
    "employmentType": "FullTime",
    "workplaceType": "hybrid",
    "country": "USA",
    "salaryRange": { "min": 211400, "max": 290600, "currency": "USD", "interval": "1 YEAR" },
    "url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
    "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
    "postedAt": "2026-04-07T17:12:35.753+00:00",
    "updatedAt": null,
    "descriptionText": null,
    "scrapedAt": "2026-10-18T02:19:43.742000+00:00"
}
```

Which fields each platform fills in:

| Field | Greenhouse | Lever | Ashby |
| --- | --- | --- | --- |
| `source`, `company`, `jobId`, `title`, `location`, `department`, `url`, `applyUrl`, `postedAt`, `scrapedAt` | ✅ | ✅ | ✅ |
| `companyName`, `updatedAt` | ✅ | — | — |
| `team`, `employmentType`, `workplaceType`, `country`, `salaryRange` | — | ✅ when the company fills them in | ✅ when the company fills them in |
| `descriptionText` | ✅ with **Include job descriptions** | ✅ with **Include job descriptions** | ✅ with **Include job descriptions** |

Notes:

- Jobs open in several offices list all of them in `location`, separated by `; `.
- `workplaceType` is always lowercase: `onsite`, `hybrid`, `remote` (or `unspecified` on some Lever boards).
- `salaryRange.interval` is given as the platform states it, e.g. `per-year-salary` (Lever) or `1 YEAR` (Ashby).
- `country` is a two-letter code on Lever (`US`) and a name on Ashby (`USA`, `United Kingdom`).

### Get only new jobs every day

1. Turn on **Only new jobs**.
2. Run the Actor once. The first run returns every matching job and remembers them.
3. [Schedule](https://docs.apify.com/platform/schedules) the Actor to run daily (or hourly). Each run now returns only jobs posted since the previous one — often just a handful, so it costs almost nothing.
4. Optional: add an [integration](https://docs.apify.com/platform/integrations) to send each run's new jobs to Slack, email or Google Sheets.

How it works:

- The Actor remembers the jobs it has **returned** in a key-value store named `jobs-scraper-history` in your Apify account. Jobs left out by your filters, by **Max results** or by a cost limit are not remembered, so they show up as soon as they qualify.
- Jobs that close are forgotten automatically, so the memory stays small.
- Running several setups, for example one for remote engineers and one for London sales roles? Give each its own **History name** so they don't affect each other.
- To start over, change the **History name**, or delete the record `seen-<history name>` in the `jobs-scraper-history` store.

### How much does it cost?

You pay only for the jobs you get: **$1.00 per 1,000 jobs**, plus a tiny start fee per run ($0.00005). There's no monthly fee, and full job descriptions don't cost extra.

| You collect | You pay |
| --- | --- |
| 100 jobs | about $0.10 |
| 1,000 jobs | about $1.00 |
| 10,000 jobs | about $10.00 |

With **Only new jobs** on a daily schedule, you pay only for that day's new postings — for example, 30 new jobs cost about $0.03.

Ways to keep the cost down:

- Use **Title keywords** and **Locations**. Jobs your filters leave out are never charged.
- Turn on **Only new jobs** for scheduled runs, so you never pay twice for the same job.
- Set **Max results**, or a maximum cost per run in the run options. The Actor stops cleanly at the limit, and you're never charged for jobs you don't receive.

### Integrations and API

Results are stored in an Apify dataset, so you can:

- **Export** to JSON, CSV, Excel, XML or HTML from the **Output** tab or the API.
- **Connect** to Google Sheets, Slack, Zapier, Make, n8n, Airbyte, webhooks and more with [Apify integrations](https://docs.apify.com/platform/integrations).
- **Run it from your code** with the [Apify API](https://docs.apify.com/api/v2). Find your API token in Apify Console under **Settings → API & Integrations**, and keep it secret.

Python example (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient('<YOUR_APIFY_TOKEN>')

run = client.actor('piping_satisfaction/jobs-scraper').call(run_input={
    'ashbyBoards': ['ramp', 'linear'],
    'locations': ['remote'],
})

for job in client.dataset(run.default_dataset_id).iterate_items():
    print(job['company'], '|', job['title'], '|', job['location'])
```

Or with a single HTTP request, which runs the Actor and returns the jobs when it finishes:

```bash
curl -X POST "https://api.apify.com/v2/acts/piping_satisfaction~jobs-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer <YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"greenhouseBoards": ["discord"], "keywords": ["engineer"]}'
```

### Tips

- **Many companies at once:** you can list dozens or hundreds of boards in one run. They're fetched in parallel.
- **Can't find a company?** It may use a different system (Workday, Workable, SmartRecruiters…), which this Actor doesn't cover yet.
- **Big companies:** some boards have hundreds of open roles (OpenAI lists 800+). **Max results** (default 1,000) counts all companies together, so raise it for large lists, or use filters.
- **Descriptions for AI:** turn on **Include job descriptions** to get clean plain text, ready for keyword search, classification or LLM summaries.

### Limitations

- Only **public** job boards on Greenhouse, Lever and Ashby are supported. Jobs a company keeps internal or unlisted are not visible.
- Salary, team, workplace type and country appear only when the company fills them in.
- Salaries are currently returned for Lever and Ashby jobs, not yet for Greenhouse jobs.
- The Actor returns jobs that are open at the time of the run; it doesn't keep an archive of closed jobs.

### FAQ

**Is it legal to scrape job postings?**
This Actor only reads the public job-board APIs that Greenhouse, Lever and Ashby provide so companies can show their openings on their own websites. It doesn't log in, bypass any protection, or collect applicant data. Job descriptions can sometimes mention people (for example a hiring manager's name), so if you store or republish the data, make sure your use follows applicable laws such as GDPR and the companies' and platforms' terms. If in doubt, ask a lawyer.

**A company returned "board not found". Why?**
Check the board name in the company's careers URL. If the company moved to a different applicant tracking system, its old board name no longer works.

**Why did a run with Only new jobs return 0 results?**
Nothing new was posted since the last run with the same History name. That's normal on quiet days — and you only pay the tiny start fee.

**How fresh is the data?**
Live: every run reads the job boards at that moment.

**Can you add another platform or field?**
Possibly! Open an issue in the **Issues** tab with the platform or field you need and a few example companies.

### Feedback and support

Found a bug, or have an idea for a feature? Please open an issue in the **Issues** tab. Include the run link and the board names you used, and you'll get a reply as soon as possible.

# Actor input Schema

## `greenhouseBoards` (type: `array`):

Greenhouse board names, e.g. "discord" from job-boards.greenhouse.io/discord. You can also paste the full board URL.

## `leverCompanies` (type: `array`):

Lever company names, e.g. "spotify" from jobs.lever.co/spotify. You can also paste the full board URL.

## `leverRegion` (type: `string`):

Most Lever boards are global. Use EU only for boards hosted at jobs.eu.lever.co.

## `ashbyBoards` (type: `array`):

Ashby board names, e.g. "linear" from jobs.ashbyhq.com/linear. You can also paste the full board URL.

## `keywords` (type: `array`):

Keep only jobs whose title contains at least one of these words (not case-sensitive). Leave empty to keep all jobs.

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

Keep only jobs whose location or workplace type contains at least one of these terms, e.g. "Remote" or "London" (not case-sensitive). Leave empty to keep all locations.

## `includeDescription` (type: `boolean`):

Add the full job description as plain text. This makes each result much larger.

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

Stop after saving this many jobs in total, across all companies.

## `onlyNewJobs` (type: `boolean`):

Return only jobs that earlier runs with the same History name have not returned yet. The first run returns every matching job. Ideal for daily schedules and alerts.

## `historyName` (type: `string`):

Which memory "Only new jobs" uses. Give each saved setup (e.g. "remote-engineers", "london-sales") its own name so they don't affect each other. Letters, numbers, ".", "\_" and "-" only.

## Actor input object example

```json
{
  "greenhouseBoards": [
    "discord"
  ],
  "leverCompanies": [
    "spotify"
  ],
  "leverRegion": "global",
  "ashbyBoards": [
    "linear"
  ],
  "keywords": [],
  "locations": [],
  "includeDescription": false,
  "maxResults": 1000,
  "onlyNewJobs": false,
  "historyName": "default"
}
```

# Actor output Schema

## `results` (type: `string`):

All collected jobs as a table.

# 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 = {
    "greenhouseBoards": [
        "discord"
    ],
    "leverCompanies": [
        "spotify"
    ],
    "ashbyBoards": [
        "linear"
    ],
    "keywords": [],
    "locations": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("piping_satisfaction/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 = {
    "greenhouseBoards": ["discord"],
    "leverCompanies": ["spotify"],
    "ashbyBoards": ["linear"],
    "keywords": [],
    "locations": [],
}

# Run the Actor and wait for it to finish
run = client.actor("piping_satisfaction/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 '{
  "greenhouseBoards": [
    "discord"
  ],
  "leverCompanies": [
    "spotify"
  ],
  "ashbyBoards": [
    "linear"
  ],
  "keywords": [],
  "locations": []
}' |
apify call piping_satisfaction/jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,piping_satisfaction/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/gH9D0mSWcEUfcG9sG/builds/jVN48IzPMhNjI5GdE/openapi.json
