# Career Site Jobs Search: Greenhouse, Lever, Ashby (`maliksaid/career-site-jobs`) Actor

Search job postings from company career sites on Greenhouse, Lever, Ashby, Workable and Recruitee ATS boards. A built-in index of 280 company boards, no company list needed. Filter by keyword, location and remote. Get title, department, salary and apply link. Monitor mode finds new and closed jobs.

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

## Pricing

from $2.00 / 1,000 jobs

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Career Site Jobs Search: Greenhouse, Lever, Ashby

Search job postings from company career sites in one run. The Actor reads the public job board APIs of Greenhouse, Lever, Ashby, Workable and Recruitee. A built-in index of 1,001 company boards comes with it, so you do not need a company list. Recruiters, job seekers, sales teams and analysts use it to find open roles and track hiring.

### What it covers

Counted on 24 October 2026 with a full run.

| ATS | Company boards in the index | Jobs found |
|---|---|---|
| Greenhouse | 390 | 32,852 |
| Ashby | 338 | 10,660 |
| Lever | 178 | 6,448 |
| Workable | 66 | 932 |
| Recruitee | 29 | 433 |
| **Total** | **1,001** | **51,325** |

The index includes companies such as Stripe, Airbnb, Databricks, Cloudflare, OpenAI, Notion, Ramp, Spotify, Palantir, Careem, Tamara and Unifonic. You can add any other public board on these five ATS with the **Extra companies** option.

### What you get

One record per job, in the same format for every ATS.

