# Company Hiring Tracker - Greenhouse, Lever, Ashby and more (`amitaviv/company-hiring-tracker`) Actor

Track which companies are hiring and what changed since your last run. Paste domains, careers pages or job board URLs (Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Personio, Teamtailor, Workday). One record per company: open jobs, velocity, new and closed jobs. Or one per job.

- **URL**: https://apify.com/amitaviv/company-hiring-tracker.md
- **Developed by:** [Amit Aviv](https://apify.com/amitaviv) (community)
- **Categories:** Lead generation, Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.20 / 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.
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 Hiring Tracker - Greenhouse, Lever, Ashby and more

Paste a list of companies (website domains, careers pages, job board URLs or names) and get **one hiring-signal record per company**: how many jobs are open, in which functions, departments, countries and seniority levels, how fast new jobs are being posted, and **which jobs were opened and closed since your previous run**. Or switch to one record per job. The actor reads the public job board APIs that the job board vendors publish for showing a company's jobs on its own website, so a run takes seconds to minutes, needs no proxy, and you pay only for the companies (or jobs) delivered. A company whose job board cannot be found is not charged; it is named in the run summary with the reason.

Built for:

- **Outbound sales**: companies that are hiring are companies that are spending. Spot accounts that just opened sales, data or security roles, or that are growing a team in your region.
- **VC and market research**: hiring velocity and team mix across a portfolio or a market map, run weekly.
- **Competitor tracking**: see the roles your competitors open and close, week by week.

### What you get

**Mode "companies" (default): one record per company**

- `companyName`, `companyDomain`, `ats` (which job board), `boardUrl`, `careersPageUrl`, `matchedBy` (how the board was found)
- `isHiring`, `openJobs`, `countComplete`
- Hiring velocity: `jobsPostedLast7Days`, `jobsPostedLast30Days`, `jobsPostedLast90Days`, `jobsWithPostingDate`, `medianJobAgeDays`, `newestJobPostedAt`, `oldestJobPostedAt`
- Team mix: `jobsByFunction`, `jobsByDepartment`, `jobsBySeniority`, `leadershipJobs`, `jobsByCountry`, `jobsByLocation` (top 30), `jobsByWorkplace`, `remoteJobs`, `jobsByEmploymentType`, `keywordMatches` (counts for your title keywords)
- `latestJobs`: the 10 most recently posted jobs (job id, title, department, function, seniority, location, date, link)
- Changes since the previous run: `isFirstRun`, `previousRunAt`, `openJobsAtPreviousRun`, `openJobsChange`, `newJobsSinceLastRun`, `closedJobsSinceLastRun`, `functionChanges`, `newJobs` and `closedJobs` (up to 100 listed each; the counts are always complete)

**Mode "jobs": one record per open job**

- `title`, `companyName`, `companyDomain`, `ats`, `boardUrl`, `department`, `team`, `function`, `seniority`, `employmentType`, `workplace`
- `location`, `locations`, `country` (ISO code)
- `postedAt`, `daysSincePosted`, `updatedAt`, `url`, `applyUrl`
- `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryText` where the board publishes pay
- `description` (plain text, optional)

Both modes write a run summary (`OUTPUT_SUMMARY` in the run's key-value store): what was requested, delivered and charged, which companies could not be resolved and why, and why the run stopped.

What it does not return: recruiter or hiring-manager names, e-mails or phone numbers, applicant data, anything behind a login, and jobs from career sites built on job boards it does not support (see Limits).

### Supported job boards

| Job board | Recognised from | Found by name guess | Posting dates | Salary |
|---|---|---|---|---|
| Greenhouse | boards.greenhouse.io/…, job-boards.greenhouse.io/…, embeds | yes | yes | jobs mode: pay-range block or a "Salary: $X - $Y" line |
| Lever | jobs.lever.co/…, jobs.eu.lever.co/… | yes | yes | when published |
| Ashby | jobs.ashbyhq.com/… | yes | yes | when published |
| Workable | apply.workable.com/…, <company>.workable.com | yes | yes | no |
| SmartRecruiters | careers.smartrecruiters.com/…, jobs.smartrecruiters.com/… | no | yes | no |
| Recruitee | <company>.recruitee.com | yes | yes | when published |
| Personio | <company>.jobs.personio.de / .com | yes (only with a live board page) | yes | no |
| Teamtailor | <company>.teamtailor.com or a career site that loads Teamtailor | yes | yes | no |
| Workday | <tenant>.wd<N>.myworkdayjobs.com/<site> | no | up to 30 days only (see Limits) | no |

### How a company is resolved to its job board

1. **A job board URL** (`https://jobs.lever.co/palantir`) or `board:name` (`greenhouse:stripe`) is used as given. `matchedBy: "board-url"`.
2. **A domain or careers page URL** (`stripe.com`, `https://linear.app/careers`): the actor reads the page given, the homepage, then up to a few careers pages they link to (4 pages by default), looking for links to or embeds of a supported job board. A board found there is used only when it is verifiably the company's: the page embeds it, or the board's name resembles the domain or the website's name, or its job pages live on the company's domain, or its company name matches. A careers page that links another company's board (remote.com/jobs links the job site Jobgether's Lever board) is not taken for the company's own. `matchedBy: "careers-page"`.
3. **Otherwise, or for a company name** (`Stripe`), the name is tried on Greenhouse, Lever, Ashby, Workable, Recruitee, Personio and Teamtailor. A board found this way is used only when it has open jobs and is verifiably the company's: its job pages are on the company's domain (`guess-domain`), its company name is the same (`guess-name`), or its job texts name the company (`guess-text`). Turn this off with **Try the company name on the job boards**.
4. **A LinkedIn, Crunchbase, Y Combinator, Wellfound, Glassdoor or Indeed company URL** is read as the company name in it (`linkedin.com/company/back-market` is tried as "back market"); other pages of such sites are refused with a message. The directory's own job board is never used.

Measured on 101 well-known tech company domains (run `TysjMPtBua6LNzPIZ`, 2026-10-14): 72 resolved (26 from the careers page, 46 by a verified name guess), 29 not: 21 link no supported board (their careers sites are custom or use an unsupported vendor), 5 refused requests from the cloud (HTTP 403/429), 2 link another company's board, 1 links a board that no longer exists. Unresolved companies were not charged. For them, paste the job board URL.

### How to use

1. Paste your companies into **Companies**, one per line. Domains from a CRM export work as they are.
2. Keep **Output** on "One record per company" for hiring signals, or choose "One record per job".
3. Press Start. For a weekly signal, save the input as a task and schedule it: from the second run on, every company record says what changed.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `companies` | list of text | none (required) | Domains, careers pages, job board URLs, `board:name`, company names, or LinkedIn/Crunchbase/Y Combinator company URLs. Duplicates (the same board reached twice) are delivered and charged once. |
| `mode` | `companies` or `jobs` | `companies` | One record per company (event "company") or per job (event "job"). |
| `keywords` | list of text | empty | Title keywords. Companies mode: counted per keyword in `keywordMatches`. Jobs mode: only jobs whose title contains one of them. |
| `locationKeywords` | list of text | empty | Jobs mode: only jobs whose location or country contains one of them. |
| `remoteOnly` | yes/no | no | Jobs mode: only remote jobs. |
| `postedWithinDays` | number | empty | Jobs mode: only jobs posted in the last N days (undated jobs are left out). |
| `maxJobsPerCompany` | number | 500 | Jobs mode: at most this many jobs per company, newest first. |
| `includeDescription` | yes/no | no | Jobs mode: add the description as plain text (see Legal for what is removed). |
| `onlyNew` | yes/no | no | Jobs mode: deliver and charge only jobs not delivered by earlier runs. |
| `monitorKey` | text | empty | Name of an independent tracking history (e.g. one per list or per client). |
| `guessBoards` | yes/no | yes | Try the company name on the job boards when the website links none. |
| `maxPagesPerWebsite` | number | 4 | Pages of a company's website read to find its board (1-10). |
| `proxyConfiguration` | proxy | off | Not needed for the job board APIs; can help when company websites refuse cloud requests. |

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

### Example result (companies mode)

From run `rZw7wrA18GdDIZ4GC` on 2026-10-14 (build 0.1.12), shortened (`latestJobs` cut to one entry); this was a repeat run, so the change fields are filled:

```json
{
  "id": "ashby:linear",
  "companyName": "Linear",
  "companyDomain": "linear.app",
  "ats": "ashby",
  "boardUrl": "https://jobs.ashbyhq.com/Linear",
  "careersPageUrl": "https://linear.app/careers",
  "matchedBy": "careers-page",
  "isHiring": true,
  "openJobs": 30,
  "countComplete": true,
  "jobsPostedLast7Days": 0,
  "jobsPostedLast30Days": 4,
  "jobsPostedLast90Days": 11,
  "jobsWithPostingDate": 30,
  "medianJobAgeDays": 133,
  "newestJobPostedAt": "2026-10-06T00:55:56.942Z",
  "oldestJobPostedAt": "2021-04-27T20:13:45.158Z",
  "jobsByFunction": {"Design": 7, "Engineering": 7, "Customer Success & Support": 5, "Sales": 5, "Data & AI": 2, "Marketing": 2, "Product": 2},
  "jobsByDepartment": {"GTM": 15, "Product": 13, "Operations": 2},
  "jobsBySeniority": {"mid": 23, "lead": 7},
  "leadershipJobs": 0,
  "jobsByCountry": {"US": 18, "unknown": 8, "GB": 2, "SG": 2},
  "jobsByLocation": {"North America": 18, "Europe": 8, "London": 2, "Singapore": 2},
  "jobsByWorkplace": {"remote": 27, "hybrid": 3, "onsite": 0, "unspecified": 0},
  "remoteJobs": 27,
  "jobsByEmploymentType": {"full-time": 30},
  "keywordMatches": null,
  "latestJobs": [
    {"jobId": "6dd5500a-4bd4-4f57-ba09-7b07acefecf4", "title": "Account Executive, Enterprise", "department": "GTM", "function": "Sales", "seniority": "mid", "location": "Singapore", "postedAt": "2026-10-06T00:55:56.942Z", "url": "https://jobs.ashbyhq.com/Linear/6dd5500a-4bd4-4f57-ba09-7b07acefecf4"}
  ],
  "isFirstRun": false,
  "previousRunAt": "2026-10-14T17:05:19.976Z",
  "openJobsAtPreviousRun": 30,
  "openJobsChange": 0,
  "newJobsSinceLastRun": 0,
  "closedJobsSinceLastRun": 0,
  "functionChanges": {},
  "newJobs": [],
  "closedJobs": [],
  "input": "linear.app",
  "scrapedAt": "2026-10-14T18:27:37.043Z"
}
```

On a first run for a company (`isFirstRun: true`) the change fields are `null` and the lists empty. `newJobs`, `closedJobs` and `latestJobs` hold `jobId`, `title`, `department`, `function`, `seniority`, `location`, `postedAt` and `url` of each job.

### Example result (jobs mode)

From run `qiRhXpdHzjZIaTzVN` on 2026-10-14, unedited:

```json
{
  "id": "greenhouse:stripe:8267736",
  "jobId": "8267736",
  "title": "Software Engineer",
  "companyName": "Stripe",
  "companyDomain": "stripe.com",
  "ats": "greenhouse",
  "boardUrl": "https://job-boards.greenhouse.io/stripe",
  "department": "Acquisitions & Other",
  "team": "Global Operations",
  "function": "Engineering",
  "seniority": "mid",
  "employmentType": null,
  "workplace": null,
  "location": "Seattle, WA",
  "locations": ["Seattle, WA"],
  "country": "US",
  "postedAt": "2026-10-09T16:47:42.000Z",
  "daysSincePosted": 4,
  "updatedAt": "2026-10-09T16:47:59.000Z",
  "url": "https://stripe.com/jobs/search?gh_jid=8267736",
  "applyUrl": null,
  "salaryMin": 190400,
  "salaryMax": 285600,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salaryText": "$190,400.00 - $285,600.00/yr",
  "description": null,
  "input": "stripe.com",
  "scrapedAt": "2026-10-14T16:16:32.137Z"
}
```

| Field | Meaning |
|---|---|
| `id` | `<ats>:<board>:<job id>` (jobs) or `<ats>:<board>` (companies); stable between runs. |
| `function` | One of Engineering, Data & AI, Product, Design, Sales, Marketing, Customer Success & Support, Finance, People & HR, Legal & Compliance, Operations, Security, IT, Healthcare, Other: from the title, else the department. A heuristic. |
| `seniority` | intern, entry, mid, senior, lead, manager, director, executive: from the title (and the board's own experience level where it has one). A heuristic; "mid" when the title says nothing. `leadershipJobs` counts manager, director and executive. |
| `employmentType` | full-time, part-time, contract, temporary, internship, apprenticeship, volunteer, or `null` when the board does not say. |
| `workplace` | remote, hybrid, onsite, or `null`: the board's own value, else "remote"/"hybrid" in the location or title. |
| `country` | ISO code from the board or read from the location text (city, state, country); `null` (`unknown` in counts) when unsure. |
| `countComplete` | `false` when a board could not be read to the end (SmartRecruiters and Workday are paged; the counts then cover what was read and no job is reported closed). |

Fields a board does not provide are `null`, never missing.

### Pricing

Pay per event. You pay for what is delivered, not for time or memory.

| Event | Free | Bronze | Silver | Gold and above |
|---|---|---|---|---|
| Actor start (once per run, per GB of memory; the default 512 MB counts as one) | $0.0036 | $0.003 | $0.0027 | $0.0024 |
| Company record delivered (companies mode) | $0.0048 | $0.004 | $0.0036 | $0.0032 |
| Job record delivered (jobs mode) | $0.0006 | $0.0005 | $0.00045 | $0.0004 |

Worked examples (Bronze):

- A weekly check of 250 accounts, of which 200 resolve: 200 × $0.004 + $0.003 = **$0.803** per run, $3.21 for four weekly runs. The 50 unresolved accounts cost nothing.

- All open jobs of 10 companies, 1,500 jobs in total: 1,500 × $0.0005 + $0.003 = **$0.753**.

- A daily "new jobs only" schedule over the same 10 companies that finds 12 new jobs: 12 × $0.0005 + $0.003 = **$0.009**; a day with nothing new costs $0.003.

- Companies whose board is not found, and jobs that are filtered out, are not charged. The same board reached twice in one input (for example `stripe.com` and `greenhouse:stripe`) is delivered and charged once.

- The maximum total charge you set for a run is respected: the actor stops before it would go over, keeps what was delivered, and says so in the status message (run `JrWcGWiXyDIu0uygr`: a $0.012 limit delivered 2 companies).

### Reliability and failure behaviour

| What happened | Run status | Status message |
|---|---|---|
| Records delivered | Succeeded | `Scraped 3 companies from 3 queries` |
| Some companies not resolved | Succeeded | `Scraped 72 companies from 101 queries — 29 of 101 queries failed ("revolut.com": no job board found for revolut.com: its website could not be read (HTTP 403) …)` |
| Jobs mode, filters match nothing | Succeeded | `0 results: the job board has no jobs for this query — Linear: 30 open jobs, none matched the filters` |
| No company could be resolved | **Failed** | `No companies delivered: no job board found for …. No results were charged (only the start fee applies).` |
| Input the actor cannot use | **Failed**, within seconds | `Nothing to do: the input has no "companies". …` |
| Maximum charge reached | Succeeded | `… — stopped at the maximum charge set for this run` |
| Nothing new (onlyNew) | Succeeded | `0 new jobs in 2 monitored queries (no results charged; only the start fee applies)` |

- Requests are retried up to 3 times with increasing pauses on network errors, HTTP 429 and 5xx. Lever is read at most once per second, as its robots.txt asks.
- Records are delivered one company at a time. A run that is aborted, times out or is resurrected keeps what was delivered and never delivers or charges the same record twice.
- Counts were checked against the boards' own totals on 2026-10-14: Stripe 724 (Greenhouse), Databricks 894 (Greenhouse), Bosch about 5,030 (SmartRecruiters; within 1 of the board's own total, which moves while the 50 pages are read), Hugging Face 6 (Workable), bunq 21 (Recruitee), Zendesk 142 (Workday; 140 later that day). Velocity, department, workplace and employment-type counts were recomputed from the raw APIs for Stripe, Palantir and Linear and matched.
- In every test run of 2026-10-14 the charged events equalled the records delivered (for example 11,157 jobs = 11,157 job events in run `L31awqxudPsWisX5C`).

### Tracking changes between runs

**Companies mode** remembers each company's open jobs after every run (key-value store `company-hiring-tracker-monitor` in your own account, one entry per job board and tracking key). The next run compares and fills `newJobsSinceLastRun`, `closedJobsSinceLastRun`, `openJobsChange`, `functionChanges`, `newJobs` and `closedJobs`. The comparison is by job id, so a job that is re-posted under a new id counts as closed and new. History is kept per job board, so `stripe.com` and `greenhouse:stripe` share it. A job is reported closed only when both runs read the whole board. Use a different **Tracking key** (`monitorKey`) for each independent schedule; runs with the same key compare against each other.

**Jobs mode with onlyNew** delivers and charges only jobs that earlier onlyNew runs did not deliver (run `fnHcxTptR16WocsIS` delivered 344 jobs, the repeat `zOJMrd4xx5K3S0BZS` delivered 0 and charged only the start). This history is kept per company **as you wrote it** and per tracking key: keep the same text for a company between runs, because `stripe.com` and `greenhouse:stripe` are two histories and switching between them delivers every open job again. Each history remembers up to 10,000 job ids. Delete the store or change the tracking key to start over.

To be notified, add an integration to the scheduled task (email, Slack, webhook) on "run succeeded".

### Limits

- **Job boards not supported**: iCIMS, Taleo, SuccessFactors, Eightfold, Phenom, BambooHR, Jobvite, custom career sites. Companies on them are not resolved (and not charged). 21 of 101 well-known tech domains in run `TysjMPtBua6LNzPIZ` were in this group.
- **Workday** is used only when the input or the company's website names the career site. Workday shows posting dates as "Posted N Days Ago" up to "30+ Days Ago": jobs older than 30 days have no date, so `oldestJobPostedAt`, `medianJobAgeDays` and the 90-day count describe only the dated jobs (`jobsWithPostingDate` tells how many; Zendesk: 96 of 142 dated). Workday jobs have no department or employment type, no location when they are offered in several, and no description. Workday publishes no company name: when the input is a career-site URL, `companyName` is the site's tenant ("Zendesk" for zendesk.wd1.myworkdayjobs.com) and `companyDomain` is empty.
- **SmartRecruiters** is used only when named (an unknown company id is refused, not reported as "not hiring"). No descriptions. Very large boards change while they are paged, so the count can differ by one or two from the board's own total (Bosch: 5,031 read vs 5,032 listed on 2026-10-14).
- **Company websites** that refuse requests from cloud servers (HTTP 403/429) cannot be scanned; the name guess still runs. A proxy can help, or paste the board URL.
- **Function, seniority, workplace and country** are derived from free text. They are heuristics: "Account Executive" is Sales/mid, "Staff Engineer" is lead, a title that says nothing about level is "mid". A bare "Commercial" in a title counts as Sales only when no other function matches ("Software Engineer - Commercial" is Engineering).
- **Salary** appears only where the board publishes it: Ashby, Lever and Recruitee fields, Greenhouse pay-range blocks and "Salary: $X - $Y" lines (jobs mode only; on 2026-10-14 Databricks had 492 of 894 jobs with pay, Stripe 24 of 724).
- **EU-hosted Greenhouse boards** (job-boards.eu.greenhouse.io) are not supported: no public EU board API was found on 2026-10-14.
- **Memory**: the default 512 MB was enough for 11,157 jobs with descriptions from 6 boards including two of 4,000-5,000 jobs (peak 414 MB, run `L31awqxudPsWisX5C`). For larger lists with descriptions, set 1024 MB.
- Speed: 101 companies in companies mode took about 6 minutes (most of it reading company websites); companies given as board URLs take seconds each.

### FAQ

**Do I need a proxy?** No. The job board APIs are public. A proxy only helps with company websites that refuse cloud requests.

**Why is a company missing?** The run summary (`OUTPUT_SUMMARY`, `failedInputs`) gives the reason for each: website unreadable, no supported board linked, a linked board that is not the company's, or no board under its name. Paste the board URL to track it anyway.

**Why does a company show fewer jobs than its careers page?** Some companies list jobs from several boards or agencies on one page; the actor reads one board per company. `countComplete: false` also means a paged board could not be read to the end.

**Can I run it from code?** Yes: the API, the Apify clients for JavaScript and Python, Make, Zapier, n8n. The Output tab shows the URLs.

**What happens when a board changes?** A board that answers with errors fails for that company with the reason; nothing is charged for it.

### Legal and responsible use

This actor reads job postings that companies publish for anyone to see, through the public APIs their job board vendors provide for exactly that purpose. Company records contain counts and job titles, no personal data. Job records never include recruiter or hiring-manager names or contact details. Optional descriptions are employer-written text: e-mail addresses, phone numbers, personal profile links and staff names introduced by phrases such as "report to", "interview with" or "your recruiter" are removed; this is a heuristic, and a description can still name a person in another way, so treat descriptions as you would any text that may contain personal data.

You are responsible for how you use the data: respect the job boards' terms, copyright and database rights, and data-protection law (GDPR and others). Do not use the output to contact individuals without a lawful basis. If you are unsure, ask a lawyer before you build on it.

### Related actors

- [Europages Company Scraper](https://apify.com/amitaviv/europages-company-scraper): European B2B companies by industry keyword and country, each with its website domain, which you can paste straight into this actor's Companies list to see which of them are hiring.
- [Welcome to the Jungle Scraper](https://apify.com/amitaviv/welcome-to-the-jungle-scraper): job ads and per-company hiring signals (open jobs by department, tech tools, headcount) for companies that recruit through Welcome to the Jungle, common in France, rather than through their own job board.
- [SEEK Jobs Scraper](https://apify.com/amitaviv/seek-jobs-scraper): job ads from SEEK, the main job site in Australia and New Zealand, by keyword, location and classification, for employers that post there instead of on a job board this actor reads.

### Changelog

- 0.1 (2026-10-14): first version. Companies and jobs modes; Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Personio, Teamtailor and Workday; careers-page and name-guess resolution with verification; changes since the previous run; onlyNew for jobs.

# Actor input Schema

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

One company per line, as any of: its website domain (stripe.com), its careers page URL, its job board URL (https://jobs.lever.co/palantir, https://boards.greenhouse.io/airbnb, https://jobs.ashbyhq.com/linear, https://apply.workable.com/huggingface, https://careers.smartrecruiters.com/BoschGroup, https://bunq.recruitee.com, https://personio.jobs.personio.de, https://acme.teamtailor.com, https://zendesk.wd1.myworkdayjobs.com/zendesk), board:name (greenhouse:stripe), or the company name (Stripe). A LinkedIn, Crunchbase, Y Combinator, Wellfound, Glassdoor or Indeed company URL is read as the company name in it. A domain is resolved by reading the company's website for its job board (a board linked there is used only if it is verifiably the company's), then by trying its name on the job boards. A company whose board is not found is not charged and is listed in the run summary with the reason.

## `mode` (type: `string`):

companies: one record per company with open-job counts (by function, department, country, seniority), hiring velocity (jobs posted in the last 7, 30 and 90 days) and the jobs opened and closed since the previous run; one "company" charge per company delivered. jobs: one record per open job (title, location, function, seniority, posting date, salary where the board gives it); one "job" charge per job delivered. Default: companies.

## `keywords` (type: `array`):

Words or phrases matched in job titles (case and accents ignored), e.g. salesforce, data engineer, SDR. Companies mode: open jobs are counted per keyword in keywordMatches. Jobs mode: only jobs whose title contains at least one keyword are delivered. Default: empty (no title filter).

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

Jobs mode only: deliver only jobs whose location or country contains one of these, e.g. Berlin, Germany, DE, Remote. Default: empty (all locations).

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

Jobs mode only: deliver only jobs marked remote by the board or with "remote" in their location or title. Default off.

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

Jobs mode only: deliver only jobs first published at most this many days ago, e.g. 14. Jobs without a publication date are left out when this is set. Default: empty (any age).

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

Jobs mode only: at most this many jobs per company, newest first (jobs without a posting date last). Default 500, up to 100,000. Companies mode always counts every open job.

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

Jobs mode only: add the job description as plain text, with e-mail addresses, phone numbers, personal profile links and staff names introduced by phrases such as "report to" or "interview with" removed (a heuristic: employer-written text can still name people in other ways). Greenhouse descriptions need one extra request per company (made in jobs mode anyway, for salaries). SmartRecruiters and Workday descriptions are not available (null).

## `onlyNew` (type: `boolean`):

Jobs mode only: deliver (and charge) only jobs that earlier runs with this option did not deliver. For a schedule that sends you new openings. The history is kept per company as you wrote it and per tracking key: keep the same text for a company between runs (stripe.com and greenhouse:stripe are two histories). Companies mode reports new and closed jobs in every record without this option.

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

Optional name for an independent tracking history, e.g. "crm-list-a". Runs with the same key compare against each other (new/closed jobs since the previous run, and only-new jobs). Default: empty, one shared history per company.

## `guessBoards` (type: `boolean`):

When a company's website links no supported job board (or a company name was given), try its name on Greenhouse, Lever, Ashby, Workable, Recruitee, Personio and Teamtailor. A board found this way is used only if it has open jobs and is verifiably the company's: same company name, job pages on the company's domain, or job texts naming the company (matchedBy tells which). Default on.

## `maxPagesPerWebsite` (type: `integer`):

How many pages of a company's website are read to find its job board (the page given, the homepage, then careers pages it links to). Default 4, from 1 to 10.

## `proxyConfiguration` (type: `object`):

Usually not needed: the job board APIs are public. A proxy can help when company websites refuse requests from cloud servers.

## Actor input object example

```json
{
  "companies": [
    "stripe.com",
    "https://jobs.lever.co/palantir",
    "linear.app"
  ],
  "mode": "companies",
  "remoteOnly": false,
  "maxJobsPerCompany": 500,
  "includeDescription": false,
  "onlyNew": false,
  "guessBoards": true,
  "maxPagesPerWebsite": 4,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All delivered records as JSON. Companies mode, one object per company: companyName, companyDomain, ats, boardUrl, matchedBy, openJobs, jobsPostedLast7Days/30Days/90Days, jobsByFunction, jobsByDepartment, jobsByCountry, jobsBySeniority, leadershipJobs, remoteJobs, latestJobs, and against the previous run openJobsChange, newJobsSinceLastRun, closedJobsSinceLastRun, newJobs, closedJobs. Jobs mode, one object per open job: title, companyName, department, function, seniority, location, country, workplace, employmentType, postedAt, url, salaryMin, salaryMax, salaryCurrency, salaryPeriod, and description when asked for.

## `resultsCsv` (type: `string`):

All delivered records as a CSV file.

## `summary` (type: `string`):

What was requested, delivered and charged, which companies could not be resolved and why, and why the run stopped.

# 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": [
        "stripe.com",
        "https://jobs.lever.co/palantir",
        "linear.app"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("amitaviv/company-hiring-tracker").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": [
        "stripe.com",
        "https://jobs.lever.co/palantir",
        "linear.app",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("amitaviv/company-hiring-tracker").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": [
    "stripe.com",
    "https://jobs.lever.co/palantir",
    "linear.app"
  ]
}' |
apify call amitaviv/company-hiring-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,amitaviv/company-hiring-tracker"
        }
    }
}
```

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/aoLxdsHTGmErb9Sqh/builds/FcAuRXFhy7mFgp9nS/openapi.json
