# Greenhouse, Lever, Ashby & SmartRecruiters Jobs Scraper (`datadock/ats-jobs-scraper`) Actor

Every open job from company career pages on Greenhouse, Lever, Ashby, Workable and SmartRecruiters, in one clean format with salary ranges. Filter by title, location or remote, and get only new jobs on scheduled runs. No login or proxies.

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

## Pricing

from $1.50 / 1,000 job listings

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

## Greenhouse, Lever, Ashby & SmartRecruiters Jobs Scraper

Pull every open job from company career pages that run on Greenhouse, Lever, Ashby, Workable or SmartRecruiters. Paste a careers URL or write `greenhouse:stripe` and get clean, uniform job data: title, location, remote flag, department, posting date, salary range and the full description.

No proxies, no browser and no login. It reads the public job board APIs these platforms publish, so runs are fast and cheap.

### Quick start

1. Paste one or more careers URLs into **Companies**, for example `https://jobs.ashbyhq.com/ramp`.
2. Optionally add title or location keywords, or tick **Remote only**.
3. Click **Start**. A board with a few hundred jobs finishes in seconds. Download the results as JSON, CSV or Excel, or pull them through the Apify API.

### Why this scraper

- **Five platforms, one schema.** Greenhouse, Lever, Ashby, Workable and SmartRecruiters all come back in the same shape.
- **Only new jobs.** Schedule it daily and pay only for postings that appeared since the last run.
- **Salary included.** Structured pay where the platform provides it, plus a careful scan of the description where it doesn't.
- **Stable.** It uses the platforms' official public job feeds, not page scraping, so a careers page redesign doesn't break it.
- **Fair pricing.** You pay per job returned, with platform usage included.

### Who it is for

- **Job seekers** who want every opening at their dream companies in one place. Add the companies, filter by title, location or remote, and schedule it daily to see only new postings, with salary ranges included.
- **Recruiters and sourcers** tracking which teams at target companies are hiring.
- **Job boards and aggregators** that need a steady feed of fresh listings in one schema.
- **Sales and BizDev teams** using hiring as a buying signal (a company hiring five data engineers is shopping for data tools).
- **Analysts and researchers** studying hiring trends, remote work and pay transparency.

### What you get

- One schema across all five platforms, so you never write per-ATS parsing.
- Salary min, max, currency and period, from the ATS's structured compensation when available (Ashby, Lever) and otherwise from a conservative scan of the description. Left empty when unsure.
- Filters for title keywords, location keywords and remote-only.
- **Only new jobs** mode: run it on a schedule and receive only listings you have not seen before.
- A company that does not exist is skipped with a warning. It never fails the run.

### Supported platforms and URL formats

| Platform | Careers URL examples | Shorthand |
|---|---|---|
| Greenhouse | `boards.greenhouse.io/stripe`, `job-boards.greenhouse.io/stripe` | `greenhouse:stripe` |
| Lever | `jobs.lever.co/palantir` | `lever:palantir` |
| Ashby | `jobs.ashbyhq.com/ramp` | `ashby:ramp` |
| Workable | `apply.workable.com/blueground` | `workable:blueground` |
| SmartRecruiters | `jobs.smartrecruiters.com/Canva` | `smartrecruiters:Canva` |

Companies that use a custom careers domain (for example `stripe.com/jobs`) need the `ats:slug` form. The slug is the name in the company's job board URL.

### Input

| Field | Type | Description |
|---|---|---|
| `companies` | array of strings | Careers URLs or `ats:slug` pairs. Required. |
| `titleKeywords` | array of strings | Keep jobs whose title contains any keyword (case-insensitive). |
| `locationKeywords` | array of strings | Keep jobs whose location contains any keyword (case-insensitive). |
| `remoteOnly` | boolean | Keep only jobs the ATS marks as remote. Default `false`. |
| `includeDescription` | boolean | Add plain-text description to each result. Default `true`. |
| `onlyNewSinceLastRun` | boolean | Return only jobs not delivered by an earlier run. Default `false`. |

#### Example: all engineering jobs in Europe

```json
{
  "companies": ["greenhouse:stripe", "https://jobs.lever.co/palantir", "https://jobs.ashbyhq.com/ramp"],
  "titleKeywords": ["engineer", "developer"],
  "locationKeywords": ["london", "dublin", "berlin"]
}
```

#### Example: daily feed of new remote jobs

```json
{
  "companies": ["ashby:ramp", "lever:palantir"],
  "remoteOnly": true,
  "includeDescription": false,
  "onlyNewSinceLastRun": true
}
```

Schedule this one daily. Seen job IDs are stored per company in the named key-value store `ats-jobs-seen`, so each run returns only listings that appeared since the last one. Only jobs that were delivered are remembered, so changing your filters later will not hide jobs you never received.

### Output

One dataset item per job:

```json
{
  "ats": "ashby",
  "company": "ramp",
  "companyName": "ramp",
  "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Security Engineer, Cloud",
  "url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
  "location": "New York, NY (HQ)",
  "isRemote": true,
  "department": "Engineering",
  "team": "Backend",
  "employmentType": "FullTime",
  "postedAt": "2026-04-07T17:12:35.753Z",
  "updatedAt": null,
  "salaryMin": 211400,
  "salaryMax": 290600,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salarySource": "structured",
  "descriptionText": "...",
  "scrapedAt": "2026-10-18T21:11:12.043Z"
}
```