| Field | Meaning |
|---|---|
| `company` | Company name |
| `ats` | `greenhouse`, `lever`, `ashby`, `workable` or `recruitee` |
| `companyBoard` | Board name in the ATS URL |
| `jobId` | Job ID on the ATS |
| `title` | Job title |
| `department` | Department or team |
| `location`, `locations` | Main location and every listed location |
| `country` | Country, when the ATS gives it separately |
| `remote` | `true` when the job is remote. `null` when the board does not say |
| `workplaceType` | `remote`, `hybrid` or `on-site`, when the ATS states it |
| `employmentType` | For example `Full-time` |
| `seniority` | Level read from the title: `intern`, `junior`, `mid`, `senior`, `lead`, `manager`, `director` or `executive`. `null` when the title gives none |
| `companyDomain` | The company's own website domain, for index boards where it is known |
| `postedAt`, `updatedAt` | Dates in ISO 8601 |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryText` | Salary, when the employer publishes it |
| `applyUrl`, `jobUrl` | Application page and job page |
| `descriptionSnippet` | First 300 characters of the description |
| `description` | The whole description as plain text, up to 8,000 characters. Only when **Include full description** is on |
| `changeType` | `listed` in a normal search. `new` or `closed` in monitor mode |
| `closedDetectedAt` | Monitor mode: when the job left its board |

Sample record from a test run:

```json
{
    "id": "ashby:vanta:795ab93b-57d9-4387-9cc1-ea7615721ec6",
    "changeType": "listed",
    "company": "Vanta",
    "ats": "ashby",
    "companyBoard": "vanta",
    "jobId": "795ab93b-57d9-4387-9cc1-ea7615721ec6",
    "title": "Senior Analytics Engineer",
    "department": "Product Analytics",
    "location": "Remote U.S.",
    "locations": ["Remote U.S."],
    "country": "USA",
    "remote": true,
    "workplaceType": "remote",
    "employmentType": "Full-time",
    "postedAt": "2026-10-01T23:43:01.805Z",
    "updatedAt": null,
    "salaryMin": 190000,
    "salaryMax": 224000,
    "salaryCurrency": "USD",
    "salaryPeriod": "year",
    "salaryText": "$190K - $224K",
    "applyUrl": "https://jobs.ashbyhq.com/vanta/795ab93b-57d9-4387-9cc1-ea7615721ec6/application",
    "jobUrl": "https://jobs.ashbyhq.com/vanta/795ab93b-57d9-4387-9cc1-ea7615721ec6",
    "descriptionSnippet": "At Vanta, our mission is to help businesses earn and prove trust. We believe that security should be monitored and verified continuously, and we empower companies to practice better security and prove it with ease. Vanta has a kind and talented team, and while some have prior security experience,...",
    "seniority": "senior",
    "companyDomain": null,
    "closedDetectedAt": null,
    "scrapedAt": "2026-10-21T12:44:22.924Z"
}
```

Some fields are empty because the ATS does not publish them:

- Salary comes from Ashby, Lever and Recruitee, when the employer fills it in. In a full run on 21 October 2026, 3,709 jobs had a salary. The Greenhouse and Workable list APIs carry no salary.
- `updatedAt` comes from Greenhouse and Recruitee only.
- `remote` on Greenhouse is `true` only when the location or title says "Remote". Otherwise it is `null`.
- For a Lever or Ashby board you add yourself, `company` is the board name, because these APIs do not return a company name.

### How to use it

1. Enter keywords, for example `data engineer`.
2. Optional: add locations, for example `London` or `Remote`, or turn on **Remote jobs only**.
3. Set **Maximum jobs** to 0 to get every match. A full search of the index takes about 8 to 15 minutes. For a full search, set the run timeout to 3,600 seconds. If the timeout comes first, the run stops cleanly with the jobs saved so far.
4. For a daily feed, turn on **Monitor mode** and schedule the Actor. Each run returns only new jobs and jobs that closed since the last run.

### Input

| Option | Default | Meaning |
|---|---|---|
| Keywords | None | Keep jobs whose title, department or company has a keyword. Whole words, any case |
| Exclude keywords | None | Drop jobs whose title has one of these words |
| Description keywords | None | Keep jobs whose description has one of these words, for example `Python` or `Kubernetes` |
| Exclude description keywords | None | Drop jobs whose description has one of these words |
| Locations | None | Keep jobs in these places, for example `Germany` or `Remote` |
| Exclude locations | None | Drop jobs in these places, for example `India` |
| Remote jobs only | Off | Keep only remote jobs |
| Jobs with salary only | Off | Keep only jobs that state pay |
| Seniority | Any | Keep jobs at these levels: intern, junior, mid, senior, lead, manager, director, executive. Read from the title |
| Workplace type | Any | Keep remote, hybrid or on-site jobs |
| Employment type | Any | Keep full-time, part-time, contract, internship or temporary jobs |
| Posted within (days) | 0 (any) | Keep jobs posted or updated in the last N days |
| ATS platforms | All five | Limit the search to some ATS |
| Search the built-in company index | On | Turn off to search only your own companies |
| Extra companies | None | Board URLs such as `https://boards.greenhouse.io/stripe`, `greenhouse:stripe`, or a board name such as `figma` |
| Exclude companies | None | Drop jobs from these companies, by name or board name |
| Company website domains | None | Keep jobs from companies with these domains, for example `stripe.com`. Only index boards with a known domain match |
| Include full description | Off | Add the whole description as plain text, up to 8,000 characters. Each record gets larger |
| Monitor mode | Off | Return only new and closed jobs |
| Monitor mode: include updated jobs | Off | Also return jobs whose title, location, pay, workplace type or update date changed |
| Monitor state key | `default` | Name of the monitor memory. Use one key per search |
| Maximum jobs | 100 | Stop after this many jobs. Set 0 for no limit |

Example input:

```json
{
    "keywords": ["data engineer", "analytics engineer"],
    "locations": ["London", "Remote"],
    "postedWithinDays": 30,
    "maxItems": 0
}
```

Find skills that appear only in the job text, with pay stated, outside one country:

```json
{
    "descriptionKeywords": ["Kubernetes"],
    "locations": ["Remote"],
    "excludeLocations": ["India"],
    "salaryOnly": true,
    "maxItems": 200
}
```

Senior hybrid full-time roles at a list of target accounts, for a sales team:

