# Greenhouse, Lever & Ashby Jobs Scraper (6 ATS, No Login) (`echolab/ats-jobs-scraper`) Actor

Scrape job postings and salaries using the Greenhouse jobs API, Lever jobs API, Ashby jobs, Workable, SmartRecruiters and Recruitee public ATS job boards. Filter by keywords, location, remote work and posting date.

- **URL**: https://apify.com/echolab/ats-jobs-scraper.md
- **Developed by:** [Echo Lab](https://apify.com/echolab) (community)
- **Categories:** Jobs, Lead generation, Automation
- **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 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

## Greenhouse, Lever & Ashby Jobs Scraper — 6 Public ATS APIs

Fetch open job postings from six official, public ATS job board APIs without login or proxies. This job postings scraper combines the Greenhouse jobs API, Lever jobs API, Ashby jobs, Workable jobs, SmartRecruiters and Recruitee into a consistent dataset, including structured salary data when available from Ashby, Lever, Greenhouse and Recruitee. Workable and SmartRecruiters salary data is not supported.

- Normalize titles, departments, locations, remote/hybrid status, dates and application links.
- Filter by keywords, locations, remote work and posting date.
- Optional plain-text descriptions; incremental output and cost caps.
- Invalid or failed companies are skipped without billing and recorded in the key-value store `SUMMARY`.

### Use cases

Build job aggregators; monitor hiring companies for recruiting/sales intelligence; benchmark disclosed salaries; and create job alerts with Apify schedules and webhooks. Use datasets with CSV, JSON or Excel exports.

### Supported ATS platforms

| ATS | Example board URL | Input syntax |
|---|---|---|
| Greenhouse | https://boards.greenhouse.io/airbnb | `greenhouse:airbnb` |
| Lever | https://jobs.lever.co/spotify | `lever:spotify` |
| Ashby | https://jobs.ashbyhq.com/ramp | `ashby:ramp` |
| Workable | https://apply.workable.com/blueground | `workable:blueground` |
| SmartRecruiters | https://jobs.smartrecruiters.com/BoschGroup | `smartrecruiters:BoschGroup` |
| Recruitee | https://bunq.recruitee.com | `recruitee:bunq` |

Greenhouse's `job-boards` host, Lever's EU host and `careers.smartrecruiters.com` are also recognized. Lever automatically tries its EU API after a global 404. Bare slugs try Greenhouse, Lever, then Ashby, stopping at the first populated board and caching the result within the run. URLs identify a whole board, not a single posting.

### Input

| Field | Default | Meaning |
|---|---|---|
| companies | Three example boards above | Required string list: `ats:slug`, board URL or bare slug |
| keywords | \[] | Case-insensitive any-keyword title substring match |
| searchDescription | false | Also search descriptions (adds SmartRecruiters detail calls) |
| locations | \[] | Any case-insensitive substring in all locations |
| remoteOnly | false | Require explicitly remote eligible postings |
| postedWithinDays | 0 | Lookback using published/created date, then updated date; 0 disables |
| includeDescription | false | Plain text description, capped at 20,000 characters |
| maxJobsPerCompany | 10 | Saved matching jobs per board; 0 unlimited |
| maxJobs | 0 | Global saved-job cap; 0 unlimited |
| maxConcurrency | 5 | Parallel company workers, 1–10 |

```json
{"companies":["greenhouse:airbnb","lever:spotify","ashby:ramp"],"keywords":["engineer"],"remoteOnly":true,"includeDescription":true,"maxJobs":20,"maxJobsPerCompany":10}
```

### Output

```json
{"ats":"ashby","company":"ramp","companyName":null,"jobId":"34413f8d-26bf-4bbc-8ade-eb309a0e2245","title":"Security Engineer, Cloud","department":"Engineering","team":"Backend","employmentType":"FullTime","workplaceType":"hybrid","isRemote":true,"location":"New York, NY (HQ)","allLocations":["New York, NY (HQ)","Remote (Canada)","Remote (US)","Miami, FL"],"country":"USA","salaryMin":211400,"salaryMax":290600,"salaryCurrency":"USD","salaryPeriod":"1 YEAR","salaryText":"$211.4K – $290.6K • Offers Equity","publishedAt":"2026-04-07T17:12:35.753000Z","updatedAt":null,"jobUrl":"https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245","applyUrl":"https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application","scrapedAt":"2026-11-05T00:00:00Z"}
```

| Fields | Meaning |
|---|---|
| ats, company, companyName | ATS, input slug and source company name if supplied |
| jobId, title | Source identifier and job title |
| department, team, employmentType | Source classification (not guessed) |
| workplaceType, isRemote | remote/hybrid/onsite/null; remote eligibility boolean/null |
| location, allLocations, country | Primary and additional locations; country if provided |
| salaryMin, salaryMax, salaryCurrency, salaryPeriod, salaryText | Structured salary values and source summary; null when unavailable |
| publishedAt, updatedAt, scrapedAt | ISO 8601 UTC timestamps; missing source dates null |
| jobUrl, applyUrl | Public job and application links |
| description | Only present when requested; sanitized plain text |

`SUMMARY` contains `companiesOk` (count), `companiesFailed` (input/reason list), and `jobs` (saved count). Errors are not dataset items and are not billed. Empty explicit boards count as successful; invalid and not-found boards are warned and skipped.

### Pricing

You pay only for job postings saved to the dataset, plus a tiny per-run start fee; Apify platform usage is included in the price. The current price per 1,000 job postings is shown on the Pricing tab of this page. Set `maxJobs` and `maxJobsPerCompany` to cap output. Processing stops normally when Apify's dataset charge limit is reached.

### Tips / limits

- Workday, iCIMS, Taleo, LinkedIn and Indeed are **not supported**: they do not offer the supported public keyless job-board APIs used here.
- Salaries are parsed only from structured fields, never guessed from description text. Currency/period are source labels, not converted. A representative disclosed salary component/range is used, not a geographic aggregate.
- Ashby unlisted jobs are excluded. Hybrid roles can be remote eligible; `remoteOnly` uses the source eligibility flag, not just a workplace label. Unknown remote status is excluded.
- Undated jobs remain eligible for lookback filters. Availability and field completeness depend on each employer. Workable's widget can return an empty board even when another careers frontend has openings.
- SmartRecruiters paginates in 100-job pages, with a safety ceiling of 10,000 listings per company. One detail request per candidate is made only for descriptions or description search; saved-job caps stop early, but restrictive filters may still inspect many candidates. Keep company lists/caps small for quick runs.
- Requests use a descriptive User-Agent, at most 10 concurrent company workers, three attempts and exponential backoff honoring Retry-After up to 30 seconds. No guarantee that endpoints remain unchanged.

### Compliance & data source

Only official public APIs are used: `boards-api.greenhouse.io`, `api.lever.co` / `api.eu.lever.co`, `api.ashbyhq.com`, `apply.workable.com`, `api.smartrecruiters.com` and company `recruitee.com` careers APIs. No login, cookies, CAPTCHA solving, proxying or anti-bot bypass. Recruiter/hiring-manager contact fields are never copied, and emails and common phone-number formats in descriptions are redacted. Descriptions are employer-authored job content; review them before onward publication. Users are responsible for source terms and applicable laws.

### Support

Open an issue on the Actor's Issues tab.

More real output, worked pricing examples and a comparison with alternatives: https://echolab-tools.pages.dev/greenhouse-lever-ashby-jobs-scraper/

# Actor input Schema

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

Required company boards: ats:slug, supported job-board URL, or bare slug.

## `keywords` (type: `array`):

Match any keyword as a case-insensitive substring in job titles. An empty list disables this filter.

## `searchDescription` (type: `boolean`):

Also match keywords in plain-text descriptions. SmartRecruiters requires extra detail requests.

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

Match any case-insensitive substring in primary or additional job locations. Empty means all locations.

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

Only save jobs explicitly marked remote eligible by the source. Hybrid roles may qualify; unknown status is excluded.

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

Date lookback; 0 means no limit. Undated jobs remain eligible.

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

Include plain text up to 20,000 characters.

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

Maximum matching jobs saved per company. Default 10 keeps test runs small; 0 = unlimited.

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

Global saved job cap; 0 means unlimited.

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

Concurrent company workers, from 1 to 10.

## Actor input object example

```json
{
  "companies": [
    "greenhouse:airbnb",
    "lever:spotify",
    "ashby:ramp"
  ],
  "keywords": [],
  "searchDescription": false,
  "locations": [],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "includeDescription": false,
  "maxJobsPerCompany": 10,
  "maxJobs": 0,
  "maxConcurrency": 5
}
```

# Actor output Schema

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

One saved item per matching job.

# 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:airbnb",
        "lever:spotify",
        "ashby:ramp"
    ],
    "keywords": [],
    "searchDescription": false,
    "locations": [],
    "remoteOnly": false,
    "postedWithinDays": 0,
    "includeDescription": false,
    "maxJobsPerCompany": 10,
    "maxJobs": 0,
    "maxConcurrency": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("echolab/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:airbnb",
        "lever:spotify",
        "ashby:ramp",
    ],
    "keywords": [],
    "searchDescription": False,
    "locations": [],
    "remoteOnly": False,
    "postedWithinDays": 0,
    "includeDescription": False,
    "maxJobsPerCompany": 10,
    "maxJobs": 0,
    "maxConcurrency": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("echolab/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:airbnb",
    "lever:spotify",
    "ashby:ramp"
  ],
  "keywords": [],
  "searchDescription": false,
  "locations": [],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "includeDescription": false,
  "maxJobsPerCompany": 10,
  "maxJobs": 0,
  "maxConcurrency": 5
}' |
apify call echolab/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

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