# AI/ML Jobs Feed + Careers Page Scrape (`hardened_metapod/ats-jobs-scraper`) Actor

Get every open job from any company's careers board in one clean, unified format. Paste company names or careers-page URLs; the Actor detects the ATS (Greenhouse, Lever, Ashby, Workable, Recruitee, SmartRecruiters) and returns titles, locations, remote flag, salary, dates and full descriptions.

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

## Pricing

from $1.25 / 1,000 job results

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

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

## What's an Apify Actor?

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

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

## How to integrate an Actor?

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

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

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

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

# README

## ATS Jobs Scraper – Greenhouse, Lever, Ashby, Workable, Recruitee & SmartRecruiters

Get **every open job from any company's careers board in one clean, unified format** – without writing a scraper per ATS. Paste company slugs, ATS board links, or even a company's own careers page; the Actor detects which applicant-tracking system the company uses and pulls the postings from that ATS's **official public job-board API**.

**Why this Actor**

- **6 ATSs, one schema** – Greenhouse, Lever (incl. EU), Ashby, Workable, Recruitee, SmartRecruiters. Same fields for every job, so you can mix 500 companies in one run.
- **Auto-detect** – give it `stripe`, `https://www.figma.com/careers/` or `https://jobs.ashbyhq.com/ramp` and it figures out the rest.
- **Pay only for matching jobs** – title, location, remote and date filters run *before* charging.
- **Salary** – structured pay ranges where the ATS publishes them (Ashby, Lever, Greenhouse pay transparency, Recruitee) plus best-effort extraction from pay-transparency text in descriptions.
- **Fast & reliable** – plain HTTPS JSON APIs, no browser, no proxies, retries with back-off. ~1,000 jobs in a few seconds.
- **AI/ML daily new jobs mode** – a clean, de-duplicated daily feed of new AI/ML roles posted *directly by employers* (30 curated AI companies built in). No agency reposts, no junk rows, no repeats from yesterday.
- **Clear failure report** – a `RUN_SUMMARY` record in the key-value store lists every input with status (`ok`, `not_found`, `error`) and counts.

### Use cases

- Job aggregators and niche job boards (remote, AI, climate, crypto…)
- Recruiting & sales intelligence: who is hiring for what, where, and how fast
- Hiring-signal alerts for target accounts (schedule daily runs + `postedWithinDays: 1`)
- Labor-market and compensation research

### Input

| Field | Description |
|---|---|
| `companies` (required) | List of slugs (`stripe`), `ats:slug` (`lever:spotify`), ATS URLs, or careers-page URLs |
| `ats` | For plain slugs: `auto` (default, probes all ATSs) or a specific ATS |
| `includeDescription` | Adds `descriptionText` + `descriptionHtml` (default `true`) |
| `titleKeywords` / `excludeTitleKeywords` | Case-insensitive title filters |
| `locationKeywords` | Match against location, all locations, country, "remote" |
| `remoteOnly`, `postedWithinDays` | Remote and recency filters |
| `maxJobsPerCompany`, `maxJobs` | Caps (0 = unlimited) |

```json
{
  "companies": ["stripe", "https://jobs.lever.co/spotify", "ashby:ramp", "https://www.notion.so/careers"],
  "titleKeywords": ["engineer"],
  "locationKeywords": ["remote", "new york"],
  "includeDescription": true
}
```

### Output (one item per job)

```json
{
  "ats": "ashby", "companySlug": "notion", "company": "Notion",
  "jobId": "1fc309c8-da20-4ff2-84c7-8b863ece2b0a",
  "title": "Software Engineer, AI Connections (Developer Platform)",
  "department": "Engineering", "team": "Engineering",
  "location": "San Francisco, California",
  "allLocations": ["San Francisco, California", "New York, New York"],
  "country": "United States", "isRemote": true, "workplaceType": "hybrid",
  "employmentType": "FullTime",
  "salary": { "min": 211400, "max": 290600, "currency": "USD", "period": "1 YEAR", "text": "$211.4K – $290.6K • Offers Equity" },
  "postedAt": "2026-08-24T14:44:49.699Z", "updatedAt": null,
  "url": "https://jobs.ashbyhq.com/notion/1fc309c8-...", "applyUrl": "https://jobs.ashbyhq.com/notion/1fc309c8-.../application",
  "descriptionText": "…", "descriptionHtml": "…",
  "companyInput": "https://www.notion.so/careers", "scrapedAt": "2026-10-28T23:21:08.658Z"
}
```

