# ATS Job Scraper - Paste a Domain, Get Open Roles (`gganbukim/ats-job-scraper`) Actor

Paste company domains like stripe.com; the ATS board is found and verified automatically across Greenhouse, Ashby, Lever and SmartRecruiters. Returns open roles with locations, departments, salary ranges where published, how long each role has been open, and a ghost-job score. No API key, no login.

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

## Pricing

from $1.00 / 1,000 job delivereds

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/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

## ATS Job Scraper — Company Domain In, Open Roles Out

Paste **company domains** — `stripe.com`, `notion.so` — and get their open roles: titles,
locations, departments, salary ranges where the ATS publishes them, and **how long each
role has been open**.

You do not have to know which ATS a company uses or look up its board slug. The actor
works that out and verifies it, across Greenhouse, Ashby, Lever and SmartRecruiters.
Measured on ten well-known companies it resolved 10/10 in about 0.4 seconds each. Board
URLs still work if you already have them.

No API key, no login, no browser rendering. These four platforms serve their job boards
as public JSON — it is how their own careers pages are built.

### What makes this one different

Most job scrapers give you a snapshot of what is posted. This one also gives you
**`ageDays`** — how long the role has actually been open — and a **ghost score** that
compares each role against its own board's median.

The score combines how far past its board median a role has run with whether it has
been closed and reposted. Reposts are only visible to something that was watching, and
this actor reads a board once, so its scores top out at **65** — every row carries
`ghostScoreMax` saying so. If you want the rest of the scale, the lifecycle monitor
accumulates the history that unlocks it.

That single field surfaces things a snapshot cannot. From a live run:

| Days open | Company | Role |
|---|---|---|
| 1,253 | Ramp | Software Engineer, Frontend |
| 1,241 | Stripe | Backend Engineer/API, Payments and Risk |
| 790 | Stripe | Backend Engineer, Core Technology |

A role open for three and a half years at a company that hires constantly is telling you
something — whether you are a candidate deciding where to spend an afternoon, a recruiter
sizing a market, or an analyst tracking hiring health.

### Input

```jsonc
{
  "boardUrls": [
    "stripe.com",                             // domain — ATS discovered automatically
    "notion.so",
    "https://jobs.ashbyhq.com/ramp",          // board URL — used directly
    "greenhouse:databricks"                   // explicit pair
  ],
  "titleContains": ["engineer", "designer"],
  "locationContains": ["Seoul", "Remote"],
  "openLongerThanDays": 180,
  "maxJobsPerBoard": 1000
}
```

Filters run before billing — **filtered jobs are free**, and so is the discovery step.

#### Every option

The same wording you see in the Apify console, with the JSON key for API and MCP callers.

| Option | What it does | Default |
|---|---|---|
| **Companies to scrape** — `boardUrls` *(required)* | Paste plain company domains — stripe.com, notion.so — and the ATS board is found and verified automatically. Careers URLs work too: boards.greenhouse.io/\<company>, jobs.ashbyhq.com/\<company>, jobs.lever.co/\<company>, jobs.smartrecruiters.com/\<Company>. | — |
| **Max jobs per board** — `maxJobsPerBoard` | Cost control. A large employer can have 800+ open roles. | `1000` |
| **Title contains any of** — `titleContains` | Case-insensitive. Filtered jobs are not billed. | — |
| **Location contains any of** — `locationContains` | Case-insensitive, e.g. Seoul, London, Remote. | — |
| **Remote only** — `remoteOnly` | Keep roles flagged remote by the ATS, or whose location mentions remote. | `false` |
| **Posted within (days)** — `postedWithinDays` | Only roles first published within this window. 0 disables. | `0` |
| **Open longer than (days)** — `openLongerThanDays` | The opposite filter — only long-running roles. Set to 180 to look for stale or ghost postings. 0 disables. | `0` |
| **Include ghost score** — `includeGhostScore` | Score each role against its own board's median age and flag the outliers. Free. | `true` |
| **Concurrency** — `concurrency` | Boards fetched in parallel. ATS endpoints are generous, so the default is conservative rather than necessary. | `5` |
| **Proxy** — `proxyConfiguration` | Leave the default. ATS endpoints are public and rarely throttle, so runs almost always stay on cheap datacenter proxies. | `{"useApifyProxy":true}` |

