# Company Career Pages Scraper (`automation-lab/company-career-page-jobs-scraper`) Actor

Extract normalized public jobs from supplied career pages across five supported ATS platforms and JobPosting JSON-LD for recurring hiring feeds.

- **URL**: https://apify.com/automation-lab/company-career-page-jobs-scraper.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 4 total users, 3 monthly users, 70.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

## Company Career Pages Scraper

Extract normalized public job listings from supplied **company career pages**.
Give the Actor one or more career-page URLs and receive consistent job records with
title, company, location, department, employment type, description, publication date,
job URL, and application URL.

The Actor detects public Greenhouse, Lever, Ashby, SmartRecruiters, and Recruitee
boards. It also reads standard `JobPosting` JSON-LD from other server-rendered career
pages. The result is one dataset ready for recurring hiring-intelligence feeds,
spreadsheets, job boards, and data pipelines.

### What does Company Career Pages Scraper do?

The Actor turns several ATS-specific formats into one stable record shape.

It can:

- extract current public jobs from up to 50 supplied career pages;
- combine multiple companies and supported ATS platforms in one run;
- filter by keyword, location, or department;
- include full plain-text descriptions or omit them for compact feeds;
- deduplicate jobs by source and native identifier;
- stop at a global `maxItems` limit;
- preserve canonical job and application links;
- report failed sources without discarding successful sources.

Unlike company-domain discovery tools, this Actor starts from the career pages you
already know. That makes scheduled runs predictable and avoids guessing which page
belongs to an employer.

### Who is it for

**Recruiting intelligence teams** can refresh competitor hiring activity.

**Job-board operators** can normalize roles from customer-approved public sources.

**Labor-market analysts** can compare departments, locations, and role families.

**Sales and investment teams** can track hiring as a company-growth signal.

**Data engineers** can send a consistent dataset into warehouses or spreadsheets.

**Candidates and career researchers** can search several employer boards together.

### Supported career pages

| Source | Example URL shape | Extraction route |
| --- | --- | --- |
| Greenhouse | `https://boards.greenhouse.io/company` | Public board API |
| Lever | `https://jobs.lever.co/company` | Public postings API |
| Ashby | `https://jobs.ashbyhq.com/company` | Public posting API |
| SmartRecruiters | `https://careers.smartrecruiters.com/Company` | Public company API |
| Recruitee | `https://company.recruitee.com` | Public offers API |
| Other public pages | A direct career or job-detail URL | `JobPosting` JSON-LD |

The generic route requires server-rendered `JobPosting` structured data. A page that
loads jobs only after browser interaction is not silently treated as an empty board.
It fails with an actionable unsupported-page message.

### What job data can you extract?

| Field | Meaning |
| --- | --- |
| `recordId` | Stable source-prefixed identity used for deduplication |
| `sourceType` | Detected ATS or `jsonld` route |
| `sourceId` | Native job ID or canonical structured-data identity |
| `sourceUrl` | Career-page URL supplied in the run input |
| `title` | Published role title |
| `company` | Employer name from the source or your override |
| `location` | Published location, when available |
| `department` | Department, function, or team, when available |
| `employmentType` | Full-time, contract, internship, or source equivalent |
| `workplaceType` | Remote, hybrid, onsite, or source equivalent |
| `remote` | Explicit remote flag when the source provides one |
| `publishedAt` | Publication timestamp when available |
| `updatedAt` | Update timestamp when available |
| `description` | Normalized plain-text description when requested |
| `jobUrl` | Canonical public job detail URL |
| `applicationUrl` | Public application URL |
| `scrapedAt` | Time the Actor normalized the record |

Fields unavailable from a source are returned as `null`; the Actor does not invent
missing dates, workplace types, or departments.

### How to scrape company career pages