Fields the source ATS doesn't publish are `null` (e.g. Greenhouse has no employment type).

### AI/ML daily new jobs mode (`mode: "aiMlDaily"`)

Built for recruiters, AI job boards and talent-intel teams who are tired of paying for job dumps full of junk rows and agency reposts. Schedule it daily and you get only what is new.

```json
{ "mode": "aiMlDaily" }
```

That's a complete input: with `companies` empty, it uses a built-in list of 30 AI/ML employers (Anthropic, OpenAI, Databricks, xAI, Scale AI, Cohere, Perplexity, ElevenLabs, Together AI, Harvey, Sierra, Cursor, Zoox and more), each verified to return jobs. Add your own `companies` to replace the list.

What the mode does, in order (all **before** charging, so you only pay for rows you keep):

1. **Junk removal** – drops rows with no title or URL, "general application / talent pool" placeholders, expired postings, and anything older than `maxAgeDays` (default 60).
2. **AI/ML role filter** – a title taxonomy (`roleCategory`: `ml-engineering`, `research`, `llm-genai`, `data-science`, `domain-ml`, `ai-general`). Non-technical roles that carry "AI" in the title (recruiter, sales, marketing, legal, policy…) are excluded. Override with `roleKeywords` / `excludeRoleKeywords`.
3. **Employers only** – drops staffing/recruiting agencies by a known-agency list, agency-style company names ("… Staffing", "… Recruitment") and agency phrases in the description ("on behalf of our client", "C2C"). Add names with `extraAgencyNames`.
4. **De-duplication** – reposts are matched on a normalized title + company + location hash (`dedupeKey`; "Sr. ML Engineer" = "Senior Machine Learning Engineer"). The same role posted in several cities becomes one row with all cities in `allLocations` (`mergeLocations`).
5. **New since last run** – returned jobs are remembered for 120 days in the key-value store `ats-jobs-state` (key per `stateKey`) and skipped next time. `postedAfter` (YYYY-MM-DD) adds a hard date floor. A job is only remembered once it is actually saved, so a run stopped by your cost cap doesn't lose jobs.
6. **Normalized fields** – `seniority` (intern, junior, mid, senior, staff, principal, lead, manager, director, vp, executive), `remoteType` (remote, hybrid, onsite, unknown) and `salaryNormalized` (`minAnnual`, `maxAnnual`, `currency`, `sourcePeriod`; hourly ×2080, monthly ×12).

`RUN_SUMMARY.aiMlDaily` shows how many rows were dropped and why. Live test (2026-10-29, default list, `postedAfter` 2026-10-15): 5,500+ postings scanned in 8.5 s, **70 new AI/ML jobs** kept; a second run on the same state returned 0 repeats.

### Pricing

**Price:** $1.25 per 1,000 jobs (about 0.1¢ per job) on the Free plan, with lower prices on paid Apify plans (SILVER $1.10/1k, GOLD $0.95/1k). No monthly rental, and platform usage is included.

**AI/ML daily mode** is priced separately: a flat fee per run (`aiml-run`) plus a fee per new job saved (`aiml-job`): $0.05 per run + $10 per 1,000 new jobs (about $0.75 for a typical day of ~70 new jobs) — see the Pricing tab.

Pay-per-result: in generic mode you are charged per job saved to the dataset (see the Pricing tab). Filtered-out jobs, failed companies and retries are free. In AI/ML daily mode, dropped junk, agency, duplicate and already-seen jobs are free too, so a daily run usually costs cents. Set **Max cost per run** to cap spend – the Actor stops cleanly when it's reached.

### Tips & limits

- If a slug isn't found, pass the careers-page URL or `ats:slug`. The board slug is the part after the ATS domain, e.g. `boards.greenhouse.io/<slug>`.
- Careers pages that block bots or render the ATS link only via JavaScript can't be auto-detected – use the ATS board URL instead.
- Workday, iCIMS, Taleo and other ATSs without an official public job-board API are not supported.
- Only public job postings are collected – no candidate or employee personal data.

### FAQ

