# Career Page Jobs: careers page or domain in, jobs out | $1/1K (`siftsmith/career-page-jobs`) Actor

Paste careers page URLs or domains; get open jobs from the board each page links (Greenhouse, Lever, Ashby, Workday, Workable, Breezy HR, Personio, Rippling) or, if none, one matched to its domain. $0.001 per job row; unreadable pages and ATSes we don't read are free. Names: use Company Jobs.

- **URL**: https://apify.com/siftsmith/career-page-jobs.md
- **Developed by:** [Siftsmith](https://apify.com/siftsmith) (community)
- **Categories:** Jobs, Lead generation, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 job rows

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

## Career Page Jobs

Paste a company's careers page URL or domain, get its open jobs. Career Page Jobs reads the job board the company's own page links (Greenhouse, Lever, Ashby, Workday, Workable, Breezy HR, Personio or Rippling) or, when it links none, a board matched to its domain, and returns its open jobs in one schema.

**$1 per 1,000 job rows ($0.001 each), no subscription, no start fee.** Pages we can't read, pages that list several employers' jobs, and ATSes we don't read are free. You need no ATS account, API key or proxy.

Built for recruiters and staffing agencies, job boards, and sales teams tracking which accounts are hiring.

**Free coverage check:** open an issue on this Actor's Issues tab with up to 20 careers page URLs or domains. We run them through this Actor for free and reply in the issue with the result for each: the job board and how many open jobs it lists, or the reason no jobs come back. Issues are public, so list only employers, not personal details.

Have company names instead of pages? A name on its own gets a free status row here that points you to [Company Jobs](https://apify.com/siftsmith/company-jobs), which takes names, domains and board URLs, at the same price.

### How it works

1. **Give careers pages or domains**, one per line: `https://figma.com/careers`, `https://www.notion.com/careers`, `vercel.com`. Rows pasted from a spreadsheet work too, when they have a website or careers page column (`Figma	https://figma.com/careers`).
2. **The page's own link comes first.** We read the page you gave, one listing page it links and the company's homepage (for a domain: its homepage, the careers page it links and one listing page), and collect the job boards they link.
3. **A linked board is charged only when it ties back to the company.** Its website or job links are on the company's site, or the board's name is the domain's or the name the company's homepage gives itself. Each job row says how: `matchConfidence` (`high` or `medium`) and `matchedBy: "careersPageLink"`.
4. **No board link at all?** The company is matched by its domain, as Company Jobs does (`matchedBy` is then `name` or `site`, not `careersPageLink`).

Every input also gets one free status row (type `company`): found or not, why not, the board, `openJobs` and the jobs posted in the last 30 days.

**How often it finds jobs:** in a random sample of 50 single-employer careers pages and domains (October 2026), 17 returned jobs: 7 from the board the page links and 10 by matching the domain. The other 33 got a free status row saying why: for 19 we found no board on an ATS we read, 7 had a board we couldn't tie to the company, 4 had a page we couldn't read, 2 use an ATS we don't read, and 1 redirected off the company's site.

### What it doesn't cover

- **Careers pages on ATSes we don't read:** iCIMS, Oracle Recruiting, SAP SuccessFactors, Taleo, UKG, Jobvite and SmartRecruiters (its job API's robots.txt asks automated tools not to read it). Each gets a free status row that names it (`unsupportedAts`). A page that links a supported board as well is read, and its status row lists the others in `alsoLinks`.
- **Self-hosted job pages with no ATS behind them, and pages that load their board only in the browser:** these fall back to matching by domain, which can miss. Ashby and Rippling boards are charged only when a page links them or you give the board.
- **A page we can't read** (its robots.txt asks us not to read it or can't be read, it is not found (404), it answers 403 or another error, a bot challenge blocks it with a 403 or 503, or it times out) gets a free status row with the reason in `notRead`. We don't work around a block, and we don't guess the company from a page we couldn't read. Give the bare domain to have it matched by domain instead.
- **Pages that link several employers' boards** (job aggregators, a VC portfolio board's own page, an ATS vendor's customer page, agencies): free, with the boards listed in `linkedBoards` so you can pass the right one explicitly (`greenhouse:figma`). A portfolio board given as the root of the firm's own careers host (`https://jobs.firstround.com/`) is read as the firm's domain, like any root address. It returns the firm's own open jobs, if its site links its board, and never the portfolio's. A real employer whose page links a board under another name (a subsidiary or old brand) with nothing tying it to the company's site is free too: that is the price of never charging for another company's jobs.
- **Workday boards past 1,000 jobs and Rippling boards past 1,000 listings:** the first 1,000, as in Company Jobs.
- **Company names, ready-made company lists and the XML job feed:** those are in Company Jobs.

