# ATS Jobs API: Greenhouse, Lever, Workday, SmartRecruiters +7 (`ocius-data/ats-jobs-scraper`) Actor

Job listings API for 11 ATS career sites: Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Personio, Recruitee, Teamtailor, BambooHR, Rippling.

- **URL**: https://apify.com/ocius-data/ats-jobs-scraper.md
- **Developed by:** [Christian Barbosa](https://apify.com/ocius-data) (community)
- **Categories:** Jobs, Lead generation, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.80 / 1,000 unique job saveds

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 API: Greenhouse, Lever, Workday, SmartRecruiters and 7 more

**ATS Jobs Scraper** returns every open job, in one normalized row per job, straight from company career sites hosted on 11 applicant tracking systems (ATS): **Greenhouse**, **Lever**, **Ashby**, **Workday**, **SmartRecruiters**, **Workable**, **Personio**, **Recruitee**, **Teamtailor**, **BambooHR** and **Rippling**. Use it as a Greenhouse jobs API, a Lever jobs scraper, a Workday jobs scraper, a SmartRecruiters or Workable jobs API, or a Personio, Recruitee, Teamtailor, BambooHR or Rippling job board scraper: paste career page URLs, board URLs or board names and the ATS is detected automatically. Every job comes in **one normalized schema** with salary, remote flag, department, location, country, posted date and the full description. For scheduled monitoring, each run returns only **new jobs since the last run** and, optionally, the **jobs that closed** since then. You pay only per unique job saved. Companies with no jobs, filtered-out jobs, duplicates, invalid inputs and errors are free.

### Why this ATS jobs scraper

- **Data straight from the source.** The Actor reads the official public job board APIs of each ATS. No browser, no HTML guessing, no aggregator in the middle, so jobs appear as soon as the company publishes them.
- **New and closed jobs since your last run.** Monitoring mode returns only jobs not delivered before. With **Report closed jobs**, every job that was filled, expired or removed from the board is reported once, so your job board, CRM or alert list never keeps dead listings.
- **Strict "posted since" filter.** Keep only jobs published in the last 24 hours, 7 days or since a date. The filter is checked again on every job right before it is saved, so an old job is never delivered or charged.
- **Complete boards.** Workday stops every search at 2,000 results. This Actor splits large Workday boards by job category and location (tested: 2,679 of 2,679 jobs on one board).
- **Accurate salaries.** Structured pay ranges from Lever, Ashby and Greenhouse, and ranges written in the description for the rest. Every salary says where it came from (`salarySource`).
- **No duplicates.** Each job has a stable key, `jobKey` (`ats:board:jobId`), and is saved once per run even if two inputs point to the same board.
- **Clear status per company.** Every input ends with an explicit status in the `RUN_SUMMARY` record. If more than 20% of companies fail, the run is marked as failed so you notice.
- **Safe on long runs.** Progress is saved continuously. A platform restart never duplicates rows or charges twice.

### How to use it

1. In **Companies**, add one company per line. Any of these forms works:
   - Board URL: `https://boards.greenhouse.io/stripe`, `https://jobs.lever.co/spotify`, `https://jobs.ashbyhq.com/ramp`, `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`, `https://jobs.smartrecruiters.com/BoschGroup`, `https://apply.workable.com/huggingface`, `https://tado.jobs.personio.de`, `https://bunq.recruitee.com`, `https://career.teamtailor.com`, `https://hubstaff.bamboohr.com/careers`, `https://ats.rippling.com/rippling/jobs`
   - Company career page that links to one of these systems: `https://www.figma.com/careers/`
   - Explicit form: `greenhouse:stripe`, `lever:spotify`, `ashby:ramp`, `lever-eu:company` (Lever EU), `smartrecruiters:BoschGroup`, `workable:huggingface`, `personio:tado`, `recruitee:bunq`, `teamtailor:career`, `bamboohr:hubstaff`, `rippling:rippling`. For a Teamtailor career site on its own domain, use `teamtailor:https://jobs.example.com`.
   - Bare board name: `stripe` (looked up on Greenhouse, Lever and Ashby only)
2. Optional filters: **Keywords**, **Exclude keywords**, **Locations**, **Departments**, **Remote jobs only**, **Employment types**, **Seniority levels**, **Visa sponsorship stated** and **Posted since** (`2026-10-01`, `24 hours`, `7 days`, `2 weeks`).
3. Optional limits: **Max jobs per company** (newest first) and **Max jobs in total**.
4. For recurring runs, enable **Only new jobs since last run** and, if you need it, **Report closed jobs**.
5. Run the Actor and download the results as JSON, CSV or Excel, or read them through the API.

Example input for a daily engineering-jobs monitor:

```json
{
  "companies": ["https://boards.greenhouse.io/stripe", "lever:spotify", "ashby:ramp"],
  "keywords": ["engineer"],
  "postedSince": "7 days",
  "onlyNewSinceLastRun": true,
  "reportClosedJobs": true,
  "monitoringStoreName": "engineering-monitor",
  "descriptionFormat": "text"
}
```

Workday always needs the full career site URL, because Workday board names cannot be looked up.

#### Supported ATS and public source

| ATS | Accepted input | Public source used | Description |
|---|---|---|---|
| Greenhouse | `boards.greenhouse.io/{company}` | Job Board API | Included |
| Lever | `jobs.lever.co/{company}` | Postings API (global and EU) | Included |
| Ashby | `jobs.ashbyhq.com/{company}` | Job Board API | Included |
| Workday | Full career site URL | Career site JSON | One request per job |
| SmartRecruiters | `jobs.smartrecruiters.com/{Company}` | Posting API | One request per job |
| Workable | `apply.workable.com/{company}` | Careers widget API | Included |
| Personio | `{company}.jobs.personio.de` | XML job feed | Included |
| Recruitee | `{company}.recruitee.com` | Careers API | Included |
| Teamtailor | `{company}.teamtailor.com` | Career site RSS feed | Included |
| BambooHR | `{company}.bamboohr.com/careers` | Careers JSON | One request per job |
| Rippling | `ats.rippling.com/{company}/jobs` | Job board API | One request per job |

All sources are public job board endpoints that need no login or key. Every ATS returns the same fields; a field stays empty when that ATS does not publish it.

### Monitoring new and closed jobs

**Only new jobs since last run** keeps a named key-value store (default name `ats-jobs-monitor`) that remembers, per company, the jobs already delivered. Later runs skip them, so you are charged only for new jobs. The first monitored run of a company returns all matching jobs.

**Report closed jobs** compares the full list of jobs on each board with the jobs delivered in earlier runs. A job that is no longer listed is saved once as a row with `recordType: "closed"`, with its title, location, job link, `firstSeenAt` and `closedDetectedAt`. Safeguards against false closures:

- **Two-run confirmation.** A job is reported only when it is missing from two complete listings in a row. A temporary ATS error never produces a closure. In practice, a closure appears one scheduled run after the job disappears.
- Closures are checked only when the complete job list of the board was read in that run. A failed, partial or truncated listing is skipped and checked again on the next run.
- A job is reported as closed only once, and it is never delivered or charged again afterwards, even if it reappears on the board.
- Filters do not create closures: a job that still exists but no longer matches your filters is not reported.

Use a different **Monitoring store name** for each independent monitor (for example one per client). To start over, delete the store in **Storage**. Closed jobs can be detected only for jobs delivered while **Report closed jobs** was enabled.

### Integrations: schedules, API, webhooks, Sheets, Zapier, Make and AI agents

**Schedules.** Save your input as a task, then open **Schedules** in Apify Console and run it hourly or daily. With monitoring enabled, each scheduled run returns only what changed.

**API.** Run the Actor and get the results in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/ocius-data~ats-jobs-scraper/run-sync-get-dataset-items?token=YOUR_API_TOKEN&format=json" \
  -H "Content-Type: application/json" \
  -d '{"companies": ["ashby:ramp"], "postedSince": "24 hours"}'
```

Python, with the official `apify-client` package:

```python
from apify_client import ApifyClient
client = ApifyClient("YOUR_API_TOKEN")
run = client.actor("ocius-data/ats-jobs-scraper").call(run_input={"companies": ["lever:spotify"], "onlyNewSinceLastRun": True})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["recordType"], job["title"], job["applyUrl"])
```

The results of the latest run are always at `https://api.apify.com/v2/acts/ocius-data~ats-jobs-scraper/runs/last/dataset/items?token=YOUR_API_TOKEN&status=SUCCEEDED` (add `&format=csv` for CSV).