### Output

```json
{
  "ats": "greenhouse",
  "boardSlug": "stripe",
  "jobId": "8077887",
  "title": "Backend Engineer/API, Payments and Risk",
  "url": "https://boards.greenhouse.io/stripe/jobs/8077887",
  "location": "Dublin, Ireland",
  "department": "Engineering",
  "companyName": "Stripe",
  "firstPublishedAt": "2023-03-16T09:12:00-04:00",
  "ageDays": 1241,
  "ghostScore": 65,
  "ghostScoreMax": 65,
  "ghostScoreBasis": "age-only",
  "ghostReasons": ["open 1241 days (board median 49)", "older than a year"],
  "boardMedianAgeDays": 49,
  "scrapedAt": "2026-08-13T09:22:14.001Z"
}
```

#### Every field

You are billed per role delivered, so here is everything a row can contain.

| Field | What it is |
|---|---|
| `ats` | Which platform the board runs on: `greenhouse`, `ashby`, `lever` or `smartrecruiters`. |
| `boardSlug` | The company's slug on that platform. `ats` + `boardSlug` identifies the board. |
| `companyDomain` | The domain you passed, when the board was discovered from it. Absent when you passed a board URL directly, because that is the only case where we do not know it. |
| `companyName` | Company name as the board publishes it. |
| `jobId` | The platform's id for the role, as a string. |
| `title` | Role title. |
| `url` | Public application URL. |
| `location` | Location string as published. Boards vary from `"Remote"` to a full city/region/country. |
| `department` | Department or team. On Greenhouse this comes from a separate departments lookup — see below. |
| `team` | A finer-grained team, where the platform has one separate from department. |
| `employmentType` | Full-time, contract, intern, and so on. |
| `workplaceType` | Remote / hybrid / on-site, where the platform publishes it as its own field. |
| `isRemote` | Boolean remote flag, where the platform publishes one. |
| `isListed` | `false` for a role that is delisted but not deleted — Ashby is the only platform that distinguishes the two. |
| `salaryRange` | `min`, `max`, `currency`, `interval` — only when the board actually publishes pay. |
| `requisitionId` | The company's internal requisition number, where the board exposes it. |
| `firstPublishedAt` | When the role was first posted. Everything about ageing rests on this. |
| `updatedAt` | Last edit to the posting, where the board publishes it. |
| `ageDays` | Days open, computed from `firstPublishedAt`. |
| `scrapedAt` | When this run read the board. |
| `ghostScore` | 0–`ghostScoreMax`. Only with **Include ghost score** on. |
| `ghostScoreMax` | The highest score reachable in this run — `65` here, because republish history needs a watcher over time and this actor takes one snapshot. **Read the score against this, not against 100.** |
| `ghostScoreBasis` | What the score could see: `age-only` here. The lifecycle monitor reaches `age-and-republish-history`. |
| `ghostReasons` | Plain-language reasons, e.g. `"open 1241 days (board median 49)"`. |
| `boardMedianAgeDays` | The board's own median age, which is what the score is relative to. A 200-day role is unremarkable on a board whose median is 180. |

#### Varies by platform

Every board gives an id, a title and a first-published date, which is what the ageing and
ghost scoring rest on. The rest depends on which ATS the company runs. Measured over one
run across two boards:

| Field | Greenhouse | Ashby |
|---|---|---|
| `department` | 200/200 | 136/136 |
| `updatedAt`, `requisitionId` | 200/200 | 0/136 |
| `team`, `employmentType`, `isRemote`, `isListed` | 0/200 | 136/136 |
| `salaryRange` | 0/200 | 0/136 |

Nothing is invented to fill a gap: a field the board does not publish is simply absent
from the row. `salaryRange` is empty above because neither of those two boards posts one
— Lever boards frequently do, and Lever is also where `workplaceType` comes from.

### Who this is for