Field notes:

- `isRemote` is `true`, `false` or `null` when the ATS does not say. `remoteOnly` keeps only `true`.
- `salarySource` is `structured` (from the ATS's compensation data), `description` (parsed from the text) or `null`.
- `updatedAt` is filled where the ATS exposes it (Greenhouse). `companyName` is the real name where the ATS provides it, otherwise the slug.
- With `includeDescription` off, `descriptionText` is `null`. SmartRecruiters needs one extra request per job to read descriptions and pay, so turning descriptions off makes large SmartRecruiters boards much faster, but salary is then not extracted for them.

### Use it from code

Run it and get the jobs back in one HTTP call:

```bash
curl -X POST "https://api.apify.com/v2/acts/datadock~ats-jobs-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies": ["ashby:openai", "greenhouse:anthropic"], "includeDescription": false}'
```

Or with the JavaScript client:

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('datadock/ats-jobs-scraper').call({
  companies: ['ashby:openai', 'greenhouse:anthropic'],
  onlyNewSinceLastRun: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

AI agents can call it through the [Apify MCP server](https://apify.com/mcp) as a tool for fetching live job data.

### Pricing

**$1.50 per 1,000 job listings.** You pay only for jobs returned. Platform usage is included, with no extra compute charges.

| You pull | You pay |
|---|---|
| 100 jobs | $0.15 |
| 1,000 jobs | $1.50 |
| 10,000 jobs | $15.00 |

A tiny start fee of $0.00005 applies per run. With `onlyNewSinceLastRun`, repeat runs return only new postings, so a daily check costs a few cents.

The Actor stops pushing jobs as soon as your maximum charge per run is reached.

### Limits

- Only jobs published on the public board are returned. Unlisted or internal postings are not visible.
- Boards with thousands of jobs (for example Bosch on SmartRecruiters) take longer with descriptions on.
- Salary parsing is deliberately conservative. A missing salary means none was found with confidence, not that none exists.

### Is it legal to scrape job postings from Greenhouse, Lever and Ashby?

Scraping publicly available data is generally legal, and job postings are data companies publish on purpose so candidates can find them. This Actor reads the public job board APIs that Greenhouse, Lever, Ashby, Workable and SmartRecruiters provide for exactly that. It **does not log in and does not collect private data** such as applicant details or email addresses.

Job descriptions can occasionally mention a person, such as a hiring manager. Personal data is protected by GDPR in the European Union and other laws around the world, so don't process it without a legitimate reason. If you're unsure whether your use is legitimate, consult your lawyers. You can also read Apify's blog post on the [legality of web scraping](https://blog.apify.com/is-web-scraping-legal/).

### FAQ

**How do I find a company's slug?** Open the company's job board and look at the URL. In `jobs.lever.co/palantir` the slug is `palantir`. If the company's careers page sits on its own domain, click any job's **Apply** link, which usually reveals the platform URL.

**Can I feed results into Google Sheets, Slack or my CRM?** Yes. Use Apify's built-in integrations (Zapier, Make, webhooks) or the API on the Actor's dataset.

**My company's platform isn't supported.** [Open an issue](https://apify.com/datadock/ats-jobs-scraper/issues/open) with the careers URL. Requests decide which platform gets added next.

### Feedback and support

Found a bug, a missing field or a board that doesn't work? [Open an issue on the Issues tab](https://apify.com/datadock/ats-jobs-scraper/issues/open). Issues are answered quickly, and requests decide what gets built next.

# Actor input Schema

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

Careers page URLs (for example https://boards.greenhouse.io/stripe) or ATS:slug pairs (for example greenhouse:stripe). Supported ATS names: greenhouse, lever, ashby, workable, smartrecruiters.

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

Keep a job if its title contains any of these words (case-insensitive). Leave empty for all jobs.

## `locationKeywords` (type: `array`):

Keep a job if its location contains any of these words (case-insensitive). Leave empty for all locations.

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

Keep only jobs the ATS marks as remote. Jobs with unknown remote status are dropped.

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

Add the plain-text job description to each result. Turn off for smaller output and faster SmartRecruiters runs.

## `onlyNewSinceLastRun` (type: `boolean`):

Return only jobs not delivered by a previous run. Seen job IDs are stored per company in the named key-value store ats-jobs-seen.

## Actor input object example

```json
{
  "companies": [
    "greenhouse:stripe",
    "https://jobs.lever.co/palantir",
    "https://jobs.ashbyhq.com/ramp"
  ],
  "remoteOnly": false,
  "includeDescription": true,
  "onlyNewSinceLastRun": false
}
```

# Actor output Schema

## `jobs` (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": [
        "greenhouse:stripe",
        "https://jobs.lever.co/palantir",
        "https://jobs.ashbyhq.com/ramp"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datadock/ats-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": [
        "greenhouse:stripe",
        "https://jobs.lever.co/palantir",
        "https://jobs.ashbyhq.com/ramp",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("datadock/ats-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": [
    "greenhouse:stripe",
    "https://jobs.lever.co/palantir",
    "https://jobs.ashbyhq.com/ramp"
  ]
}' |
apify call datadock/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

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