# Career Site Jobs Scraper: Greenhouse, Lever, Ashby, Workday (`steadyscrape/career-site-jobs-scraper`) Actor

Scrape job listings straight from company career sites on Greenhouse, Lever, Ashby and Workday (plus Recruitee, Personio, BambooHR, Breezy): title, department, location, remote, salary, posted date, apply link, description. Export to JSON, CSV or Excel, or via API.

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

## Pricing

$1.50 / 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.

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 Scraper: Greenhouse, Lever, Ashby, Workday

Scrape **job listings straight from company career sites**. This career site jobs scraper reads the job boards companies run
on their applicant tracking system (ATS) — **Greenhouse**, **Lever**, **Ashby** and **Workday**, plus **Recruitee**,
**Personio**, **BambooHR** and **Breezy HR** — and gives every open job, one row per job: **company**, **title**,
**department and team**, **locations**, **remote / hybrid / on-site**, **employment type**, **salary range with currency and
period** when the employer publishes it, **posted and updated dates**, the **direct apply link** and the **full
description** as text and HTML.

Give it companies the way you have them: an **ATS board link** (`boards.greenhouse.io/airbnb`, `jobs.lever.co/palantir`,
`jobs.ashbyhq.com/ramp`, `nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`…), any **job link** of that board, the
company's own **careers page** (it is read for the ATS link or embed it carries), a **domain** (`figma.com`) or just the
**company name** (`Palantir Technologies`). Filter by title keywords, location, remote, department and posting date, cap
the jobs per company, and schedule it with **only new jobs** to get each new opening once.

### What you can do with this career site jobs scraper

- **Build a job board or a job feed** from employers' own postings, with the apply link that goes to the employer, not to
  an aggregator.
- **Track hiring at target companies**: which teams are hiring, where, how many roles, how fast — for sales prospecting,
  investing, competitive intelligence or recruiting.
- **Salary research**: Greenhouse pay ranges, Ashby compensation and Lever salary ranges, with currency and period, are
  published by US-pay-transparency employers.
- **Monitor new openings**: schedule a daily run with *Only jobs not seen before* and get alerts for new roles only.
- **Feed an AI agent or a spreadsheet** with clean, uniform job data across eight ATSs.