- **Recruiters and talent researchers** — which competitors are hiring, where, for what, and how fast they fill it.
- **B2B sellers** — a company hiring for a role is buying the tools around it.
- **Market researchers** — open roles by function, level and city, straight from the source rather than an aggregator.
- **Candidates and job-board operators** — skip the postings that have been open for two years.

### Common uses

- **Job market research** — how long roles stay open by function, level and city.
- **Recruiting intelligence** — which competitors are hiring, where, and how fast they fill.
- **Lead generation** — companies hiring for a role are buying the tools around it.
- **Candidate triage** — skip the postings that have been open for two years.
- **Investor signals** — headcount direction from careers pages, before it reaches filings.

### Pricing

Pay per job delivered. Filtered jobs and failed boards **cost nothing**.

Starting a run costs $0.00002 — the platform's $0.00001 minimum, charged once per GB of memory, and these Actors run on 2 GB. That is two cents per thousand runs, and it is the only charge not tied to a row you received.

### Other Actors in this family

Same engines, same billing, no account or API key on any of them.

**YouTube & video**

- [Download YouTube Subtitles in Bulk — SRT, VTT & Text](https://apify.com/gganbukim/youtube-transcript-scraper) — Bulk subtitles from videos, channels or playlists — text, SRT, VTT or RAG chunks.
- [Export YouTube Comments to CSV — Replies and Likes](https://apify.com/gganbukim/youtube-comments-scraper) — Every comment and reply thread, with likes, authors and creator flags.
- [List Every Video on a YouTube Channel — Export to CSV](https://apify.com/gganbukim/youtube-channel-scraper) — A channel's whole back catalogue plus a subscriber and RSS summary row.
- [Find YouTube Sponsors — Brand Deals, Codes & Links](https://apify.com/gganbukim/youtube-sponsorship-finder) — Which brands pay which creators, with the campaign link, the code and the timestamp.
- [YouTube Search API — Bulk Results, No Quota](https://apify.com/gganbukim/youtube-search-scraper) — Many search terms at once, every result as a row, filtered before you are billed.
- [Track Deleted YouTube Videos & Title Changes](https://apify.com/gganbukim/youtube-channel-monitor) — What a channel quietly changed: deleted videos, rewritten titles, view velocity.
- [YouTube Creator Email Finder & Sponsor Lookup](https://apify.com/gganbukim/youtube-creator-leads) — A channel list into leads: the published email, audience bands, and who already sponsors them.
- [Export a YouTube Playlist to CSV — Every Video](https://apify.com/gganbukim/youtube-playlist-scraper) — Any playlist as a table, with each video position in it.

**Search demand**

- [AnswerThePublic Alternative — Autocomplete Keyword API](https://apify.com/gganbukim/long-tail-keyword-scraper) — One seed into hundreds of real keywords from Google, YouTube and Amazon autocomplete.
- [Google Trends API — Today's Trending Searches, No Key](https://apify.com/gganbukim/google-trends-scraper) — Today's trending searches by country, with traffic bands and the news behind them.

**E-commerce**

- [Export Any Shopify Store's Products to CSV or JSON](https://apify.com/gganbukim/shopify-product-scraper) — Any Shopify catalogue: variants, SKUs, live prices, stock, images, collections.
- [New Shopify Product Alerts — Competitor Drop Tracker](https://apify.com/gganbukim/shopify-new-arrivals-monitor) — Only what a store launched since the last run. Scanning is free.

**Hiring**

- [Ghost Job Detector — Track Reposts, Closures & Edits](https://apify.com/gganbukim/job-lifecycle-monitor) — What changed on a careers page: opened, closed, quietly reposted, or a ghost job.

### FAQ

**Does it need an API key?** No. All four platforms serve public JSON.

**How does it find the board from a domain?** It guesses the slug from the domain and
validates it against the ATS board API — a board either returns jobs or it does not, so
the answer is verified rather than inferred. If that misses, it reads the company careers
page for an ATS link and validates that instead. Rows discovered this way carry a
`companyDomain` field.

**Which ATS are supported?** Greenhouse, Ashby, Lever and SmartRecruiters — verified live.

**Where does `ageDays` come from?** The ATS publishes a first-published date on every
role. It is not estimated.

**Can I track when a role *closes*?** Not from a single run — no ATS publishes that. Use
the **Job Lifecycle Monitor**, which watches boards on a schedule and reports closures,
reposts and silent edits.

**Can I run it on a schedule?** Yes, via Apify Schedules, webhooks, or the API. Also
available over MCP for AI agents.

**Is it legal to scrape job boards?** This one has an unusually clean answer. Greenhouse,
Ashby, Lever and SmartRecruiters each publish a documented, public, no-authentication job
board API, and they publish it *so that* job listings get syndicated. There is no login,
nothing bypassed, and no rate limit worked around. Each company's own terms are a separate
contract question. Not legal advice.

**Will I get blocked or rate-limited?** These are official JSON endpoints built for
syndication, not pages defended against scraping — which is why this Actor runs on cheap
datacenter proxies and rarely needs anything else. That is also the practical reason to
prefer ATS boards over aggregator sites.

**How much does 1,000 roles cost?** $1.00, plus $0.00002 for the run. Roles your filters
remove and boards that fail are never billed.

**Can I export the results to Excel or Google Sheets?** Yes. Every run's dataset
downloads as CSV, Excel, JSON, XML or RSS from the Storage tab, or straight from the API
if you want a live link a spreadsheet can pull.

**Can I connect it to Zapier, Make or n8n?** Yes — Apify publishes integrations for all
three, plus webhooks that fire when a run finishes. A common setup is a schedule here and
a webhook into your own database or Slack.

**Do I need to write code?** No. Fill the form in the console and press Start. If you do
want code, the Apify client libraries for Python and JavaScript call this the same way,
and it is available over MCP so an AI agent can call it directly.

# Actor input Schema

## `boardUrls` (type: `array`):

Paste plain company domains — stripe.com, notion.so — and the ATS board is found and verified automatically. Careers URLs work too: boards.greenhouse.io/<company>, jobs.ashbyhq.com/<company>, jobs.lever.co/<company>, jobs.smartrecruiters.com/<Company>.

## `maxJobsPerBoard` (type: `integer`):

Cost control. A large employer can have 800+ open roles.

## `titleContains` (type: `array`):

Case-insensitive. Filtered jobs are not billed.

## `locationContains` (type: `array`):

Case-insensitive, e.g. Seoul, London, Remote.

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

Keep roles flagged remote by the ATS, or whose location mentions remote.

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

Only roles first published within this window. 0 disables.

## `openLongerThanDays` (type: `integer`):

The opposite filter — only long-running roles. Set to 180 to look for stale or ghost postings. 0 disables.

## `includeGhostScore` (type: `boolean`):

Score each role against its own board's median age and flag the outliers. Free.

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

Boards fetched in parallel. ATS endpoints are generous, so the default is conservative rather than necessary.

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

Leave the default. ATS endpoints are public and rarely throttle, so runs almost always stay on cheap datacenter proxies.

## Actor input object example

```json
{
  "boardUrls": [
    "stripe.com",
    "https://jobs.ashbyhq.com/ramp"
  ],
  "maxJobsPerBoard": 1000,
  "remoteOnly": false,
  "postedWithinDays": 0,
  "openLongerThanDays": 0,
  "includeGhostScore": true,
  "concurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per open role, with how long it has been open and a ghost-job score.

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

Counts for this run: rows delivered, rows filtered, items that failed, the proxy tier used, and whether the run stopped at its charge limit.

# 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 = {
    "boardUrls": [
        "stripe.com",
        "https://jobs.ashbyhq.com/ramp"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("gganbukim/ats-job-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 = { "boardUrls": [
        "stripe.com",
        "https://jobs.ashbyhq.com/ramp",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("gganbukim/ats-job-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 '{
  "boardUrls": [
    "stripe.com",
    "https://jobs.ashbyhq.com/ramp"
  ]
}' |
apify call gganbukim/ats-job-scraper --silent --output-dataset

```

## MCP server setup

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