```json
{
    "companyDomains": ["stripe.com", "figma.com", "reddit.com"],
    "seniorityLevels": ["senior", "lead", "manager"],
    "workplaceTypes": ["hybrid"],
    "employmentTypes": ["full-time"],
    "maxItems": 0
}
```

### Monitor mode

The first monitor run returns every matching job as `new`. Later runs return:

- `new`: a job that was not returned before.
- `closed`: a job returned before that is no longer on its board.
- `updated`: only when **Monitor mode: include updated jobs** is on. A job returned before whose title, location, pay, workplace type or update date changed. The record holds the new values.

A board that fails to load is skipped for closed checks. So a network error never marks jobs as closed.

### Pricing

You pay per event:

- $0.005 when the run starts.
- Per job saved to your dataset: $0.0025 on the Free and Bronze plans, $0.00225 on Silver, and $0.002 on Gold and above. That is $2.50, $2.25 or $2.00 per 1,000 jobs.
- Jobs skipped by your filters cost nothing. In monitor mode, jobs already returned cost nothing.

| Jobs saved | Free and Bronze | Silver | Gold and above |
|---|---|---|---|
| 100 | $0.26 | $0.23 | $0.21 |
| 1,000 | $2.51 | $2.26 | $2.01 |
| 10,000 | $25.01 | $22.51 | $20.01 |

Costs include the start fee and are rounded up to the cent.

Filters run before a job is saved, so a narrow search costs less than a broad one.

### Use it from the API or a schedule

Get jobs in one call, for example from a script or an AI agent:

```bash
curl -X POST "https://api.apify.com/v2/acts/maliksaid~career-site-jobs/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keywords": ["data engineer"], "locations": ["London"], "maxItems": 50}'
```

For a daily feed of new and closed jobs, create a schedule in Apify Console with **Monitor mode** on and a fixed **Monitor state key**. Each run then returns only the changes. Apify integrations send the results to Make, Zapier, n8n, Google Sheets, Slack or a webhook.

### FAQ

**How fresh are the jobs?** Each run reads the live job boards, so the jobs are as current as the employer's own careers page.

**Where does the index come from?** From public company career boards on the five ATS, including boards that companies shared in public hiring posts. Every board answered its public API with at least one job when it was added. `companyDomain` is filled when the board's own job links or the company's post name its website.

**How do I add a company?** Paste its board URL, write `greenhouse:company`, or type the board name, such as `figma`, in **Extra companies**. Ask on the Issues tab to have it added to the index.

**Why is the salary empty for some jobs?** The Greenhouse and Workable list APIs carry no salary. Ashby, Lever and Recruitee carry it when the employer fills it in. Use **Jobs with salary only** to keep only jobs with pay.

**Why are Workday and SmartRecruiters missing?** Their terms or robots.txt do not allow this kind of access.

**Does it return recruiter contacts?** No. It removes emails and phone numbers on purpose and never saves recruiter or hiring manager names.

**How do I filter by level or job type?** Use **Seniority**, **Workplace type** and **Employment type**. Seniority is read from the job title, so a title such as "Software Engineer" has no level.

**How do I get a daily feed?** Turn on **Monitor mode** and schedule the Actor. See the section above.

### Data sources and use

- The Actor reads documented public job board APIs that need no login or key.
- It sends at most one request per second to each ATS.
- On Recruitee, it reads each company's robots.txt first and skips boards that disallow the API.
- It saves job posting fields only. It does not save recruiter or hiring manager names, emails or phone numbers. Emails and phone numbers are removed from the description snippet and the full description.
- Workday and SmartRecruiters are not included. Their terms or robots.txt do not allow this kind of access.

### Support

Open an issue on the Issues tab if a company board is missing or a field looks wrong. You can also ask for a company to be added to the index.

# Actor input Schema

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

Keep jobs whose title, department or company contains at least one keyword, for example "data engineer" or "product manager". Keywords match whole words and ignore case. Leave empty to keep every job.

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

Drop jobs whose title contains any of these words, for example "intern" or "senior".

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

