# Company Career Pages Jobs Monitor (`taekop/career-site-jobs-monitor`) Actor

Live job postings straight from company career sites, with new/closed tracking, salary ranges, seniority and tech-stack signals. No proxies, no blocks.

- **URL**: https://apify.com/taekop/career-site-jobs-monitor.md
- **Developed by:** [Seungtaek Oh](https://apify.com/taekop) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.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/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

## Company Career Pages Jobs Monitor

*An independent tool. It is not affiliated with or endorsed by Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee, Workday, Gem, Breezy, Rippling or Personio. Those names are used only to say which platforms it can read.*

**Know which of your target companies is hiring, for what, and what just changed.** Give it a list of companies and it returns their live job postings from their own career sites, marks every job as new or closed since your last run, and adds salary ranges, seniority and the technologies each role asks for. A hiring summary per company shows the whole picture in one record.

It finds each company's applicant tracking system (ATS) and reads the job board through the endpoint that platform serves **without authentication**. No proxies, no logins and no browser, so it is fast and light.

### What you get

- **Live and direct.** Every run reads the company's own job board right now. No stale database, no third-party copy.
- **Any company input.** Company name (`airbnb`), domain (`stripe.com`), careers-page URL, ATS board URL, or `ats:slug` (`greenhouse:airbnb`). The ATS is detected automatically.
- **What changed.** Set a **Monitor key** and every job is marked `new`, `existing` or `reopened`. Turn on **Only new jobs** and a daily schedule becomes a hiring-alert feed. Closed jobs are reported too, at no charge. A job that disappears for a moment and comes back is not billed as new again.
- **Salary ranges, normalized.** Taken from the ATS when it publishes them (Ashby, Lever, Recruitee) and read from the job text when it does not (for example Greenhouse pay-transparency sections). Currency and pay interval (year, month, hour) are detected.
- **Seniority and work mode.** Inferred from the title and ATS fields: intern, junior, mid, senior, staff/principal, manager, director, executive. Remote, hybrid or on-site when the job says so.
- **Technology signals.** About 90 technologies detected in the title and description (Python, Kubernetes, Snowflake, Salesforce, ...). Filter by them, or read them as a company's stack.
- **Hiring summary per company (optional).** Open roles, split by department, seniority and work mode, remote share, share of jobs with a salary, and the most requested technologies.
- **Precise filters.** Title, department, location, work mode, seniority, technology, salary present, posted within N days. Keywords match **whole words** (`intern` does not match "International"); add `*` for a prefix (`intern*` matches internship). Technology filters understand common names and aliases (`golang`, `k8s`, `postgres`); any other word is searched in the job text.

### Supported career platforms

| ATS | How it is read |
|---|---|
| Greenhouse | job board endpoint (no authentication) |
| Lever (global and EU) | postings endpoint (no authentication) |
| Ashby | job board endpoint (no authentication), with compensation |
| SmartRecruiters | postings endpoint (no authentication) |
| Recruitee | offers endpoint used by careers sites (no authentication) |
| Gem | job posts endpoint (no authentication) |
| Breezy | positions feed (no authentication). The feed has no job text, so technology tags come from the title and the salary field only. |
| Rippling | job board endpoint (no authentication). A job that is open in several places is returned once, with all its locations. Job text and posting date need one extra request per job, so runs with many jobs are slower. |
| Personio | public XML job feed (no authentication) |
| Workday | careers-site job search endpoint (no authentication). **Needs the careers page URL**, for example `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`. Returns up to 2,000 jobs per company. Posting dates are estimated from "Posted 3 days ago". When a company has more than 2,000 jobs the list is marked `listIncomplete` in the run summary and closed jobs are not reported for it. |

A company only works if its careers page is hosted on one of these platforms. For Workday, paste the careers page URL; a plain company name cannot be resolved. Companies with a fully custom careers page that is not backed by one of them cannot be read. If you enter a careers-page URL, the Actor looks for an embedded link to a supported platform.

### Typical uses

- **Sales and account-based marketing.** Which of your target accounts is hiring SDRs, data engineers or a new CFO? Hiring is a buying signal.
- **Recruiting and competitive intelligence.** Watch competitors' open roles, locations and pay ranges every day.
- **Job boards and aggregators.** Pull fresh, direct-apply postings for a fixed list of employers.
- **Market research.** Compare how many jobs mention a technology across a set of companies.
- **AI agents.** A clean, structured tool for "what is company X hiring for right now?".

### Input

Only **Companies** is required.

```json
{
  "companies": ["airbnb", "stripe.com", "https://jobs.lever.co/palantir", "smartrecruiters:BoschGroup", "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"],
  "titleKeywords": ["engineer", "data"],
  "workModes": ["remote", "hybrid"],
  "hasSalaryOnly": true,
  "maxJobsPerCompany": 25
}
```

Watch a list of companies and only get what is new each day:

```json
{
  "companies": ["airbnb", "openai", "bunq"],
  "monitorKey": "sales-targets",
  "changesOnly": true,
  "includeClosedJobs": true
}
```

The first run with **Only new jobs** saves a baseline and returns nothing (and costs nothing). From the next run on, you get the jobs that appeared since. Use the same Monitor key on every scheduled run, and do not run two runs with the same key at the same time.

| Field | What it does |
|---|---|
| `companies` | Names, domains, URLs or `ats:slug`. |
| `titleKeywords` / `excludeTitleKeywords` | Keep or drop jobs by words in the title. |
| `departmentKeywords`, `locationKeywords` | Match department/team and location text. |
| `workModes`, `seniorityLevels` | Pick remote/hybrid/on-site and seniority levels. |
| `techTagsAny` | Keep jobs that mention any of these technologies. Common names and aliases are recognized; other words are searched in the text. |
| `hasSalaryOnly`, `postedWithinDays` | Only jobs with a salary range; only recent jobs. |
| `maxJobsPerCompany`, `maxTotalJobs` | Limits. Filters run first, then the newest jobs are returned. `maxTotalJobs` is also your cost cap: it counts every billed record, job records and company summaries together. |
| `includeDescription` | Add the job text as plain text or HTML. |
| `redactContactDetails` | On by default. Replaces e-mails and phone numbers in descriptions. |
| `includeCompanySummary` | Add one hiring-summary record per company. |
| `monitorKey`, `changesOnly`, `includeClosedJobs` | Change tracking between runs. |
| `probeAllAts` | For a plain name, return every ATS that has a board with that name instead of the first match. |

### Output

One record per job. Example (real run, shortened):

```json
{
  "recordType": "job",
  "changeType": "new",
  "company": "OpenAI",
  "ats": "ashby",
  "title": "Technical Sourcer, Research SWE",
  "url": "https://jobs.ashbyhq.com/OpenAI/2d1785b0-8bfa-485e-97d2-a19ff3e15570",
  "department": "People",
  "location": "San Francisco",
  "locations": ["San Francisco", "New York City", "Seattle", "US - Remote"],
  "employmentType": "full_time",
  "seniority": "mid",
  "salaryMin": 171000,
  "salaryMax": 240000,
  "salaryCurrency": "USD",
  "salaryInterval": "YEAR",
  "salarySource": "ats",
  "postedAt": "2026-10-01T00:37:32.541Z",
  "techTags": ["machine learning"]
}
```

`salarySource` tells you where the range came from: `ats` (published as structured data) or `text` (read from the job description). `changeType` is only filled when a Monitor key is set: `new`, `existing`, or `closed` for jobs that disappeared.

With **hiring summary** on, you also get one record per company, for example:

```json
{
  "recordType": "company_summary",
  "company": "OpenAI",
  "openJobsTotal": 835,
  "remoteSharePercent": 3.1,
  "withSalaryPercent": 80.8,
  "topTechTags": [{ "tag": "python", "jobs": 229 }, { "tag": "llm", "jobs": 96 }]
}
```

You can download results as JSON, CSV, Excel or HTML, or read them through the Apify API.

### Pricing

You pay per result, with no subscription:

- **job**: charged for each job record returned.
- **company-summary**: charged for each hiring-summary record (only when you turn it on).
- Closed-job records are free.

See the **Pricing** tab for the current prices. Set **Max jobs in total** to cap the cost of a run.

### FAQ

**What does it access, and what am I responsible for?** The Actor requests the job-board endpoints that these platforms serve to anyone without a login, the same data a company's own careers page loads. It does not log in, solve CAPTCHAs, or bypass protection, and it waits and retries politely when a platform asks it to slow down. It cannot promise that every platform permits every kind of reuse. Each platform's terms and the laws that apply to you are your responsibility, so check them for your use case, especially before reselling or republishing the data.

**Does it collect personal data?** It does not log in or collect applicant data. Job postings are written by employers and can mention recruiters by name. By default the Actor replaces e-mail addresses and phone numbers in job descriptions with `[email removed]` and `[phone removed]`. You can turn that off with **Remove e-mails and phone numbers from descriptions**, and then you are responsible for handling that data lawfully.

**Why was my company not found?** The company must host its jobs on one of the supported platforms, and it must currently have at least one open job (a company that closes its last job is reported as not found). Try an explicit form such as `greenhouse:company-name`, or paste the URL of the company's job board. Names that differ from the platform's internal name (for example a brand name) may need the explicit form. The run summary lists every input that was not found.

**How do I find a company's platform?** Open its careers page and look at the address of a job posting: `boards.greenhouse.io/...`, `jobs.lever.co/...`, `jobs.ashbyhq.com/...`, `jobs.smartrecruiters.com/...`, `...recruitee.com`, `jobs.gem.com/...`, `...breezy.hr`, `ats.rippling.com/.../jobs`, `...jobs.personio.de` or `...myworkdayjobs.com`.

**Does it return LinkedIn or Indeed jobs?** No. It only returns jobs from the employer's own career site.

**How fresh is the data?** It is read live on every run. Posting dates are the dates the employer's platform reports.

**How long can job texts be?** Descriptions are limited to the first 100,000 characters of each job, which is far more than a normal posting.

**How accurate are seniority and technology tags?** They are rule-based guesses from the job title and text, not human labels. They are right most of the time and wrong sometimes, so use them to filter and rank, not as ground truth.

**What exactly does "new" mean?** A job is `new` the first time it appears on a company's board after you start using a Monitor key. It does not depend on your filters: if you widen a filter later, jobs that were already on the board stay `existing` and are not reported as new. A job that disappears and comes back within 14 days is `reopened`, not `new`, so a platform hiccup never bills the same job twice. What you receive is remembered only after it was actually delivered, so a run that stops early (cost cap, error) delivers the rest next time and never repeats what you already got.

**Can I trust the match when I enter a name or domain?** For a plain name or domain the Actor tries each supported platform. When the platform reports the company's real name, a board whose name contradicts the input is rejected instead of returned. `matchConfidence` tells you how sure the match is: `exact` (you gave an `ats:slug` or a board URL), `name_verified` (the platform's company name matches yours), `page_link` (found as a link on the careers page you gave) or `unverified` (Lever, Ashby, Workday, Gem and Rippling do not report a company name, so check the `url` of a few jobs). SmartRecruiters, Greenhouse, Recruitee, Breezy and Personio report a name and are checked against yours; legal forms such as GmbH or Inc. are ignored. A careers-page URL is never guessed from its domain: it is only used if the page links to a supported platform.