**Webhooks.** In the Actor or task, open **Integrations**, add a webhook for the event `ACTOR.RUN.SUCCEEDED` and point it at your endpoint. The payload contains `resource.defaultDatasetId`, which you use to download the new and closed jobs.

**Google Sheets.** Add the Google Sheets integration in the **Integrations** tab of your task, or download the dataset as CSV and import it. Use `jobKey` as the unique column to update rows, and `recordType` to mark closed jobs.

**Zapier, Make and n8n.** Use the official Apify app or node. Trigger: "Actor run finished". Action: "Get dataset items". Then send each new job to Slack, email, Airtable or your ATS.

**MCP and AI agents.** The Actor runs with limited permissions, so it is available through the Apify MCP server (`https://mcp.apify.com`). Add `ocius-data/ats-jobs-scraper` to the server's Actor list and an AI agent can call it with plain inputs such as `{"companies": ["stripe"], "keywords": ["data"]}`.

### Pricing and cost calculator

Pay per event. There is no start fee. The price of the `job` event is lower on higher Apify plans:

| Your Apify plan | Price per `job` | Per 1,000 jobs |
|---|---|---|
| Free | **USD 0.003** | USD 3.00 |
| Bronze | **USD 0.0025** | USD 2.50 |
| Silver | **USD 0.002** | USD 2.00 |
| Gold, Platinum and Diamond | **USD 0.0018** | USD 1.80 |

