# ATS Jobs Feed: Greenhouse, Lever, Ashby, Workable & more (`psgameplay121/ats-jobs-feed`) Actor

Every open job from company career sites on 6 ATS in one clean schema, with salary, seniority, job function, remote/hybrid, country, tech stack and visa fields. Auto-detects the ATS from a company name. Incremental mode bills only new or changed jobs.

- **URL**: https://apify.com/psgameplay121/ats-jobs-feed.md
- **Developed by:** [Vivek Sinha](https://apify.com/psgameplay121) (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

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

Get **every open job straight from company career sites** on six applicant-tracking systems, in **one clean schema**, with the fields recruiters, sales teams and job boards actually need already filled in: **salary, seniority, job function, remote/hybrid/onsite, country, tech stack, years of experience and visa sponsorship**.

Give it a list of companies. You can paste career-site URLs, or just company names, and the Actor finds the right job board for you.

### Why this Actor

- **6 ATS platforms, 1 schema.** Greenhouse, Lever, Ashby, Workable, SmartRecruiters and Recruitee all come back with the same field names, so you never write per-ATS glue code.
- **Auto-detection.** Type `ramp`, `notion.so` or `figma` and the Actor checks all six ATS and uses the board it finds.
- **Enriched, decision-ready rows.** Every job gets a normalized `jobFunction`, `seniority`, `workplaceType`, `employmentType`, `countryCode`, a `salaryMin`/`salaryMax`/`salaryCurrency`/`salaryPeriod` (structured where the ATS publishes it, otherwise parsed from pay-transparency text), `techKeywords`, `yearsExperienceMin` and `visaSponsorship`.
- **Monitoring mode that saves you money.** Turn on **Incremental mode** and schedule the Actor daily: it remembers what it saw before and outputs **only new or changed jobs**, so you only pay for what's new. Optionally get a free `closed` row when a job disappears.
- **You only pay for jobs that match your filters.** Title, location, workplace type, job function, seniority and posting-age filters run *before* anything is charged.
- **Free hiring-signal summaries.** Every run saves a per-company summary (open jobs, new jobs in the last 7 and 30 days, mix by function, seniority and workplace type, top locations, salary-transparency share, median USD salary, top tech) to the key-value store as `COMPANY_SUMMARIES`, at no extra cost.
- **Fast and reliable.** It reads each ATS's public job-board endpoint, so there's no browser, no proxy and no captcha. A 13-company test run that returned 492 jobs (including Bosch Group with 5,000+ openings) finished in about a minute in local testing.
- **Privacy by default.** Email addresses in job text are redacted unless you switch that off.

### Who uses it

- **Sales and RevOps:** find companies hiring for the role your product replaces or supports ("hiring 5+ data engineers" = buying signal).
- **Recruiters and staffing agencies:** a daily feed of fresh roles at target accounts.
- **Job boards and niche aggregators:** back-fill listings with direct-from-employer jobs and real apply links.
- **Market and compensation research:** salary bands, remote share and tech-stack demand across companies.
- **AI agents and RAG:** clean plain-text descriptions with consistent metadata.

### Input

| Field | What it does |
|---|---|
| `companies` | Career-site URLs (`https://boards.greenhouse.io/stripe`, `https://jobs.lever.co/spotify`, `https://jobs.ashbyhq.com/ramp`, `https://apply.workable.com/huggingface`, `https://careers.smartrecruiters.com/Equinox`, `https://bunq.recruitee.com`), `ats:slug` (`lever:palantir`), or a bare company slug or domain (`ramp`, `notion.so`). |
| `titleKeywords` / `excludeTitleKeywords` | Keep or drop jobs by title words. |
| `locationKeywords` | e.g. `London`, `Germany`, `Remote`. |
| `workplaceTypes` | `remote`, `hybrid`, `onsite`. |
| `jobFunctions` | `engineering`, `data-ml`, `product`, `design`, `sales`, `marketing`, `customer`, `operations`, `finance`, `people-hr`, `legal-compliance`, `security`, `it`, `other`. |
| `seniorities` | `intern`, `junior`, `mid`, `senior`, `staff`, `principal`, `lead`, `manager`, `director`, `vp`, `c-level`. |
| `postedWithinDays` | Only jobs posted in the last N days. |
| `descriptionFormat` | `text` (default), `html`, `both` or `none`. |
| `incremental`, `stateKey`, `emitClosedJobs` | Monitoring mode (see above). Use one `stateKey` per watchlist. |
| `companySummariesToDataset` | Also add the per-company summary rows to the dataset. |
| `maxJobsPerCompany`, `maxJobs` | Hard caps. You can also set a maximum charge per run. |

Example:

```json
{
  "companies": ["https://boards.greenhouse.io/stripe", "lever:spotify", "ramp", "notion.so"],
  "jobFunctions": ["engineering", "data-ml"],
  "workplaceTypes": ["remote", "hybrid"],
  "postedWithinDays": 30,
  "incremental": true,
  "stateKey": "fintech-watchlist"
}
```

### Output (real example, description shortened)

```json
{
  "recordType": "job",
  "jobKey": "ashby:ramp:f564dcf9-9390-4a3f-896f-8047a5086040",
  "ats": "ashby",
  "companyName": "Ramp",
  "title": "Mobile Engineer, Android",
  "department": "Engineering",
  "team": "Mobile",
  "jobFunction": "engineering",
  "seniority": "mid",
  "employmentType": "full-time",
  "workplaceType": "hybrid",
  "location": "New York, NY (HQ)",
  "allLocations": ["New York, NY (HQ)", "Remote (Canada)", "San Francisco, CA", "Remote (US)"],
  "countryCode": "US",
  "salaryMin": 189000,
  "salaryMax": 330000,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salarySource": "structured",
  "compensationSummary": "$189K – $330K • Offers Equity",
  "techKeywords": ["Python", "TypeScript", "Kotlin", "SQL", "React", "Flask"],
  "postedAt": "2025-07-31T21:58:34.674Z",
  "daysSincePosted": 446,
  "jobUrl": "https://jobs.ashbyhq.com/ramp/f564dcf9-9390-4a3f-896f-8047a5086040",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/f564dcf9-9390-4a3f-896f-8047a5086040/application",
  "descriptionText": "ABOUT RAMP\n\nRamp is building the smart infrastructure for finance teams…",
  "descriptionWordCount": 728,
  "contentHash": "a322c596f9e2eee4",
  "changeType": null
}
```

The free `COMPANY_SUMMARIES` record for the same run reported, for Ramp: 169 open jobs, 52 posted in the last 30 days, 82% hybrid, 96% with a published salary, and a median USD salary midpoint of $221,550.

Export as JSON, CSV, Excel or XML, or connect via API, webhooks, Make, Zapier, n8n or MCP.

### Pricing

Pay per event, with no subscription:

- **$3.00 per 1,000 jobs** returned (after your filters; in incremental mode only new or changed jobs).
- **$5.00 per 1,000 company-summary rows**, only if you ask for them in the dataset. The key-value-store copy is free.
- Rows for jobs that closed (incremental mode) are free.

Set a maximum charge per run and the Actor stops cleanly when it is reached.

### FAQ

**Is this legal?** The Actor only reads the public job-board endpoints each ATS publishes so that job seekers and aggregators can list an employer's openings. It does not log in, bypass protections or collect candidate data. Job descriptions can still mention people, so email addresses are redacted by default. You are responsible for how you use the data.

**My company wasn't found.** Paste the full career-site URL. Auto-detect guesses the slug from the name and won't find boards with unusual slugs, or companies on ATS platforms not supported yet.

**Which ATS platforms are not supported yet?** Workday, iCIMS, Taleo, SuccessFactors and custom career sites.

**How accurate are seniority and function?** They are rule-based labels from the job title and department. They work well for common titles, but treat edge cases ("Manager" titles, mixed roles) as best-effort. Salary is only returned when the employer publishes it.

**How fresh is the data?** It is fetched live from the employer's board at run time, not from a cached database.

# Actor input Schema

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

One per line. Accepts a career-site URL (https://boards.greenhouse.io/stripe, https://jobs.lever.co/spotify, https://jobs.ashbyhq.com/ramp, https://apply.workable.com/huggingface, https://careers.smartrecruiters.com/Equinox, https://bunq.recruitee.com), an explicit 'ats:slug' (greenhouse:airbnb, lever:palantir), or just a company slug or domain (ramp, notion.so). Bare slugs are auto-detected across all 6 ATS.

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

Keep only jobs whose title contains at least one of these words (case-insensitive). Leave empty for all jobs.

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

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

## `locationKeywords` (type: `array`):

Keep jobs whose location contains any of these, e.g. London, Germany, Remote.

## `workplaceTypes` (type: `array`):

Keep only these workplace types (detected from ATS fields and text).

## `jobFunctions` (type: `array`):

Keep only these normalized job functions.

## `seniorities` (type: `array`):

Keep only these normalized seniority levels (detected from the title).

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

0 = any age.

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

How to return the job description. Plain text is best for search, AI and spreadsheets.

## `maxDescriptionChars` (type: `integer`):

Longer descriptions are cut to this length.

## `redactEmailAddresses` (type: `boolean`):

Replaces any email address found in job text with \[email redacted].

## `companySummariesToDataset` (type: `boolean`):

Summaries (open jobs, new jobs last 7/30 days, mix by function/seniority/remote, salary transparency, top tech) are always saved free of charge to the key-value store as COMPANY_SUMMARIES. Tick this to also get them as dataset rows (charged per summary).

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

Remembers jobs seen in earlier runs with the same state key, and outputs only jobs that are new or whose content changed. Ideal for scheduled daily runs: you are only charged for new/changed jobs.

## `stateKey` (type: `string`):

Use a different key per watchlist so runs don't share memory.

## `emitClosedJobs` (type: `boolean`):

In incremental mode, add a free row with changeType = closed for jobs that disappeared since the last run.

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

0 = no limit.

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

0 = no limit. You can also cap spend with the run's maximum charge setting.

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

How many companies to fetch at the same time.

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/stripe",
    "lever:spotify",
    "ramp"
  ],
  "postedWithinDays": 0,
  "descriptionFormat": "text",
  "maxDescriptionChars": 20000,
  "redactEmailAddresses": true,
  "companySummariesToDataset": false,
  "incremental": false,
  "stateKey": "default",
  "emitClosedJobs": false,
  "maxJobsPerCompany": 0,
  "maxJobs": 0,
  "maxConcurrency": 5
}
```

# Actor output Schema

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

All rows returned by this run.

## `runReport` (type: `string`):

Summary of the run: companies found, jobs returned 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 = {
    "companies": [
        "https://boards.greenhouse.io/stripe",
        "lever:spotify",
        "ramp"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("psgameplay121/ats-jobs-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/stripe",
        "lever:spotify",
        "ramp",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("psgameplay121/ats-jobs-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/stripe",
    "lever:spotify",
    "ramp"
  ]
}' |
apify call psgameplay121/ats-jobs-feed --silent --output-dataset

```

## MCP server setup

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