**Is this legal?** The Actor reads job postings that companies publish through their ATS's public job-board APIs, which exist precisely so job listings can be syndicated. You are responsible for how you use the data.
**How fresh is it?** Live – each run reads the boards at that moment.

# Actor input Schema

## `mode` (type: `string`):

"AI/ML daily new jobs" keeps only AI/ML roles (title taxonomy), drops staffing agencies and junk rows, merges reposts and multi-location duplicates, adds seniority/remote/annual-salary fields and, by default, returns only jobs not returned by earlier runs. Schedule it daily for a clean feed.

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

One per line. Accepts a board slug ("stripe"), an explicit ATS prefix ("lever:spotify", "ashby:ramp"), an ATS board URL (https://boards.greenhouse.io/airbnb, https://jobs.ashbyhq.com/ramp) or a company's own careers page (https://www.example.com/careers) – the Actor detects the embedded ATS. In "AI/ML daily new jobs" mode you can leave this empty to use the built-in list of 30 AI/ML employers.

## `ats` (type: `string`):

Which ATS to try for plain slugs. "auto" probes all supported ATSs in order and uses the first board that has jobs.

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

Adds descriptionText (clean plain text) and descriptionHtml. For SmartRecruiters this needs one extra request per job.

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

Keep only jobs whose title contains at least one of these words (case-insensitive). Filtering happens before charging – you only pay for matching jobs.

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

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

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

Keep only jobs whose location/country contains one of these (e.g. "London", "US", "Remote").

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

Keep only jobs flagged remote by the ATS or with 'remote' in the location.

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

0 = no date filter. Jobs without a posting date are kept.

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

0 = unlimited.

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

0 = unlimited. The run also stops automatically when your 'Max cost per run' is reached.

## `roleKeywords` (type: `array`):

AI/ML mode only. Leave empty to use the built-in taxonomy (machine learning, ML engineer, research scientist, LLM, inference, computer vision, NLP, data scientist, AI engineer…). If set, these keywords replace it. Short acronyms (ML, AI, NLP) match whole words only.

## `excludeRoleKeywords` (type: `array`):

AI/ML mode only. Leave empty for the default list of non-technical roles (recruiter, sales, account executive, marketing, legal…). Provide your own list to replace it.

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

AI/ML mode only. Remembers returned jobs (by normalized title + company + location) in the key-value store "ats-jobs-state" and skips them next time. Memory lasts 120 days.

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

AI/ML mode only. Use a different name to keep separate 'seen' lists for different schedules or company lists.

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

AI/ML mode only. Skip jobs posted before this date (YYYY-MM-DD). Jobs without a posting date are kept.

## `maxAgeDays` (type: `integer`):

AI/ML mode only. Postings older than this are dropped as stale. 0 = off.

## `mergeLocations` (type: `boolean`):

AI/ML mode only. One row per role per company; extra locations are merged into allLocations.

## `excludeAgencies` (type: `boolean`):

AI/ML mode only. Drops postings from known staffing agencies or whose company name/description reads like an agency ('our client', 'staffing firm', C2C…).

## `extraAgencyNames` (type: `array`):

AI/ML mode only. Added to the built-in agency list.

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

How many companies to fetch at once.

## Actor input object example

```json
{
  "mode": "generic",
  "companies": [
    "stripe",
    "https://jobs.lever.co/spotify",
    "ashby:ramp"
  ],
  "ats": "auto",
  "includeDescription": true,
  "remoteOnly": false,
  "postedWithinDays": 0,
  "maxJobsPerCompany": 0,
  "maxJobs": 0,
  "onlyNewSinceLastRun": true,
  "stateKey": "ai-ml-daily",
  "maxAgeDays": 60,
  "mergeLocations": true,
  "excludeAgencies": true,
  "concurrency": 5
}
```

# Actor output Schema

## `overview` (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",
        "https://jobs.lever.co/spotify",
        "ashby:ramp"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("hardened_metapod/ats-jobs-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "companies": [
        "stripe",
        "https://jobs.lever.co/spotify",
        "ashby:ramp",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("hardened_metapod/ats-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "companies": [
    "stripe",
    "https://jobs.lever.co/spotify",
    "ashby:ramp"
  ]
}' |
apify call hardened_metapod/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

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