# Company Career Page Jobs & Hiring Companies: Greenhouse, Lever (`afanasenko/company-career-jobs-scraper`) Actor

Company career page jobs: find companies hiring for a role in a city, then get their jobs from their own career pages — Greenhouse, Lever, Ashby, Workday and 11 more. Or give company names, domains, URLs or job links. Title, location, salary, description, apply link; only new jobs, hiring changes.

- **URL**: https://apify.com/afanasenko/company-career-jobs-scraper.md
- **Developed by:** [Andrey Afanasenko](https://apify.com/afanasenko) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $30.00 / 1,000 companies

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

## Company Career Page Jobs & Hiring Companies: Greenhouse, Lever

[![Apify Actor](https://apify.com/actor-badge?actor=afanasenko/company-career-jobs-scraper)](https://apify.com/afanasenko/company-career-jobs-scraper)

> **Find companies hiring for a role in a city, then get all their jobs from their own career pages.**
> Search "data engineer in Berlin", or list the companies yourself — by name, domain or careers URL — and the job board system is found for you. Fifteen systems: Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, BambooHR, Personio, Teamtailor, Jobvite, iCIMS, Recruitee, UltiPro, Oracle Recruiting Cloud and ADP Workforce Now. One price per company covers all its jobs, full descriptions included — and "Only new jobs since the last run" turns any list of companies into a hiring monitor.

> **🆓 Free to start:** 10 companies a month on the free plan · the first 25 matching jobs of each in full · the next 25 shown with their title, location and date.

### 🎯 What it does

**Find who is hiring.** Give job titles and places (`data engineer in Berlin`, `product designer in New York`). The actor searches current job listings, takes the companies hiring for each search, finds each company's own job board and returns its matching jobs — straight from the source, with full descriptions and apply links. Every row says which search found it (`searchTitle`, `searchLocation`, `foundVia: "search"`). Companies the search finds but whose job board we cannot read (often recruiting agencies) are skipped for free.

**Or list companies:** one per line: a name (`Stripe`), a domain (`stripe.com`) or a careers page or job-board URL (`https://jobs.lever.co/palantir`, `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`).

**Or paste job links:** one job page per line from an employer's own career site (`https://acme.wd5.myworkdayjobs.com/External/job/Remote/Data-Engineer_R1234`). Only the linked jobs are read — on Workday, UltiPro and Oracle each straight from its own page, without listing the whole board.

**The actor finds the job board.** It reads the company's own careers pages for a link, an embed or a redirect to a job board system, and when the site does not say, it checks whether the company's name answers on each system. Every row says how the board was found (`detectedFrom`), so you know which matches came from the company's own site.

**You get** one row per open job: company, job board system, title, department, team, location (and every location), country, remote or not, workplace type, employment type, salary when the job posts one, the date it was posted, the full description as text and as HTML, the job page and the apply link.

**Filters** run before anything is charged: words in the title (and words to exclude), locations, remote only, and how recently the job was posted.

**Companies we cannot find are free** and named in the run's status message, in the User Message and as a `career_page_not_found` row — with the system they use when their careers page runs on one this actor does not read (Eightfold, Oracle Taleo, SAP SuccessFactors and others).

### 📦 Output sample

Every job is one row. Here is what a row holds:

- **The company** — `company`, `companyInput`, `domain`, `ats`, `careersUrl`, `detectedFrom`, `foundVia`, `searchTitle`, `searchLocation`.
- **The job** — `jobId`, `title`, `department`, `team`, `employmentType`, `postedAt`, `jobUrl`, `applyUrl`.
- **Where** — `location`, `locations`, `country`, `isRemote`, `workplaceType`.
- **Pay** — `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryText` (from the job's own salary fields, or the range written in its description).
- **The description** — `descriptionText`, `descriptionHtml`.
- **The row** — `rowStatus`, `changeStatus`, `note`, `scrapedAt`.

A real row for `Ramp` (27.10.2026), description shortened:

```json
{
  "rowStatus": "job",
  "company": "Ramp",
  "companyInput": "Ramp",
  "domain": null,
  "ats": "Ashby",
  "careersUrl": "https://jobs.ashbyhq.com/ramp",
  "detectedFrom": "name-match",
  "foundVia": "input",
  "searchTitle": null,
  "searchLocation": null,
  "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Security Engineer, Cloud",
  "department": "Engineering",
  "team": "Backend",
  "location": "New York, NY (HQ)",
  "locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)", "Miami, FL"],
  "country": "USA",
  "isRemote": true,
  "workplaceType": "hybrid",
  "employmentType": "Full-time",
  "salaryMin": 211400,
  "salaryMax": 290600,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salaryText": "$211.4K - $290.6K",
  "postedAt": "2026-04-07T17:12:35.753Z",
  "descriptionText": "ABOUT RAMP\n\nRamp is building the smart infrastructure for finance teams…",
  "descriptionHtml": "<h1><strong>About Ramp</strong></h1><p>Ramp is building…",
  "jobUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
  "changeStatus": null,
  "scrapedAt": "2026-10-27T13:25:24.217Z",
  "note": null
}
```

The dataset has three views:

| View | What it shows |
|---|---|
| **Jobs** | Company, title, location, remote, workplace, employment type, department, salary, posted, apply link, system |
| **Jobs with descriptions** | The same jobs with the full description text and the salary split into numbers |
| **Companies and job boards** | Each company you gave, the job board found for it and how — or why it was not found |

### ⚡ Quick start

1. Put a job title and a place in **Job titles to search for** — `data engineer in Berlin` — or one company per line in **Companies** (`stripe.com`, `Ramp`, `https://jobs.lever.co/palantir`). Both can be combined.
2. Optional: add **Job title keywords** (`engineer`) or **Locations** (`United States`, `Berlin`, `DE`).
3. Click **Save & Start**. A company with dozens of jobs takes a few seconds.

Started without a company, the actor runs a short sample — 10 open jobs at Stripe — and says so.

### 🧭 When to use it

- **Recruiting and sourcing** — watch the companies you place candidates with, and see each new opening the day it is posted.
- **Sales prospecting** — hiring is a buying signal: a company opening five data-engineering roles is building a data team.
- **Job boards and aggregators** — fill a niche job board straight from employers' own career pages, with full descriptions and apply links.
- **Market and competitor research** — which teams a competitor is growing, where, and at what salary.
- **Job seekers and AI agents** — "every remote product role at these 30 companies posted this week", in one run.

### 🛠 Input

| Field | What it does |
|---|---|
| **Job titles to search for** | One title per line. Each is searched in every location — or write the place on the line: `data engineer in Berlin`. |
| **Locations** | Cities or countries (Berlin, New York, Germany, DE). Searched with the titles above; without titles, a filter on every company's jobs. |
| **Max companies from the search** | How many of the hiring companies found are read — 25 by default, the most-listed first. |
| **Companies** | One per line: a name, a domain or a careers page / job-board URL. A domain or URL finds the board most reliably. |
| **Job links** | One job page per line, from a supported system. Only the linked jobs are read; all linked jobs of one company cost one company price. A job that is no longer open is free and named. |
| **Max jobs per company** | Up to 1,000 matching jobs per company (the default) — all covered by one company price. |
| **Include job descriptions** | On by default. Turn off for a faster run of titles, locations and links. The price is the same. |
| **Job title keywords** | Keep jobs whose title contains any of these words. |
| **Exclude title keywords** | Drop jobs whose title contains any of these words. |
| **Remote jobs only** | Only jobs the board marks or names as remote. |
| **Posted within (days)** | Only jobs published in the last N days. Jobs whose board publishes no date (Jobvite) are left out when this is set. |
| **Only new jobs since the last run** | Deliver only jobs you have not received from this actor before, and list jobs taken down since as `job_closed` rows. Later checks cost the repeat-check price. |

Minimal inputs:

```json
{ "jobTitles": ["data engineer in Berlin", "product designer in New York"] }
```

```json
{ "companies": ["stripe.com", "Ramp", "https://jobs.lever.co/palantir"] }
```

```json
{ "jobUrls": ["https://job-boards.greenhouse.io/anthropic/jobs/5449831008", "https://acme.wd5.myworkdayjobs.com/External/job/Remote/Data-Engineer_R1234"] }
```

### 💰 How much will scraping company career pages cost?

| Event | Price (same on every Apify plan) | When |
|---|---|---|
| **Company** | $0.03 | A company delivers at least one matching job — all of them up to 1,000, descriptions included |
| **Repeat check** | $0.002 | With "Only new jobs since the last run": a company checked again — its new and closed jobs, or the news that nothing changed |
| **Job search** | $0.01 | One job title searched in one place that returns listings |

**Free:** a company with no matching job, a company whose job board we could not find (given or found by the search), a job link whose job is no longer open, and a search that returns no listing. Filters are applied before you pay.

| You ask for | You pay |
|---|---|
| 5 companies, every open job | 5 × $0.03 = $0.15 |
| 3 job links at one company, 1 at another | 2 × $0.03 = $0.06 |
| The same 5, weekly, only new jobs | 5 × $0.002 = $0.01 a week after the first run |
| "data engineer in Berlin" + "product designer in New York" | 2 searches ($0.02) + $0.03 per hiring company with a matching job on its own career page — measured 27.10: 6 companies, $0.20 |

Your Apify plan's monthly credit covers this; there is no subscription to this actor.

### 🆓 Free vs paid plan

| | Free plan | Paid plan |
|---|---|---|
| Companies a month | 10 | No limit |
| Job searches a run | 2 | Up to 20 |
| Jobs per company | 25 in full, the next 25 shown locked | Up to 1,000, all in full |
| Locked jobs show | Title, company, location, department, date | — |

A company we could not find never uses one of the free plan's companies.

### 🧪 Filter recipes

**Remote engineering roles at a list of startups:**

```json
{ "companies": ["Ramp", "Notion", "Linear", "Plaid"], "titleKeywords": ["engineer"], "remoteOnly": true }
```

**Everything posted this month in Europe, without internships:**

```json
{ "companies": ["tado.com", "bunq.com", "polestar.com"], "locations": ["Germany", "Netherlands", "Sweden"], "postedWithinDays": 30, "excludeTitleKeywords": ["intern", "werkstudent"] }
```

**Who is hiring data engineers in Berlin and product designers in New York:**

```json
{ "jobTitles": ["data engineer in Berlin", "product designer in New York"], "maxCompanies": 40 }
```

**A weekly hiring monitor** — schedule this and receive only what is new:

```json
{ "companies": ["stripe.com", "brex.com", "plaid.com"], "onlyNewJobs": true }
```

### 🔁 Only new jobs since the last run

With **Only new jobs since the last run** on, the actor remembers which jobs it delivered to your Apify account for each company's board. The next run delivers only jobs it has not delivered before (`changeStatus: "new"`), and adds a free `job_closed` row for every job it delivered before that the board no longer lists. Every company checked again costs the small repeat-check price, whatever changed.

- The first run delivers everything at the company price and is the starting point.
- Memory is per Apify account and per job board: `Stripe`, `stripe.com` and its board URL are the same jobs.
- Jobs the free plan showed locked are not remembered, so they still arrive as new after you upgrade.
- Two runs started at the same moment for the same company can both report a job as new — schedule one run at a time.
- A board not read in full (a large Workday board cut by "Max jobs per company") does not report closed jobs that run.
- **Hiring Changes** (Storage tab, free): per company, how many matching jobs were opened and taken down since the last run, by department and by location, and which companies are growing or shrinking. They are counted against what the job board listed on the last run, not against what you received, so "Max jobs per company" does not change them. A company read for the first time is the starting point and is not counted in the totals. When a board was not read in full on both runs (more matching jobs than one run reads, or a page that did not answer), its opened and closed jobs are shown as unknown, with the reason.

### 📡 Live status & storage records

| Record | What it holds |
|---|---|
| **Jobs** (dataset) | One row per job, plus one row per company not found or not read |
| **Live Status** | Progress while the run works; the result or the reason when it ends |
| **User Message** | What happened and what to change — companies not found, filters that matched nothing, free-plan limits |
| **Run Summary** | Per company: the board found and how, jobs listed, matched, delivered, locked, new and closed |
| **Hiring Changes** | With "Only new jobs since the last run": per company, jobs opened and taken down since the last run, by department and location |
| **Free-Plan Limits Applied** | The free-plan limits this run hit; empty on paid plans |

### 🤖 Programmatic / API use

```bash
curl -X POST "https://api.apify.com/v2/acts/afanasenko~company-career-jobs-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"companies": ["stripe.com", "Ramp"], "titleKeywords": ["engineer"]}'
```

For a monitor, schedule the actor with `"onlyNewJobs": true` and read only rows with `"rowStatus": "job"` (new jobs) or `"job_closed"` (taken down).

### 💡 Tips & Best Practices

#### Getting maximum results

- Give a **domain** rather than a name when you have it: the actor then reads the company's own careers page, which is how most Workday, iCIMS and Teamtailor boards are found.
- For a big employer on Workday, paste the Workday URL itself (`https://acme.wd5.myworkdayjobs.com/External`) — it is always found.
- If a company is not found, open its careers page, click through to the job list, and paste that URL.

#### Cost optimization

- One price covers up to 1,000 jobs, so add companies, not runs.
- A search costs $0.01 and finds up to 50 listings; raise "Max companies from the search" rather than adding near-identical titles.
- Use "Only new jobs since the last run" on a schedule — after the first run a company costs the repeat-check price, not the company price.

#### Performance

- Greenhouse, Lever, Ashby, Workable, Personio, Teamtailor and Recruitee list every job with its description at once: hundreds of jobs in a second or two.
- Workday, SmartRecruiters, BambooHR, Jobvite, iCIMS, UltiPro, Oracle Recruiting Cloud and ADP Workforce Now show each job's description on its own page, so those are read one by one; turn off **Include job descriptions** for a much faster run there.

#### Data quality

- Salaries come from the board's own salary fields where it has them, otherwise from the range written in the description; `salaryText` shows what was read.
- Jobvite does not publish posting dates; `postedAt` is empty there.

### ❓ FAQ

**How does it find companies hiring for a role?** It searches current job listings for each title and place, takes the employers, and reads each one's own career page. Jobs come from the employer's job board, not from the listing — so descriptions are complete and the apply link is the employer's own.

**Why are some companies from the search skipped?** Many listings are posted by recruiting agencies or by employers on systems this actor does not read. They are free and counted in the Run Summary. Measured 27.10 on two searches: 25 companies taken, 12 job boards found, 6 with a matching job.

**Which job board systems are supported?** Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, BambooHR, Personio, Teamtailor, Jobvite, iCIMS, Recruitee, UltiPro, Oracle Recruiting Cloud and ADP Workforce Now.

**Can I give links to single jobs?** Yes — put them in **Job links**, one per line. Only those jobs are read and delivered; all linked jobs of one company cost one company price. Links to job boards that are not the employer's own career site (LinkedIn, Indeed) are named and skipped for free.

**How does it find the job board from a company name?** It checks whether the name answers on each system's public job board, and takes the board with the most open jobs. With a domain it first reads the company's own careers pages. `detectedFrom` says which way each board was found.

**What if a company is not found?** It costs nothing. It is named in the status message and gets a `career_page_not_found` row; when its careers page runs on a system this actor does not read, the row names that system.

**Do I pay for jobs my filters remove?** No. Filters are applied before anything is charged, and a company whose jobs all fail them is free.

**Is the description complete?** Yes — the full text the company published, as plain text and as HTML.

**Can it monitor companies for new jobs?** Yes — turn on "Only new jobs since the last run" and schedule the actor. After the first run each company costs the repeat-check price.

**How fresh is the data?** Every run reads the job boards live.

### 🔗 Other related actors (same author)

- [LinkedIn Jobs Scraper](https://apify.com/afanasenko/linkedin-jobs-scraper) — job listings from LinkedIn search, with company and applicant details.

### 🛟 Support & feedback

Found a company it should find, a system it should read, or a field that looks wrong? Open an issue in the **Issues** tab with the company name or URL.

### ⚖️ Is it legal to scrape company career pages?

This actor reads job postings that companies publish on public career pages for anyone to see, and does not log in anywhere. Job postings contain no personal data beyond what an employer chooses to publish. You are responsible for how you use the data — check the terms of the sites involved and the laws that apply to you.

### 📋 Changelog

See the **Changelog** tab.

# Changelog

This Actor's version history is a separate document: https://apify.com/afanasenko/company-career-jobs-scraper/changelog.md

# Actor input Schema

## `jobTitles` (type: `array`):

One job title per line (data engineer, product designer). Each title is searched in every location below — or write the place on the line itself: "data engineer in Berlin". The companies hiring for it are found and their own career pages read for matching jobs.

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

Where: a city or a country, one per line (Berlin, New York, Germany, DE). With job titles above, each title is searched here; without them, jobs elsewhere are filtered out. "Remote" also matches remote jobs.

## `maxCompanies` (type: `integer`):

How many of the hiring companies the search finds are read — the most-listed first. A company is charged only when its career page has a matching job.

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

One company per line: a name (Stripe), a domain (stripe.com) or a careers page or job-board URL (https://jobs.lever.co/palantir). Combine with the search above, or use on its own.

## `jobUrls` (type: `array`):

One job page per line, from an employer's own career site on a supported system (Workday, Greenhouse, Lever, Ashby, UltiPro, Oracle Recruiting Cloud, ADP Workforce Now and the others). Only the linked jobs are read — the company price covers every linked job of one company. A link to a job that is no longer open is free and named.

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

The most matching jobs delivered for each company, up to 1,000 — all of them are covered by one company price.

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

Full description text and HTML for every job. Turn off for a faster run of titles, locations and links only — the price is the same.

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

Keep jobs whose title contains any of these words (engineer, product manager). Empty keeps every title.

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

Drop jobs whose title contains any of these words (intern, senior).

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

Only jobs the board marks or names as remote.

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

Only jobs published in the last N days. Jobs whose board publishes no date are left out when this is set. Empty means any date.

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

Deliver only jobs you have not received from this actor before, and list jobs taken down since as "job_closed" rows. The first run of a company costs the company price; every later check the small repeat-check price. Memory is per Apify account and company.

## Actor input object example

```json
{
  "maxCompanies": 25,
  "maxJobsPerCompany": 1000,
  "includeDescription": true,
  "remoteOnly": false,
  "onlyNewJobs": false
}
```

# Actor output Schema

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

No description

## `details` (type: `string`):

No description

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

No description

## `liveStatus` (type: `string`):

No description

## `userMessage` (type: `string`):

What happened this run and what to change — companies not found, filters that matched nothing, free-plan limits.

## `runSummary` (type: `string`):

Per company: the job board found, jobs listed, matched, delivered and locked, new and closed; prices and the stop reason.

## `hiringChanges` (type: `string`):

With «Only new jobs since the last run»: per company, jobs opened and taken down since the last run, by department and location.

## `freeLimitsApplied` (type: `string`):

Free-plan limits that affected this run. Empty on paid plans.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("afanasenko/company-career-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("afanasenko/company-career-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 '{}' |
apify call afanasenko/company-career-jobs-scraper --silent --output-dataset

```

## MCP server setup

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