**Job texts are written by third parties.** If you pass descriptions to an AI model or an automation, treat them as untrusted input.

**Are salary ranges always available?** No. Many employers do not publish one. Where they do, in the ATS or in the text, you get it. Where they do not, the salary fields are empty.

**SmartRecruiters, Workday and Rippling specifics.** Their job lists do not include the job text (Rippling also has no posting date in the list). The Actor loads it only for the jobs it needs, when you ask for descriptions or use technology or salary filters. With such a filter, or with a posting-date filter on Rippling, it inspects at most 500 jobs per company per run, newest first where dates are known; with a Monitor key the rest are inspected on the next run. If a job's text cannot be loaded (a temporary error), the job is neither billed nor marked as seen, and it is tried again on the next run. The run summary counts these jobs. Without that, technology tags and text-based salary for these jobs come from the title only.

**Something looks wrong?** Open an issue on this Actor's page with the company and the job. Platforms change their data now and then and issues are fixed quickly.

# Actor input Schema

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

One entry per company. Accepts a company name or slug (`airbnb`), a domain (`stripe.com`), a careers page URL, an ATS board URL (`https://jobs.lever.co/palantir`), or an explicit `ats:slug` such as `greenhouse:airbnb`, `smartrecruiters:BoschGroup`, `gem:company`, `breezy:company`, `rippling:company` or `personio:company`. Workday needs the careers page URL, for example `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`. The platform is detected automatically.

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