1. Open the Actor input page.
2. Add one or more public career-page URLs under **Career page URLs**.
3. Optionally add `companyName` to override an unclear ATS account name.
4. Add keyword, location, or department filters if needed.
5. Choose the maximum number of jobs.
6. Keep **Include descriptions** enabled for analysis, or disable it for compact feeds.
7. Start the run.
8. Open the **Jobs** dataset view or export JSON, CSV, Excel, or XML.

Start with a small limit when testing a new source. Once the output is correct, reuse
the same input in an Apify Schedule for recurring refreshes.

### Input parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `startUrls` | array | required | 1–50 public career-page URLs; entries may include `companyName` |
| `keyword` | string | empty | Case-insensitive phrase matched across normalized job text |
| `locations` | string\[] | `[]` | Keep locations containing any supplied value |
| `departments` | string\[] | `[]` | Keep departments or teams containing any supplied value |
| `maxItems` | integer | `1000` | Global result limit from 1 to 10,000 |
| `includeDescription` | boolean | `true` | Include normalized plain-text job descriptions |

All filters apply consistently regardless of whether a source uses an ATS adapter or
standard JSON-LD.

### Example input

```json
{
  "startUrls": [
    { "url": "https://jobs.ashbyhq.com/notion", "companyName": "Notion" },
    { "url": "https://careers.smartrecruiters.com/SmartRecruiters", "companyName": "SmartRecruiters" }
  ],
  "keyword": "engineering",
  "locations": [],
  "departments": ["Engineering"],
  "includeDescription": false,
  "maxItems": 25
}
```

This input builds one normalized engineering feed from two different ATS platforms.

### Example output

```json
{
  "recordId": "ashby:4bbfad88-0830-46c5-8d05-d95d17d583ca",
  "sourceType": "ashby",
  "sourceId": "4bbfad88-0830-46c5-8d05-d95d17d583ca",
  "sourceUrl": "https://jobs.ashbyhq.com/notion",
  "title": "Model Behavior Engineer",
  "company": "Notion",
  "location": "New York, New York",
  "department": "Engineering",
  "employmentType": "FullTime",
  "workplaceType": "OnSite",
  "remote": false,
  "publishedAt": "2026-06-18T17:06:55.020Z",
  "updatedAt": null,
  "description": null,
  "jobUrl": "https://jobs.ashbyhq.com/notion/4bbfad88-0830-46c5-8d05-d95d17d583ca",
  "applicationUrl": "https://jobs.ashbyhq.com/notion/4bbfad88-0830-46c5-8d05-d95d17d583ca/application",
  "scrapedAt": "2026-08-11T20:30:00.000Z"
}
```

The default dataset contains only validated job records. Source errors remain in the
run log and are not billed as jobs.

### How much does it cost to extract company career page jobs?

Pricing uses one **Career page run** event plus one **Normalized job** event per saved
record. The current BRONZE prices are $0.001 per run and $0.0018 per job. Higher-volume
Apify pricing tiers reduce the per-job amount.

Illustrative BRONZE totals:

| Saved jobs | BRONZE calculation |
| ---: | ---: |
| 1 | 1 run × $0.001 + 1 job × $0.0018 |
| 5 | 1 run × $0.001 + 5 jobs × $0.0018 |
| 10 | 1 run × $0.001 + 10 jobs × $0.0018 |
| 25 | 1 run × $0.001 + 25 jobs × $0.0018 |
| 100 | 1 run × $0.001 + 100 jobs × $0.0018 |

Filtering occurs before the per-job charge. Failed pages, duplicates, and rejected
records do not create a Normalized job event. Apify plan and tier assignment determine
the exact active unit price shown in Console.

### Build a recurring hiring-intelligence feed

Create an Apify Schedule with a fixed list of career pages. Send each completed dataset
to a webhook, Google Sheets integration, or warehouse. Compare `recordId` values with
the previous run to identify newly opened and removed roles.

For change detection, retain `recordId`, `publishedAt`, `updatedAt`, and `scrapedAt`.
The Actor returns the current public snapshot; it does not maintain history or send
alerts by itself.

