# Job Postings Scraper - Greenhouse, Lever, Ashby (`dataio/greenhouse-lever-job-postings`) Actor

Scrape job postings from company career sites on Greenhouse, Ashby, Lever, SmartRecruiters and Workable in one schema, with normalized function and seniority, country resolved from the city, and days each role has been open. Hiring signals for sales and recruiting.

- **URL**: https://apify.com/dataio/greenhouse-lever-job-postings.md
- **Developed by:** [Tom Awake](https://apify.com/dataio) (community)
- **Categories:** Lead generation, Jobs, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 job postings

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

### What does Job Postings Scraper do?

Every open role at any company — across the five applicant tracking
systems that publish their job boards openly, in **one schema**.

A company publishes its openings on Greenhouse, Ashby, Lever,
SmartRecruiters or Workable. Each exposes a public board, and each does it
in a completely different shape: the title is `title` in one and `text` in
another, the date is an ISO string here and a millisecond timestamp there,
the location is a plain string in one and a six-field object in the next.

And nobody knows off-hand which system a given company uses.

No API key. No account.

***

### Genuinely international, by construction

This is not a US dataset with a few foreign rows. Measured on a single run
across 8 companies — 1,805 postings, **103 countries, 183 cities**:

| Country | Postings |
|---|---|
| United Kingdom | 212 |
| United States | 185 |
| Singapore | 141 |
| Australia | 140 |
| Malaysia | 116 |
| Indonesia | 87 |
| Ireland | 65 |
| Vietnam | 58 |
| India | 56 |
| Germany | 43 |

A company in Tokyo, Berlin, São Paulo or Kuala Lumpur publishes in the
same five places as one in San Francisco. One Actor reaches all of them —
which covers more of the world than a separate scraper per country ever
would.

***

### The five things this does that the sources do not

**1. It works out which system a company uses.**

You supply a company identifier; the Actor tries all five boards and keeps
whichever answers. Measured on 36 well-known companies: **75% detected**,
7,550 postings. The rest run Workday, SuccessFactors or their own site,
and are reported as not found rather than silently skipped.

**2. One schema for five incompatible structures.**

Same columns whichever board a role came from, with `ats` recording the
origin so nothing is hidden.

**3. Days each role has been open.**

The hiring signal. A role open for six months is one the company cannot
fill — a real, persistent need. No source gives this; they give a date.
Measured: median 36 days, but **19% have been open more than 90 days**,
and the longest in the sample was 1,059 days.

**4. Normalised function and seniority.**

Greenhouse returns `8611 Security Analytics`, internal cost-centre codes
included. SmartRecruiters already returns `Engineering`. All five are
mapped onto one vocabulary, and department codes are stripped.

Seniority is read from the title when the source does not give it.
**Manager is kept separate from Staff** — they are two different career
tracks, and merging them put 28% of a sample into "Staff".

**5. Country resolved from the city.**

Greenhouse often returns `Bengaluru` with no country at all, which left
`country` empty on a quarter of rows and made country filtering useless.
Cities are resolved against a table covering every continent, and country
names are normalised so `US` and `United States` stop counting as two
countries. Coverage: **74.3% → 90.5%**.

***

### Field coverage

Measured on 1,805 postings from 8 companies across all five systems.

| Field | Coverage |
|---|---|
| `company`, `title`, `jobId`, `location`, `city` | 100% |
| `function`, `seniority`, `daysOpen`, `publishedAt` | 100% |
| `jobUrl`, `applyUrl` | 100% |
| `country` | 90.5% |
| `requisitionId` | 76.5% |
| `department` | 62.6% |
| `description` | 61.9% |
| `employmentType` | 60.6% |
| `countryCode` | 37.4% |

`description` and `countryCode` are only carried by some of the five
systems; they are left empty rather than filled with a guess.

***

### What it is for

- **B2B prospecting.** A company hiring twelve backend engineers is
  buying infrastructure. Hiring sales reps means it is expanding. The
  postings say so before any announcement does.
- **Market entry timing.** `country` and `city` across a competitor's
  whole board show where they are opening, quarter by quarter.
- **Recruitment.** Roles open past 90 days are the ones agencies get
  called about.
- **Competitive intelligence.** Team names and departments reveal an org
  chart the company never published.
- **Compensation and talent research.** Function and seniority are
  comparable across companies and countries.

Three dataset views ship with the Actor: **Postings**, **Hard-to-fill
roles** and **Global footprint**.

***

### Limits

Stated plainly, because they affect what you can conclude.

- **Roughly a quarter of companies are not reachable.** Workday,
  SuccessFactors, Taleo and bespoke career sites are not covered. The
  status message names the companies that were not found, so an empty
  result is never mistaken for "not hiring".
- **You supply the identifier, not the company name.** It is the slug in
  the careers URL — `stripe` in `boards.greenhouse.io/stripe`. Pasting the
  full URL works. A company name that is not the slug will not resolve.
- **`daysOpen` is time since first published, not time unfilled.** Some
  boards re-publish a role rather than update it, which resets the clock;
  others leave evergreen listings open permanently. Treat a very old date
  as "long-running requisition", not as a precise vacancy duration.
- **Function and seniority are inferred.** They are good enough to
  segment thousands of rows, not to adjudicate a single one. A title like
  "Risk Operations Associate" under a "User Policy Operations" department
  is a genuine judgement call.
- **The city table covers where qualified hiring concentrates**, not every
  town. An unlisted city leaves `country` empty rather than guessed.
- **A posting is not a hire.** Boards carry cancelled, evergreen and
  pipeline roles. Volume is a signal of intent, not of headcount.
- **Public boards only.** Confidential and internal-only roles never
  appear, by design.

***

### How much does it cost?

You pay per job posting returned: **$0.003 each**, that is $3.00 per 1,000. There is no start fee, and subscription plans pay less per job posting.

The example input below asks for up to 1,000 job postings, so it costs **$3.00 at most**.

If a run reaches the spending limit you set, the output stops at that limit and never goes past it. You are never charged for rows that were not delivered.

### Use Job Postings Scraper as an API

Call it from your own code with the Apify client, here in Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("DataIO/greenhouse-lever-job-postings").call(run_input={
    'companies': ['stripe', 'notion', 'grab'],
    'maxItems': 1000,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

It also works from JavaScript, Make, Zapier, n8n, and from AI agents through the Apify MCP server.

### Other actors you might like

- [npm Downloads Scraper](https://apify.com/DataIO/npm-registry-download-trends): Package Trends & Growth
- [WordPress Plugin Scraper](https://apify.com/DataIO/wordpress-org-plugin-stats): Installs & Ratings
- [crt.sh Subdomain Finder](https://apify.com/DataIO/crtsh-subdomain-finder): Certificate Search

### FAQ

#### Is it legal to use this data?

The actor reads public data from its official source, without logging in and without bypassing any access control. What you do with the data, for example contacting people listed in it, is your responsibility under the laws that apply to you, such as GDPR in Europe.

#### Can I run it on a schedule?

Yes. Create a schedule in Apify Console, daily or weekly for example, and each run delivers a fresh dataset, which you can send by email, webhook or integration.

#### Can AI agents use it?

Yes. It is available through the Apify MCP server, and every input field is described in its input schema, so an agent can call it directly.

### Sources

Public job board APIs of
[Greenhouse](https://boards-api.greenhouse.io),
[Ashby](https://api.ashbyhq.com),
[Lever](https://api.lever.co),
[SmartRecruiters](https://api.smartrecruiters.com) and
[Workable](https://apply.workable.com) — the same endpoints that power the
careers pages companies publish deliberately. No authentication. This
Actor is not affiliated with any of them.

# Actor input Schema

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

Company identifiers, one per line — the slug from the careers board URL, for example stripe from boards.greenhouse.io/stripe. You can paste the full URL instead and it will be cleaned up. The Actor works out which system each company uses.

## `ats` (type: `string`):

Leave on automatic unless you already know. Automatic tries all five and keeps whichever answers.

## `functions` (type: `array`):

Normalised job function. Leave empty for all. Measured split on a 1,805-job sample: Engineering 31%, Sales 17%, Marketing 10%, Operations 9%.

## `countries` (type: `array`):

Country names or codes, for example Singapore, India, Germany, US. Matched against country, location and country code.

## `titleKeyword` (type: `string`):

Keeps only jobs whose title contains this text, for example platform, account executive, designer.

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

Around 20% of postings in a typical run are remote.

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

Recent postings are the freshest signal. Median posting age is 36 days, but 19% have been open for more than 90 days.

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

One row per posting. The longest-open roles are kept first, since those are the strongest hiring signal.

## Actor input object example

```json
{
  "companies": [
    "stripe",
    "notion",
    "grab"
  ],
  "ats": "auto",
  "remoteOnly": false,
  "maxItems": 1000
}
```

# Actor output Schema

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

Every row produced by the run, in JSON.

# 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": [
        "stripe",
        "notion",
        "grab"
    ],
    "maxItems": 1000
};

// Run the Actor and wait for it to finish
const run = await client.actor("dataio/greenhouse-lever-job-postings").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": [
        "stripe",
        "notion",
        "grab",
    ],
    "maxItems": 1000,
}

# Run the Actor and wait for it to finish
run = client.actor("dataio/greenhouse-lever-job-postings").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": [
    "stripe",
    "notion",
    "grab"
  ],
  "maxItems": 1000
}' |
apify call dataio/greenhouse-lever-job-postings --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dataio/greenhouse-lever-job-postings"
        }
    }
}
```

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/JtsgMc2NMucky2bfI/builds/xfjwR885RrDHTQMgJ/openapi.json
