# Company Career Page Jobs Scraper — 6 ATS Platforms (`humble-echidna/ats-jobs`) Actor

Enter company names or careers pages for every open job, fetched live from each company's board on Greenhouse, Lever, Ashby, Recruitee, Personio and Teamtailor, in one format. Filter by keyword, location or date, or get only jobs posted since your last run.

- **URL**: https://apify.com/humble-echidna/ats-jobs.md
- **Developed by:** [Michael Costa](https://apify.com/humble-echidna) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 jobs

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What does Company Career Page Jobs Scraper do?

You tell it **which companies you care about**. It returns **every open job at those companies** in **one identical, clean format**, straight from each company's own careers site, whatever hiring platform (applicant tracking system, or ATS) the company uses.

It is **not** a job-board search: it doesn't search Indeed, LinkedIn, ZipRecruiter, or similar sites. It goes to each company's own careers site, so you get every job the company lists, the moment it's listed.

**Three ways to name a company**, one per line, mixed freely:

| You type | Example | How it's found |
|---|---|---|
| **Company name** | `Stripe`, `Linear`, `Palantir` | Looked up on all 6 platforms. If a company runs more than one board, you get all of them. |
| **Careers page** | `https://linear.app/careers` | The page is scanned for a link to its job board. Works when the page links it directly; many big-company pages load jobs with JavaScript, and then a name or job-board URL works better. |
| **Job-board URL** | `https://boards.greenhouse.io/stripe` or `greenhouse:stripe` | Used as is. Open any of the company's jobs to find it. |

Each run's log and the `RUN_STATS` record show what every name or page was matched to, and anything that couldn't be found, with the reason.

**Try it in one click:** the input comes pre-filled with 6 example companies, one on each platform, some typed as names and some as URLs: Linear, bunq, Greenhouse, Outreach, ottonova and Teamtailor. That's about 120 jobs, or roughly $0.24 (120 × $0.002 per job, plus $0.00005 for the run start). **Then replace them with the companies you actually want.**

6 platforms are supported, each through the public job feed it publishes for machines:

| ATS | Paste a URL like |
|---|---|
| **Greenhouse** | `https://boards.greenhouse.io/stripe` |
| **Lever** | `https://jobs.lever.co/palantir` (EU boards too) |
| **Ashby** | `https://jobs.ashbyhq.com/openai` |
| **Recruitee** | `https://bunq.recruitee.com` |
| **Personio** | `https://<company>.jobs.personio.de` |
| **Teamtailor** | `https://<company>.teamtailor.com` |

Workday, Workable, SmartRecruiters, Rippling, Breezy HR, iCIMS, SuccessFactors, Taleo, BambooHR, Jobvite, JazzHR and a dozen smaller platforms are not supported: see the FAQ for why. Their URLs are recognised and reported with the reason, and the rest of the run continues.

Paste career page URLs (mixing platforms is fine), optionally filter by title keyword, location, remote, or posting date, and get a dataset you can export to JSON, CSV, or Excel, or pull via API.

It reads each ATS's **public job-board feed**, the same data the company's own careers page shows. No login, no browser, no proxies needed, so runs are fast and cheap.

**Every job is fetched live, at the moment you run it.** There's no pre-crawled database in between: each company's board is read during your run, so every job returned is posted right now, and a job the company closed this morning isn't in your results. Job databases re-check their listings about once a day.

### Why this one?

- **One schema for 6 ATSs.** Employment type is one of `FULL_TIME`, `PART_TIME`, `CONTRACT`, `TEMPORARY`, `INTERNSHIP`, `VOLUNTEER`, and workplace type one of `remote`, `hybrid` or `onsite` (`null` when the job doesn't say). Dates are always ISO 8601 UTC. No per-platform cleanup on your side.
- **Salary where others return nothing.** Structured pay from the ATS when it has it. Otherwise it's extracted from pay-transparency text in the description ("$150,000 - $200,000 USD", "£45k–£55k per annum", "$28.50/hr"). `salarySource` tells you which. Extraction is deliberately conservative: stipends, bonuses, and funding amounts are ignored.
- **Fast.** The 6 example companies (~120 jobs, with descriptions) finish in seconds: every platform delivers a company's whole board in one feed (Teamtailor pages through it), with no per-job requests.
- **Reliable.** A broken or unreachable company never affects the others in your run. A malformed job record is skipped instead of failing its company. Rate limits are handled automatically with a shared per-site cooldown. The run log and the `RUN_STATS` record say exactly which company had a problem and why.
- **Live, not cached.** Jobs come straight from each board during your run, so nothing returned has already closed.
- **Company names filled in** even where the ATS API omits them (Lever, Ashby).

### Use cases

- **Job boards and aggregators:** keep listings fresh straight from the source.
- **Recruiters and sales teams:** track which companies are hiring for what (hiring signals).
- **Job alerts:** turn on **Only jobs new since my last run** and schedule it daily.
- **Salary and market research:** pay ranges as numbers, with currency and period.

### How much does it cost?

You pay per job returned: **$2.00 per 1,000 jobs**, plus $0.00005 each time a run starts. Filters are applied before you're charged, so you only pay for jobs you keep.

Two ways to cap a run, and the scraper stops cleanly at whichever comes first:

- **Max jobs per run** in the input.
- **Maximum cost per run** in the run options. The scraper stops fetching as soon as the limit is covered, so a capped run is also a fast one.

### Input

| Field | What it does |
|---|---|
| **Companies** | One per line: a company name, a careers page, or a job-board URL (see above). |
| Title keywords / Exclude keywords | Keep or drop jobs by words in the title (case-insensitive). |
| Locations | Keep jobs whose location or country contains any of these. Add `remote` to include remote jobs. |
| Remote jobs only | Only jobs marked remote. |
| Posted within (days) | Only recently published jobs. |
| Only jobs new since my last run | First run returns everything that matches; later runs return only jobs posted since. Remembered in your own Apify storage, per company and per filter combination. |
| Include full job description | Plain-text description (on by default). |
| Max jobs per run | Cap the total number of jobs across all companies. |
| Max jobs per company | Cap results per company. |

```json
{
  "companies": ["https://jobs.ashbyhq.com/openai", "https://boards.greenhouse.io/stripe", "lever:palantir"],
  "keywords": ["engineer"],
  "locations": ["San Francisco", "remote"],
  "postedWithinDays": 30
}
```

### Output

Every job has the same fields on every platform. Fields a platform doesn't publish are `null`.

```json
{
  "id": "ashby:openai:8fb1615c-34bf-47c4-a1d1-b7b2f836bbd3",
  "platform": "ashby",
  "company": "openai",
  "companyName": "OpenAI",
  "title": "Technical Program Manager, Compute Infrastructure",
  "department": "Technical Program Management",
  "team": "Technical Program Management",
  "location": "San Francisco",
  "locations": ["San Francisco"],
  "country": "United States",
  "remote": null,
  "workplaceType": null,
  "employmentType": "FULL_TIME",
  "postedAt": "2026-03-12T16:38:15.322000Z",
  "updatedAt": null,
  "url": "https://jobs.ashbyhq.com/openai/8fb1615c-34bf-47c4-a1d1-b7b2f836bbd3",
  "applyUrl": "https://jobs.ashbyhq.com/openai/8fb1615c-34bf-47c4-a1d1-b7b2f836bbd3/application",
  "salaryMin": 257000,
  "salaryMax": 335000,
  "salaryCurrency": "USD",
  "salaryPeriod": "YEAR",
  "salaryText": "$257K - $335K",
  "salarySource": "ats",
  "description": "ABOUT THE TEAM\n\nThe compute infrastructure team runs the GPU fleet…",
  "scrapedAt": "2026-09-24T04:30:00Z"
}
```

`id` is stable across runs (`platform:company:jobId`), so you can use it to deduplicate.

### FAQ

**Can I search all companies on Greenhouse (or any platform)?** No. This actor fetches the career pages you list, not a search across every company on a platform. The ATSs don't publish a directory of their customers. If you need a searchable database of millions of jobs, use a job-database actor instead. Use this one when you know which companies you want to track and need their complete, fresh listings.

**Can I use just one platform?** Yes. List only companies on that platform, or mix platforms freely. Each URL is detected automatically.

**How does "Only jobs new since my last run" know about my last run?** It stores the IDs of the jobs it returned in a named key-value store (`ats-jobs-seen`) in **your** Apify account, which persists between runs. Each company and filter combination has its own memory, so a daily "engineer" alert and a weekly "sales" alert on the same company don't interfere. Changing the filters starts a fresh search, and so does deleting that store. Jobs held back by **Max jobs per company** are not marked as seen, so they come through on a later run.

**A company that used to work now fails or returns fewer jobs.** ATS vendors change their public feeds without notice, so a site change is the most likely cause. The run log names the company and what didn't parse, and every other company in the run is unaffected. Please open an issue with the URL so the change can be looked at.

**A company wasn't found.** The run log and `RUN_STATS` (`unresolved`) say why, and the rest of the run continues. Common reasons: its careers page loads jobs with JavaScript (type the name instead), it has no open jobs right now, or it uses a platform this actor doesn't read (see below). Open an issue with the company and we'll check whether its platform can be added.

**It matched the wrong company.** Names are only matched when the job board's own company name agrees with what you typed, but two companies can share a name. Paste the exact job-board URL to pin it down; the `company` and `companyName` fields on every job show which board it came from.

**Salary is empty.** Only published when the company publishes it. Coverage is highest for US roles (pay-transparency laws) and Ashby-hosted companies.

**Is this legal?** It only reads platforms that publish their job feed for machines and whose terms allow this use; every platform was checked against its terms of service and robots.txt before it was added, and the ones that failed are listed below with the reason. It reads the public job-board data each company publishes for its own careers page. It collects job postings only: no recruiter names, emails, or other personal data. It identifies itself honestly (User-Agent `HumbleEchidnaApify`) and follows every site's robots.txt: a page or API a site disallows is never requested, and that company is reported with the reason instead of being skipped silently; a site's crawl delay is respected.

**Which addresses does it request?** Only public web addresses on the standard web ports (80 and 443). A careers
page on another port, or on a domain that points at a private network, is refused with the reason, and a careers
domain that doesn't exist is reported as such. Each site's robots.txt is read once per run; a site that asks for
more than 30 seconds between requests is reported rather than fetched slowly.

**Why aren't Workday, Workable, SmartRecruiters, Rippling and Breezy HR supported?** This actor only reads platforms that publish their job feed for machines and whose terms allow this use. Each platform was checked, and these five were dropped (their URLs are still recognised and reported with the reason):

- **SmartRecruiters** (since 1.0.1): its robots.txt disallows its job API for every automated client except LinkedIn's.
- **Workday** (since 1.0.2): its career sites can only be read through the internal backend of Workday's own career-site web app, not a documented public API or feed, and Workday's terms require a separate agreement for its APIs.
- **Workable** (since 1.0.2): its job-widget endpoint is documented only for embedding a company's jobs on that company's own website, not as an API for third parties.
- **Breezy HR** (since 1.0.2): its terms forbid accessing the service by any means other than its publicly supported interfaces, and name scraping.
- **Rippling** (since 1.0.2): its job-board endpoint isn't a documented public API, and a terms-of-service review didn't clear it for automated third-party use.

More platforms were checked in 1.0.3 (September 2026) and none could be added. Their URLs are now recognised and reported with the reason instead of "no supported job board":

- **iCIMS**: its terms forbid "any robot, spider or other automatic device, process, or means to access the Website for any purpose", and its only documented jobs API needs credentials.
- **SAP SuccessFactors**: its job feeds are set up by SAP for one named partner at a time ("Partners are asked to not re-distribute customer jobs to other 3rd parties"), and its career hosts' robots.txt disallows automated clients.
- **Oracle Taleo**: no documented public job feed, and Oracle's terms forbid "any robot, spider, scraper or other automated means" without written permission.
- **BambooHR**: its careers-list endpoint isn't documented, and its terms forbid using the service "with any robot, spider, other automated device, or manual process to monitor or copy any content".
- **Jobvite**: its job feed is documented for a company's own websites, and its terms forbid scraping and providing job postings to third parties.
- **JazzHR**: its feed is documented for building "a custom careers page on your company's website", and its API needs a key.
- **Pinpoint**: its acceptable use policy, which applies to anyone using its sites, forbids republishing or distributing its data for commercial use or a third party's benefit.
- **Trakstar Hire**: its acceptable use policy binds every visitor and forbids "letting other persons access or use the Services".
- **JobScore**: its terms limit its content to "personal, non-commercial, and informational purposes".
- **Manatal**: its terms forbid access "for reproduction purposes" and any commercial distribution of its data.
- **Zoho Recruit** and **Homerun**: their job feeds are private links each company shares with the job boards it chooses.
- **Comeet**, **JOIN** and **Freshteam**: their job APIs need a token or key from the company (Freshteam's robots.txt also disallows them).
- **Dover**: its terms forbid robots and scrapers, and its robots.txt disallows its API.
- **Gem**: its job-board API is documented only for a company's own careers page.
- **Polymer**: its documented public API didn't return jobs for live career pages when checked.

### Versions

Current version: **1.0**. See the Changelog tab for what changed in each version.

# Changelog

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

# Actor input Schema

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

One company per line, in any of three forms: a company name (e.g. Stripe), the company's careers page (e.g. https://linear.app/careers), or its job-board URL (e.g. https://boards.greenhouse.io/stripe, or the short form greenhouse:stripe). The list starts with 6 examples, one on each of the 6 platforms; replace them with your own. Names are looked up on Greenhouse, Lever, Ashby, Recruitee, Personio and Teamtailor.

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

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

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

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

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

Keep only jobs whose location or country contains any of these, e.g. London, Germany, US. Add "remote" to also keep remote jobs.

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

Keep only jobs the ATS marks as remote.

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

Keep only jobs published in the last N days. Leave empty for all.

## `onlyNewJobs` (type: `boolean`):

The first run returns all matching jobs; later runs return only jobs posted since. The actor remembers what it already returned in a storage in your own Apify account, separately for each company and each combination of filters, so different searches never hide each other's jobs. Changing the filters starts a fresh search. Use it with a schedule for job alerts or hiring-signal monitoring.

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

Adds the plain-text job description. Turn off for faster, smaller results.

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

Stop after this many jobs in total, across all companies. Leave empty for no limit. The run also stops cleanly at the maximum cost per run you set in the run options, whichever comes first.

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

Stop after this many matching jobs per company. Leave empty for no limit.

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

How many companies are fetched at the same time. The default suits almost every run; lower it if a career site starts rate-limiting.

## Actor input object example

```json
{
  "companies": [
    "Linear",
    "bunq",
    "https://boards.greenhouse.io/greenhouse",
    "https://jobs.lever.co/outreach",
    "https://ottonova.jobs.personio.de",
    "https://career.teamtailor.com"
  ],
  "remoteOnly": false,
  "onlyNewJobs": false,
  "includeDescription": true,
  "maxConcurrency": 5
}
```

# Actor output Schema

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

No description

## `runStats` (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": [
        "Linear",
        "bunq",
        "https://boards.greenhouse.io/greenhouse",
        "https://jobs.lever.co/outreach",
        "https://ottonova.jobs.personio.de",
        "https://career.teamtailor.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("humble-echidna/ats-jobs").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": [
        "Linear",
        "bunq",
        "https://boards.greenhouse.io/greenhouse",
        "https://jobs.lever.co/outreach",
        "https://ottonova.jobs.personio.de",
        "https://career.teamtailor.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("humble-echidna/ats-jobs").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": [
    "Linear",
    "bunq",
    "https://boards.greenhouse.io/greenhouse",
    "https://jobs.lever.co/outreach",
    "https://ottonova.jobs.personio.de",
    "https://career.teamtailor.com"
  ]
}' |
apify call humble-echidna/ats-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,humble-echidna/ats-jobs"
        }
    }
}
```

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/CoamxPurti9bbonKi/builds/nBMGmyYDI1aQfsBHM/openapi.json