| Event | Price | When it is charged |
|---|---|---|
| `job` | See the table above | One unique open job saved to the dataset. |
| `job-closed` | **USD 0.003** per closed job | One closed job reported with **Report closed jobs**. Never charged otherwise. |

**What one price covers.** One run reads all 11 supported ATSs together, returns the same normalized fields for all of them, and includes "only new jobs" monitoring and closed-job detection. Single-ATS tools can cost less per job but cover one system each, and curated career-site feeds typically cost about twice as much per job.

Never charged: companies with no jobs, jobs removed by your filters, duplicates, jobs already delivered in monitoring mode, invalid inputs and errors. Your maximum cost per run is respected for both events together: the Actor saves only what it can charge and stops as soon as the limit is reached. Closed jobs that did not fit are kept and reported on the next run.

Examples at the Free plan price (USD 0.003 per job). On Bronze, Silver and higher plans the `job` part costs less.

| Scenario | Events | Cost on the Free plan |
|---|---|---|
| One company with 200 open jobs | 200 `job` | USD 0.60 |
| 20 companies, engineering jobs only (about 15 each) | 300 `job` | USD 0.90 |
| 50 companies, full boards (about 100 each) | 5,000 `job` | USD 15.00 |
| Daily monitor of 100 companies after the first run: about 150 new and 120 closed jobs per day | 150 `job` + 120 `job-closed` | USD 0.81 per day, about USD 24 per month |
| Same monitor without closed jobs | 150 `job` | USD 0.45 per day, about USD 14 per month |

To keep costs down, use filters, **Posted since** and **Max jobs per company**: filtered-out jobs are free. **Defects:** if a run delivers wrong or incomplete jobs because of a defect in the Actor, open an issue with the run ID. Defects are fixed with priority, usually within 72 hours.