### Export jobs to spreadsheets and data pipelines

Every run writes standard Apify dataset records. You can:

- download CSV or Excel for recruiting research;
- stream JSON into a warehouse;
- connect the dataset to Make or Zapier;
- trigger a webhook after scheduled runs;
- query only selected fields through the Dataset API;
- use `recordId` as an upsert key.

For compact feeds, set `includeDescription` to `false` before exporting.

### Run with the Apify API using cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~company-career-page-jobs-scraper/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "startUrls": [{"url": "https://jobs.ashbyhq.com/notion", "companyName": "Notion"}],
    "keyword": "engineering",
    "maxItems": 10,
    "includeDescription": false
  }'
```

The response contains the run ID and default dataset ID.

### Run with JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/company-career-page-jobs-scraper').call({
  startUrls: [{ url: 'https://jobs.ashbyhq.com/notion', companyName: 'Notion' }],
  keyword: 'engineering',
  maxItems: 10,
  includeDescription: false,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Run with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/company-career-page-jobs-scraper').call(run_input={
    'startUrls': [{'url': 'https://jobs.ashbyhq.com/notion', 'companyName': 'Notion'}],
    'keyword': 'engineering',
    'maxItems': 10,
    'includeDescription': False,
})

for item in client.dataset(run['defaultDatasetId']).iterate_items():
    print(item)
```

### Use Company Career Pages Scraper with MCP

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/company-career-page-jobs-scraper"
```

#### Claude Desktop

Add this server entry to the Claude Desktop MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/company-career-page-jobs-scraper"
    }
  }
}
```

#### Cursor

Open **Settings → MCP**, add a remote server named `apify`, and use the same
`https://mcp.apify.com?tools=automation-lab/company-career-page-jobs-scraper` URL.

#### VS Code

Add the remote Apify MCP URL to your workspace MCP configuration, then enable the
`automation-lab/company-career-page-jobs-scraper` tool for the chat agent.

#### Example prompts for MCP

- “Use Company Career Pages Scraper to extract engineering roles from these three company career pages.”
- “Run the career page Actor and return London jobs from this Lever board as a normalized dataset.”
- “Use the Apify MCP tool to build a feed from these Ashby, Recruitee, and SmartRecruiters pages.”

### Reliability, retries, and partial failures

Transient timeouts, rate limits, and common server errors receive bounded retries.
Permanent 4xx responses are not retried blindly. The Actor fetches sources sequentially
to keep traffic conservative and stops scheduling output after `maxItems`.

If one URL fails but another succeeds, the run preserves the successful jobs and logs
the failed source. If every URL fails, the run fails instead of returning a misleading
empty dataset.

No residential proxy, browser, login, or cookie fallback is used. Supported public
routes currently return the promised fields without those paid resources.

### Limitations

- The Actor does not discover a career page from a generic company homepage.
- Supported ATS coverage is Greenhouse, Lever, Ashby, SmartRecruiters, and Recruitee.
- Other pages must expose server-rendered `JobPosting` JSON-LD.
- Workday, Oracle HCM, and browser-only custom portals are not supported by this Actor.
- Output completeness depends on fields published by the employer and ATS.
- `applicationUrl` can equal `jobUrl` when a source exposes one canonical link.
- The Actor returns a current snapshot, not historical changes.
- A source can change its public endpoint or structured markup without notice.

Use source-specific Automation Lab Actors when you need an unsupported portal or
source-specific fields beyond this normalized contract.

### Responsible use and legality

Use the Actor only for public job data you are permitted to collect. Review employer
website terms, robots guidance, applicable database rights, and local law. Do not use
job data to discriminate unlawfully, identify private applicants, bypass access
controls, or automate prohibited outreach.

Job descriptions may be copyrighted. Store and redistribute only what your use case
allows. You are responsible for retention, attribution, and downstream processing.

