# Career Site Job Listings Scraper: Greenhouse, Lever & More (`nimait/career-site-job-listings-scraper`) Actor

Turn any Greenhouse, Lever, Ashby or Workday careers URL into clean job data. Every posting comes back in one identical shape: title, company, department, location, ISO country, remote flag, salary range, full description and apply link. Mix boards freely. Pay per job returned.

- **URL**: https://apify.com/nimait/career-site-job-listings-scraper.md
- **Developed by:** [Nima K](https://apify.com/nimait) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 per job listing extracteds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Career Site Job Listings Scraper: Greenhouse, Lever & More

Scrape job listings from any company career site built on **Greenhouse, Lever, Ashby or Workday** — four
systems that between them power most company career pages. Paste a careers URL and get its open
jobs back as clean data: every posting in one identical shape: job title, company,
department, location, ISO country code, remote and workplace flags, employment type, publication
date, the full job description as HTML and as plain text, the published salary range where the
employer discloses one, and a direct apply link. You can mix boards from all four systems in a
single run; the Actor works out which system each URL belongs to on its own.

### Who it's for

- **Job-market analytics** — track hiring volume, locations, titles and disclosed pay over time.
- **Recruiting and talent tools** — keep a live picture of who is hiring for what.
- **Job aggregators and job boards** — fill your listings from the source, in one schema.
- **Sales and talent intelligence** — open roles are one of the clearest signals of what a company
  is building, funding and prioritising.

### What it does

- **Four job-board systems, detected automatically.** Greenhouse, Lever, Ashby and Workday. Paste
  any mix of them; no per-site configuration.
- **One schema for everything.** 21 fields, same names and same types no matter where a job came
  from, so you can sort, filter and join across employers without cleaning anything up.
- **Structured salary data.** Where an employer publishes pay, you get `min`, `max`, `currency` and
  the pay interval as numbers — not a sentence to parse. The original text is kept too.
- **Countries normalized to ISO codes.** Boards write the same country five different ways ("US",
  "USA", "United States", "United States of America"). You get `US` in `country` and the board's own
  wording in `countryName`.
- **Gets past Workday's 2,000-job ceiling.** Workday never returns more than 2,000 jobs for one
  search. Switch on the deep crawl and the Actor searches category by category and merges the
  results — 2,615 jobs from NVIDIA's board in testing, against 2,000 without it.
- **Remote and workplace flags.** `isRemote` and `workplaceType` (`on-site` / `hybrid` / `remote`)
  come straight from the board where it publishes them.
- **A bad URL doesn't sink the run.** Each board is reported separately; the rest keep going.

### Input

Only one field is required: the list of career-board URLs. Copy them from the employer's careers
page — they look like `boards.greenhouse.io/stripe`, `jobs.lever.co/spotify`,
`jobs.ashbyhq.com/ramp` or `nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`.

A realistic run — four fintech boards, salary data on, capped at 200 jobs each while you check the
output:

```json
{
    "startUrls": [
        "https://jobs.ashbyhq.com/ramp",
        "https://boards.greenhouse.io/stripe",
        "https://boards.greenhouse.io/coinbase",
        "https://boards.greenhouse.io/robinhood"
    ],
    "includeDescription": true,
    "includeCompensation": true,
    "maxJobsPerBoard": 200
}
```

| Setting                         | What it does                                                                                                                           |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| **Career board URLs**           | The boards to scrape. Mix systems freely.                                                                                              |
| **Include job description**     | Full description as HTML and plain text. On by default. See the Workday note below.                                                    |
| **Include compensation**        | Adds the salary object when the employer publishes pay. On by default, costs nothing extra.                                            |
| **Max jobs per board**          | Caps each board — the cheapest way to test before a full run. Starts at 25; `0` means everything.                                      |
| **Stop after this many seconds** | Optional early stop. `0` uses the run timeout — the Actor always finishes itself just before it.                                       |
| **Fetch Greenhouse pay ranges** | Off by default. Greenhouse hides pay behind a per-job page, so this costs one extra request per job. Only switch it on if you need it. |
| **Workday deep crawl**          | Off by default. Only for Workday employers with more than 2,000 open jobs.                                                             |
| **Include raw payload**         | Attaches the untouched board response to each record. Makes the dataset much larger.                                                   |
| **Proxy configuration**         | Leave as it is. Datacenter proxy is included in your plan and is all these boards need.                                                |

**Workday and descriptions.** Workday is the only one of the four that serves descriptions one job
at a time. With "Include job description" on, a Workday board costs one extra request per job and
you get descriptions, publication dates and countries. With it off, those three fields come back
empty for Workday jobs and the board is far faster and cheaper. The other three systems return
descriptions with the listing, so the setting costs them nothing.

**Long board lists and the run timeout.** Boards are crawled in the order you list them, and the
Actor stops itself shortly before your run timeout rather than being killed by it. When that
happens the run still finishes normally: everything scraped so far is in the dataset, the log says
which boards were not started, and the run status message says it stopped early. As a rough guide,
a 2,000-job Workday board with descriptions on takes about 15 minutes, while Greenhouse, Lever and
Ashby boards of any size take well under a minute each. If you are scraping many big boards, split
them across several runs, cap "Max jobs per board", turn "Include job description" off, or raise the
run timeout in the run options.

### What you get back

One record per job. This is a real record from Ramp's board, with the description and the original
pay payload shortened for readability:

```json
{
    "source": "ashby",
    "company": "Ramp",
    "boardUrl": "https://jobs.ashbyhq.com/ramp",
    "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
    "title": "Security Engineer, Cloud",
    "department": "Engineering",
    "team": "Backend",
    "location": "New York, NY (HQ)",
    "secondaryLocations": ["Remote (Canada)", "Remote (US)", "Miami, FL"],
    "country": "US",
    "countryName": "USA",
    "isRemote": true,
    "workplaceType": "hybrid",
    "employmentType": "FullTime",
    "publishedAt": "2026-04-07T17:12:35.753Z",
    "updatedAt": null,
    "descriptionHtml": "<h1><strong>About Ramp</strong></h1><p>Ramp is building the smart infrastructure for finance teams…</p>",
    "descriptionText": "ABOUT RAMP\n\nRamp is building the smart infrastructure for finance teams…",
    "compensation": {
        "min": 211400,
        "max": 290600,
        "currency": "USD",
        "interval": "yearly",
        "raw": {
            "compensationTierSummary": "$211.4K – $290.6K • Offers Equity",
            "scrapeableCompensationSalarySummary": "$211.4K - $290.6K"
        }
    },
    "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
    "jobUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245"
}
```

`jobId` is the employer's own identifier and stays the same between runs, so it works as a
deduplication key. Download results as JSON, CSV, Excel or XML, or pull them from the Apify API.

### What each system provides

Employers can only give you what their job board publishes. Here is what to expect, measured on
live boards in August 2026.

| Field                       | Greenhouse                         | Lever        | Ashby        | Workday                       |
| --------------------------- | ---------------------------------- | ------------ | ------------ | ----------------------------- |
| Title, location, apply link | yes                                | yes          | yes          | yes                           |
| Department                  | yes                                | yes          | yes          | —                             |
| Team                        | —                                  | yes          | yes          | —                             |
| Country code                | best-effort, from location text    | yes (native) | yes (native) | yes (needs descriptions on)   |
| Remote / workplace type     | guessed from location wording      | yes (native) | yes (native) | guessed from location wording |
| Employment type             | —                                  | yes          | yes          | yes                           |
| Published date              | yes                                | yes          | yes          | needs descriptions on         |
| Last-updated date           | yes                                | —            | —            | —                             |
| Description                 | yes                                | yes          | yes          | needs descriptions on         |
| Salary range                | rare, and only with the pay option | rare         | usually      | never published               |

**Salary coverage we actually measured.** Ashby is far ahead: 117 of Ramp's 122 jobs carried a
structured pay range. Lever publishes pay on a small minority of postings (1 of 25 sampled at
Spotify). Greenhouse is uneven — GitLab published a range on 13 of 40 jobs sampled, Stripe on none
of those sampled, and it costs an extra request per job to find out. Workday exposes no salary data
at all through its job board.

**Countries.** Lever, Ashby and Workday all state the country, so those come back complete —
we saw 25 of 25 populated for each in a mixed test run. Greenhouse states no country anywhere, so it
is inferred from the location text and only when that text names a country outright: "Dublin,
Ireland" becomes `IE`, but "San Francisco, CA" is left empty rather than guessed as Canada. On a
Stripe sample, 5 of 25 jobs got a country this way. `countryName` always carries something readable
when a country is known.

**What a board costs to fetch.** Roughly, per board:

| System     | Listing requests                                          | Extra requests                                                                                   |
| ---------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| Greenhouse | 1 for the whole board (550 jobs at Stripe in one request) | +1 per job, only if you turn on Greenhouse pay ranges                                            |
| Lever      | 1 per 100 jobs                                            | none                                                                                             |
| Ashby      | 1 for the whole board (122 jobs at Ramp)                  | none                                                                                             |
| Workday    | 1 per 20 jobs — 2,000 jobs is about 100 requests          | +1 per job for descriptions; deep crawl takes a 2,000-job board to roughly 150+ listing requests |

Workday is the slow one by design: it caps pages at 20 jobs and refuses to return more than 2,000
results per search. Everything else is cheap.

### Pricing

**Pay per job returned.** You are charged for each job record the Actor delivers — no job, no
charge — plus your normal Apify platform usage. Capping "Max jobs per board" caps what you spend,
and if a run hits your budget limit it stops cleanly and keeps the jobs it already collected rather
than failing.

> **$0.002 per job listing.** Pay-per-event pricing — you are only charged for the job rows the
> Actor actually delivers.

A note for Apify free-plan users: your plan comes with a monthly usage allowance, and a paid Actor
draws on it like anything else. Start with "Max jobs per board" set to something small so you can
see the output shape before committing a large run.

### Fair use and how it behaves

- **Public data only.** Everything here comes from the same public job-board endpoints that render
  the employer's own careers page. No logins, no cookies, no accounts, no CAPTCHA solving, nothing
  behind a paywall.
- **robots.txt is respected.** Workday serves a different robots.txt for every employer, so it is
  fetched and honoured per employer at run time. A board that disallows crawling is skipped, and the
  run summary says so by name — it is never quietly reported as an employer with no jobs.
- **Polite by default.** Requests are rate-limited per host, crawl delays declared in robots.txt are
  obeyed, and failures back off instead of hammering.
- **Honest identification.** The Actor identifies itself in its user agent rather than pretending to
  be a browser.
- No personal data is collected beyond what employers publish about their own vacancies.

### Limitations worth knowing

- **Greenhouse salary data is sparse and expensive.** It lives on each job's own page, so collecting
  it means one request per job, and plenty of boards publish none at all.
- **Greenhouse publishes no country field.** It is inferred from the location text, and only when
  that text actually names a country — so expect empty countries on boards that write "London" or
  "San Francisco, CA".
- **Ashby's country is free text**, so it is normalized on a best-effort basis. The ISO code is what
  we matched; `countryName` keeps the board's original wording so you can always check.
- **Workday publishes no salary data**, and its descriptions, publication dates and countries only
  arrive if you leave "Include job description" on.
- **Workday caps searches at 2,000 results.** The deep crawl is the way past it; without it, very
  large employers are truncated and the log says so.
- **Boards move.** Companies switch systems and retire board URLs; a URL that no longer exists is
  reported as a clear per-board error and the remaining boards still run.
- Only these four systems are supported today. Career pages built on anything else are rejected with
  an explanatory message.

### Support

Found a board that behaves oddly, or want another job-board system supported? Open an issue on the
Actor page with the board URL — that is the fastest way to get it looked at.

***

#### For developers

```bash
npm install
npm run build
npm test                    # unit tests, no network
apify run                   # reads storage/key_value_stores/default/INPUT.json
```

`SPEC.md` documents the verified behaviour of all four job-board APIs and the publishing checklist.
Source layout: `src/main.ts` (run lifecycle and charging), `src/detect.ts` (URL → board),
`src/adapters/*` (one per system), `src/normalize.ts` and `src/countries.ts` (field cleanup),
`src/http.ts` (throttled client), `src/robots.ts` (robots.txt).

# Actor input Schema

## `startUrls` (type: `array`):

One URL per line. Copy them straight from the employer's careers page. Supported shapes: https://boards.greenhouse.io/{company}, https://jobs.lever.co/{company}, https://jobs.ashbyhq.com/{Company} (capitalisation matters here), https://{company}.wd5.myworkdayjobs.com/{SiteName}. A URL that is not recognised is reported in the log and the other boards still run. Boards are crawled in the order given, and the run stops itself before the run timeout — so with a long list, put the boards you care about most first, or split them across several runs.

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

Adds the full job description to every record, both as HTML and as plain text. Workday is the exception worth knowing about: it only serves descriptions one job at a time, so turning this off skips a request per Workday job and makes those boards much faster and cheaper — but the description, publication date and country come back empty for Workday jobs. Greenhouse, Lever and Ashby are unaffected either way.

## `includeCompensation` (type: `boolean`):

Adds a tidy salary object (minimum, maximum, currency, pay interval) whenever the employer publishes pay. Costs nothing extra: the pay data arrives with the job listing. Ashby boards publish pay most often, Lever rarely, Workday never.

## `maxJobsPerBoard` (type: `integer`):

Stops after this many jobs from each board. It starts at 25 so your first run is quick and cheap — set it to 0 once you are happy, and every job on every board is returned. Worth keeping a limit on Workday boards: those cost one extra request per job when descriptions are on.

## `maxRunTimeSecs` (type: `integer`):

Safety stop. The Actor always finishes by itself shortly before the run timeout, so a long board list ends with a proper summary and the jobs it did collect instead of being killed. Set a number here to stop even sooner. Leave at 0 to use the run timeout.

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

Leave this alone unless you have a reason not to. Datacenter proxy is included in your Apify plan and is all these job boards need — residential proxy would cost far more and buys you nothing here.

## `fetchGreenhousePay` (type: `boolean`):

Greenhouse publishes salary ranges only on each job's own page, so switching this on means one extra request per job. On a 500-job board that turns 1 request into 501 and makes the run noticeably slower and more expensive. Worth it only if you specifically need Greenhouse pay data — many Greenhouse boards publish none at all (in our testing roughly a third of jobs had pay data on the boards that publish any, and none on the boards that don't).