### Output example and field dictionary

```json
{
  "recordType": "job",
  "jobKey": "ashby:ramp:318e7138-151e-4ed2-8b85-a017d234ef12",
  "company": "ramp",
  "ats": "ashby",
  "boardToken": "ramp",
  "jobId": "318e7138-151e-4ed2-8b85-a017d234ef12",
  "title": "Manager, Mid-Market Solutions Consulting",
  "department": "Sales",
  "team": "Solutions Consulting",
  "location": "New York, NY (HQ)",
  "countryCode": "US",
  "workplaceType": "HYBRID",
  "isRemote": true,
  "employmentType": "FULL_TIME",
  "salaryMin": 195000,
  "salaryMax": 298000,
  "salaryCurrency": "USD",
  "salaryPeriod": "YEAR",
  "salarySource": "ats",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/318e7138-151e-4ed2-8b85-a017d234ef12/application",
  "postedAt": "2026-10-31T19:31:59.653Z",
  "input": "https://jobs.ashbyhq.com/ramp",
  "sourceUrl": "https://api.ashbyhq.com/posting-api/job-board/ramp?includeCompensation=true",
  "scrapedAt": "2026-11-01T19:36:53.268Z"
}
```

| Field | Meaning | When it is null |
|---|---|---|
| `recordType` | `job` (open job) or `closed` (job delivered earlier and no longer listed). | Never |
| `jobKey` | Stable unique key `ats:boardToken:jobId`. Use it to deduplicate and update rows. | Never |
| `company` | Company name published by the ATS. Lever, Ashby, Workday, BambooHR and some Rippling boards do not publish it, so the board name is used. | Never |
| `ats`, `boardToken`, `jobId` | ATS (`greenhouse`, `lever`, `ashby`, `workday`, `smartrecruiters`, `workable`, `personio`, `recruitee`, `teamtailor`, `bamboohr`, `rippling`), board identifier (Workday: `tenant/site`; Teamtailor custom domain: the host name) and job identifier. | Never |
| `requisitionId` | Internal requisition number. | Lever and Ashby; Greenhouse when not published |
| `title` | Job title. | Never |
| `department`, `team` | Broad group and specific team. | When the company does not set them |
| `location`, `locations` | Primary location and all locations, as written by the company. | `location` when none is published |
| `countryCode` | ISO 3166-1 alpha-2 country of the primary location. | When the location names no country |
| `workplaceType`, `isRemote` | `REMOTE`, `HYBRID` or `ONSITE`; `isRemote` is true when the job can be done fully remote from at least one location. | `workplaceType` when not published |
| `employmentType`, `employmentTypeRaw` | `FULL_TIME`, `PART_TIME`, `CONTRACT`, `TEMPORARY` or `INTERNSHIP`, and the value as published. | When not published or not one of these |
| `seniority` | Level inferred from the title: `INTERN`, `ENTRY`, `MID`, `SENIOR`, `PRINCIPAL`, `DIRECTOR` or `EXECUTIVE`. Rule-based and free, so you can filter or group jobs without an AI step. | When the title has no clear level (for example "Product Designer") |
| `yearsExperienceMin` | Minimum years of experience requested in the description (for example 5 for "5+ years of experience"). | When the description does not state it |
| `visaSponsorship` | `true` when the description states that visa sponsorship is offered, `false` when it states that it is not. | When the description does not mention sponsorship (most jobs) |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` | Pay range, ISO 4217 currency and period (`YEAR`, `MONTH`, `WEEK`, `DAY`, `HOUR`). | When no salary is published |
| `salarySource`, `salaryText` | `ats` (structured field) or `description` (job text), and the salary as written. | When no salary is found |
| `descriptionText`, `descriptionHtml` | Full description as plain text and as HTML. | When removed with **Description format**; not present in closed rows |
| `jobUrl`, `applyUrl` | Public job page and application page. | `jobUrl` when the ATS gives none |
| `postedAt`, `updatedAt` | First publication and last update, ISO 8601 UTC. | When the ATS does not publish them |
| `firstSeenAt`, `closedDetectedAt` | Closed rows only: when the job was first delivered and when the closure was detected. | Not present in open-job rows |
| `input`, `sourceUrl`, `scrapedAt` | Input line, public ATS endpoint and collection time (UTC). | Never |

The output tab has four views: **Overview**, **New and closed**, **Salaries** and **All fields**.

### Statuses and errors

Statuses are in the `RUN_SUMMARY` record, never in the dataset.

| Status | Meaning | Charged | What to do |
|---|---|---|---|
| `OK` | Jobs were saved. | Per job | Nothing |
| `NO_NEW_JOBS` | Monitoring: every matching job was delivered before. Closed jobs may still be reported. | Only closed jobs | Nothing |
| `NO_MATCHES` | The board has jobs, but none matched your filters. | No | Relax the filters. |
| `NO_JOBS` | The board exists and has no open jobs. | No | Nothing |
| `DUPLICATE_INPUT` | Same board as an earlier line. | No | Remove the line. |
| `NOT_PROCESSED` | Your maximum cost, **Max jobs in total** or the timeout was reached first. | No | Raise the limit or split the list. |
| `INVALID_INPUT` | The line is not a valid URL or board name. | No | Correct the line. |
| `NOT_FOUND` | The ATS says this board does not exist. | No | Check the spelling. |
| `ATS_NOT_DETECTED` | No supported ATS was found for this name or page. | No | Paste the board URL. |
| `BLOCKED` | The ATS kept rate-limiting after all retries. | No | Run again later or set a proxy. |
| `ERROR` | Unexpected response from the ATS. | No | Open an issue with the run ID. |

Per company, `RUN_SUMMARY` also shows jobs on the board, jobs delivered, `closedJobs`, `closedJobsPending` (missing once, waiting for confirmation) and the reason when fewer jobs were delivered. At run level it shows field completeness and the reconciliation between rows saved and events charged for both events.

### Known limits

- **Supported systems:** Greenhouse, Lever (global and EU), Ashby, public Workday career sites, SmartRecruiters, Workable, Personio, Recruitee, Teamtailor, BambooHR and Rippling. iCIMS, SAP SuccessFactors and Oracle Taleo are not supported, because they do not offer a public job board endpoint that works the same way for every company.
- **Bare names** are looked up only on Greenhouse, Lever and Ashby. For the other systems, use the board URL or the `ats:name` form.
- **SmartRecruiters** answers an unknown company with an empty list, so a misspelled identifier is reported as `NOT_FOUND`. Some identifiers are case-sensitive: copy it from `jobs.smartrecruiters.com/{identifier}`.
- **Teamtailor** feeds do not include the employment type. Feeds with 100 jobs or more are not used for closed-job detection, because the feed size limit is not documented.
- **Career page detection** works when the page links to the ATS in its HTML. For pages built entirely with JavaScript, paste the board URL. The Actor never guesses a board from the domain name.
- **Bare names** may match an inactive board on another ATS. When a name exists on several systems, the board with the most jobs is used. Use URLs for full control.
- **Workday, SmartRecruiters, BambooHR and Rippling** need one request per job for the description: about 2 to 3 minutes per 1,000 jobs.
- **Posted since** drops jobs that have no publication date, because they cannot be verified.
- **Closed jobs** are reported after two complete listings in a row without the job, so the first closure appears on the third monitored run at the earliest. Each monitor remembers up to 20,000 jobs per company and keeps closed-job details for the 5,000 most recent; older jobs that close are dropped without a row and without charge.
- **Closed rows** carry the job page in both `jobUrl` and `applyUrl`, because the application page no longer exists.
- **Salaries in text** are read conservatively; unusual formats are left empty rather than guessed.

### FAQ

**Is it legal?** The Actor reads job postings that companies publish on public job boards through the ATS public APIs. No login is used and no personal data about candidates or recruiters is collected.

**How do I get only jobs posted in the last 24 hours?** Set **Posted since** to `24 hours`. Combine it with **Only new jobs since last run** for an hourly or daily feed.

**How do I detect expired or filled jobs?** Enable **Only new jobs since last run** and **Report closed jobs**, and schedule the task. Rows with `recordType: "closed"` are the jobs removed since the previous run.

**Why is `department` empty for some jobs?** The company did not assign one in its ATS.

**Is this an alternative to other Greenhouse or Lever job APIs?** Yes. It covers 11 ATSs with one schema, detects closed jobs without a separate Actor, and charges per job instead of per run.

**Do you offer volume pricing?** For more than 1 million rows per month, open an issue on the Actor page to discuss a volume price.

### Related Actors from ocius-data

Built to the same standard: a status for every input in `RUN_SUMMARY`, actionable error messages and list-based inputs, so they chain well in one workflow:

- **[Google Jobs Scraper API - New Jobs, Pay per Job](https://apify.com/ocius-data/google-jobs-reliable)**: job listings from LinkedIn, Indeed and other boards through Google Jobs, with the same "only new jobs" monitoring mode.

### Changelog

- **November 2026** Seven new ATSs in the same Actor and schema: SmartRecruiters, Workable, Personio, Recruitee, Teamtailor, BambooHR and Rippling (URL, `ats:name` form and career page detection). Country fix: France and the United Kingdom now return `FR` and `GB`.
- **November 2026** New fields `seniority`, `yearsExperienceMin` and `visaSponsorship`, inferred from the title and description at no extra cost, and new filters **Seniority levels** and **Visa sponsorship stated**.
- **0.2** (November 2026) Closed-job detection (`recordType: "closed"`, event `job-closed`) with two-run confirmation against false closures; `recordType` and `jobKey` fields; "New and closed" view; the **Posted since** filter is documented as re-checked before saving.
- **0.1** (November 2026) First version: Greenhouse, Lever, Ashby and Workday; automatic ATS detection; normalized schema; structured and text salaries; Workday 2,000-result cap bypass; monitoring mode; per-company statuses and run summary.

### Support and related Actors

Open an issue on the Actor page. Issues are answered within one business day. Need jobs from aggregators as well? **Google Jobs Scraper** by the same publisher returns listings from LinkedIn, Indeed and other job boards through Google Jobs, with the same monitoring mode.

# Actor input Schema

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

One company per line. Accepted: a board URL (https://boards.greenhouse.io/stripe, https://jobs.lever.co/spotify, https://jobs.ashbyhq.com/ramp, https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite, https://jobs.smartrecruiters.com/BoschGroup, https://apply.workable.com/huggingface, https://tado.jobs.personio.de, https://bunq.recruitee.com, https://career.teamtailor.com, https://hubstaff.bamboohr.com/careers, https://ats.rippling.com/rippling/jobs), a company career page that links to one of these systems (https://www.figma.com/careers/), the form ats:name (greenhouse:stripe, lever:spotify, ashby:ramp, smartrecruiters:BoschGroup, workable:huggingface, personio:tado, recruitee:bunq, teamtailor:career, bamboohr:hubstaff, rippling:rippling), or a bare board name (stripe), which is looked up on Greenhouse, Lever and Ashby only. Workday always needs the full career site URL.

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

Keep jobs that contain at least one of these words or phrases in the title, department or team (case-insensitive). Terms of 3 letters or fewer, such as AI or QA, must match a whole word. Leave empty to keep all jobs.

## `keywordScope` (type: `string`):

Where keywords are searched. Searching the description finds more jobs but also more false matches.

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

Drop jobs whose title contains any of these words or phrases, for example intern or senior.

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

Keep jobs with at least one location containing one of these texts, or whose country code matches, for example London, Germany, US or Remote.

## `departments` (type: `array`):

Keep jobs whose department or team contains one of these texts, for example Engineering or Sales.

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

Keep only jobs that can be done fully remote from at least one location (workplace type Remote, or a location that says Remote).

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

Keep only these employment types. Jobs whose ATS does not publish a recognizable type are dropped when this filter is set. Leave empty to keep all.

## `seniorityLevels` (type: `array`):

Keep only these seniority levels, inferred from the job title. Jobs whose title has no clear level are dropped when this filter is set. Leave empty to keep all.

## `visaSponsorshipOnly` (type: `boolean`):

Keep only jobs whose description states that visa sponsorship is offered. Most job posts do not mention it, so this filter returns few jobs.

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

Keep jobs published on or after this point. Use a date (2026-10-01) or a period (24 hours, 7 days, 2 weeks, 3 months). The filter is checked again on every job right before it is saved. Jobs without a publication date are dropped when this filter is set.

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

For scheduled or API runs. Remembers, per company, the jobs already delivered in a named key-value store that is kept between runs, and returns and charges only jobs not delivered before. The first monitored run for a company returns all matching jobs.

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

Requires Only new jobs since last run. Each run also reports, once, every job delivered in an earlier run that is no longer listed on the company board (filled, expired or removed). Closed jobs are saved as rows with recordType closed and are charged with the job-closed event (see Pricing in the README).

## `monitoringStoreName` (type: `string`):

Name of the key-value store that remembers delivered jobs. Use a different name for each independent monitor (for example one per client or per search). Delete the store to start over.

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

Maximum number of jobs saved per company, newest first. 0 means no limit.

## `maxTotalJobs` (type: `integer`):

Stop the run after this many jobs in total. 0 means no limit. Your maximum cost per run is always respected as well.

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

Which description fields are filled. Removing descriptions makes downloads much smaller; the price per job is the same.

## `fetchGreenhouseSalaries` (type: `boolean`):

Greenhouse publishes structured pay ranges only on each job's own page. When enabled, the Actor makes one extra request per Greenhouse job to read them. Disable for faster runs; salaries written in the description are still extracted.

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

Maximum number of requests in flight at the same time across all companies.

## `maxRetries` (type: `integer`):

How many times a failed or rate-limited request is attempted, with increasing waits between attempts.

## `proxyConfiguration` (type: `object`):

Optional. The ATS APIs are public and normally work without a proxy. If an ATS keeps rate-limiting, the Actor switches automatically to Apify datacenter proxies when available. Set a proxy here only if you need a specific one.

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp",
    "https://workday.wd5.myworkdayjobs.com/Workday"
  ],
  "keywordScope": "title",
  "remoteOnly": false,
  "visaSponsorshipOnly": false,
  "onlyNewSinceLastRun": false,
  "reportClosedJobs": false,
  "monitoringStoreName": "ats-jobs-monitor",
  "maxJobsPerCompany": 25,
  "maxTotalJobs": 0,
  "descriptionFormat": "textAndHtml",
  "fetchGreenhouseSalaries": true,
  "maxConcurrency": 10,
  "maxRetries": 5
}
```

# Actor output Schema

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

One normalized row per unique job across Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Personio, Recruitee, Teamtailor, BambooHR and Rippling, plus closed-job rows when Report closed jobs is enabled.

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

Status per company (OK, NO_NEW_JOBS, NO_MATCHES, NO_JOBS, DUPLICATE_INPUT, NOT_PROCESSED, INVALID_INPUT, NOT_FOUND, ATS_NOT_DETECTED, BLOCKED, ERROR), field completeness and charge reconciliation.

# 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",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp",
        "https://workday.wd5.myworkdayjobs.com/Workday"
    ],
    "maxJobsPerCompany": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("ocius-data/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": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp",
        "https://workday.wd5.myworkdayjobs.com/Workday",
    ],
    "maxJobsPerCompany": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("ocius-data/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": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp",
    "https://workday.wd5.myworkdayjobs.com/Workday"
  ],
  "maxJobsPerCompany": 25
}' |
apify call ocius-data/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

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