Keep jobs whose title contains at least one of these words, for example `engineer`, `account executive`. Whole words only; add `*` for a prefix (`engineer*`).

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

Drop jobs whose title contains any of these words, for example `intern*`, `recruiter`. Whole words only; add `*` for a prefix.

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

Keep jobs whose department or team contains one of these words, for example `sales`, `data`. Whole words only.

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

Keep jobs whose location contains one of these words, for example `London`, `Germany`, `UK`. Whole words only, so `UK` does not match Ukraine.

## `workModes` (type: `array`):

Keep only these work modes. Leave empty for all.

## `seniorityLevels` (type: `array`):

Seniority is inferred from the job title. Leave empty for all.

## `techTagsAny` (type: `array`):

Keep jobs that mention one of these technologies, for example `python`, `kubernetes`, `golang`, `snowflake`. Common names and aliases are recognized; any other word is searched in the job text.

## `hasSalaryOnly` (type: `boolean`):

Keep only jobs where a salary range was published in the ATS or found in the job text.

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

Keep jobs published or updated in the last N days. Jobs without a date are dropped when this is set.

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

Maximum number of jobs returned per company, newest first. Filters are applied to the full job list before this limit. Ignored when "Only new jobs" is on, which always returns every new job.

## `maxTotalJobs` (type: `integer`):

