# Greenhouse & Lever Jobs Scraper: Ashby, Workable, Personio API (`jordydev/ats-job-feed`) Actor

Scrape every open job from any company on Greenhouse, Lever, Ashby, Workable, Recruitee and Personio via their public job APIs. One clean schema with salary, remote type, location and apply URL. Auto-detects the ATS, filters by keyword and country, and alerts you to new jobs only. $2 per 1,000 jobs.

- **URL**: https://apify.com/jordydev/ats-job-feed.md
- **Developed by:** [Jordy](https://apify.com/jordydev) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 jobs

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 Jobs Scraper: Ashby, Workable, Recruitee & Personio Jobs API

**Get every open job from any list of companies on Greenhouse, Lever, Ashby, Workable, Recruitee and Personio, in one clean format, for $2 per 1,000 jobs.**

Paste company names, board URLs or `ats:slug` codes and get all their open positions straight from the employers' own applicant-tracking systems (ATS), read through the public job-posting APIs those vendors provide for exactly this. No HTML scraping, no logins, no proxies. Filter on keywords, location, country, remote and posting date, and switch on **new-jobs-only mode** for a daily feed of just the jobs that appeared since the last run.

### What can it do?

- ✅ **6 ATS platforms, one schema**: Greenhouse, Lever (global and EU), Ashby, Workable, Recruitee and Personio return identical fields.
- ✅ **Auto-detects the ATS** from a company name or careers-page URL, so you don't need to know the slug.
- ✅ **Salary when published**: Greenhouse pay ranges, Ashby compensation, Lever salary range, Personio/Recruitee fields. Never guessed.
- ✅ **Filters**: keywords and excluded keywords (title or description), locations, countries (ISO codes or `REMOTE`), remote/hybrid, posted within N days, employment type.
- ✅ **New-jobs-only mode**: remembers which jobs you already had per feed name, so a scheduled run returns only new postings.
- ✅ **Exports**: JSON, CSV, Excel, HTML from the Output tab, plus `jobs.csv` and a `RUN_SUMMARY` per run. Works with schedules, webhooks, Zapier, Make, Slack and Google Sheets.
- ✅ **Privacy built in**: e-mail addresses and phone numbers in job descriptions are removed.

### Use cases

- **Recruiters and agencies**: watch a shortlist of target companies and get new roles the morning they are posted.
- **Job boards and newsletters**: backfill listings for niche boards (remote, data, Europe, startups) with clean apply URLs.
- **Sales and lead generation**: use hiring as a buying signal; a company opening five data roles is likely buying data tools.
- **Market and salary research**: compare roles, locations and published pay ranges across competitors.
- **Job seekers**: one alert for every company you'd like to work for.

### How to use it

1. Click **Try for free** and paste your companies into **Companies**, one per line: `greenhouse:airbnb`, `https://jobs.lever.co/spotify` or just `Hugging Face`.
2. Optional: add keywords, countries or remote filters, and switch on **Only new jobs since last run** with a feed name.
3. Click **Start**. Jobs appear in the **Output** tab; download them or use `jobs.csv` from the run's storage.
4. For a daily job alert, save the input as a **task** and add a **schedule**. Connect Slack, Google Sheets, Zapier, Make or a webhook under **Integrations**.

### Example input

```json
{
  "companies": ["greenhouse:airbnb", "lever:spotify", "https://jobs.ashbyhq.com/ramp", "personio:personio", "Hugging Face"],
  "keywords": ["engineer", "data"],
  "countries": ["NL", "DE", "REMOTE"],
  "postedWithinDays": 30,
  "maxJobsPerCompany": 50,
  "onlyNewJobs": true,
  "stateName": "eu-engineering"
}
```

`companies` accepts four forms: `ats:slug` (fastest: `greenhouse:airbnb`, `lever:spotify`, `ashby:ramp`, `workable:huggingface`, `recruitee:bunq`, `personio:personio`), a job-board URL, a careers-page URL (scanned for an embedded board) or a plain company name.

#### Call it from the API

```bash
curl -X POST "https://api.apify.com/v2/acts/jordydev~ats-job-feed/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies":["greenhouse:airbnb","lever:spotify"],"keywords":["data"],"maxJobsPerCompany":20}'
```

### Example output

Real rows from test runs on 27-28 Oct 2026:

| company | ats | title | location | countryCode | remoteType | salary | postedAt |
|---|---|---|---|---|---|---|---|
| Airbnb | greenhouse | Associate Principal, Procurement Innovation Supplier Programs | United States | US | remote | 102,000–120,000 USD | 2026-09-24 |
| Airbnb | greenhouse | Business Development Manager | Berlin, Germany | DE | hybrid | – | 2026-06-10 |
| Personio SE & Co. KG | personio | Staff Software Engineer, Data Platform | Munich; Berlin | DE | – | – | 2024-11-13 |

One row as JSON (description shortened):

```json
{
  "jobId": "personio:personio:1834171",
  "ats": "personio",
  "company": "Personio SE & Co. KG",
  "title": "Staff Software Engineer, Data Platform",
  "department": "Product and Tech",
  "team": "Engineering",
  "location": "Munich; Berlin",
  "city": "Munich",
  "countryCode": "DE",
  "isRemote": false,
  "employmentType": "full-time",
  "seniority": "principal-staff",
  "postedAt": "2024-11-13T14:10:41.000Z",
  "jobUrl": "https://personio.jobs.personio.de/job/1834171",
  "descriptionText": "The Role: …",
  "isNew": true
}
```

Every row has the same fields for every ATS: `jobId` (stable, `ats:slug:id`), `ats`, `company`, `title`, `department`, `team`, `location`, `city`, `region`, `countryCode`, `isRemote`, `remoteType`, `employmentType`, `seniority`, `salaryMin/Max/Currency/Period/Text/Source`, `postedAt`, `updatedAt`, `applyUrl`, `jobUrl`, `descriptionText` or `descriptionHtml`, `isNew`, `firstSeenAt`, `detectedVia`, plus a run envelope (`tool`, `status`, `errorCode`, `checkedAt`).

### How much does it cost?

Pay per event: you only pay for results, not for Apify compute.

| Event | Price | When it's charged |
|---|---|---|
| Job | **$0.002** ($2 per 1,000 jobs) | One job row in your dataset |
| Company checked (new-jobs mode) | **$0.0005** | New-jobs-only mode only: one company board checked, also when it has no new jobs |
| Actor start | $0.00005 | Apify's standard fee per run start (per GB of memory) |

**Free**: companies whose board isn't found, unsupported sources and fetch errors.

Examples:

- 50 companies × ~40 jobs = 2,000 jobs ≈ **$4.00** for the first full run.
- Daily alert over the same 50 companies with ~20 new jobs ≈ 50 × $0.0005 + 20 × $0.002 = **$0.065 a day**.

You can set a maximum cost per run; the Actor checks the budget before every row and stops cleanly at the cap. Apify's free plan includes monthly credit, so you can try it at no cost.

### Supported sources

| ATS | Endpoint used | Basis for reuse |
|---|---|---|
| Greenhouse | `boards-api.greenhouse.io/v1/boards/{slug}/jobs` | Public Job Board API, documented, no auth ([docs](https://developers.greenhouse.io/job-board.html)) |
| Lever | `api.lever.co/v0/postings/{slug}` (and EU host) | Postings API; published postings are public and may be used by third parties ([docs](https://github.com/lever/postings-api)) |
| Ashby | `api.ashbyhq.com/posting-api/job-board/{slug}` | Public Job Posting API ([docs](https://developers.ashbyhq.com/docs/public-job-posting-api)) |
| Workable | `apply.workable.com/api/v1/widget/accounts/{slug}` | Public endpoint behind Workable's own jobs widget ([help](https://help.workable.com/hc/en-us/articles/115012771647)) |
| Recruitee | `{slug}.recruitee.com/api/offers/` | Careers Site API ([docs](https://docs.recruitee.com/reference/offers)). From 10 Feb 2027 Recruitee requires a per-company token (see FAQ) |
| Personio | `{slug}.jobs.personio.de/xml` | Public XML feed for careers-site integration ([docs](https://developer.personio.de/reference/get_xml)) |
| SmartRecruiters | – | Not supported: its API policy forbids large-scale extraction. Returns a free `SOURCE_NOT_SUPPORTED` row |
| LinkedIn, Indeed | – | Not supported: their terms forbid automated extraction |

Need Workday or Teamtailor employers, or no company list at all? Use [EU Jobs Search](https://apify.com/jordydev/eu-job-search).

### FAQ

**Is this scraping? Is it legal?**
No HTML is scraped. Every job comes from the public job-posting API or feed the ATS vendor provides for publishing vacancies to third-party sites. Employers publish these postings for wide distribution, but check the terms of each ATS and employer for your own use case. Terms were checked on 27 Oct 2026; this is a technical summary, not legal advice.

**Is personal data collected?**
No applicant data is ever read. Recruiter e-mail addresses and phone numbers inside descriptions are removed (`[email removed]`, `[phone removed]`).

**How do I find a company's ATS slug?**
You don't have to: paste the careers-page URL or company name and leave **Auto-detect ATS** on. The slug is the last part of the board URL, e.g. `jobs.lever.co/`**`spotify`**.

**Why does a company return `BOARD_NOT_FOUND`?**
It probably uses an ATS that isn't supported (Workday, SmartRecruiters, iCIMS…) or a different slug. That row is free.

**How does new-jobs-only mode work?**
The Actor stores the job IDs it returned per `stateName` in a named key-value store in your account. The first run returns everything (baseline); later runs return only jobs not seen before. Use one `stateName` per feed.

**Is it polite to the job boards?**
Yes. Usually one request per board; requests to the same API host are spaced by `requestDelayMs` (default 500 ms) whatever the concurrency, with back-off on 429/5xx and `Retry-After`.

**What changes for Recruitee in 2027?**
From 10 Feb 2027 Recruitee requires an `X-Careers-Sites-Token` issued to each company. The `recruiteeToken` input covers a company reading its own board; other companies' Recruitee boards may then return a free error row.

### Related Actors

More tools by the same developer, all pay-per-event with the same clean output style:

- [EU Jobs Search (Workday, Greenhouse, Lever & Teamtailor)](https://apify.com/jordydev/eu-job-search): search live jobs from 6,500+ European employers' career sites by keyword and country, no company list needed.
- [Bulk PageSpeed Insights & Core Web Vitals Checker](https://apify.com/jordydev/pagespeed-bulk-audit): Lighthouse scores, LCP/CLS/INP and top fixes for hundreds of URLs, with CSV and PDF report.
- [Website Screenshot & HTML to PDF](https://apify.com/jordydev/screenshot-pdf): full-page PNG/JPEG screenshots and PDFs of any URL or raw HTML, cookie banners removed.
- [Broken Link Checker & SEO Site Audit (Site QA Crawler)](https://apify.com/jordydev/site-qa-crawler): broken links, redirects, SEO basics, WCAG accessibility and SSL expiry in one crawl, with a fix list and PDF.

***

Related searches: Greenhouse jobs scraper, Greenhouse jobs API, Lever jobs scraper, Lever postings API, Ashby job board API, Workable jobs scraper, Recruitee jobs, Personio XML feed, ATS jobs API, career site jobs, company job monitor, new job alerts, hiring signals, job board backfill.

# Actor input Schema

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

One per line. Use "ats:slug" (fastest, e.g. greenhouse:airbnb, lever:spotify, ashby:ramp, workable:huggingface, recruitee:bunq, personio:tado), a job-board URL (https://jobs.lever.co/spotify), a careers-page URL that embeds one of these boards, or just a company name ("Hugging Face") to auto-detect.

## `autoDetectAts` (type: `boolean`):

For company names and careers-page URLs, find the board by probing each supported ATS with name-based slugs, or by scanning the careers page for a board link. Off = only "ats:slug" and board URLs are accepted.

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

Keep jobs whose title contains at least one of these words or phrases (whole words, case- and accent-insensitive). Empty = all jobs.

## `excludeKeywords` (type: `array`):

Drop jobs whose title contains any of these, e.g. "intern", "senior".

## `searchInDescription` (type: `boolean`):

Search the full job description as well as the title.

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

Keep jobs whose location text contains one of these, e.g. "Amsterdam", "Berlin", "remote". Empty = anywhere.

## `countries` (type: `array`):

Keep jobs in these countries, as ISO 3166 alpha-2 codes (NL, DE, US…). Add REMOTE to also keep fully remote jobs without a country. The country is read from the ATS or guessed from the location text.

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

Keep only jobs marked remote (by the ATS workplace field or a "Remote" location).

## `includeHybrid` (type: `boolean`):

With "Remote jobs only", also keep hybrid jobs.

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

Keep jobs published (or, if the ATS has no publish date, updated) in the last N days. 0 = no limit.

## `employmentTypes` (type: `array`):

Keep only these types. Empty = all (including jobs with an unknown type).

## `onlyNewJobs` (type: `boolean`):

Remember which jobs were already returned (in a named key-value store) and return only new ones. The first run returns everything and sets the baseline. Schedule the Actor daily for a new-jobs feed.

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

Same as "Only new jobs" (kept for compatibility with the spec name). If either is on, new-jobs mode is on.

## `stateName` (type: `string`):

Separate memory per feed. Use a different name for each saved task with different companies or filters.

## `descriptionFormat` (type: `string`):

Recruiter e-mail addresses and phone numbers are removed in all formats.

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

0 = no limit.

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

Stop after this many jobs (a cost cap).

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

Requests to the same ATS host are always spaced out, whatever this is.

## `requestDelayMs` (type: `integer`):

Politeness: minimum gap between two requests to the same API host.

## `personioLanguage` (type: `string`):

Language code for Personio XML feeds (en, de, …). Empty = the board's default.

## `recruiteeToken` (type: `string`):

Only for a single company's own Recruitee board. Recruitee announced that its Careers Site API will need a token from 10 Feb 2027; until then it is not needed.

## Actor input object example

```json
{
  "companies": [
    "greenhouse:airbnb",
    "https://jobs.ashbyhq.com/ramp",
    "Hugging Face"
  ],
  "autoDetectAts": true,
  "keywords": [],
  "excludeKeywords": [],
  "searchInDescription": false,
  "locations": [],
  "countries": [],
  "remoteOnly": false,
  "includeHybrid": false,
  "postedWithinDays": 0,
  "employmentTypes": [],
  "onlyNewJobs": false,
  "onlyNewSinceLastRun": false,
  "stateName": "default",
  "descriptionFormat": "text",
  "maxJobsPerCompany": 0,
  "maxJobs": 5000,
  "maxConcurrency": 4,
  "requestDelayMs": 500,
  "personioLanguage": "en"
}
```

# Actor output Schema

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

No description

## `csv` (type: `string`):

No description

## `summary` (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:airbnb",
        "https://jobs.ashbyhq.com/ramp",
        "Hugging Face"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("jordydev/ats-job-feed").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",
        "https://jobs.ashbyhq.com/ramp",
        "Hugging Face",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("jordydev/ats-job-feed").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",
    "https://jobs.ashbyhq.com/ramp",
    "Hugging Face"
  ]
}' |
apify call jordydev/ats-job-feed --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jordydev/ats-job-feed"
        }
    }
}
```

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/ASYjZM19dhYmhVK6n/builds/6cxSeT1HAUp3vwZVa/openapi.json
