# ATS Jobs Scraper: Greenhouse, Lever, Ashby & More (`resonating_quoll/ats-jobs-scraper`) Actor

Search jobs across 12,000+ verified career boards (Greenhouse, Lever, Ashby, Workable, Recruitee, Personio) or your own list. Seniority, country, salary and remote filters, new and closed job alerts, hiring velocity per company. $1.50/1K jobs.

- **URL**: https://apify.com/resonating_quoll/ats-jobs-scraper.md
- **Developed by:** [Michael Best](https://apify.com/resonating_quoll) (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.05 / 1,000 job results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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: Greenhouse, Lever, Ashby & More

![ATS Jobs Scraper pipeline: 6 ATS APIs, one schema, level and country filters, new and closed jobs, hiring velocity](https://api.apify.com/v2/key-value-stores/WmxXg7tu4yTt456JW/records/ats-readme-v1.png)

**Search open jobs across 12,000+ verified company career boards at once, or pull every job from the companies you choose, from Greenhouse, Lever, Ashby, Recruitee, Personio and Workable. One clean dataset with seniority, country and remote type on every job, alerts for new *and closed* jobs, and hiring velocity per company.**

Type a keyword like `data engineer`, tick **Search every company**, and get matching jobs from ~10,300 company boards with open jobs, from OpenAI and Stripe to thousands of YC-style startups. Or paste your own list in any form you have it: company names (`Stripe`), board URLs (`https://jobs.lever.co/spotify`), careers pages (`https://www.notion.com/careers`), domains (`linear.app`) or slugs (`ramp`). The Actor finds each company's board for you.

It reads the official public job-board APIs these ATSs publish for exactly this purpose, so runs need no proxies, browser or login, and don't break when a careers page is redesigned.

**Price: $1.50 per 1,000 jobs.** You pay only for jobs in your results, and in monitoring mode only for *new* jobs.

### Why this jobs scraper?

| | |
|---|---|
| 🌍 **Search many companies at once** | A built-in directory of 12,000+ verified boards on all 6 ATSs (~10,300 with open jobs, 300,000+ jobs). Search them all by keyword, location, remote and date. Most job-board scrapers make you know the companies first; this one finds them. |
| 📋 **Paste companies in any form** | Names, board URLs, careers-page URLs, domains, slugs or `ats:slug`, one per line. Careers pages are scanned for an embedded board, and unknown slugs are auto-detected. |
| ⭐ **Preset lists** | One click adds verified lists: AI & ML, fintech, developer tools, top tech, climate, health. |
| 🧩 **6 ATSs, one schema** | Greenhouse, Lever, Ashby, Recruitee, Personio and Workable in one dataset with the same field names. |
| 🔔 **New and closed jobs (monitoring)** | Remembers what earlier runs returned and outputs only new postings, plus a free `closed` row when a job you saw before is taken down. Schedule it daily and you have a new-job alert feed, and you pay only for new jobs. |
| 📈 **Hiring velocity** | A `HIRING_VELOCITY` record ranks every company read in the run by open jobs and jobs posted in the last 7/30/90 days: a ready-made buying-signal and talent-intel list. |
| 🧭 **Seniority, country, job type, remote type** | Every job gets `seniority` (intern → executive), `country` (ISO code), `job_type` and `remote_type` (remote / hybrid / onsite), plus a stable `global_id`. Filter on any of them, or on a salary floor. |
| 📈 **Salary parsing** | Structured pay ranges from Ashby, Lever and Recruitee, plus min/max/currency parsed from pay-transparency text elsewhere. |
| 🎯 **Filters and de-duplication** | Keywords, exclude keywords, locations, remote only, posted since, max results, per-company caps; de-duplicated by URL and company + title + location. |

### What data do you get?

One normalized record per open job, with the same field names whatever the ATS:

`event` · `global_id` · `company` · `ats` · `board_slug` · `job_id` · `title` · `seniority` · `job_type` · `remote_type` · `country` · `countries` · `department` · `team` · `location` · `all_locations` · `remote` · `workplace_type` · `employment_type` · `salary_min` · `salary_max` · `salary_currency` · `salary_interval` · `salary_source` · `salary_yearly_max` · `posted_at` · `updated_at` · `job_url` · `apply_url` · `description_text` · `description_html` (optional) · `scraped_at`

In monitoring mode, jobs that disappeared from a board since the last run come as `event: "closed"` rows with `first_seen_at` and `closed_detected_at` (never charged).

Export it as JSON, CSV, Excel or HTML, or pull it through the Apify API.

### Use cases

- **Job seekers:** search every company for "staff data engineer, remote" or watch 20 dream companies and get only new openings every morning.
- **Job boards and newsletters:** build a niche board (remote, AI, fintech, climate) straight from company career pages, the original source rather than reposted aggregator listings.
- **Sales and lead generation:** hiring is a buying signal. Find every company hiring for "Salesforce admin" or "data platform" this week and feed them into your CRM, Clay or a sheet.
- **Recruiters and talent intelligence:** track competitors' hiring by department, location and seniority.
- **Market and salary research:** collect pay-transparency ranges across thousands of companies.
- **AI agents and RAG:** a clean, structured jobs feed for LLM apps.

### How to use it

**Your own list of companies**

1. Click **Try for free**. The input is prefilled with `Stripe`, `https://jobs.lever.co/spotify` and `ramp`, 5 jobs each, so your first run takes seconds and costs about 2 cents.
2. Replace them with your companies, one per line, in any form. Set **Max jobs per company** to `0` to get every open job.
3. Optionally add filters, then click **Start**.

**Search every company**

1. Enable **Search every company in the directory**.
2. Add keywords (e.g. `machine learning engineer`) and/or locations, **Remote only** or **Posted since**.
3. Set **Max results** (e.g. `500`) to cap cost and run time. A full sweep reads every board and takes about 5-10 minutes; with Max results it stops as soon as enough jobs are found.

#### What can I put in "Companies"?

| You type | What happens |
|---|---|
| `Stripe`, `Scale AI` | Looked up by name in the directory (any ATS). If missing, slug guesses are auto-detected. |
| `https://jobs.lever.co/spotify`, `job-boards.greenhouse.io/figma`, `jobs.ashbyhq.com/ramp`, `bunq.recruitee.com`, `acme.jobs.personio.de`, `apply.workable.com/acme` | Board read directly. |
| `https://www.notion.com/careers` | The page is opened and scanned for an embedded/linked ATS board. |
| `linear.app` | Directory lookup, then the site's /careers and /jobs pages are scanned. |
| `ramp` | Directory, else auto-detected on Greenhouse, Lever and Ashby, then Recruitee, Personio and Workable. |
| `lever:spotify` | Exactly that board. |

Through the API you can still pass the original object format `{"ats": "greenhouse", "slug": "stripe", "name": "Stripe"}`.

### Input example

```json
{
  "companies": ["Stripe", "https://jobs.lever.co/spotify", "notion.com", "lever:palantir"],
  "presetLists": ["ai-ml"],
  "keywords": ["engineer", "data"],
  "excludeKeywords": ["intern"],
  "locations": ["New York", "Remote"],
  "postedSince": "14 days",
  "onlyNew": true,
  "stateKey": "my-watchlist"
}
```

Search every company:

```json
{ "searchAllCompanies": true, "keywords": ["rust engineer"], "remoteOnly": true, "maxResults": 200 }
```

Senior remote Python roles open to US candidates, paying at least 150k a year:

```json
{ "searchAllCompanies": true, "keywords": ["python"], "seniority": ["senior", "staff"], "remoteTypes": ["remote"], "countries": ["US"], "minSalaryYearly": 150000, "maxResults": 100 }
```

| Field | Description |
|---|---|
| `companies` | One per line: names, board URLs, careers pages, domains, slugs, `ats:slug` (or `{ats, slug, name}` objects via API). |
| `presetLists` | `ai-ml`, `fintech`, `devtools`, `top-tech`, `climate`, `health`. |
| `searchAllCompanies` | Search the whole directory (~10,300 boards with jobs). |
| `directoryAts` | Limit the directory search to some of `greenhouse`, `lever`, `ashby`, `recruitee`, `personio`, `workable`. |
| `keywords` / `excludeKeywords` | Keep / drop jobs whose title contains any of these (case-insensitive). |
| `searchDescription` | Also match keywords in the description. |
| `locations` | Keep jobs whose location contains any of these strings. |
| `remoteOnly` | Keep only remote / remote-eligible jobs. |
| `postedSince` | `YYYY-MM-DD` or relative (`7 days`, `2 weeks`, `1 month`). |
| `seniority` | Keep these levels: `intern`, `junior`, `mid`, `senior`, `staff`, `principal`, `lead`, `manager`, `head`, `director`, `vp`, `executive`. |
| `jobTypes` | `full_time`, `part_time`, `contract`, `internship`, `temporary`. |
| `remoteTypes` | `remote`, `hybrid`, `onsite`. |
| `countries` / `countriesIncludeRemoteUnknown` | ISO codes or names (`US`, `Germany`); optionally keep remote jobs with no stated country. |
| `requireSalary` / `minSalaryYearly` | Only jobs with a salary / with a yearly maximum of at least this much (hourly and monthly pay annualized). |
| `onlyNew` / `stateKey` | Output only jobs not returned by previous runs with the same state name. |
| `reportClosedJobs` | With `onlyNew`, also output a free `closed` row for jobs that disappeared (default `true`). |
| `includeDescriptionHtml` | Add the original HTML description (default `false`). |
| `velocityDays` | Window for `new_jobs_{N}d` in `HIRING_VELOCITY` (default 7). |
| `includeDescription` | Include plain-text description (default `true`). |
| `dedupeByTitle` | Merge same company + title across locations. |
| `maxJobsPerCompany` | Cap per company (0 = no limit). |
| `maxResults` | Cap for the whole run (0 = no limit). |
| `maxConcurrency` | Your listed boards fetched in parallel (default 3, max 10). |

### Output example

```json
{
  "company": "Ramp",
  "event": "open",
  "global_id": "ats_143fa3766650b2e1",
  "ats": "ashby",
  "board_slug": "ramp",
  "job_id": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Security Engineer, Cloud",
  "seniority": null,
  "job_type": "full_time",
  "remote_type": "hybrid",
  "country": "US",
  "countries": ["US", "CA"],
  "department": "Engineering",
  "team": "Backend",
  "location": "New York, NY (HQ)",
  "all_locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)", "Miami, FL"],
  "remote": true,
  "workplace_type": "Hybrid",
  "employment_type": "FullTime",
  "salary_min": 211400,
  "salary_max": 290600,
  "salary_currency": "USD",
  "salary_interval": "year",
  "salary_source": "ats",
  "posted_at": "2026-04-07T17:12:35.753000Z",
  "updated_at": null,
  "job_url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "apply_url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
  "description_text": "ABOUT RAMP\n\nRamp is building the smart infrastructure for finance teams...",
  "scraped_at": "2026-10-26T08:10:40Z"
}
```

`RUN_SUMMARY` in the run's key-value store lists, per company, how many jobs were fetched, matched, de-duplicated, already seen and output, plus any error and inputs that couldn't be understood.

`HIRING_VELOCITY` (same store) looks like:

```json
{ "window_days": 7, "companies": [
  { "company": "Stripe", "ats": "greenhouse", "open_jobs": 712, "new_jobs_7d": 45, "new_jobs_30d": 204, "new_jobs_90d": 532, "share_new_30d": 0.287, "newest_posting": "2026-10-26T20:53:41Z" }
] }
```

#### Field notes

- `seniority` comes from the title first (what the employer wrote), then the ATS's own level field. `null` when neither says.
- `country` is the first ISO code found in the ATS's structured location, then the location text (cities, US states, `Austin, TX`, `Toronto, ON`).
- `global_id` is stable across runs and Actors for the same ATS job.
- `remote` is `true` when the ATS marks the job remote / remote-eligible, or a location mentions "Remote".
- `salary_*` is `null` when the company publishes no range. `salary_source` is `ats` (structured field) or `description` (parsed from text).
- `posted_at` is what the ATS reports (Greenhouse `first_published`, Lever `createdAt`, Ashby `publishedAt`, Recruitee `published_at`, Personio `createdAt`, Workable `published_on`), in UTC.

### Pricing

**$1.50 per 1,000 jobs** ($0.0015 per job in your dataset), pay per event. No start fee, and nothing to pay for boards with zero matching jobs, so a directory-wide search that finds 40 jobs costs 6 cents.

| Example | Jobs | Cost |
|---|---|---|
| First test run (3 companies × 5 jobs) | 15 | ~$0.02 |
| "Rust engineer" across all ~10,300 companies | ~25 | ~$0.04 |
| Full snapshot of 10 mid-size companies | ~1,000 | ~$1.50 |
| Daily new-job alerts for 50 companies (`onlyNew`) | ~30 new/day | ~$1.35/month |

Filters are applied before charging. Set **Max results** or a **maximum charge per run** and the Actor stops cleanly when it's reached.

### Monitoring: get alerts for new jobs

1. Fill in your company list and filters, and enable **Only new jobs** with a **Monitoring state name** such as `my-watchlist`.
2. Click **Save as a new task**.
3. In the task, open **Schedules** and run it daily (for example at 8:00) or hourly.
4. Add an integration so new jobs come to you: **Slack**, **email**, **Google Sheets**, **webhook**, **Zapier**, **Make** or **n8n**.

The first run returns everything that matches and saves it as "seen". Each later run returns only jobs that appeared since. Older postings that a cap such as **Max jobs per company** left out of earlier runs are not sold to you later as "new": on companies you already monitor, a job posted more than 3 days before the previous run is skipped (free, counted as `backlog_skipped` in `RUN_SUMMARY`). Companies you add to the list later still get their full first batch. Use a different state name for each watchlist so schedules don't interfere.

#### Run it from the API

```bash
curl -X POST "https://api.apify.com/v2/acts/resonating_quoll~ats-jobs-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies": ["Stripe", "https://jobs.lever.co/spotify", "ramp"], "remoteOnly": true}'
```

### FAQ

**How is this different from other ATS job scrapers?**
Most require you to know each company's ATS and slug and only read the boards you list. This Actor also searches a built-in directory of 12,000+ verified boards on all six ATSs, adds seniority / country / remote type, reports closed jobs and hiring velocity, accepts names, URLs, careers pages and domains, and offers ready-made company lists.

**Is it legal to scrape Greenhouse, Lever, Ashby, Recruitee, Personio and Workable jobs?**
The Actor uses only the public, unauthenticated job-board APIs and feeds these platforms provide so companies' listings can be shown on other sites (`boards-api.greenhouse.io`, `api.lever.co`, `api.ashbyhq.com/posting-api`, `{company}.recruitee.com/api/offers`, `{company}.jobs.personio.de/xml`, `apply.workable.com/api/v1/widget`). It collects job postings only, no personal data. You're responsible for how you use the data.

**Do I need proxies or a login?**
No. These are public APIs, so runs are fast and cheap.

**How many companies are in the directory, and how fresh is it?**
12,033 boards (Greenhouse, Lever, Ashby, plus 245 Workable, Recruitee and Personio boards), each verified live when the directory was built (~10,300 had open jobs). Jobs are always fetched live at run time. Any other Recruitee, Personio or Workable company works when you list it by URL or slug.

**What if a company can't be found?**
It gets an error in `RUN_SUMMARY`, the rest of the run continues, and you aren't charged for it. Try its careers-page URL or a job URL instead of the name.

**Why is a salary missing?**
Many companies don't publish pay. Where the ATS has no structured pay field, salary comes from pay-transparency text in the description, and is `null` when none is stated.

**Does it support Workday or SmartRecruiters?**
No. SmartRecruiters' API policy prohibits systematic data extraction and Workday has no public jobs API with clear terms, so we leave them out.

### Limitations

- The directory is mostly Greenhouse, Lever and Ashby; it has only 245 Workable, Recruitee and Personio boards so far. Any other company on those works when you list it.
- Closed-job detection only runs on boards read completely in that run (a board cut short by Max results is skipped, so it never reports false closures).
- Seniority and country are inferred from text and can be `null` or occasionally wrong.
- Workable rate-limits its public API; very long Workable lists run slower.
- Description salary parsing skips hourly rates.
- `posted_at` reflects what each ATS reports, and some companies re-publish old jobs.

### Use with AI agents (Apify MCP)

Give Claude, Cursor, ChatGPT or any MCP client this Actor as a tool through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp):

```json
{
  "mcpServers": {
    "apify": { "url": "https://mcp.apify.com?tools=resonating_quoll/ats-jobs-scraper" }
  }
}
```

The first connection signs you in to Apify in the browser (OAuth). Then ask, for example: *"Find senior remote data engineering jobs in the US posted this week, salary 150k+"* or *"Which AI companies are hiring fastest right now?"* The agent can also read `HIRING_VELOCITY` from the run's key-value store.

### Directory sources and credits

The company directory was built from openly licensed lists and every board was verified against its live API:

- [LastRound AI ATS directory](https://lastroundai.com/blog/salary-transparency-study-2026) by LastRound AI, licensed [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). We filtered it to live boards and added job counts.
- Workable, Recruitee and Personio slugs from [OpenJobs](https://github.com/outscal/OpenJobs) (MIT, © 2026 Santiago Fernández de Valderrama and Outscal), [africa-ats-directory](https://github.com/Thelastpoet/africa-ats-directory) (MIT) and [ever-jobs](https://github.com/ever-jobs/ever-jobs) (MIT). Full notices ship in `src/DIRECTORY_LICENSES.txt`.
- [ats-jobs-mcp](https://github.com/groundtruthtools/ats-jobs-mcp) `directory.json` by Ground Truth, used under the MIT License:

```
MIT License

Copyright (c) 2026 Ground Truth

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

### More from this developer

- [Remote Jobs Scraper](https://apify.com/resonating_quoll/remote-jobs-feed): terms-clean remote jobs straight from 5,800+ employer career pages plus Remote OK and Jobicy, deduplicated and trust-scored.
- [Startup Funding Tracker: SEC Form D](https://apify.com/resonating_quoll/sec-form-d-leads): companies that just raised money, from SEC Form D filings, with a weekly funding drop summary.

### Feedback

Found a bug or want another ATS supported? Open an issue on the **Issues** tab.

# Changelog

This Actor's version history is a separate document: https://apify.com/resonating_quoll/ats-jobs-scraper/changelog.md

# Actor input Schema

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

One company per line, in any of these forms: a company name ("Stripe"), a career-board URL ("https://jobs.lever.co/spotify", "job-boards.greenhouse.io/figma", "jobs.ashbyhq.com/ramp", "bunq.recruitee.com", "acme.jobs.personio.de", "apply.workable.com/acme"), a careers-page URL or domain ("https://www.notion.com/careers", "linear.app"), a board slug ("ramp") or "ats:slug" ("lever:spotify"). Names and domains are matched against the built-in directory of 12,000+ verified company boards (12,033 at the last rebuild) first; careers pages are scanned for an embedded Greenhouse, Lever, Ashby, Recruitee, Personio or Workable board; unknown slugs are auto-detected. The old object format {"ats": "greenhouse", "slug": "stripe", "name": "Stripe"} still works through the API.

## `presetLists` (type: `array`):

Add ready-made, verified lists of companies to your input: ai-ml (OpenAI, Anthropic, Cohere, Perplexity...), fintech (Stripe, Ramp, Plaid, Brex...), devtools (Vercel, Supabase, Datadog, Linear...), top-tech (Airbnb, Figma, Notion, Discord...), climate and health.

## `searchAllCompanies` (type: `boolean`):

Search jobs across the whole built-in directory (12,000+ verified boards, ~10,300 with open jobs, on Greenhouse, Lever, Ashby, Recruitee, Personio and Workable) instead of only the companies you list. Combine with keywords, locations, remote only or posted since, and set Max results. A full sweep reads every board and takes about 5-10 minutes.

## `directoryAts` (type: `array`):

Limit the directory search to these ATSs (empty = all).

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

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

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

Drop jobs whose title contains any of these words (case-insensitive), e.g. "senior", "intern", "manager".

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

If enabled, keywords are matched against the job description too, not just the title.

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

Keep only jobs whose location contains at least one of these strings (case-insensitive), e.g. "New York", "London", "Germany".

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

Keep only jobs that are remote (taken from the ATS workplace field when available, otherwise inferred from the location text).

## `postedSince` (type: `string`):

Keep only jobs posted (or first published) on or after this date. Accepts YYYY-MM-DD or a relative value such as "7 days" or "2 weeks".

## `seniority` (type: `array`):

Keep only these levels (from the job title, or the ATS's own level field). 'unknown' keeps jobs whose level can't be told.

## `jobTypes` (type: `array`):

Keep only these employment types (normalized across ATSs). 'unknown' keeps jobs that don't state one.

## `remoteTypes` (type: `array`):

Keep only remote, hybrid or on-site jobs. More precise than 'Remote only', which also keeps hybrid roles that mention remote.

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

Keep jobs located in these countries. ISO codes or names, e.g. "US", "GB", "Germany". Uses the ATS's country field when it has one, else the location text (cities and US states are recognised).

## `countriesIncludeRemoteUnknown` (type: `boolean`):

With a country filter, also keep remote jobs whose location names no country (e.g. just "Remote").

## `requireSalary` (type: `boolean`):

Keep only jobs that publish a pay range (structured or in the description).

## `minSalaryYearly` (type: `integer`):

Keep jobs whose top of range, annualized, is at least this amount in the listed currency. Jobs without a salary are dropped when set.

## `onlyNew` (type: `boolean`):

Remember job IDs output by previous runs (in a named key-value store) and only output jobs not seen before. Ideal for scheduled runs. The first run outputs everything that matches; later runs skip older postings a cap left out (backlog), free.

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

Name used to store seen job IDs for 'Only new jobs'. Use a different name per watchlist so separate schedules don't interfere.

## `reportClosedJobs` (type: `boolean`):

In monitoring mode, also output a free (not charged) record with event "closed" for each previously output job that is no longer listed on a board read in full this run.

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

Include the full job description (HTML stripped). Disable for smaller output.

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

Also include the posting's original HTML in description_html (larger dataset).

## `dedupeByTitle` (type: `boolean`):

Jobs are always de-duplicated by URL and by company + title + location. Enable this to also merge postings with the same company + title in different locations into one record.

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

Optional cap on the number of jobs output per company (0 = no limit). The form is prefilled with 5 so your first test run is quick and costs about 2 cents; set it to 0 to get every open job.

## `maxResults` (type: `integer`):

Stop after this many jobs in total (0 = no limit). Recommended when searching every company. You are never charged for more jobs than this.

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

How many of YOUR listed boards to fetch in parallel (directory-wide searches use a fixed, polite 16).

## `velocityDays` (type: `integer`):

Window for the HIRING_VELOCITY record: new jobs per company posted in the last N days.

## Actor input object example

```json
{
  "companies": [
    "Stripe",
    "https://jobs.lever.co/spotify",
    "ramp"
  ],
  "searchAllCompanies": false,
  "searchDescription": false,
  "remoteOnly": false,
  "countriesIncludeRemoteUnknown": false,
  "requireSalary": false,
  "onlyNew": false,
  "stateKey": "default",
  "reportClosedJobs": true,
  "includeDescription": true,
  "includeDescriptionHtml": false,
  "dedupeByTitle": false,
  "maxJobsPerCompany": 5,
  "maxResults": 0,
  "maxConcurrency": 3,
  "velocityDays": 7
}
```

# Actor output Schema

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

One normalized record per open job (title, company, location, remote, salary, dates, apply URL).

## `hiringVelocity` (type: `string`):

Companies ranked by new jobs posted in the last N days (all open jobs on each board read).

## `runSummary` (type: `string`):

Per-company counts, errors and closed-job totals.

# 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": [
        "Stripe",
        "https://jobs.lever.co/spotify",
        "ramp"
    ],
    "maxJobsPerCompany": 5,
    "maxResults": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("resonating_quoll/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": [
        "Stripe",
        "https://jobs.lever.co/spotify",
        "ramp",
    ],
    "maxJobsPerCompany": 5,
    "maxResults": 0,
}

# Run the Actor and wait for it to finish
run = client.actor("resonating_quoll/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": [
    "Stripe",
    "https://jobs.lever.co/spotify",
    "ramp"
  ],
  "maxJobsPerCompany": 5,
  "maxResults": 0
}' |
apify call resonating_quoll/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

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