# ATS Job Scraper — Greenhouse, Lever, Ashby, Workable (`automation_craft/ats-jobs-scraper`) Actor

Scrape jobs from Greenhouse, Lever, Ashby, Workable & SmartRecruiters career boards into ONE unified schema. Paste careers URLs, get title, location, remote flag, salary, description.

- **URL**: https://apify.com/automation\_craft/ats-jobs-scraper.md
- **Developed by:** [Automation Craft](https://apify.com/automation_craft) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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?

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

## ATS Job Scraper — Greenhouse, Lever, Ashby, Workable & SmartRecruiters

Scrape open jobs from company career boards on **five applicant-tracking systems in one run, in one unified format**. Paste careers-page URLs (or company tokens), press Start, and get clean, deduplicated job records: title, department, location, remote flag, employment type, salary data where published, posting date, apply link, and (optionally) the full job description.

No login, no API keys, no proxies to configure, no code.

### Why this Actor

Most job scrapers cover a single ATS, so monitoring companies across Greenhouse *and* Lever *and* Ashby means running three actors with three different output formats. This Actor covers all five major ATS platforms and normalizes everything into **one schema**, so your spreadsheet, database, or agent pipeline only needs to understand one shape.

Built for:

- **Recruiters & sourcers** — track competitor hiring across every board they use.
- **Job seekers** — one watchlist across all the companies you care about, with keyword and remote-only filters.
- **Sales & market intelligence** — hiring signals by department, team, and location.
- **Job boards & aggregators** — a normalized multi-ATS ingestion feed.

### How to use it

Add one entry per company under **Companies**, in any of these formats:

| Format | Example |
|---|---|
| Careers-page URL (auto-detected) | `https://boards.greenhouse.io/stripe`, `https://jobs.lever.co/spotify`, `https://jobs.ashbyhq.com/linear`, `https://apply.workable.com/huggingface`, `https://careers.smartrecruiters.com/ServiceNow` |
| `provider:token` pair | `greenhouse:stripe`, `lever:spotify`, `ashby:linear`, `workable:huggingface`, `smartrecruiters:ServiceNow` |
| Bare company token | `duolingo` — probed against all five providers automatically |

URLs tolerate `http/https`, `www.`, trailing paths, and job-page deep links. EU-hosted Greenhouse/Lever boards are detected from `eu.` URLs. SmartRecruiters tokens are **case-sensitive** (`ServiceNow`, not `servicenow`).

Optional settings: **keyword filter** (title/department/team/location), **remote only**, **full descriptions**, and exact caps per company and per run — caps are honored exactly.

### What "unified" means — per-provider field coverage

Every job record has the same fields. Where an ATS does not publish a field, you get `null` — we never guess. This table is the exact contract:

| Unified field | Greenhouse | Lever | Ashby | Workable | SmartRecruiters |
|---|:-:|:-:|:-:|:-:|:-:|
| `title`, `url`, `applyUrl`, `jobId` | ✅ | ✅ | ✅ | ✅ | ✅ |
| `departments` | ✅ | ✅ | ✅ | ✅ | ✅ |
| `team` | — | ✅ | ✅ | ✅ (function) | ✅ (function) |
| `location` + `allLocations` | ✅ (+offices) | ✅ | ✅ (+secondary) | ✅ | ✅ (one per posting) |
| `remote` flag | ⚠️ only when the location says "Remote" | ✅ | ✅ | ✅ | ✅ |
| `employmentType` | — | ✅ | ✅ | ✅ | ✅ |
| `compensation` | — | ✅ when published | ✅ structured ranges | — | — |
| `publishedAt` | ✅ | ✅ | ✅ | ✅ | ✅ |
| `updatedAt` | ✅ | — | — | — | — |
| `descriptionHtml` + `descriptionText` | ✅ | ✅ | ✅ | ✅ | ✅ (one extra request per job) |
| `companyName` | ✅ | — (token only) | — (token only) | ✅ | ✅ |

### Output example

```json
{
  "type": "job",
  "provider": "ashby",
  "company": "linear",
  "jobId": "1e6cbc0e-…",
  "title": "Senior Product Engineer",
  "departments": ["Engineering"],
  "team": "Product Engineering",
  "location": "North America",
  "allLocations": ["North America", "Europe"],
  "remote": true,
  "workplaceType": "Remote",
  "employmentType": "FullTime",
  "compensation": { "compensationTierSummary": "$180K – $240K • Equity" },
  "publishedAt": "2026-07-14T18:02:11.000Z",
  "url": "https://jobs.ashbyhq.com/linear/1e6cbc0e-…",
  "applyUrl": "https://jobs.ashbyhq.com/linear/1e6cbc0e-…/application",
  "descriptionHtml": null,
  "descriptionText": null
}
```

Export as JSON, CSV, Excel, or read it from the API — standard Apify dataset.

### Pricing — what a run actually costs

Pay per event, only for what you receive:

| Event | Price |
|---|---|
| Actor start | $0.005 |
| Job record delivered | $0.001 |
| Full description delivered (only with *Include full job descriptions* on) | +$0.001 per job that got one |

Worked examples: 200 jobs across 3 companies = **$0.205**. The same 200 jobs with full descriptions = **$0.405**. Unknown company, empty board, or zero filter matches = **you pay only the start fee** — a status record in the dataset explains exactly what happened.

### What this Actor does NOT do

- ❌ It does not scrape job boards like LinkedIn, Indeed, or Naukri — it reads **company career boards** hosted on Greenhouse, Lever, Ashby, Workable, and SmartRecruiters.
- ❌ It does not return applicant data, salary data the company didn't publish, or fields marked "—" in the coverage table above.
- ❌ It does not discover companies for you — you supply the companies; it gets you every open job they list.
- ❌ It cannot see unlisted/internal-only postings.

### Fair-use & compliance

This Actor reads the same public, unauthenticated job-board endpoints the companies' own careers pages use, at a polite request rate. It collects no personal data and works with no login. You are responsible for complying with applicable laws and the target sites' terms in your jurisdiction and use case.

### Tips

- The **bare token** probe picks the first provider where the company has open jobs — when you know the provider, use `provider:token` or the URL form for precision.
- Very large boards: `maxJobsPerCompany` and `maxJobsTotal` are exact caps, so you can bound cost precisely.
- `remote: null` means the ATS didn't say — filter with `remoteOnly` only when you need certainty, and remember Greenhouse boards only match via location text.

***

*Questions or a provider you'd like added? Open an issue via the Actor's Issues tab — feedback shapes the roadmap.*

### More data tools by Automation Craft

- [LinkedIn Jobs Scraper](https://apify.com/automation_craft/linkedin-jobs-scraper) — LinkedIn public job search, no login, real posted dates.
- [Substack Scraper](https://apify.com/automation_craft/substack-scraper) — posts, Notes, profiles & leaderboards.
- [US Building Permits Scraper](https://apify.com/automation_craft/us-building-permits-scraper) · [US New Business Registrations](https://apify.com/automation_craft/us-new-business-registrations-scraper) — official-source B2B lead feeds.

# Actor input Schema

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

One entry per company. Accepts (1) a careers-page URL from any supported ATS — e.g. `https://boards.greenhouse.io/stripe`, `https://jobs.lever.co/spotify`, `https://jobs.ashbyhq.com/linear`, `https://apply.workable.com/huggingface`, `https://careers.smartrecruiters.com/ServiceNow` — (2) a `provider:token` pair like `greenhouse:stripe`, or (3) a bare company token, which is probed against all five providers. SmartRecruiters tokens are case-sensitive.

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

Attach the full job description (HTML + plain text) to every job. Adds a small per-job charge; for SmartRecruiters this needs one extra request per job.

## `searchText` (type: `string`):

Keep only jobs whose title, department, team, or location contains this text (case-insensitive). You are only charged for jobs that match.

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

Keep only jobs the ATS explicitly marks as remote. Note: Greenhouse has no remote flag, so Greenhouse jobs only match when their location text says "Remote".

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

Cap on charged job records per company board. Honored exactly.

## `maxJobsTotal` (type: `integer`):

Overall cap on charged job records for the run. Honored exactly.

## Actor input object example

```json
{
  "companies": [
    "greenhouse:stripe",
    "lever:spotify",
    "ashby:linear"
  ],
  "maxJobsPerCompany": 100,
  "maxJobsTotal": 300
}
```

# 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",
        "lever:spotify",
        "ashby:linear"
    ],
    "maxJobsPerCompany": 100,
    "maxJobsTotal": 300
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/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",
        "lever:spotify",
        "ashby:linear",
    ],
    "maxJobsPerCompany": 100,
    "maxJobsTotal": 300,
}

# Run the Actor and wait for it to finish
run = client.actor("automation_craft/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",
    "lever:spotify",
    "ashby:linear"
  ],
  "maxJobsPerCompany": 100,
  "maxJobsTotal": 300
}' |
apify call automation_craft/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

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