# Career Site Jobs Scraper (`natanr123/career-site-jobs-scraper`) Actor

Search open jobs by title, country and more across about 3,000 companies' own career sites, or get every open job from your own list of companies (Greenhouse, Lever, Ashby, Workday, Oracle, Workable, Recruitee, Personio, Teamtailor), in one format, with a monitoring mode for new and closed jobs.

- **URL**: https://apify.com/natanr123/career-site-jobs-scraper.md
- **Developed by:** [Natan Rubinstein](https://apify.com/natanr123) (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.40 / 1,000 job with descriptions

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

## Career Site Jobs Scraper

**Search every open job by title, country and more** across about 3,000 companies' own career sites, or give it **your own list of companies** and get all of their openings, live — in one clean format, whichever applicant tracking system (ATS) each company uses. Turn on monitoring and each run returns only the jobs that are **new or changed since the last run**, plus the ones that **closed**.

Supported career sites: **Greenhouse, Lever, Ashby, Workday, Oracle Recruiting Cloud, Workable, Recruitee, Personio and Teamtailor**.

### Why this Actor

- **Search, or your own company list.** Type a job title (plus country, workplace type, employment type or posting age if you like) to search about 3,000 companies' career sites at once, with no list needed. Or paste the companies you care about — customers, prospects, competitors, a portfolio — and get all of their openings, not a sample.
- **Fresh from the source.** Our index re-reads every company it covers every day, so a search answers in seconds. Companies outside the index, or read longer ago than you allow (*Max data age*), are read live from their own boards during your run.
- **One format for nine ATSs.** Title, locations with country codes, workplace type (remote / hybrid / on-site), employment type, pay range when the employer publishes one, posting date, department, full description and a stable job ID.
- **Monitoring built in.** Schedule it daily with *Only jobs that are new or changed* on, and you have a hiring-alert feed. Closed jobs are reported only after they are missing twice in a row, and on boards read page by page (Lever, Workday, Oracle) each one is checked directly first, so a hiccup on a career site never shows up as hundreds of "closed" jobs. A job counts as changed when what a reader sees changes: its title, location, team, employment or workplace type, or listed pay. A company re-saving all its jobs is not a change, so you don't pay for it.
- **Fair pricing.** Pay per job, plus a small fee per company read live. Turn descriptions off for a cheaper list.

### How to use it

**To search:** leave **Companies** empty and fill in **Job title contains any of**, for example `data engineer`, and optionally countries, workplace type, employment type and *Posted within the last N days*. The run returns the most recently posted matching jobs across the companies in our index, up to **Max results** (1,000 by default; 0 returns every match). With monitoring on, each run returns the matches that are new since the last one instead (any beyond *Max results* come in the next run).

**For your own companies:**

1. Paste one company per line into **Companies**: a job-board link (best), or the company's website or name. For example:

   - `https://job-boards.greenhouse.io/gitlab`
   - `https://jobs.lever.co/spotify`
   - `https://jobs.ashbyhq.com/ramp`
   - `https://adobe.wd5.myworkdayjobs.com/external_experienced`
   - `https://apply.workable.com/mercari`

   A short form works too: `greenhouse:gitlab`, `lever:spotify`, `workday:adobe.wd5/external_experienced`.
   To control how a company's name appears in the results, put it in front: `JPMorgan Chase | oracle:jpmc.fa.oraclecloud.com/CX_1001`. Otherwise the name comes from the career site or its address.
2. Optionally set filters (title words, countries, workplace type, posted within N days) and a cap per company.
3. Run it, or schedule it. Download the results as JSON, CSV or Excel, or connect them to n8n, Make, Zapier or your own code through the Apify API.

**Only have websites or names?** Paste `stripe.com` or `Stripe`. The Actor looks for the job board on the company's careers pages, or tries the name on the job systems where that works, and uses a board only when the board confirms it belongs to that company (its own name, or job links on the company's website). Anything it cannot confirm is listed as *not found* in OUTPUT, with the reason, and is not charged; it never guesses. Workday and Oracle sites are found only through the careers pages, so for those a link is the surest input.

Not sure which link a company uses? Open the company's careers page and click any job: the address of the job page shows the system (for example `job-boards.greenhouse.io/...` or `...myworkdayjobs.com/...`).

