# ATS Job Feed - Greenhouse, Lever, Ashby, Workable & more (`clearpipe/ats-job-feed`) Actor

Collect public jobs from Greenhouse, Lever, Ashby, Workable, SmartRecruiters and Recruitee. Resolve supported boards from URLs, domains or names. Normalized fields, available salaries and descriptions, filters and new-jobs-only mode.

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

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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 Job Feed: Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee

Collect open jobs from companies using the six supported applicant tracking systems (ATS) listed below. Paste board URLs, careers pages, websites or plain company names. When the actor resolves a supported board, it reads the public feed and returns normalized jobs with available descriptions and published salaries, subject to your filters and limits.

**One actor instead of six.** Use the same output fields across the supported platforms. Companies that can't be resolved get an error row with a reason and no job-result event fee. Platform usage charges can still apply. Spending limits, interrupted runs and source or storage failures can leave output incomplete; check the run status and error rows.

### What you can do with it

- **Job boards and aggregators.** Keep a niche board filled with fresh jobs from a list of target companies.
- **Job alerts.** Schedule it daily with **Only jobs not seen in earlier runs** and use an Apify integration or the API to send the new postings to Slack, email or a sheet.
- **Recruiting and sales signals.** Hiring is a buying signal. Track which companies are hiring for which teams, where, and how fast.
- **Market and salary research.** Compare roles, locations, remote share and posted pay ranges across companies.
- **Your own job search.** Watch the 50 companies you would love to work at and get only what is new.

### Supported ATS platforms

| ATS | Example input | Notes |
|---|---|---|
| Greenhouse | `https://boards.greenhouse.io/airbnb`, embedded boards | departments, offices, workplace type |
| Lever | `jobs.lever.co/spotify` | team, commitment, all locations, salary range when posted (US and EU instances) |
| Ashby | `https://jobs.ashbyhq.com/openai` | salary ranges and remote flag when posted |
| Workable | `apply.workable.com/huggingface` | function, remote flag |
| SmartRecruiters | `https://jobs.smartrecruiters.com/ServiceNow` | experience level; descriptions fetched per job |
| Recruitee | `bunq.recruitee.com` | remote / hybrid / on-site, salary when posted |

### Input

**Companies**, one per line, any mix of:

| You paste | What happens |
|---|---|
| A job board URL | used directly (most reliable) |
| A careers page URL, e.g. `https://example.com/careers` | the page is scanned for a supported board link or embed |
| A website, e.g. `notion.so` | the site and its `/careers` and `/jobs` pages are scanned, then common board names are tried |
| A company name, e.g. `Stripe` | common board names are tried on every supported ATS; the board with the most jobs is selected; similar names can resolve to the wrong company, so check `company` and `boardSlug`, or supply the exact board URL |

Filters (all optional): **Title contains any of**, **Title excludes**, **Location contains any of**, **Remote jobs only**, **Posted within (days)**, **Max jobs per company**.

Example input:

```json
{
  "companies": ["https://boards.greenhouse.io/airbnb", "jobs.lever.co/spotify", "notion.so", "Ramp"],
  "titleKeywords": ["engineer"],
  "locations": ["United States", "remote"],
  "postedWithinDays": 30,
  "onlyNewJobs": false
}
```

### Output

One row per saved job. Illustrative example with shortened values:

```json
{
  "status": "ok",
  "company": "Ramp",
  "ats": "ashby",
  "boardSlug": "ramp",
  "jobId": "4a1c...",
  "title": "Security Engineer, Cloud",
  "department": "Engineering",
  "team": "Security",
  "location": "New York, NY (HQ)",
  "locations": ["New York, NY (HQ)"],
  "country": "United States",
  "isRemote": true,
  "workplaceType": "remote",
  "employmentType": "FullTime",
  "postedAt": "2026-04-07T17:12:35.753000+00:00",
  "url": "https://jobs.ashbyhq.com/ramp/...",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/.../application",
  "salaryMin": 211400,
  "salaryMax": 290600,
  "salaryCurrency": "USD",
  "salaryInterval": "1 YEAR",
  "salarySummary": "$211.4K – $290.6K • Offers Equity",
  "descriptionText": "About Ramp ...",
  "scrapedAt": "2026-10-10T23:40:00+00:00"
}
```

Fields an ATS does not publish can be `null` or absent. Salary fields are filled only when the company posts pay on its board (common on Ashby and Lever, required by law in several US states).

A company that can't be resolved returns one error row without a job-result event fee:

```json
{ "input": "Some Company", "status": "error", "errorCode": "ATS_NOT_DETECTED", "error": "No Greenhouse, Lever, Ashby, ... board found. Paste the careers page or job board URL for best results." }
```

| errorCode | Meaning |
|---|---|
| `ATS_NOT_DETECTED` | No supported board found from that input. Paste the careers page or board URL instead. |
| `NOT_FOUND` | The board URL points to a board that doesn't exist (wrong slug, or the company left that ATS). |
| `UPSTREAM_ERROR` / `NETWORK` | The ATS returned an error or timed out on all 3 attempts. Try again later. |

