# ATS Jobs Scraper (`lightmoon/ats-jobs-scraper`) Actor

Open roles pulled from company career pages while your run executes, not from a copied index. Name the companies, get 27 fields per role across Greenhouse, Lever, Ashby, Workday.

- **URL**: https://apify.com/lightmoon/ats-jobs-scraper.md
- **Developed by:** [Stable](https://apify.com/lightmoon) (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.10 / 1,000 job postings

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

Name the companies you care about. Every open role on their career pages comes
back as one table, **fetched at the moment you press run** — not served from an
index built hours ago.

```json
{ "companyName": "Ramp",        "platform": "ashby",
  "title":       "Security Engineer, Cloud",
  "locationRaw": "New York, NY (HQ)",   "isRemote": true,
  "postedAt":    "2026-04-07T17:12:35+00:00",
  "applyUrl":    "https://jobs.ashbyhq.com/ramp/34413f8d…/application" }
```

**27 fields per role**, identical columns across **Greenhouse, Lever, Ashby and
Workday**, so a thousand companies sort in one spreadsheet. Nothing is inferred:
a field the employer did not publish comes back empty rather than guessed.

### You do not need to know which system a company uses

Type the company name. It is looked up on each system until a board answers.

```
stripe
monzo
https://jobs.ashbyhq.com/ramp
https://jobs.lever.co/veeva
https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite
```

![Company names and board URLs go in; open roles come out as one table](https://api.apify.com/v2/key-value-stores/lightmoon~lightmoon-assets/records/ats-input.png)

Names and URLs can be mixed freely. The run summary tells you which system each
company turned out to be on — often the most useful thing in the output.

One exception, stated plainly: a **Workday** board is a host plus a tenant plus a
site rather than a single name, so Workday needs the career-site URL. A bare name
is looked up on the other three.

### What one row looks like

```json
{
  "platform": "ashby",
  "companyToken": "ramp",
  "companyBoardUrl": "https://jobs.ashbyhq.com/ramp",
  "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Security Engineer, Cloud",
  "jobUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
  "locationRaw": "New York, NY (HQ)",
  "additionalLocations": "Remote (Canada), Remote (US), Miami, FL",
  "country": "USA",
  "isRemote": true,
  "workplaceType": "Hybrid",
  "employmentType": "FullTime",
  "departments": "Engineering",
  "team": "Backend",
  "postedAt": "2026-04-07T17:12:35.753+00:00",
  "descriptionText": "ABOUT RAMP\n\nRamp is building the smart infrastructure for finance teams…",
  "scrapedAt": "2026-08-10T18:20:06+00:00"
}
```

![Five real rows: job title, company, system, location, department and publication date](https://api.apify.com/v2/key-value-stores/lightmoon~lightmoon-assets/records/ats-output.png)

Rows are flat — no nested objects — so CSV and Excel exports open correctly with
no post-processing.

### Fields

All 27, and every one of them comes from the employer's own board.

![All 27 fields grouped by employer, role, location, filing, dates and posting](https://api.apify.com/v2/key-value-stores/lightmoon~lightmoon-assets/records/ats-fields.png)

| Field | What it is |
|---|---|
| `platform` | Which system the row came from |
| `companyToken`, `companyName`, `companyBoardUrl` | The employer and their board |
| `title`, `jobUrl`, `applyUrl` | The role, its page, and where the form is |
| `jobId`, `internalJobId`, `requisitionId` | Identifiers, for de-duplicating across runs |
| `locationRaw`, `additionalLocations`, `country` | Location as posted, plus any secondary ones |
| `isRemote`, `workplaceType` | Remote status, and the employer's own term for it |
| `employmentType` | Full time, contract, and so on, in the employer's wording |
| `departments`, `team`, `offices` | How the employer files the role internally |
| `postedAt`, `updatedAt`, `applicationDeadline` | Dates as published |
| `descriptionHtml`, `descriptionText` | Full posting, as markup and as readable prose |
| `customFields` | Fields this employer invented — pay band, travel, clearance. JSON |
| `language`, `scrapedAt` | Posting language, and when the row was collected |

Not every system publishes every field. Greenhouse gives offices and an edit
date; Ashby gives employment type and a real remote flag; Lever gives team and
commitment; Workday gives country and a posting date. **A field the system does
not publish is left empty rather than filled in with a guess.**

**On `isRemote`:** it is set when the posting says so — from the system's own
flag where there is one, otherwise from what the posting states. A job that never
mentions it comes back with the field missing rather than `false`. Guessing would
quietly mislabel a large part of any dataset.

### Filters

Leave them all empty and you get everything. Set several and a job has to satisfy
all of them.

- **Job title contains / does not contain** — keep `engineer`, drop `intern`
- **Location contains** — matched against the posting's location and the offices
- **Department contains** — Greenhouse, Lever and Ashby publish departments
- **Remote only** — keeps jobs that say they are remote
- **First published after** / **Last updated after** — for scheduled runs that
  collect only what changed
- **Workday keyword** — searches at the source, which is far quicker than pulling
  a two-thousand-role site and filtering afterwards

Plus **maximum jobs in total** and **maximum jobs per company**, so one large
employer cannot fill the entire result.

### Use cases

**Track a list of target employers.** Paste your companies, set *Last updated
after* to yesterday, schedule it daily.

**Find out which system a competitor recruits on.** Run one company and read the
`platform` column in the run summary.

**Build a hiring dataset for a market.** A few hundred companies, filtered by
location keyword, exported to CSV.

**Watch one team.** One company, *Department contains* `Engineering`, *Job title
contains* `backend`.

### What to expect from a run

One board returns anywhere from a handful of roles to several hundred — on real
companies we have measured 77 open roles at one employer and 556 at another. A
run over a list of companies is the sum of their boards, so set **maximum jobs
in total** while you are exploring rather than harvesting.

Three of the four systems hand over a whole board in a single request.
**Workday is the exception:** its search results carry no descriptions and no
real dates, so each role costs one extra request. A Workday-heavy list with
descriptions switched on is the slow case, and the only one worth planning
around.

Every run writes a summary to the key-value store: jobs found per system, which
companies came back empty, and which lookups failed. So a partial result stays
legible — you can always tell "this employer has no open roles" apart from
"this employer could not be found".

### How much does it cost?

You pay **per job returned**, at one flat rate — whatever the row turned out to
contain and whichever system it came from.

A company whose board returns nothing costs nothing — including the lookups that
had to fail before the right system was found — and the run stops the moment it
reaches whatever spending cap you set on it.

The first run defaults to **100 jobs**, which is deliberately small: enough to
see whether the fields suit you before committing to a long company list.

Apify's free plan includes **$5 of platform credit each month**, so you can
evaluate this without entering a card.

Every run writes a summary listing how many rows were billed.

### Limits

- Only **public** boards are readable. Internal or unlisted postings are not —
  on Ashby, roles the company took off its own board are skipped.
- **Workday** is the slow one: its search results carry only a title, a location
  and relative text like "Posted Today", so descriptions and real dates need one
  extra request per role. With descriptions off, Workday rows have no posting
  date, and the field is left empty rather than invented.
- A company that has moved systems, or never used one of these four, is reported
  as a failed lookup in the run summary rather than silently returning nothing.
- `companyName` is only present where the system publishes it — Greenhouse and
  Workday do, Lever and Ashby do not.

### Output

The Output tab has three views: **Overview** (title, company, system, location,
dates, link), **Descriptions** (with the full text), and **All fields**. Export
any of them to CSV, Excel, JSON or XML, or read them from the API.

The run summary in the key-value store lists jobs found per system, which
companies were empty and which failed — so a partial result is always legible.

### FAQ

**How do I find a company's board?**
Open their careers page and paste the URL. If it is Greenhouse, Lever or Ashby
you can also just type the company name.

**What if a company is not on any of the four?**
It is listed as a failed lookup in the run summary and the rest of the run
continues. Nothing is charged for a lookup that returned nothing.

**Why is Workday slower than the rest?**
Its search results do not include descriptions or real dates, so each role needs
a second request. The other three hand over a whole board at once.

**Can I get salary data?**
Only what the employer published. It appears in `descriptionText` when the
company wrote it there, and in `customFields` when they made it a field.

**How do I avoid duplicates across scheduled runs?**
De-duplicate on `jobId`, which is stable for the life of a posting.

**How fresh is the data?**
It is fetched while your run is executing. There is no cached index in between,
so a role published minutes ago appears in the next run, and one that was taken
down stops appearing. That is the whole reason this reads boards directly.

**Is it legal to collect job postings this way?**
These are public career pages that employers publish deliberately and want
distributed as widely as possible — no login, no paywall and no personal data is
involved. Only publicly visible postings are read, at a polite request rate.
Check your own jurisdiction and the employer's terms if you plan to republish
the descriptions, which remain the employer's copyright.

**Does it work on the free plan?**
Yes. Apify's free plan includes $5 of monthly platform credit, and the default
100-job cap is sized so a first run fits inside it comfortably. Nothing degrades
silently: when a spending cap is reached the run stops and says so in the
summary.

### Other scrapers we maintain

Same engine, same pricing model, same promise: flat rows, stated limits,
and no charge for a row that came back empty.

| | |
|---|---|
| **[Google Maps Contact Scraper](https://apify.com/lightmoon/google-maps-contact-scraper)** | Local businesses by category and city — phone, website, split address and opening hours. |
| **[Local Business Phone Scraper](https://apify.com/lightmoon/local-business-phone-scraper)** | The same job from directory sources, with Better Business Bureau accreditation on the row. |
| **[YouTube Influencer Scraper](https://apify.com/lightmoon/youtube-influencer-scraper)** | Creators by niche, with the website and socials they publish and a subscriber filter. |
| **[Telegram Channel Messages Scraper](https://apify.com/lightmoon/telegram-messages-scraper)** | Posts from public channels — text, views, reactions and media links, no account needed. |
| **[Threads Hashtag Scraper](https://apify.com/lightmoon/threads-hashtag-scraper)** | Threads posts by hashtag or keyword, with author, engagement and links. |
| **[RedNote Xiaohongshu Profile Scraper](https://apify.com/lightmoon/rednote-profile-scraper)** | Public RedNote creator profiles in bulk — followers, bio, IP location and their notes. |

# Actor input Schema

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

One per line. A plain company name is looked up on Greenhouse, Lever and Ashby until one of them answers, so you do not need to know which system an employer uses. A career-site URL is used directly — and Workday can only be reached that way, since a Workday board is a host plus a tenant plus a site rather than a single name.

## `platforms` (type: `array`):

Narrow the search when you already know which system a company uses — it saves the lookups that would have failed.

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

On by default. On Greenhouse the same request also carries departments and offices, so turning this off gives a plainer row rather than a cheaper one. On Workday the description is where the real posting date lives — without it, the date is left empty rather than guessed.

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

Stops the run once this many rows are stored. Set 0 for no limit.

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

Keeps one large employer from filling the whole result. 0 means no per-company cap.

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

Case-insensitive. A job is kept if its title contains at least one of these.

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

Case-insensitive. Drops jobs whose title contains any of these — useful for `intern`, `senior`, `manager`.

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

Matched against the posting's location and the company's offices, for example `Berlin`, `Germany`, `EMEA`.

## `departmentKeywords` (type: `array`):

For example `Engineering`, `Sales`. Greenhouse, Lever and Ashby publish departments; Workday does not.

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

Keeps only jobs that say they are remote. Postings that do not mention it either way are dropped rather than guessed at.

## `postedAfter` (type: `string`):

Keeps jobs first published on or after this date.

## `updatedAfter` (type: `string`):

Keeps jobs edited on or after this date — the usual way to fetch only what changed since the last run. Greenhouse is the only one of the four that publishes an edit date.

## `workdaySearchText` (type: `string`):

Optional. Workday sites can be searched at the source, which is much faster than pulling a 2,000-role site and filtering afterwards. Applies to Workday boards only.

## `concurrency` (type: `integer`):

Raise it for long company lists. Also governs how many Workday job pages are fetched in parallel.

## Actor input object example

```json
{
  "companies": [
    "stripe",
    "monzo",
    "https://jobs.ashbyhq.com/ramp",
    "https://jobs.lever.co/veeva",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
  ],
  "platforms": [
    "greenhouse",
    "lever",
    "ashby",
    "workday"
  ],
  "includeDescription": true,
  "maxJobs": 100,
  "maxJobsPerCompany": 10,
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "locationKeywords": [],
  "departmentKeywords": [],
  "remoteOnly": false,
  "concurrency": 5
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `descriptions` (type: `string`):

No description

## `all` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "companies": [
        "stripe",
        "monzo",
        "https://jobs.ashbyhq.com/ramp",
        "https://jobs.lever.co/veeva",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
    ],
    "maxJobsPerCompany": 10,
    "titleKeywords": [],
    "excludeTitleKeywords": [],
    "locationKeywords": [],
    "departmentKeywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("lightmoon/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",
        "monzo",
        "https://jobs.ashbyhq.com/ramp",
        "https://jobs.lever.co/veeva",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    ],
    "maxJobsPerCompany": 10,
    "titleKeywords": [],
    "excludeTitleKeywords": [],
    "locationKeywords": [],
    "departmentKeywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("lightmoon/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",
    "monzo",
    "https://jobs.ashbyhq.com/ramp",
    "https://jobs.lever.co/veeva",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
  ],
  "maxJobsPerCompany": 10,
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "locationKeywords": [],
  "departmentKeywords": []
}' |
apify call lightmoon/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

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