Looking for jobs by search query instead of by company? Use our
[Google Jobs Scraper](https://apify.com/steadyscrape/google-jobs-scraper).

### Input

| Field | What it does |
| --- | --- |
| **Companies** | One per line: an ATS board or job link, a careers page, a domain or a company name. |
| **Max jobs per company** | Stop each company after this many jobs, newest first (0 = every open job). |
| **Title keywords** / **Exclude title keywords** | Keep jobs whose title has a word starting with one of these; drop those with an excluded one. |
| **Location contains** | Keep jobs with a location or country containing one of these (`London`, `Germany`, `Remote`). |
| **Remote only** | Keep jobs the board marks remote, or whose locations say Remote. |
| **Department contains** | Keep jobs whose department or team contains one of these (Workday: its job category). |
| **Posted within (days)** | Keep jobs posted in the last N days. |
| **Only jobs not seen before** | Monitoring: skip jobs an earlier run with the same companies and filters delivered. |

```json
{
    "companies": ["https://boards.greenhouse.io/airbnb", "https://jobs.lever.co/palantir", "Ramp", "https://www.notion.com/careers"],
    "titleKeywords": ["engineer"],
    "remoteOnly": true,
    "maxJobsPerCompany": 50
}
```

#### How a company is found

- **A board or job link** of any of the eight ATSs is read directly (the surest way).
- **A careers page** is read for the board it links to or embeds (Greenhouse embeds, Lever and Ashby job links, Workday,
  Recruitee, Personio, BambooHR and Breezy links); if it has none, one level of same-site "Careers" / "Jobs" links is followed.
- **A domain or a name** is tried as a board slug on Greenhouse, Lever and Ashby (`Palantir Technologies` →
  `palantirtechnologies`, `palantir-technologies`, `palantir`); a domain whose slug finds nothing is then read as a website.
  A name finds the board whose slug matches it, which is usually the company but not always: the row's `boardUrl` and
  `resolvedBy` say what was found.

### Output: career site jobs data

```json
{
    "rowType": "job",
    "status": "ok",
    "input": "https://jobs.ashbyhq.com/ramp",
    "resolvedBy": "board_link",
    "companyName": "Ramp",
    "ats": "ashby",
    "boardSlug": "ramp",
    "boardUrl": "https://jobs.ashbyhq.com/ramp",
    "jobId": "40f43993-21e8-4db1-a9f4-7a3e6098a9ba",
    "requisitionId": null,
    "title": "Customer Experience Associate (Evening Shift)",
    "department": "Customer Experience",
    "team": "Customer Experience",
    "location": "New York, NY (HQ)",
    "locations": ["New York, NY (HQ)", "San Francisco, CA", "Remote (US)", "Denver, CO", "Miami, FL"],
    "country": "USA",
    "remote": true,
    "workplaceType": "hybrid",
    "employmentType": "full_time",
    "employmentTypeText": "FullTime",
    "salaryMin": 60000,
    "salaryMax": 70000,
    "salaryCurrency": "USD",
    "salaryPeriod": "year",
    "salaryText": "$60K - $70K",
    "postedAt": "2026-11-06T15:57:51.702Z",
    "updatedAt": null,
    "url": "https://jobs.ashbyhq.com/ramp/40f43993-21e8-4db1-a9f4-7a3e6098a9ba",
    "applyUrl": "https://jobs.ashbyhq.com/ramp/40f43993-21e8-4db1-a9f4-7a3e6098a9ba/application",
    "description": "About Ramp\nRamp is building the smart infrastructure for finance teams…",
    "descriptionHtml": "<h1><strong>About Ramp</strong></h1><p>Ramp is building…",
    "position": 1,
    "scrapedAt": "2026-11-06T18:45:30.829Z"
}
```

A company that gives no job gets one **free** row saying why:

```json
{
    "rowType": "status",
    "status": "no_open_jobs",
    "input": "https://jobs.lever.co/kraken",
    "ats": "lever",
    "url": "https://jobs.lever.co/kraken",
    "reason": "The Lever board https://jobs.lever.co/kraken lists no open job.",
    "scrapedAt": "2026-11-06T18:45:30.829Z"
}
```

### Career site job fields

| Field | Description |
| --- | --- |
| `companyName` | The name the board publishes (Greenhouse, Recruitee, Personio's hiring entity, Breezy); otherwise the board slug in Title Case. |
| `ats` | `greenhouse`, `lever`, `ashby`, `workday`, `recruitee`, `personio`, `bamboohr` or `breezy`. |
| `boardSlug`, `boardUrl` | The company's id on its ATS and its job board. |
| `jobId`, `requisitionId` | The job's id on the board; the requisition id where published (Greenhouse, Workday). |
| `title`, `department`, `team` | As the board names them (Lever: department and team; Personio: department and recruiting category). |
| `location`, `locations`, `country` | The primary location, every location, and the country where the board states it. |
| `remote`, `workplaceType` | `true`/`false`/`null`; `remote`, `hybrid`, `onsite` or `null`. |
| `employmentType`, `employmentTypeText` | `full_time`, `part_time`, `contract`, `temporary`, `internship` or `null`, and the board's own words. |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryText` | The pay range when the board publishes it (`hour`, `day`, `week`, `month`, `year`, `one_time`). |
| `postedAt`, `updatedAt` | ISO 8601 UTC; Workday and BambooHR publish the day only. |
| `url`, `applyUrl` | The job page and the direct application link. |
| `description`, `descriptionHtml` | The full description as text and as HTML. |
| `input`, `resolvedBy` | The company as you wrote it, and how its board was found (`board_link`, `careers_page`, `name_guess`). |
| `position`, `scrapedAt` | The job's place in its company's results, and when it was read. |
| `rowType`, `status`, `reason` | `job`/`ok`, or `status` with `invalid_input`, `not_found`, `no_ats_detected`, `no_open_jobs`, `no_matching_jobs` or `blocked` and the reason. |

### Pricing

Pay per event: **US$0.0015 per job** delivered, description, salary and apply link included. Status rows (company not
found, no ATS detected, no open jobs, nothing matching the filters, invalid input, blocked) are **free**, and there is no
start fee. 1,000 jobs cost US$1.50. Set a maximum cost per run and the Actor stops exactly there.

### Limits

- Workday lists 20 jobs a page and at most 2,000 per query; a larger Workday site is read newest first and then by job
  category, so its biggest categories may still stop at 2,000. Each Workday job is one more request for its description,
  so large Workday sites take a few minutes.
- Salaries come only where the employer publishes them in the ATS: Workday publishes none as data (some write it in the
  description), Greenhouse only on boards with pay transparency.
- A company name is matched to Greenhouse, Lever and Ashby slugs only; Workday, Recruitee, Personio, BambooHR and Breezy
  boards are found from a link, a careers page or a domain.
- SmartRecruiters is not read: its API's robots.txt admits only LinkedIn's crawler. A career site whose robots.txt closes it
  (some Workday sites, Personio subdomains without a public page) gets a free `blocked` row and is not read.

### Is it legal to scrape career site jobs?

Job postings are published by employers to be found and applied to, and this Actor reads them through the ATS vendors'
public job-board APIs that exist to put those postings on career sites (Greenhouse's Job Board API, Lever's Postings
API, Ashby's posting API, and the feeds the other ATSs publish for career pages). No login, no personal data of
candidates, robots.txt honoured on every host, and each host paced (Lever's crawl delay included). Check that your use of
the data complies with the laws that apply to you and with the employers' terms.

This Actor is not affiliated with, endorsed by or sponsored by Greenhouse, Lever, Ashby, Workday, Recruitee, Personio,
BambooHR, Breezy HR or any employer whose jobs it reads; their names are used only to say which job boards it reads.

# Actor input Schema

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

One company per line, in any of these forms: an ATS job board link (<code>https://boards.greenhouse.io/airbnb</code>, <code>https://jobs.lever.co/palantir</code>, <code>https://jobs.ashbyhq.com/ramp</code>, <code>https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite</code>, <code>https://bunq.recruitee.com</code>, <code>https://acme.jobs.personio.de</code>, <code>https://acme.bamboohr.com/careers</code>, <code>https://acme.breezy.hr</code>) or any job link of it; the company's careers page (<code>https://www.notion.com/careers</code>), read for the ATS link or embed it carries; a domain (<code>figma.com</code>); or a company name (<code>Palantir Technologies</code>). A domain or a name is first tried as a board slug on Greenhouse, Lever and Ashby. A board link is the surest: a name finds the board whose slug matches it.

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

Stop each company after this many jobs (newest first where the board dates them). 0 means every open job.

## `titleKeywords` (type: `array`):

Keep only jobs whose title has one of these words or phrases at the start of a word, case and accents ignored: <code>engineer</code> finds "Software Engineering Manager", <code>AI</code> finds "AI Researcher" but not "Maintenance". Leave empty to keep every title.

## `excludeTitleKeywords` (type: `array`):

Drop jobs whose title has one of these, e.g. <code>senior</code>, <code>intern</code>, <code>manager</code>.

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

Keep only jobs with a location (or country) containing one of these, e.g. <code>London</code>, <code>Germany</code>, <code>Remote</code>, <code>NY</code>.

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

Keep only jobs the board marks remote (or whose locations say Remote).

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

Keep only jobs whose department or team contains one of these, e.g. <code>engineering</code>, <code>sales</code>. On Workday the job category is used.

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

Keep only jobs posted in the last N days. 0 means any date. A board that publishes no date for a job leaves it out when this is set.

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

Return only jobs no earlier run with the same companies and filters delivered: schedule the Actor daily to get each new opening once. The memory lives in your account's key-value store <code>steadyscrape-career-site-jobs-state</code>.

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/airbnb",
    "https://jobs.lever.co/palantir"
  ],
  "maxJobsPerCompany": 10,
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "locations": [],
  "remoteOnly": false,
  "departments": [],
  "postedWithinDays": 0,
  "onlyNewJobs": false
}
```

# Actor output Schema

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

No description

## `summary` (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": [
        "https://boards.greenhouse.io/airbnb",
        "https://jobs.lever.co/palantir"
    ],
    "maxJobsPerCompany": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyscrape/career-site-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": [
        "https://boards.greenhouse.io/airbnb",
        "https://jobs.lever.co/palantir",
    ],
    "maxJobsPerCompany": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("steadyscrape/career-site-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": [
    "https://boards.greenhouse.io/airbnb",
    "https://jobs.lever.co/palantir"
  ],
  "maxJobsPerCompany": 10
}' |
apify call steadyscrape/career-site-jobs-scraper --silent --output-dataset

```

## MCP server setup

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