### What you get

One row per job:

| Field | Example |
|---|---|
| `title` | Sr. Manager Risk & Controls |
| `companyName` | Adobe |
| `location`, `locations[]`, `countryCodes` | San Jose · \[{city, region, countryCode}] · \["US"] |
| `workplaceType` | remote / hybrid / onsite (when the career site says) |
| `employmentType` | full_time, part_time, contract, temporary, internship, other |
| `salary` | {min: 137100, max: 301400, currency: "USD", period: "year", source: "text"} |
| `postedAt`, `postedAtPrecision` | 2026-10-16T00:00:00Z · day |
| `url`, `applyUrl` | the job's own page |
| `descriptionText` / `descriptionHtml` | the full description |
| `department`, `team`, `requisitionId`, `experienceLevel` | when the ATS provides them |
| `jobKey`, `ats`, `boardKey`, `atsJobId` | stable identifiers for de-duplication |
| `changeType`, `firstSeenAt`, `lastSeenAt` | monitoring runs: new / updated / unchanged / closed |
| `source.fetchedAt`, `source.fromCache` | when the job was read from its career site, and whether it came from our daily index |

Each run also writes an **OUTPUT** record listing every company line with its status (ok, empty, not found, blocked…) and how many jobs it returned.

### Pricing

Pay per event:

| Event | Price |
|---|---|
| Job with description | $2.00 per 1,000 jobs |
| Job without description | $0.80 per 1,000 jobs |
| Company checked | $0.003 per company read live from its career site |

Companies taken from our daily index are not charged as checked. Closed-job rows are free. Lower prices apply on higher Apify plans. Set a maximum cost per run in the run options and the Actor stops cleanly when it is reached.

### How it collects data, and the rules it keeps

- It reads only **public job postings**, from the endpoints each company's career site itself uses or that the ATS documents. No logins, no accounts.
- It **obeys robots.txt** on every request and identifies itself in its user agent.
- It **never works around a block**: no CAPTCHA solving, no proxy rotation. A site that refuses is reported as blocked in OUTPUT.
- It asks politely: about one request per second per site.
- **No personal data**: recruiter names and contact fields are never collected, and emails and phone numbers are removed from descriptions.
- Some sites ask that their content not be used to train AI models. Where they say so, each job carries that signal in `source.contentSignals` (for example `ai-train=no`). Please respect it.

You are responsible for how you use the data. Respect each site's terms and the law where you work.

### FAQ

**Which companies does a search cover?** About 3,000 companies with the most openings on Greenhouse, Lever, Ashby, Workday, Workable, Recruitee, Personio and Teamtailor, and more are added over time. Any other company works too: list it in *Companies* and it is read live.

**Which companies can I add?** Any company whose jobs are on one of the supported systems. Lines the Actor cannot match to a supported board are listed in OUTPUT (not found, unsupported or duplicate) and are not charged.

**How fresh is the data?** Our index re-reads its companies every day, and a run uses that copy when it is more recent than *Max data age* (30 hours by default). Set *Max data age* to 0 to read every company live, exactly as its career site shows it at that moment. Every job's `source.fetchedAt` says when it was read.

**Why does Workday take longer?** Workday lists 20 jobs per page and needs one request per job for the description, so large Workday employers take a few minutes. Turn descriptions off for a fast list. Very large Workday boards are read country by country to get past Workday's 2,000-job display limit.

**Does it work for "jobs at company X" questions from AI agents?** Yes: call the Actor with one company and read the dataset.

*This Actor is not affiliated with or endorsed by Greenhouse, Lever, Ashby, Workday, Oracle, Workable, Recruitee, Personio or Teamtailor. Their names are used only to say which career sites it can read.*

# Actor input Schema

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

Jobs whose title contains at least one of these words or phrases (not case-sensitive), for example data engineer, product manager, nurse. With no companies listed, this searches every company in our index.

## `countries` (type: `array`):

Two-letter country codes, for example US, GB, DE, IL. Jobs whose country cannot be determined are left out when this is set.

## `workplaceTypes` (type: `array`):

Keep only these. Jobs whose career site does not say are left out when this is set.

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

Keep only these. Jobs whose career site does not say are left out when this is set (a title like "Intern" counts as saying).

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

