# ATS Jobs Scraper & API - Lever, Greenhouse, Workable, Recruitee (`ahmed.fouaad82/ats-public-jobs`) Actor

ATS jobs scraper: public job postings from Lever, Greenhouse, Workable and Recruitee boards in one schema, with salary, remote flags and dedup. No login.

- **URL**: https://apify.com/ahmed.fouaad82/ats-public-jobs.md
- **Developed by:** [Ahmed Fouad](https://apify.com/ahmed.fouaad82) (community)
- **Categories:** Jobs, Lead generation, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

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

### ATS Jobs Scraper & API - Lever, Greenhouse, Workable, Recruitee

This **ATS jobs scraper** collects **public job postings from Lever, Greenhouse, Workable and Recruitee** into one consistent dataset. Use ATS-hosted careers URLs or `{ "ats": "lever", "token": "palantir" }` pairs. This is a public job-board API collector—not an applicant search, employee directory, global job search index, or browser scraper.

The Actor checks current robots.txt before each API origin, uses plain HTTP with polite delays, and does not use proxies, login sessions, CAPTCHA solving, or access-control workarounds. Source references: [Lever](https://github.com/lever/postings-api), [Workable](https://apply.workable.com/), [Recruitee](https://recruitee.com/), and [Greenhouse](https://developers.greenhouse.io/job-board.html).

### Why use a unified ATS jobs feed?

Build job dashboards, monitor hiring demand, synchronize an internal opportunity database, or research published job descriptions. One schema replaces four separate integrations. Apify provides scheduled runs, API access, monitoring, integrations, and dataset exports.

Features include title/location filters, remote filtering, source update-date filtering, run-level deduplication, per-board and global result limits, and optional cross-run saved-ID tracking. No applicant records or separate recruiter contact fields are collected.

### What data can this ATS jobs scraper extract?

| Field | Type | Meaning |
| --- | --- | --- |
| `ats`, `company`, `boardToken` | string | Source ATS and company name or board token |
| `jobId`, `title` | string | Source identifier and published title |
| `departments` | array | Source departments/teams |
| `location`, `locations` | string / array | Published location labels |
| `remote` | boolean / null | Fully remote indicator when the feed exposes one |
| `employmentType` | string / null | Original employment category |
| `postedAt`, `updatedAt` | ISO string / null | Source dates, not extraction-time substitutes |
| `url`, `applyUrl` | string / null | Public posting and application page |
| `descriptionText`, `descriptionHtml` | string | Description, including available requirement sections |
| `salary` | object / null | Structured salary range when the API publishes one |
| `scrapedAt` | ISO string | Extraction timestamp |

Missing data remains null. Salary figures embedded only in prose remain in the description; no salary is invented. Source field availability differs: Workable and Lever often lack update dates, Greenhouse often lacks employment type and explicit remote status. Lever `postedAt` uses the source creation timestamp. Company names fall back to the token where a feed omits the name. Greenhouse application links use the posting page. Raw HTML is untrusted; sanitize before rendering.

### How to scrape public ATS jobs

1. Start with defaults to collect 20 current Stripe jobs from Greenhouse.
2. Replace `boards` with company URLs or explicit ATS/token pairs.
3. Set filters and limits, then run.
4. Download the dataset in various formats such as JSON, HTML, CSV, or Excel, or use the API tab.

```json
{
  "boards": [
    "https://jobs.lever.co/palantir",
    "https://apply.workable.com/huggingface/",
    "https://bunq.recruitee.com/",
    "https://job-boards.greenhouse.io/stripe"
  ],
  "maxResults": 100,
  "maxResultsPerBoard": 25,
  "remote": "all"
}
```

ATS detection supports those ATS-hosted board URLs and the corresponding API URLs. Arbitrary custom career domains, links embedded in custom websites, Lever EU boards, Ashby, and SmartRecruiters are not supported in this release. Ashby's API origin returned HTTP 401 for robots.txt; SmartRecruiters' API robots.txt disallowed general crawlers. Neither is fetched by this Actor. Requests fail closed when robots rules cannot be obtained (except 404/410), disallow access, or endpoints deny access. Standard robots redirects are followed only to HTTPS hosts resolving publicly; API redirects are not followed.

### How much does scraping ATS jobs cost?

**First 25 jobs of every run are free; after that, $2 per 1,000 jobs.** One result = one job posting saved.

**First 25 jobs of every run are free**; after that, pay **$2 per 1,000 saved jobs** (`result` event, $0.002/job). The Pricing tab is authoritative. Filtered and duplicate jobs are not charged as results. After the free prefix, the Actor checks the remaining charge allowance before fetching/saving and stops at the reported charge limit. Account plan terms and any separately displayed platform charges still apply.

### Input and advanced options

See the input tab for full configuration options. Up to 20 boards are accepted. `maxResults` is global; `maxResultsPerBoard` keeps a large first company from consuming the entire budget. Requests have a minimum one-second interval and respect published crawl delays.

`titleContains` and `locationContains` are case-insensitive substrings. `updatedSince` is inclusive and applies strictly to `updatedAt`; records with no update timestamp are excluded rather than treating publication as an update. `remote: "only"` requires true, while `"exclude"` requires false; unknown remote values match only `"all"`. Hybrid is not classified as fully remote.

For incremental mode, create a dedicated Apify key-value store, select it in `incrementalStoreId`, and set `incremental: true`. The input grants read/write access only to that selected store. Its `SEEN` record remembers ATS + board + job ID for jobs actually saved. Reuse that store on later scheduled runs. Use sequential runs: concurrent runs can race when updating shared state. Existing job IDs are suppressed even if their descriptions change. Clear `SEEN` or use a new store to reset. IDs are retained without automatic expiry; periodically rotate the store for very large histories. Without incremental mode, deduplication is within the current run only. A platform interruption between a dataset write and its checkpoint can cause an at-least-once replay; transactional exactly-once delivery is not promised.

### Output example

Illustrative format, not a claim that this particular job exists:

```json
{
  "ats": "lever",
  "company": "example-company",
  "boardToken": "example-company",
  "jobId": "00000000-0000-0000-0000-000000000001",
  "title": "Software Engineer",
  "departments": ["Engineering"],
  "location": "Remote",
  "locations": ["Remote"],
  "remote": true,
  "employmentType": "Full-time",
  "postedAt": "2026-01-15T12:00:00.000Z",
  "updatedAt": null,
  "url": "https://jobs.lever.co/example-company/00000000-0000-0000-0000-000000000001",
  "applyUrl": "https://jobs.lever.co/example-company/00000000-0000-0000-0000-000000000001/apply",
  "descriptionText": "Build useful products.",
  "descriptionHtml": "<p>Build useful products.</p>",
  "salary": null,
  "scrapedAt": "2026-01-16T12:00:00.000Z"
}
```

The default key-value store contains `SUMMARY` with per-board counts, errors and stop reason, plus `STATE` for recovery. Partial success is possible: a failed board is recorded while other boards continue. A run fails if no jobs were saved and board errors occurred; zero matching jobs with no source errors is a valid success.

### FAQ and responsible use

#### Why did a board return no jobs?

Check the board token, source availability, filter settings, incremental history, and `SUMMARY`. ATS providers or companies may close boards or change policies. This Actor does not bypass a 401, 403, CAPTCHA, or robots prohibition.

#### Does this collect applicant or employee data?

No applicant APIs, application submissions, private profiles, or employee records are requested. Public job descriptions can contain incidental contact details supplied by the employer. You are responsible for a lawful purpose, source terms, and applicable privacy rules such as GDPR; consult legal counsel if unsure. No contact-enrichment fields are produced.

Use the Issues tab for reproducible problems and the API tab for programmatic access. Include the board URL and run ID, not credentials or applicant information.

# Actor input Schema

## `boards` (type: `array`):

1-20 HTTPS ATS-hosted career board URLs or objects with ats and token. Supported: lever, workable, recruitee, greenhouse. Custom career domains and blocked ATS are not supported.

## `maxResults` (type: `integer`):

Global cap on matching saved postings. First 25 results of every run are free.

## `maxResultsPerBoard` (type: `integer`):

Cap each company feed so multi-company runs can sample every board.

## `titleContains` (type: `string`):

Case-insensitive title substring; empty includes all titles.

## `locationContains` (type: `string`):

Case-insensitive substring across all published locations.

## `remote` (type: `string`):

Only/exclude require explicit true/false. Unknown remote values match all only; hybrid is not fully remote.

## `updatedSince` (type: `string`):

Inclusive source updatedAt filter. Jobs with unknown update dates are excluded. Posted date is never substituted.

## `incremental` (type: `boolean`):

Remember saved IDs across runs using the selected shared key-value store. Sequential runs only; edits to an existing ID are not emitted again.

## `incrementalStoreId` (type: `string`):

Select a persistent key-value store. Required when incremental is enabled; READ and WRITE permission is requested only for this selected store.

## `delayMillis` (type: `integer`):

Minimum interval per API origin and pause between boards. robots crawl-delay is also honored.

## Actor input object example

```json
{
  "boards": [
    {
      "ats": "greenhouse",
      "token": "stripe"
    }
  ],
  "maxResults": 20,
  "maxResultsPerBoard": 1000,
  "titleContains": "",
  "locationContains": "",
  "remote": "all",
  "updatedSince": "",
  "incremental": false,
  "delayMillis": 1000
}
```

# Actor output Schema

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

Saved public postings

## `diagnostics` (type: `string`):

Run summary and current state

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("ahmed.fouaad82/ats-public-jobs").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("ahmed.fouaad82/ats-public-jobs").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 '{}' |
apify call ahmed.fouaad82/ats-public-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ahmed.fouaad82/ats-public-jobs"
        }
    }
}
```

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/ICB1UXzRFXC1h61ho/builds/Oatwg5x9B560LwJDD/openapi.json