### Input

| Field | What it does |
|---|---|
| `careerPages` | Careers page URLs or company domains, one per line. A board you give explicitly (`greenhouse:figma`, a board URL) is read as given. A root address such as `https://jobs.acme.com/` is read as the domain acme.com. |
| `minMatchConfidence` | `medium` (default) or `high`. A linked board below it returns no jobs and costs nothing. |
| `titleKeywords`, `excludeTitleKeywords`, `locationKeywords`, `departmentKeywords`, `remoteOnly`, `countries`, `postedWithinDays`, `requirePostedDate` | Filters, applied before charging: a job that doesn't pass is never charged. With `postedWithinDays`, Workday jobs listed as posted 30+ days ago are left out when the window is 30 days or less; with a longer window they count as undated (see `requirePostedDate`). |
| `onlyNewJobs`, `stateKey` | For scheduled runs: return (and charge) only jobs an earlier run with the same state key didn't return. |
| `maxJobsPerCompany`, `maxItems` | Caps per company and per run. The Console form starts at 10 per company; an API call with no cap gets up to 1,000 per company. |
| `includeDescription` | Adds the job description where the board gives one. In Workday descriptions, email addresses, phone numbers and labelled recruiter or contact lines are removed automatically. The scrub matches patterns, so a contact written an unusual way, such as a name inside a sentence, can remain. Check descriptions if your board must show none. Descriptions from other ATSes are delivered as the employer wrote them. |

### Output

Job rows (type `job`) and status rows (type `company`) are in the run's dataset, split by the **Jobs** and **Companies (status)** views. A job row has the same fields as in Company Jobs: title, location, country, remote, department, posted date, job URL, the ATS and board, and `matchConfidence` and `matchedBy`.

