# Careers Page Jobs Scraper — Any Company Website, Any ATS (`northbell/careers-page-jobs-scraper`) Actor

Give company websites, get every open role: finds the job board behind each careers page (Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Recruitee, Personio, Breezy) and reports roles new and closed since your last run. Sites with no board found are listed with the reason. No login.

- **URL**: https://apify.com/northbell/careers-page-jobs-scraper.md
- **Developed by:** [Northbell](https://apify.com/northbell) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 open roles

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

## Careers Page Jobs Scraper — Any Company Website, Any ATS

**You know the companies, not their job boards.** Give this Actor a list of company websites — `stripe.com`, `notion.so` — and it finds the job board behind each careers page, reads every open role from it, and tells you which roles are **new** and which **closed** since your last run. No board names to look up, no login.

**$1 per 1,000 open roles returned**, plus $0.01 per run.

It reads nine applicant-tracking systems through their public job board APIs: **Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Recruitee, Personio and Breezy**. A board API returns every open role at once, so "new since last run" and "closed since last run" are exact, not a sample.

**Measured on 11 October 2026 with 34 well-known company websites (US and Europe): a board was found for 28 of them** — 8 from links on the site, 20 by the company name — 4,923 open roles in one run of 112 seconds. Of the 6 without one, 5 run a careers system of their own (not one of the nine), and 1 is on Workday but its careers page did not answer in time on that run (an earlier run found it).

### How a board is found

Each company row says how its board was found, in `detectedBy`:

| `detectedBy` | how |
|---|---|
| `url` | you gave the job board link itself (`jobs.lever.co/acme`, `acme.wd5.myworkdayjobs.com/Careers`) |
| `page` | the website, or the careers page it links to, links to or embeds the board |
| `name` | nothing on the pages: the company name (from the domain) was asked of Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Personio and Breezy, and the board was kept only because it names the company |

**A company with no board found is not a guess.** It comes back as a company row with `provider: null` and the reason: `no-ats-found` (the pages have no link to a supported board, and no board answers to the name), `site-unreachable`, `board-not-found` (a board link that does not exist) or `not-a-url`. Large companies that run their own careers system (not one of the nine) end up here. Those rows are free.

`nameMatches: false` on a company row means the board found does not carry the site's name: a group site that links to a subsidiary's board, for example. Check those by eye. When the board was found by the company name and more than one job board carries that name, the one with the most open roles is used and the others are listed in `otherBoards` (provider, board, open roles), because two companies can share a name. Turn off **Also try the company name as the board name** to use only links found on the site.

### Run it again and again

- **On a schedule** — save your input as a task and add a **Schedule**: the same company list every morning. Send each run to Google Sheets, Slack or email with an integration on the task.
- **Watch for changes** — Set **What to return** to *Only roles that are new since the last run*: each run returns, and charges for, only the roles that appeared since the last one. Roles that closed come back as free `closed` rows, and each company row counts both.
- **In bulk** — Put every company in **Company websites** (up to 500) in one run.

### What you get

Three kinds of row, tagged by `type`:

- **`job`** — one open role: `title`, `department`, `location`, `remote`, `seniority`, `employmentType`, the pay range when the company published one (`salaryMin`, `salaryMax`, `salaryCurrency`), `postedAt`, `jobUrl`, and `isNewSinceLastRun`. Plus `website`, `provider`, `board` and `detectedBy`. The full text with **Include the full description**.
- **`closed`** — a role that was open on your last run and is gone now, with its title and when it was last seen. Free.
- **`company`** — one per website: the board found, `openRoles`, `newRoles`, `closedRoles` and `netChange` since the last run (or why no board was found). Free.

The Companies view of the dataset is a hiring tracker on its own: which of your companies opened roles this week, and which went quiet.

### Input

```json
{
  "websites": ["stripe.com", "notion.so", "https://jobs.lever.co/acme"],
  "titleKeywords": "engineer, data",
  "output": "changes"
}
```

Filters (title, location, remote, posted within N days, experience level, employment type, published pay) change what is returned, never what is compared: a role you filtered out is not reported as closed.

### Pricing

**$1 per 1,000 open roles returned**, plus $0.01 per run. Closed roles, company rows and companies with no board found are not charged. With **What to return** set to *Only new*, a daily run over the same companies charges only for the roles that appeared since the day before. If a run reaches your **Maximum total charge**, it stops there and remembers only the roles it returned: the rest still count as new on the next run, and closed roles are listed before the charged ones so none are lost.

### For AI agents

Call with `{"websites": ["stripe.com"]}`. Rows have `type`: `job`, `closed` or `company`. A company with no board has `provider: null` and a `reason`; nothing is invented.

### Related

One applicant-tracking system, any company on it, by board name: [Greenhouse Jobs Scraper](https://apify.com/northbell/greenhouse-jobs-scraper) · [Lever Jobs Scraper](https://apify.com/northbell/lever-jobs-scraper) · [Ashby Jobs Scraper](https://apify.com/northbell/ashby-jobs-scraper) · [Workday Jobs Scraper](https://apify.com/northbell/workday-jobs-scraper).

Pay, skills and remote read out of the postings: [Job Posting Parser](https://apify.com/northbell/job-posting-parser).

Also reading the same companies on LinkedIn? [Job Dedupe & Merge](https://apify.com/northbell/job-dedupe-merge) turns both datasets into one row per job, with every URL it was found at.

# Actor input Schema

## `websites` (type: `array`):

One company per line: its website (stripe.com), its careers page, or a job board link (jobs.lever.co/acme). The Actor finds the job board behind the site. Up to 500 per run.

## `output` (type: `string`):

Schedule this Actor daily or weekly with the same companies and pick "Only new" to pay only for new roles. The first run returns everything (there is nothing to compare with yet). Closed roles and the per-company rows are always free.

## `titleKeywords` (type: `string`):

Comma-separated. Keep roles whose title contains any of these, e.g. "engineer, data".

## `locationKeywords` (type: `string`):

Comma-separated. Matched against every location of the role, e.g. "london, remote".

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

Keep only roles the board marks as remote.

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

Keep only roles first published in the last N days. Roles with no publish date are left out when you set this, and counted in OUTPUT.

## `experienceLevels` (type: `array`):

Keep only these levels, read from the job title (the same words on every northbell job scraper; each row also gets a seniority column). Product, account and other non-people "managers" count as mid level.

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

From the board's own employment type where it states one, else from the title. Roles whose type is not stated are left out when you set this, and counted in OUTPUT.

## `withSalaryOnly` (type: `boolean`):

Keep only roles where the company published a pay range.

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

Off by default for a smaller, faster table. On Workday, Workable and SmartRecruiters the text is one more request per returned role.

## `trackChanges` (type: `boolean`):

The board returns every open role at once, so the difference from your previous run is exact — not a sample. First run marks everything as unknown (null).

## `guessFromName` (type: `boolean`):

When the site does not link to its job board (many careers pages are drawn by JavaScript), ask Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Personio and Breezy for a board with the company's name, and keep it only when the board names the company. Rows found this way say detectedBy "name". Turn off to use only links found on the site.

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

Stops returning roles from one company after this many. The new/closed comparison still uses the whole board.

## Actor input object example

```json
{
  "websites": [
    "stripe.com",
    "notion.so",
    "airbnb.com"
  ],
  "output": "all",
  "remoteOnly": false,
  "withSalaryOnly": false,
  "includeDescription": false,
  "trackChanges": true,
  "guessFromName": true,
  "maxJobsPerCompany": 50
}
```

# Actor output Schema

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

Every open role with its fields.

## `companies` (type: `string`):

One row per website: board found, open, new and closed roles.

# 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 = {
    "websites": [
        "stripe.com",
        "notion.so",
        "airbnb.com"
    ],
    "maxJobsPerCompany": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("northbell/careers-page-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 = {
    "websites": [
        "stripe.com",
        "notion.so",
        "airbnb.com",
    ],
    "maxJobsPerCompany": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("northbell/careers-page-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 '{
  "websites": [
    "stripe.com",
    "notion.so",
    "airbnb.com"
  ],
  "maxJobsPerCompany": 50
}' |
apify call northbell/careers-page-jobs-scraper --silent --output-dataset

```

## MCP server setup

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