# Job Postings Scraper: Greenhouse, Lever, Ashby & Workable (`mossgreen_zed/ats-job-postings-scraper`) Actor

Get every open job from any company's careers page via official public ATS feeds: Greenhouse, Lever, Ashby, Workable, Recruitee, SmartRecruiters. Paste a board URL, slug or company website. Salary, remote flag, location filters, new-jobs-only mode. $1 per 1,000 jobs.

- **URL**: https://apify.com/mossgreen_zed/ats-job-postings-scraper.md
- **Developed by:** [Joshua Temko](https://apify.com/mossgreen_zed) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.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.

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

## Job Postings Scraper: Greenhouse, Lever, Ashby, Workable, Recruitee & SmartRecruiters

**Get every open job from any company's careers page as clean JSON, CSV or Excel, straight from the official public job-board feeds the careers pages themselves use.** Paste a board URL, a slug such as `stripe`, or just a company website such as `notion.so`. The Actor finds the company's applicant tracking system (ATS) and returns one row per job: title, department, team, locations, remote flag, employment type, **salary range**, posting date, job URL and apply URL.

No login, no browser, no proxies, no API key. It reads public JSON feeds over plain HTTP, so runs are fast and cheap: a 750-job company takes a few seconds.

### Supported ATSs

| ATS | Example input | Notes |
|---|---|---|
| Greenhouse | `https://boards.greenhouse.io/stripe`, `greenhouse:stripe` | Also job-boards.greenhouse.io and embed URLs |
| Lever | `https://jobs.lever.co/palantir`, `lever:palantir` | US and EU Lever, structured salary ranges |
| Ashby | `https://jobs.ashbyhq.com/ramp`, `ashby:ramp` | Structured compensation, workplace type |
| Workable | `https://apply.workable.com/huggingface` | Remote flag, locations |
| Recruitee | `https://bunq.recruitee.com` | Hybrid/remote/on-site, salary when published |
| SmartRecruiters | `https://careers.smartrecruiters.com/ServiceNow` | Paginates large boards (thousands of jobs) |

**Don't know the ATS?** Enter a bare slug (`ramp`) and all six are tried in parallel, or enter the company website (`linear.app`) and the home and careers pages are scanned for an ATS link.

### Who uses it

- **Sales and recruiting agencies: hiring signals.** A company hiring five SDRs needs sales tools; a company hiring its first data engineer needs data tools. Watch a list of target accounts and get new roles every morning.
- **Job boards and niche job sites.** Fill a remote, AI, crypto or climate job board with fresh, deduplicated jobs from hundreds of companies with salaries.
- **Job seekers and career coaches.** Track dream companies and get only new matching roles (for example "product manager" + "Remote").
- **Market, compensation and VC research.** Count open roles by department, track headcount growth and compare published pay ranges.

### Input

| Field | Description |
|---|---|
| `companies` (required) | Board URLs, `ats:slug` pairs, bare slugs or company websites, one per line. |
| `titleKeywords` / `excludeTitleKeywords` | Keep titles containing any of these words / drop titles containing any of these. |
| `locations`, `departments` | Keep jobs whose location or department/team contains any of these. |
| `remoteOnly` | Only remote jobs. |
| `postedWithinDays` | Only jobs published in the last N days. |
| `onlyNewJobs` | **Monitoring mode.** Remembers what it has already returned, so scheduled runs only return and charge for new jobs. |
| `includeDescription` | Adds the full plain-text description. |
| `parseSalaryFromDescription` | Finds pay-transparency ranges like `$120,000 - $150,000` in the description when the ATS has no structured range. Default on. |
| `maxJobsPerCompany`, `maxJobs` | Caps (newest first). |

```json
{
  "companies": ["https://boards.greenhouse.io/stripe", "lever:palantir", "ramp", "notion.so"],
  "titleKeywords": ["engineer", "developer"],
  "locations": ["remote", "new york"],
  "onlyNewJobs": true
}
```

### Output (one row per job)

Real row from a test run on 2026-10-30:

```json
{
  "company": "Ramp",
  "ats": "ashby",
  "boardSlug": "ramp",
  "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Security Engineer, Cloud",
  "department": "Engineering",
  "team": "Backend",
  "location": "New York, NY (HQ)",
  "locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)", "Miami, FL"],
  "isRemote": true,
  "workplaceType": "Hybrid",
  "employmentType": "FullTime",
  "salaryMin": 211400,
  "salaryMax": 290600,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salaryText": "$211.4K – $290.6K • Offers Equity",
  "salarySource": "ats",
  "postedAt": "2026-04-07T17:12:35+00:00",
  "url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
  "companyInput": "ramp",
  "scrapedAt": "2026-10-30T19:29:05+00:00"
}
```

A per-company summary (which ATS was found, open jobs, matched jobs, and any companies that couldn't be resolved) is saved to the `RUN_SUMMARY` record in the run's key-value store.

### Pricing (pay per event)

- **$1.00 per 1,000 job postings** returned ($0.001 per job), plus Apify's tiny standard actor-start fee.
- **Companies that can't be found cost nothing**, and filtered-out jobs cost nothing: you only pay for rows in your dataset.
- In `onlyNewJobs` mode, a daily watch of 50 companies usually costs a few cents a day.
- Set "Maximum cost per run" in the run options to cap spending; the Actor stops cleanly when it's reached.

### Scheduling job alerts

1. Enter your companies and filters, and turn on **Only jobs not seen in previous runs**.
2. Save as a task and add a **Schedule** (for example daily at 8:00).
3. Connect an integration (Slack, email, Google Sheets, Zapier, Make, webhook) to get new jobs delivered.

### FAQ

**Is this legal?** It reads the public job-board APIs that Greenhouse, Lever, Ashby, Workable, Recruitee and SmartRecruiters publish for exactly this purpose (showing jobs on careers pages and job sites). No login, no personal data, only job postings that companies have chosen to publish.

**Why do some jobs have no salary?** Many companies don't publish pay. Structured ranges come from Lever, Ashby and Recruitee; for others the description is scanned for US pay-transparency ranges.

**A company wasn't found.** Check `RUN_SUMMARY` to see which boards were tried. Companies using Workday, iCIMS, Taleo or their own systems aren't supported yet. Paste their board URL if you know it, or open an issue and tell us which ATS you need.

**Does it include descriptions?** Turn on `includeDescription` to get them as clean plain text (SmartRecruiters descriptions aren't included).

# Actor input Schema

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

One per line. Any of: an ATS board URL (https://boards.greenhouse.io/stripe, https://jobs.lever.co/palantir, https://jobs.ashbyhq.com/ramp, https://apply.workable.com/huggingface), an explicit pair like greenhouse:stripe, a bare board slug (auto-detected across all supported ATSs), or a company website such as notion.so (its careers page is scanned for an ATS link).

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

Keep only jobs whose title contains at least one of these words (case-insensitive). Leave empty for all jobs.

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

Drop jobs whose title contains any of these words, e.g. intern, senior.

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

Keep only jobs whose location text contains one of these, e.g. New York, Remote, United Kingdom.

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

Keep only jobs in matching departments or teams, e.g. Engineering, Sales.

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

Keep only jobs flagged remote by the ATS or with Remote/Anywhere in the location.

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

0 = any age. Jobs without a posting date are kept.

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

Remembers job IDs in a named key-value store, so scheduled runs only return (and only charge for) newly posted jobs. Perfect for daily job alerts or hiring-signal monitoring.

## `stateStoreName` (type: `string`):

Named key-value store used by 'Only new jobs'. Use a different name per watch list.

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

Adds a 'description' field with the job description converted to clean text. Makes the dataset much larger.

## `descriptionMaxChars` (type: `integer`):

Truncate descriptions to this many characters. 0 = no limit.

## `parseSalaryFromDescription` (type: `boolean`):

When the ATS has no structured pay range, find US-style pay-transparency ranges such as $120,000 - $150,000 in the description text.

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

0 = no limit. Newest jobs are kept first.

## `maxJobs` (type: `integer`):

Stop after this many job rows (you are only charged for rows returned). 0 = no limit.

## `atsToTry` (type: `array`):

Which ATSs to probe when you give a bare slug or website. Default: all six.

## `maxConcurrency` (type: `integer`):

Maximum parallel HTTP requests. The default is polite and fast.

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout. Failed requests are retried.

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/stripe",
    "lever:palantir",
    "ramp",
    "notion.so"
  ],
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "locations": [],
  "departments": [],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "onlyNewJobs": false,
  "stateStoreName": "ats-jobs-seen",
  "includeDescription": false,
  "descriptionMaxChars": 0,
  "parseSalaryFromDescription": true,
  "maxJobsPerCompany": 0,
  "maxJobs": 0,
  "atsToTry": [
    "greenhouse",
    "lever",
    "ashby",
    "workable",
    "recruitee",
    "smartrecruiters"
  ],
  "maxConcurrency": 10,
  "requestTimeoutSecs": 30
}
```

# Actor output Schema

## `results` (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/stripe",
        "lever:palantir",
        "ramp",
        "notion.so"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mossgreen_zed/ats-job-postings-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/stripe",
        "lever:palantir",
        "ramp",
        "notion.so",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("mossgreen_zed/ats-job-postings-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/stripe",
    "lever:palantir",
    "ramp",
    "notion.so"
  ]
}' |
apify call mossgreen_zed/ats-job-postings-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mossgreen_zed/ats-job-postings-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/xDUdOagczbg6tHRik/builds/rL5EfMuTQPqAkftLo/openapi.json