### Troubleshooting

**The run says no `JobPosting` JSON-LD was found.**

Use the direct ATS board URL when possible. The supplied custom page may load jobs only
in a browser or may link to a supported ATS on another domain.

**A supported board returns HTTP 404.**

Check that the URL includes the current public company/account slug. Retired or renamed
boards return a permanent error.

**My filters returned zero jobs.**

Run once without filters and inspect the source's exact location and department text.
Filters use case-insensitive substring matching, but they cannot match fields the source
does not publish.

**Descriptions are empty.**

Confirm `includeDescription` is `true`. Some list APIs or JSON-LD records do not publish
a description; that field remains `null` rather than being fabricated.

### Related Automation Lab Actors

- [Multi-ATS Career Site Jobs API](https://apify.com/automation-lab/multi-ats-career-site-jobs-api) for a focused Greenhouse and Lever workflow.
- [Workday Jobs Scraper](https://apify.com/automation-lab/workday-jobs-scraper) for public Workday career sites.
- [Jobvite Careers Jobs Scraper](https://apify.com/automation-lab/jobvite-careers-jobs-scraper) for Jobvite boards and source-specific fields.

Choose this Actor when you already have several supplied career-page URLs and want one
normalized dataset across supported platforms.

### FAQ

**Can I schedule this Actor?**

Yes. Save the input, create an Apify Schedule, and use datasets or webhooks to compare
successive snapshots.

**Can I scrape several companies in one run?**

Yes. Supply up to 50 career pages. `maxItems` applies across the entire combined run.

**Does it scrape private or login-only jobs?**

No. It handles public career pages only and does not accept credentials.

**Does it use AI to infer missing job fields?**

No. Normalization is deterministic. Missing source fields remain `null`.

**Will filters cost extra?**

No separate filter event exists. Only saved normalized jobs create item events.

**Can I export directly to CSV?**

Yes. Open the run dataset and choose CSV, Excel, JSON, XML, or another supported format.

**How do I monitor new roles?**

Schedule recurring runs and upsert or compare by `recordId`. History and alerts belong
in your downstream workflow.

# Actor input Schema

## `startUrls` (type: `array`):

Public company career pages. Greenhouse, Lever, Ashby, SmartRecruiters, and Recruitee boards are detected automatically; other pages must publish JobPosting JSON-LD. Add companyName to override the inferred employer name.

## `keyword` (type: `string`):

Keep jobs containing this case-insensitive phrase in the title, company, location, department, employment type, or description.

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

Keep jobs whose location contains any supplied value. Leave empty for all locations.

## `departments` (type: `array`):

Keep jobs whose department or team contains any supplied value. Leave empty for all departments.

## `maxItems` (type: `integer`):

Maximum number of matching jobs saved across all supplied career pages.

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

Include normalized plain-text job descriptions. Disable for smaller, faster monitoring feeds.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://jobs.ashbyhq.com/notion",
      "companyName": "Notion"
    }
  ],
  "keyword": "",
  "locations": [],
  "departments": [],
  "maxItems": 10,
  "includeDescription": true
}
```

# Actor output Schema

## `overview` (type: `string`):

Normalized jobs in the overview dataset view.

# 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 = {
    "startUrls": [
        {
            "url": "https://jobs.ashbyhq.com/notion",
            "companyName": "Notion"
        }
    ],
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/company-career-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 = {
    "startUrls": [{
            "url": "https://jobs.ashbyhq.com/notion",
            "companyName": "Notion",
        }],
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/company-career-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 '{
  "startUrls": [
    {
      "url": "https://jobs.ashbyhq.com/notion",
      "companyName": "Notion"
    }
  ],
  "maxItems": 10
}' |
apify call automation-lab/company-career-page-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/company-career-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/C3iZjdLXrBnB8qBlE/builds/maNeDbjL4k65xZNaB/openapi.json