Keep jobs whose description contains at least one of these words, for example "Python" or "Kubernetes". Use this for skills that appear only in the job text. Whole words, any case. Leave empty to skip.

## `excludeDescriptionKeywords` (type: `array`):

Drop jobs whose description contains any of these words, for example "clearance" or "relocation".

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

Keep jobs in any of these places. Use a city, region or country as the employer writes it, for example "London", "Germany" or "Remote". Leave empty for every location.

## `excludeLocations` (type: `array`):

Drop jobs in any of these places, for example "India" to keep remote jobs outside India. Whole words, any case.

## `remoteOnly` (type: `boolean`):

Keep only jobs that the ATS or the location marks as remote.

## `salaryOnly` (type: `boolean`):

Keep only jobs that state pay (a minimum, maximum or salary text). Greenhouse and Workable list APIs carry no salary, so their jobs are dropped when this is on.

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

Keep only jobs at these levels. The level is read from the job title, for example "Senior", "Staff" or "Head of". Jobs whose title gives no level are dropped when this is set.

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

Keep only remote, hybrid or on-site jobs. Jobs whose board does not state the workplace type are dropped when this is set. "Remote jobs only" keeps working as before.

## `employmentTypes` (type: `array`):

Keep only these employment types. Jobs whose board does not state one are dropped when this is set.

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

Keep only jobs posted or updated in the last N days. 0 means any date.

## `atsPlatforms` (type: `array`):

Which applicant tracking systems to search. Leave empty for all five.

## `useBuiltInIndex` (type: `boolean`):

Search every company board in the built-in index. Turn off to search only the companies you list below.

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

Optional. Add company job boards by URL, for example https://boards.greenhouse.io/stripe, https://jobs.lever.co/spotify, https://jobs.ashbyhq.com/openai, https://apply.workable.com/blueground or https://bunq.recruitee.com. You can also write greenhouse:stripe, or just a board name such as figma. A bare name is tried on every selected ATS, so it may also match another company that uses the same name.

## `excludeCompanies` (type: `array`):

Drop jobs from these companies. Use the company name or its board name, for example "stripe".

## `companyDomains` (type: `array`):

Keep only jobs from companies with these website domains, for example stripe.com. Only index boards whose domain is known can match. Index boards of other companies are not read, so the run is fast.

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

Add a description field with the whole job description as plain text, up to 8,000 characters. Emails and phone numbers are removed. Each record gets larger.

## `monitorMode` (type: `boolean`):

Return only changes since the last monitor run with the same state key: jobs that are new, and jobs that were removed from their board (closed). The first run returns every matching job as new. Schedule the Actor with this on to get a daily change feed.

## `includeUpdated` (type: `boolean`):

In monitor mode, also return a job seen before when its title, location, pay, workplace type or update date changed. These records have changeType "updated". Off by default.

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

Name of the memory that monitor mode uses. Use a different key for each separate search.

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

Stop after saving this many jobs. The default keeps test runs short and cheap. Set 0 for no limit.

## Actor input object example

```json
{
  "keywords": [
    "engineer"
  ],
  "excludeKeywords": [],
  "descriptionKeywords": [],
  "excludeDescriptionKeywords": [],
  "locations": [],
  "excludeLocations": [],
  "remoteOnly": false,
  "salaryOnly": false,
  "seniorityLevels": [],
  "workplaceTypes": [],
  "employmentTypes": [],
  "postedWithinDays": 0,
  "atsPlatforms": [],
  "useBuiltInIndex": true,
  "companies": [],
  "excludeCompanies": [],
  "companyDomains": [],
  "includeDescription": false,
  "monitorMode": false,
  "includeUpdated": false,
  "stateKey": "default",
  "maxItems": 100
}
```

# Actor output Schema

## `results` (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 = {
    "keywords": [
        "engineer"
    ],
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("maliksaid/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": ["engineer"],
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("maliksaid/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": [
    "engineer"
  ],
  "maxItems": 100
}' |
apify call maliksaid/career-site-jobs --silent --output-dataset

```

## MCP server setup

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