# Career Site Jobs Scraper: Greenhouse, Lever & Ashby (`plainsource/career-site-jobs`) Actor

Search about 180,000 open jobs from 5,600 company career sites on Greenhouse, Ashby and Recruitee in one run, plus live Lever and Workable boards for companies you list. Fuzzy title match, salary, seniority and remote filters. $0.90 per 1,000 jobs; daily-feed repeats and closed jobs are free.

- **URL**: https://apify.com/plainsource/career-site-jobs.md
- **Developed by:** [Plainsource](https://apify.com/plainsource) (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 job 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

Search open jobs straight from company career sites hosted on **Greenhouse, Lever, Ashby, Workable and Recruitee**. Results come from each platform's public job-board API, the same feed the company's own careers page uses. That means no login, no proxies, and no third-party job board in the middle.

It's built for recruiters and sourcers tracking who is hiring, job-board and newsletter builders who need a clean daily feed, and anyone who wants a list of open roles at specific companies without visiting each careers page.

### What you get

- **Two ways to search**
  - **All companies:** leave *Companies* empty to search every board in the index (about 5,600 company boards on Greenhouse, Ashby and Recruitee, roughly 186,000 open jobs, as of October 2026).
  - **Your list:** paste career-site URLs or board ids, and those boards are fetched live during the run. This is the only way to search Lever and Workable boards (see Limits).
- **Title search that forgives word order.** "senior data engineer" matches "Data Engineer, Senior" and "Sr. Data Engineer". Turn on exact matching if you want literal phrases.
- **Location filters** by city or region text, `remote`, or two-letter country codes.
- **Seniority** (intern, entry, mid, senior, lead, manager, director, executive) worked out from the job title.
- **Salary** when the employer publishes it. Ashby and Lever often do; Greenhouse usually doesn't.
- **Duplicate merging.** A role posted once per city becomes one row with every location listed.
- **Daily feed mode.** *Only new or changed jobs* returns just what changed since your last run. You aren't charged again for jobs you already received, and closed jobs can be reported for free.

### Ready-made searches

Copy one into the JSON input tab and adjust.

**Remote data and analytics engineering jobs posted this week**

```json
{ "keywords": ["data engineer", "analytics engineer"], "workplaceTypes": ["remote"], "postedWithinDays": 7, "maxItems": 200 }
```

**Jobs in Canada with published salaries**

```json
{ "keywords": ["software engineer", "product manager"], "locations": ["CA"], "hasSalary": true, "maxItems": 200 }
```

**Daily feed of new roles at a fixed list of companies** (schedule it once a day; repeat jobs are not charged)

```json
{ "companies": ["greenhouse:airbnb", "lever:palantir", "ashby:ramp", "workable:huggingface"], "onlyNew": true, "reportClosed": true, "stateKey": "my-companies" }
```

**Entry-level and intern roles only**

```json
{ "keywords": ["engineer", "analyst", "designer"], "seniorityLevels": ["intern", "entry"], "postedWithinDays": 14, "maxItems": 300 }
```

**US machine learning jobs whose description mentions visa sponsorship**

```json
{ "keywords": ["machine learning engineer"], "locations": ["US"], "descriptionKeywords": ["visa sponsorship", "sponsor visas"], "postedWithinDays": 14, "maxItems": 100 }
```

Description filters refetch the matching boards live, so keep the title keywords specific.

### Input example

```json
{
  "keywords": ["data engineer", "analytics engineer"],
  "excludeKeywords": ["intern"],
  "locations": ["Toronto", "remote"],
  "postedWithinDays": 14,
  "maxItems": 200
}
```

To check specific companies only:

```json
{
  "companies": ["https://boards.greenhouse.io/airbnb", "lever:palantir", "ashby:ramp", "workable:huggingface"],
  "keywords": ["engineer"]
}
```

A company can be given as a board URL, as `ats:id` (`greenhouse:stripe`), or as a bare id (`stripe`). Bare ids are looked up in the index and then tried on every platform. If nothing is found, the run log and the `SUMMARY` record say so.

### Output example

One row from a real run on 2026-10-21:

```json
{
  "id": "ashby:ramp:7cd46077-05fe-4cd7-816f-5528638342f1",
  "ats": "ashby",
  "companyId": "ramp",
  "company": "Ramp",
  "title": "Software Engineer, Frontend, Growth",
  "department": "Engineering",
  "team": "Engineering",
  "employmentType": "FullTime",
  "workplaceType": "hybrid",
  "location": "New York, NY (HQ)",
  "locations": [
    "New York, NY (HQ)",
    "San Francisco, CA"
  ],
  "countryCode": "US",
  "salary": {
    "min": 200000,
    "max": 310000,
    "currency": "USD",
    "period": "year",
    "text": "$200K – $310K • Offers Equity"
  },
  "postedAt": "2026-09-21T22:38:37.920Z",
  "updatedAt": null,
  "url": "https://jobs.ashbyhq.com/ramp/7cd46077-05fe-4cd7-816f-5528638342f1",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/7cd46077-05fe-4cd7-816f-5528638342f1/application",
  "seniority": "mid",
  "duplicateGroupId": "fc1d3990fc21",
  "changeStatus": null,
  "scrapedAt": "2026-10-21T14:16:51.471Z"
}
```

| Field | Notes |
|---|---|
| `id` | Stable id: `ats:company:jobId`. Use it to join runs. |
| `company`, `companyId` | Company name and its board id on the ATS. |
| `title`, `department`, `team`, `employmentType` | As published. Some fields are empty on some platforms. |
| `workplaceType` | `remote`, `hybrid`, `onsite`, or `null` when the employer didn't say. |
| `location`, `locations`, `countryCode` | Primary location, plus every location after duplicate merging. |
| `salary` | `{ min, max, currency, period, text }` or `null`. |
| `seniority` | Worked out from the title. `mid` means no level word was found. |
| `postedAt`, `updatedAt` | ISO dates from the ATS. Lever and Workable don't publish update times. |
| `url`, `applyUrl`, `otherUrls` | Job page, application page, and the URLs of merged duplicates. |
| `duplicateGroupId` | The same value for the same company and title. |
| `changeStatus` | `new`, `updated` or `closed` in daily feed mode, otherwise `null`. |
| `description` | Plain text, only when *Include full description* is on. |

### Pricing

You pay per job returned. The current price is shown on the Pricing tab.

- **Not charged:** jobs you already received (daily feed mode), and closed-job rows.
- **Spending cap:** the maximum cost you set for a run is respected. The run stops adding results when it is reached.

### How fresh is the data?

- **Company list given:** every board is fetched live during your run.
- **All companies:** results come from a snapshot refreshed once a day, so a job posted this morning may show up tomorrow. If that matters, give a company list.
- **Descriptions:** these are always fetched live. Snapshot rows don't carry them, so when you ask for descriptions or filter on description text, the matching boards are refetched live.

### Limits

- **Five platforms only.** Companies on Workday, iCIMS, SmartRecruiters, Taleo, SuccessFactors, or their own in-house systems are not covered.
- **Lever and Workable are company-list only.** Their APIs are slow to sweep (Lever asks for one request per second; Workable takes several seconds per board), so they are not in the daily all-companies snapshot. List the companies you want and they are fetched live.
- **The index isn't every company.** It is the set of boards discovered so far. If a company you need is missing, add it to *Companies* and it is fetched live.
- **Seniority is a guess from the title**, so "Engineer II" style titles may land in `mid`.
- **Lever is fetched at one request per second**, as its robots.txt asks, and Workable at a similar pace. A list of 100 Lever or Workable companies takes a couple of minutes.

### Data and conduct

- **Published postings only.** This Actor reads job postings that employers publish for wide distribution, through documented public endpoints. It doesn't log in, use proxies, or bypass anything.
- **Platforms skipped on purpose:** SmartRecruiters, because its API host's robots.txt only allows LinkedIn's crawler.
- **Removal requests:** if you are an employer and want your board removed from the index, open an issue on the Issues tab and it will be removed within 14 days.

### Integrations

- **Schedules:** the Actor works with Apify schedules, webhooks, the API, and the Apify MCP server.
- **A typical setup:** a daily schedule with *Only new or changed jobs* turned on, sending the results to Google Sheets, Slack or a webhook.

# Actor input Schema

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

A job matches if its title contains every word of any one phrase, in any order. "senior data engineer" matches "Data Engineer, Senior". Common abbreviations are expanded (Sr, Eng, SWE, PM, ML). Leave empty to return every job.

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

Drop jobs whose title matches any of these phrases, e.g. "intern" or "manager".

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

Keep jobs whose location contains any of these, e.g. "Toronto", "Berlin", "remote". Two-letter country codes (US, CA, GB, DE, ES, MX...) also work.

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

Only keep jobs the ATS marks with one of these types. Greenhouse often leaves this unspecified, so including "unspecified" avoids dropping those jobs.

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

Only jobs first published in the last N days. 0 means any date.

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

Optional. Limit the search to these companies and fetch their boards live. Required for Lever and Workable boards, which are not in the daily snapshot. Accepts board URLs (https://jobs.lever.co/palantir), "ats:id" (greenhouse:airbnb), or a plain board id (stripe). Unknown ids are tried on every supported ATS and reported if not found.

## `ats` (type: `array`):

Only search these platforms. Empty means all five. Without a company list, Lever and Workable are skipped (they are only fetched live for listed companies).

## `seniorityLevels` (type: `array`):

Detected from the job title. "mid" means no level word was found.

## `hasSalary` (type: `boolean`):

Keep only postings that publish pay (common on Ashby and Lever, rare on Greenhouse).

## `descriptionKeywords` (type: `array`):

Keep jobs whose description matches any of these phrases, e.g. "visa sponsorship" or "Kubernetes". Turns on description fetching for matched boards, so runs take longer.

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

Add the plain-text job description to each result.

## `exactMatch` (type: `boolean`):

Match keywords as exact substrings instead of word-by-word.

## `dedupe` (type: `boolean`):

Many companies post the same role once per city. When on, these become one row with all locations listed and the other URLs in otherUrls.

## `onlyNew` (type: `boolean`):

Return only jobs that are new or changed since your last run with the same state key. You are not charged for jobs you already received. Use this with a schedule for a daily feed.

## `reportClosed` (type: `boolean`):

With "Only new or changed jobs", also output jobs that disappeared since the last run, marked changeStatus: closed. These rows are not charged.

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

Keeps separate "seen jobs" memories for different searches. Use one key per saved search.

## `maxItems` (type: `integer`):

Upper limit on paid results per run. Newest jobs come first.

## Actor input object example

```json
{
  "keywords": [
    "software engineer"
  ],
  "excludeKeywords": [],
  "locations": [],
  "workplaceTypes": [],
  "postedWithinDays": 30,
  "companies": [
    "https://boards.greenhouse.io/airbnb",
    "lever:palantir",
    "ashby:ramp"
  ],
  "ats": [],
  "seniorityLevels": [],
  "hasSalary": false,
  "descriptionKeywords": [],
  "includeDescription": false,
  "exactMatch": false,
  "dedupe": true,
  "onlyNew": false,
  "reportClosed": false,
  "stateKey": "default",
  "maxItems": 50
}
```

# Actor output Schema

## `jobs` (type: `string`):

One row per matching job posting.

## `summary` (type: `string`):

Data source, counts and warnings for the run.

# 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 = {
    "keywords": [
        "software engineer"
    ],
    "postedWithinDays": 30,
    "companies": [
        "https://boards.greenhouse.io/airbnb",
        "lever:palantir",
        "ashby:ramp"
    ],
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("plainsource/career-site-jobs").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 = {
    "keywords": ["software engineer"],
    "postedWithinDays": 30,
    "companies": [
        "https://boards.greenhouse.io/airbnb",
        "lever:palantir",
        "ashby:ramp",
    ],
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("plainsource/career-site-jobs").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 '{
  "keywords": [
    "software engineer"
  ],
  "postedWithinDays": 30,
  "companies": [
    "https://boards.greenhouse.io/airbnb",
    "lever:palantir",
    "ashby:ramp"
  ],
  "maxItems": 50
}' |
apify call plainsource/career-site-jobs --silent --output-dataset

```

## MCP server setup

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

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/LfQMjUurZyZ8w9YOe/builds/3cAfSjUKrhnTbsdre/openapi.json