### Scheduled feeds (only new jobs)

Turn on **Only jobs not seen in earlier runs** and schedule the actor (Apify Console, Schedules). Successful sequential runs skip job IDs already delivered for the same input company list. Changing the input company list starts a fresh history. A run with no new matching jobs can succeed with an empty dataset. Skipped jobs incur no job-result event fee; platform usage can still apply.

Avoid overlapping runs of the same feed: concurrent runs do not share a lock. Storage failures or interruptions can also affect history persistence. This is not an exactly-once delivery guarantee.

History identifies each job by **ATS platform + board slug + job ID**. Editing a previously delivered job's title or location does not make it new. A repost with a new ID, or the same role on another board, can be delivered again. Closed jobs disappear from the upstream feed; this actor does not emit closure records. To reconcile closures, compare full snapshots with `onlyNewJobs` disabled, and account for incomplete runs before removing jobs.

**Dates:** `postedAt` comes from the ATS publication or creation date, not the actor's first-seen time. A missing date remains `null`. The **Posted within (days)** filter keeps jobs with missing or unparseable dates; check `postedAt` if you require confirmed recent postings.

**Spending limits:** reaching the event spending limit stops further job delivery. The run can still succeed; its final status warns that output may be incomplete. A successful run status alone does not establish that every company or job was collected.

### Pricing

**Free during launch.** Apify platform usage still applies. The implemented paid event is one saved successful job row; error rows and skipped jobs do not trigger that event. Published pricing determines whether event fees apply. Check the Actor's current pricing before running it.

### How it works

The actor reads public job feeds from supported ATS providers without rendering pages in a browser. Direct board URLs reduce dependence on careers-page layouts; automatic discovery and upstream feeds can still change or fail. Only publicly posted jobs are returned.

### FAQ

**A company I need isn't found by name.** Paste its careers page URL. If its jobs are on Workday, iCIMS, Taleo or another ATS not in the table above, they aren't supported yet. Tell us in the Issues tab which one you need.

**How fresh is the data?** It is read live from the ATS at run time.

**What happens when an ATS rate-limits requests?** Feed requests retry HTTP 429, server errors and network failures up to three attempts with fixed increasing delays. There is no unlimited retry or `Retry-After` handling. An exhausted request returns an error row containing the final error, not a per-attempt history. A failed SmartRecruiters description request is logged and can leave the saved job without its description.

**Can I use a large company list?** Companies are processed in parallel within the concurrency setting. Large lists may need longer runs; check the run status and output because spending limits, source failures and interruptions can leave a list incomplete.

### Support

Open an issue in the Issues tab with the input you used and the run link.

# Actor input Schema

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

One per line. Any of: a job board URL (boards.greenhouse.io/airbnb, jobs.lever.co/spotify, jobs.ashbyhq.com/openai, apply.workable.com/..., jobs.smartrecruiters.com/..., name.recruitee.com), a careers page URL, a website (notion.so) or a company name (Stripe). Board and careers URLs are the most reliable.

## `titleKeywords` (type: `array`):

Keep jobs whose title contains at least one of these words (case-insensitive), e.g. engineer, designer. Empty keeps all.

## `excludeTitleKeywords` (type: `array`):

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

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

Keep jobs whose location or country contains one of these, e.g. London, United States, US. Add remote to also keep remote jobs.

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

Keep only jobs the ATS marks as remote.

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

Keep only jobs first published in the last N days. Empty keeps all.

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

For scheduled runs: return only jobs that did not appear in a previous run with the same company list. You are not charged for jobs already seen.

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

Adds the full description as clean plain text.

## `includeDescriptionHtml` (type: `boolean`):

Also adds the original description HTML.

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

Upper limit of jobs returned per company, after filters.

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

How many companies to process at once. The default suits most runs.

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/airbnb",
    "jobs.lever.co/spotify",
    "notion.so",
    "Ramp"
  ],
  "remoteOnly": false,
  "onlyNewJobs": false,
  "includeDescription": true,
  "includeDescriptionHtml": false,
  "maxJobsPerCompany": 1000,
  "maxConcurrency": 5
}
```

# Actor output Schema

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

One row per open job, plus one error row per company that could not be resolved.

## `jobsFull` (type: `string`):

Every field, including full description text.

# 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": [
        "https://boards.greenhouse.io/airbnb",
        "jobs.lever.co/spotify",
        "notion.so",
        "Ramp"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("clearpipe/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": [
        "https://boards.greenhouse.io/airbnb",
        "jobs.lever.co/spotify",
        "notion.so",
        "Ramp",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("clearpipe/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": [
    "https://boards.greenhouse.io/airbnb",
    "jobs.lever.co/spotify",
    "notion.so",
    "Ramp"
  ]
}' |
apify call clearpipe/ats-job-feed --silent --output-dataset

```

## MCP server setup

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