On a Greenhouse board that gives a work mode instead of a place (`Hybrid`, `Distributed`, `N/A`), the place and country come from the board's own office list, which can differ from the job's description (we don't read descriptions for places). The mode is in `workplaceType`, and a `locationKeywords` search matches it.

A status row adds what the page said: `careersPage` (the page the board was linked from), `notRead` (why a page couldn't be read), `unsupportedAts` and `alsoLinks`, or `linkedBoards` for a page that lists several employers.

### How much does it cost?

$0.001 per job row returned, and nothing else. A company with 40 open jobs, all returned, costs $0.04. Status rows, companies not found, pages we couldn't read, multi-employer pages, unsupported ATSes, low-confidence matches and duplicate jobs are free. Set `maxItems` or the run's maximum charge to cap the spend.

If a run restarts (Apify moves it to another server, it is rebooted, or you resurrect it), it carries on from its own dataset: jobs already in it are not returned or charged again, and a company that already has its status row is not looked up again. That includes companies marked `skipped` or `timedOut` and companies whose jobs were cut short (`jobsTruncated`), so resurrecting a run that hit `maxItems`, its maximum charge or its timeout returns nothing new for them. To get the rest, start a new run with those companies. If a restarted run can't read its earlier rows in full, it stops with a message before charging anything more, so nothing is charged twice.

### Related tools

- [Workday Jobs](https://apify.com/siftsmith/workday-jobs): for companies on Workday, takes career site URLs, names or domains and lets Workday's own filters (countries, job families, time types, remote types, where the site offers them) narrow a large site. Same $0.001 per job row.

### Support

Open an issue on this Actor's Issues tab with the input and what you expected; we read every one.

# Actor input Schema

## `careerPages` (type: `array`):

One per line: a company's careers page URL (https://figma.com/careers) or its domain (figma.com). The job board the page links is read first; a page that links no board is matched by its domain. A company name on its own is not looked up: it gets a free status row. Use Company Jobs for names. An explicit board (greenhouse:figma, a board URL) is read as given. A page we can't read, a page listing several employers' boards, and an ATS we don't read each get one free status row.

## `minMatchConfidence` (type: `string`):

How sure the page-to-board match must be before its jobs are returned and charged. 'medium' (default) or 'high'. A board the page links is 'high' when its website or job links are on the company's site, or its name matches the domain (or the name the company's homepage gives itself) and the company's homepage or careers page links it or its careers page lists its jobs; 'medium' when only its name matches. Boards below it return no jobs and cost nothing; the free status row (type 'company') says why and gives the board to pass explicitly. Boards you type explicitly are always used. Low-confidence matches are never charged.

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

Keep jobs whose title contains any of these as whole words (case-insensitive): 'ML' keeps 'ML Engineer', not 'HTML Developer'. End a keyword with \* and its last word also matches longer words: 'engineer\*' also keeps 'Engineering Manager'. Empty = all.

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

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

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

Keep jobs whose location contains any of these, e.g. 'London', 'Remote'.

## `departmentKeywords` (type: `array`):

Keep jobs whose department contains any of these, e.g. 'Engineering'.

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

Keep only remote jobs.

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

Keep jobs in these countries: ISO codes (US, GB, DE) or English names (Germany). A job's country is set only when its location clearly names it (or the job board gives it); jobs whose country is unknown are dropped, never charged, and counted in each company's free status row (countryUnknown, countryOther).

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

Keep jobs posted in the last N days. Jobs with no posted date are kept unless 'Require a posted date' is on. Workday jobs listed as posted 30+ days ago are left out when the window is 30 days or less; with a longer window they count as undated (see 'Require a posted date'), and get a date from the job's own page when descriptions are on, or when the job lists several places or none and 'Require a posted date' is off.

## `requirePostedDate` (type: `boolean`):

With 'Posted within days', also drop jobs whose board gives no posted date.

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

For scheduled runs: return (and charge) only jobs that an earlier run with the same state key hasn't already returned. The first run returns every job as usual. Job IDs are kept in a key-value store named 'career-page-jobs-state' in your own Apify account. Jobs cut by a cap, the charge limit or the timeout are offered again next time, so 'new' means not returned before, not recently posted: with a cap smaller than a board, later runs return jobs that were already on it. Once a run returns a board in full (no jobsTruncated on its status row), later runs return only jobs added since. To get there, set 'Max jobs per company' above your largest board (this form starts at 10) and allow enough charge limit and timeout; Workday and Rippling boards over 1,000 jobs are never read in full. For fresh postings only, use 'Posted within days', with 'Require a posted date' to drop undated jobs.

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

With 'Skip jobs an earlier run returned': which saved state to use. Give each schedule or employer list its own key so they don't share what counts as already returned. Letters, digits and - \_ . only.

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

Jobs past this cap are not returned and not charged. The company's free status row still reports the full board size. This form starts at 10 so a first run is quick; raise it or clear it for more (up to 1,000 per company when left empty).

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

Cap on job rows (and so on spend) for the whole run. Empty = no cap beyond the run's maximum charge.

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

Adds the job description text where the board provides it (Greenhouse, Lever, Ashby, Workable, Rippling, Personio), and on Workday (read from each job's own page, one job at a time per employer). In Workday descriptions, email addresses, phone numbers and labelled recruiter or contact lines are removed automatically. The scrub matches patterns, so a contact written an unusual way, such as a name inside a sentence, can remain. Check descriptions if your board must show none. Descriptions from other ATSes are delivered as the employer wrote them. Off keeps rows small for spreadsheets.

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

How many companies to process in parallel.

## Actor input object example

```json
{
  "careerPages": [
    "https://www.figma.com/careers/",
    "https://www.notion.com/careers",
    "vercel.com",
    "https://linear.app/careers"
  ],
  "minMatchConfidence": "medium",
  "remoteOnly": false,
  "requirePostedDate": false,
  "onlyNewJobs": false,
  "stateKey": "default",
  "maxJobsPerCompany": 10,
  "includeDescription": false,
  "maxConcurrency": 5
}
```

# Actor output Schema

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

No description

## `companies` (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 = {
    "careerPages": [
        "https://www.figma.com/careers/",
        "https://www.notion.com/careers",
        "vercel.com",
        "https://linear.app/careers"
    ],
    "maxJobsPerCompany": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("siftsmith/career-page-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 = {
    "careerPages": [
        "https://www.figma.com/careers/",
        "https://www.notion.com/careers",
        "vercel.com",
        "https://linear.app/careers",
    ],
    "maxJobsPerCompany": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("siftsmith/career-page-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 '{
  "careerPages": [
    "https://www.figma.com/careers/",
    "https://www.notion.com/careers",
    "vercel.com",
    "https://linear.app/careers"
  ],
  "maxJobsPerCompany": 10
}' |
apify call siftsmith/career-page-jobs --silent --output-dataset

```

## MCP server setup

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