Stop after this many billed records across all companies (job records and company summaries). This is also a cost cap.

## `includeDescription` (type: `string`):

Add the full job description as plain text or HTML. For SmartRecruiters, Workday and Rippling this needs one extra request per job, so runs are slower, and technology tags and text-based salary for these jobs come from the title only unless this is turned on. Breezy's feed has no job text at all.

## `redactContactDetails` (type: `boolean`):

Job texts sometimes contain recruiter e-mail addresses and phone numbers. When on, they are replaced with \[email removed] and \[phone removed] in the description output.

## `includeCompanySummary` (type: `boolean`):

One extra record per company: open roles, breakdown by department, seniority and work mode, remote share, share with salary, and the most requested technologies. Billed as a separate event.

## `monitorKey` (type: `string`):

Give this watch list a name, for example `sales-targets`. The Actor remembers the jobs it has seen under this name, marks every job `new` or `existing`, and can report closed jobs. Use the same key on every scheduled run.

## `changesOnly` (type: `boolean`):

Return only jobs that were not seen in the previous run with the same Monitor key. Requires a Monitor key. The very first run for a company saves a baseline and returns nothing; new jobs appear from the next run.

## `includeClosedJobs` (type: `boolean`):

Add one free record for every job that disappeared since the last run with the same Monitor key. Only detected when a company's jobs were fetched completely.

## `probeAllAts` (type: `boolean`):

When you enter a plain name or domain, the first ATS that has jobs for it is used. Turn this on to return every ATS that has a board with that name.

## Actor input object example

```json
{
  "companies": [
    "Airbnb",
    "Palantir",
    "OpenAI",
    "bunq"
  ],
  "hasSalaryOnly": false,
  "maxJobsPerCompany": 25,
  "maxTotalJobs": 500,
  "includeDescription": "none",
  "redactContactDetails": true,
  "includeCompanySummary": false,
  "changesOnly": false,
  "includeClosedJobs": false,
  "probeAllAts": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (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": [
        "Airbnb",
        "Palantir",
        "OpenAI",
        "bunq"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("taekop/career-site-jobs-monitor").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": [
        "Airbnb",
        "Palantir",
        "OpenAI",
        "bunq",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("taekop/career-site-jobs-monitor").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": [
    "Airbnb",
    "Palantir",
    "OpenAI",
    "bunq"
  ]
}' |
apify call taekop/career-site-jobs-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,taekop/career-site-jobs-monitor"
        }
    }
}
```

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/oFPfMJ9PWH1DbkdTS/builds/YAgiUw3dqwVC48n7V/openapi.json