Keep only jobs posted in the last N days. Workday shows only "Posted 30+ Days Ago" for older jobs, so those count as 30 days old.

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

Leave empty to search every company in our index with the filters above. Or list companies, one per line, to get their jobs (any company on a supported system, in our index or not). Best: the company's job-board link, for example https://job-boards.greenhouse.io/gitlab, https://jobs.lever.co/spotify, https://jobs.ashbyhq.com/ramp, https://adobe.wd5.myworkdayjobs.com/external_experienced, an Oracle CandidateExperience link, https://apply.workable.com/mercari, https://acme.recruitee.com, https://acme.jobs.personio.de or https://acme.teamtailor.com (short form: greenhouse:gitlab). You can also paste the company's website (stripe.com) or name (Stripe): the Actor looks for the job board on the company's careers pages and confirms it belongs to that company. Lines it cannot confirm are listed as not found in OUTPUT and are not charged. Optionally put the company's name in front, separated by a bar: Acme Corp | https://jobs.lever.co/acme.

## `maxResults` (type: `integer`):

The most jobs this run returns, all companies together (closed-job rows don't count). A search without companies returns the most recently posted matches. 0 means no limit.

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

On: every job comes with its full description (billed as "Job with description"). Off: list fields only (billed as "Job without description"), and Workday and Oracle boards need far fewer requests.

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

Plain text keeps headings and bullet points as lines. HTML is the employer's original markup. Emails and phone numbers are removed from both.

## `includeSalaryDetails` (type: `boolean`):

Greenhouse returns structured pay ranges only one job at a time, so this costs one extra request per Greenhouse job. Off: pay is still read from the description text, where US postings usually state it. Lever, Ashby and Recruitee pay ranges are always included.

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

Stop after this many jobs from each company. 0 means all of them.

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

Remembers which jobs earlier runs returned (per monitor name, in a key-value store in your account) and outputs only jobs that are new or whose details changed. Schedule the Actor daily to get a feed of new openings.

## `monitorName` (type: `string`):

Separate monitors keep separate memories. Use a different name for each company list or search you schedule. Lowercase letters, digits and hyphens.

## `emitExistingOnFirstRun` (type: `boolean`):

On: the first run of a monitor outputs every current job. Off: the first run only remembers them, so later runs output just what is new.

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

Adds a row with changeType "closed" for every job that disappeared from its career site. A job counts as closed after it is missing from two runs in a row, so a hiccup on the career site never reports open jobs as closed. Closed rows are free.

## `maxDataAgeHours` (type: `integer`):

Our index re-reads every company it covers each day. Companies it read within this many hours are taken from it: fast, and not charged as "Company checked". Other companies are read live from their career sites. 0 means always read live.

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

How many career sites to read at the same time, of them at most 10 on Workday, whose sites share its servers. Each site is still asked politely, at most about one request per second.

## Actor input object example

```json
{
  "titleKeywords": [
    "data engineer"
  ],
  "countries": [
    "US"
  ],
  "maxResults": 100,
  "includeDescription": true,
  "descriptionFormat": "text",
  "includeSalaryDetails": false,
  "maxJobsPerCompany": 0,
  "onlyNewJobs": false,
  "monitorName": "default",
  "emitExistingOnFirstRun": true,
  "reportClosedJobs": false,
  "maxDataAgeHours": 30,
  "maxConcurrency": 10
}
```

# Actor output Schema

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

One item per job (or per closed job in monitoring mode), in the dataset's Overview, Details and Changes views.

## `summary` (type: `string`):

The OUTPUT record: for every input line, its status (ok, empty, not_found, blocked, unsupported...), jobs listed and jobs output.

# 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 = {
    "titleKeywords": [
        "data engineer"
    ],
    "countries": [
        "US"
    ],
    "maxResults": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("natanr123/career-site-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 = {
    "titleKeywords": ["data engineer"],
    "countries": ["US"],
    "maxResults": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("natanr123/career-site-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 '{
  "titleKeywords": [
    "data engineer"
  ],
  "countries": [
    "US"
  ],
  "maxResults": 100
}' |
apify call natanr123/career-site-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,natanr123/career-site-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/8Kh1QBp29opS8BKyR/builds/IS7My2zJl349B2j0c/openapi.json