## `workdayDeepCrawl` (type: `boolean`):

Workday never returns more than 2,000 jobs for a single search, no matter how big the employer is. Switch this on and the Actor searches job category by job category and merges the results, which is the only way to get the rest. Cost: a 2,000-job Workday board takes about 100 listing requests; with deep crawl the same board takes roughly 150 or more, depending on how many categories it has. Only turn it on for employers with more than 2,000 open jobs — for everyone else it just adds requests.

## `includeRaw` (type: `boolean`):

Attaches the untouched job-board response to each record under "raw", for fields we do not map. Makes the dataset several times larger, so leave it off unless you need it.

## Actor input object example

```json
{
  "startUrls": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
  ],
  "includeDescription": true,
  "includeCompensation": true,
  "maxJobsPerBoard": 25,
  "maxRunTimeSecs": 0,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": []
  },
  "fetchGreenhousePay": false,
  "workdayDeepCrawl": false,
  "includeRaw": false
}
```

# Actor output Schema

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

Every job found, one normalized record each: company, title, department, location, country, remote flags, salary range, description and apply URL. Same shape for all four job-board systems.

## `jobsCsv` (type: `string`):

The same records as a spreadsheet-ready CSV file.

## `dataset` (type: `string`):

Browse, filter and export the results in Apify Console.

# 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 = {
    "startUrls": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
    ],
    "maxJobsPerBoard": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("nimait/career-site-job-listings-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 = {
    "startUrls": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    ],
    "maxJobsPerBoard": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("nimait/career-site-job-listings-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 '{
  "startUrls": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
  ],
  "maxJobsPerBoard": 25
}' |
apify call nimait/career-site-job-listings-scraper --silent --output-dataset

```

## MCP server setup

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