# Greenhouse, Lever, Ashby & Workday Jobs Scraper (6 ATS) (`contented_catwalk/greenhouse-lever-ashby-jobs`) Actor

Company career-site jobs from 6 ATS (Workday, Greenhouse, Lever, Ashby, SmartRecruiters, Workable) in one normalized dataset: title, location, remote, department, normalized salary, dates, apply URL, description. Official public APIs only.

- **URL**: https://apify.com/contented_catwalk/greenhouse-lever-ashby-jobs.md
- **Developed by:** [Orvix](https://apify.com/contented_catwalk) (community)
- **Categories:** Jobs, Lead generation, AI
- **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?

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: Workday, Greenhouse, Lever, Ashby, SmartRecruiters & Workable

Pull **job listings from six applicant-tracking systems (Greenhouse, Lever, Ashby, Workday, SmartRecruiters and Workable)** into one clean, normalized dataset. Give it a list of company boards (for example `stripe`, `spotify`, `ramp`, a Workday careers URL, `Ubisoft2`, `huggingface`) and get every open job with title, locations, remote flag, department, employment type, posting date, apply link, full description and **parsed salary range** where the company publishes one.

It uses only the **official public job-board APIs** these ATS platforms provide for career pages, and for Workday the same public JSON the company's own careers page loads (fetched at low concurrency). No login, no browser, no proxies, so runs are fast and cheap.

### What you can use it for

- Job board backfill and job aggregation (ATS jobs feed)
- Hiring signals and sales intelligence: which companies are hiring, for which teams, where
- Salary benchmarking from published pay ranges (pay transparency data)
- Remote job lists, startup jobs, tech jobs monitoring
- Recruiting research and talent market maps

### Input

| Field | What it is |
|---|---|
| `greenhouseBoards` | Greenhouse board tokens: the part after `boards.greenhouse.io/` or `job-boards.greenhouse.io/` (e.g. `stripe`, `airbnb`, `gitlab`) |
| `leverCompanies` | Lever slugs: the part after `jobs.lever.co/` (e.g. `spotify`, `palantir`) |
| `ashbyBoards` | Ashby board names: the part after `jobs.ashbyhq.com/` (e.g. `ramp`, `notion`, `linear`) |
| `workdaySites` | Full Workday careers URLs, e.g. `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite` (a `/en-US/` part is fine) |
| `smartRecruitersCompanies` | SmartRecruiters company IDs from `jobs.smartrecruiters.com/<id>` (case-sensitive, e.g. `Ubisoft2`, `BoschGroup`) |
| `workableAccounts` | Workable accounts from `apply.workable.com/<account>/` (e.g. `huggingface`) |
| `keywords` | Optional. Keep jobs whose title, department or team contains any keyword |
| `locations` | Optional. Keep jobs whose location contains any of these strings |
| `remoteOnly` | Optional. Only remote jobs |
| `includeDescription` | Default `true`. Set `false` for smaller output |
| `maxItems` | Optional cap on total jobs (0 = no limit) |

You can paste full board URLs too; the Actor extracts the slug.

```json
{
  "greenhouseBoards": ["stripe", "airbnb"],
  "leverCompanies": ["spotify"],
  "ashbyBoards": ["ramp", "notion"],
  "workdaySites": ["https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"],
  "smartRecruitersCompanies": ["Ubisoft2"],
  "workableAccounts": ["huggingface"],
  "keywords": ["engineer", "data"],
  "remoteOnly": false,
  "maxItems": 500
}
```

### Output (one normalized schema for all six ATS)

```json
{
  "source": "ashby",
  "company": "ramp",
  "companyBoard": "ramp",
  "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Security Engineer, Cloud",
  "locations": ["New York, NY (HQ)", "Remote (US)"],
  "remote": true,
  "department": "Engineering",
  "team": "Backend",
  "employmentType": "FullTime",
  "postedAt": "2026-04-07T17:12:35.753Z",
  "updatedAt": null,
  "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-.../application",
  "jobUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-...",
  "salaryMin": 211400,
  "salaryMax": 290600,
  "salaryCurrency": "USD",
  "salaryPeriod": "YEAR",
  "salaryText": "$211.4K – $290.6K • Offers Equity",
  "descriptionText": "...",
  "descriptionHtml": "..."
}
```

`source` is one of `greenhouse`, `lever`, `ashby`, `workday`, `smartrecruiters`, `workable`. Salary comes from the ATS's structured fields (Lever `salaryRange`, Ashby compensation) or is parsed from the published pay range in the description (Greenhouse, Workday, SmartRecruiters, Workable), including formats like `$120K–$150K`, `€71.000 – €85.000` and `120,000 USD - 189,750 USD`. Fields a company doesn't publish are `null`.

A run summary per board (found / kept / errors) is saved to the key-value store as `SUMMARY`.

### FAQ

**Is this legal / allowed?** It reads the official public job-board endpoints that Greenhouse, Lever and Ashby offer so companies can show jobs on their career pages: Greenhouse's Job Board API (public GET, no auth), Lever's Postings API, Ashby's public Job Posting API, SmartRecruiters' public Posting API and Workable's public job-board widget API. For Workday it reads only the public, unauthenticated job JSON that the company's careers page itself calls, two requests at a time with pauses. No login or proxies. It collects only published job postings, no candidate or personal data. You are responsible for how you use the data.

**How do I find a company's board name?** Open the company's careers page and look for links to `boards.greenhouse.io/<name>`, `jobs.lever.co/<name>`, `jobs.ashbyhq.com/<name>`, `<company>.wdN.myworkdayjobs.com/<site>`, `jobs.smartrecruiters.com/<id>` or `apply.workable.com/<account>`. Paste the URL; the Actor extracts what it needs.

**Big Workday sites?** Workday returns at most 2,000 jobs per search, so for larger sites the Actor automatically splits the search by a filter such as job category and merges the results without duplicates.

**Speed?** Workday and SmartRecruiters need one extra request per job for the description. Turn off `includeDescription` for a fast list-only run.

**What if a board name is wrong?** That company is logged as failed in `SUMMARY` (with a warning in the log) and the run continues with the others. If every board fails, or no board is given, the run fails with a clear message instead of silently returning nothing.

**Duplicates?** Jobs are deduplicated by source + board + job ID and by apply URL within a run.

**Rate limits?** The Actor requests one board at a time with a short pause and retries politely on errors.

**Cost?** Pay per result: $0.0015 per job saved ($1.50 per 1,000) plus $0.005 per run start at the default 1 GB memory. If you set a maximum cost for a run, the Actor fetches only as many jobs as that budget covers. Run with empty input and it does a 30-job demo.

### Ghost-job signals (listing age and reposts)

Every row has `listingAgeDays` (days since the job's own posted date). Turn on **Track listing history** to also get
`isRepost`, `repostOfJobIds`, `timesPosted`, `roleAgeDays` (how long the role has really been open, across reposts) and
`isNewSinceLastRun`. History lives in a named key-value store in *your* Apify account (`historyStoreName`). The first run
builds it, and signals appear from the second run on, so schedule it daily or weekly. A repost means the same title and first location
reappeared under a new job ID after an earlier ID of that role was taken down. Two openings listed at the same time are not counted as reposts.
With history on, each board is listed in full (needed for correct detection), and you are still charged only for the rows saved. No extra charge.
Note: SmartRecruiters refreshes its posted date often, so use `roleAgeDays` there rather than `listingAgeDays`.

# Actor input Schema

## `greenhouseBoards` (type: `array`):

Greenhouse board token: the part after boards.greenhouse.io/ or job-boards.greenhouse.io/ (e.g. stripe, airbnb, gitlab). Full URLs are accepted.

## `leverCompanies` (type: `array`):

Lever company slug: the part after jobs.lever.co/ (e.g. spotify, palantir). Full URLs are accepted; EU-hosted Lever boards are detected automatically.

## `ashbyBoards` (type: `array`):

Ashby board name: the part after jobs.ashbyhq.com/ (e.g. ramp, notion, linear). Full URLs are accepted.

## `workdaySites` (type: `array`):

Full Workday careers URL, e.g. https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite (tenant, wdN data center and site name are read from it; /en-US/ is fine). Uses the same public JSON the careers page loads, at low concurrency.

## `smartRecruitersCompanies` (type: `array`):

Company identifier from jobs.smartrecruiters.com/<id> (case-sensitive, e.g. Ubisoft2, BoschGroup, ServiceNow). Full URLs are accepted. Uses the official public Posting API.

## `workableAccounts` (type: `array`):

Account from apply.workable.com/<account>/ (e.g. huggingface). Full URLs are accepted. Uses Workable's public job-board widget API.

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

Keep jobs whose title, department or team contains ANY of these (case-insensitive, e.g. engineer, data). Leave empty for all jobs.

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

Keep jobs whose location contains ANY of these (case-insensitive, e.g. London, Remote, United States). Leave empty for all locations.

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

Keep only jobs the ATS marks as remote (or whose location says Remote).

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

Include the full job description as HTML and plain text. Turn off for a much smaller dataset.

## `maxItems` (type: `integer`):

Stop after this many jobs in total, shared fairly across all boards. 0 = no limit. Prefilled with 100 for a cheap first run; set 0 to get every job.

## `trackHistory` (type: `boolean`):

Remember every job seen per board in a named key-value store in your account, then flag reposts (same title + location re-listed under a new job ID after the old one disappeared), new-since-last-run jobs, times posted and how long the role has really been open. The first run builds the history; signals appear from the second run on. No extra charge.

## `historyStoreName` (type: `string`):

Named key-value store that keeps the history between runs. Use a different name per project or schedule. Lowercase letters, digits and hyphens.

## Actor input object example

```json
{
  "greenhouseBoards": [
    "stripe"
  ],
  "leverCompanies": [
    "spotify"
  ],
  "ashbyBoards": [
    "ramp"
  ],
  "remoteOnly": false,
  "includeDescription": true,
  "maxItems": 100,
  "trackHistory": false,
  "historyStoreName": "ats-jobs-history"
}
```

# Actor output Schema

## `jobs` (type: `string`):

All job postings (one item per job, normalized across Greenhouse, Lever and Ashby).

## `summary` (type: `string`):

Per-board results: found, kept, and any errors.

# 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 = {
    "greenhouseBoards": [
        "stripe"
    ],
    "leverCompanies": [
        "spotify"
    ],
    "ashbyBoards": [
        "ramp"
    ],
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("contented_catwalk/greenhouse-lever-ashby-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 = {
    "greenhouseBoards": ["stripe"],
    "leverCompanies": ["spotify"],
    "ashbyBoards": ["ramp"],
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("contented_catwalk/greenhouse-lever-ashby-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 '{
  "greenhouseBoards": [
    "stripe"
  ],
  "leverCompanies": [
    "spotify"
  ],
  "ashbyBoards": [
    "ramp"
  ],
  "maxItems": 100
}' |
apify call contented_catwalk/greenhouse-lever-ashby-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,contented_catwalk/greenhouse-lever-ashby-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/EkwBdxdvLrl7Ja1FB/builds/pSsyYIte6yyLUgOd4/openapi.json
