# Company Hiring Signals: Careers Pages & ATS Jobs (`truswen/company-hiring-signals`) Actor

Check which companies are hiring right now. For each company website: careers page, job board (Greenhouse, Lever, Ashby, Workable, Personio, Recruitee …), open jobs with function and seniority, and sales-ready hiring signals with a 0–100 score.

- **URL**: https://apify.com/truswen/company-hiring-signals.md
- **Developed by:** [Benjamin Zsigri](https://apify.com/truswen) (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 $2.00 / 1,000 company checkeds

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 Hiring Signals: Careers Pages & ATS Jobs

Find out which companies are **hiring right now**, and for what. Give it a list of company websites; for every company you get **one row** with:

- **Is it hiring?** `true`, `false` (the careers page says there are no openings, or the job board is empty) or `null` when it could not be told
- **Careers page** and **job board** (Greenhouse, Lever, Ashby, Workable, Personio, Recruitee, Breezy HR, BambooHR read in full; Workday, SmartRecruiters, Teamtailor, iCIMS, SuccessFactors and others recognised)
- **Open jobs**: title, department, location, country, remote, employment type, posted date and link, each with a **job function** (sales, engineering, marketing, finance …) and a **seniority level** (executive, VP, director, manager, senior, junior, intern …)
- **Hiring signals** ready for sales and recruiting: `hiring_sales`, `hiring_engineering`, `hiring_leadership`, `high_volume_hiring`, `recent_postings`, `remote_roles`, `multi_country_hiring`, your own keywords, and a **hiring score from 0 to 100**

It reads company websites in any language (English, German, French, Dutch, Spanish, Italian, Portuguese, Polish, Nordic and Hungarian careers pages are understood) and takes the output of **Google Maps Scraper**, **Website Contacts Scraper** or any dataset with a website column.

### Who it is for

- **B2B sales teams**: a company that is hiring has budget and new problems. A company hiring five salespeople needs a CRM, sales tools and training; a company hiring engineers needs dev tools and cloud; one hiring its first HR manager needs payroll and HR software. Sort your account list by who is hiring for the role your product serves.
- **Recruitment agencies**: find the companies that are hiring for the roles you place, with the job links, before your competitors call them.
- **Investors and market research**: which companies in a sector are growing, and in which functions and countries.
- **Agencies and freelancers**: a company hiring a marketing manager or a developer is a company that needs that work done now.

### How it works

1. Each company website is opened at its homepage. The crawler looks for the **careers page** first (Careers, Jobs, Karriere, Emplois, Vacatures, Werken bij …), and opens nothing else unless it has page budget left.
2. If the careers page lives on another domain (e.g. `acme-careers.com`), that link is followed. If the site links no careers page at all, the two conventional paths `/careers` and `/jobs` are tried.
3. Every page is checked for a link to a **job board** (applicant tracking system). When the provider publishes an open job-board feed (the same data its own careers page shows to every visitor), all jobs are read from it.
4. Without a readable job board, the jobs come from the careers page itself: **schema.org JobPosting** data (what Google for Jobs reads), then links that look like individual job ads. A careers page that says "no open positions" makes the row `is_hiring: false`.
5. Every job gets a function and a seniority level, and the row gets its counts, signals and score.

You can also paste a job board URL directly (`https://jobs.lever.co/acme`, `https://boards.greenhouse.io/acme`): it is read without crawling a website.

#### What the Actor does NOT do

- It does not log in anywhere and it does not solve or bypass CAPTCHAs.
- It respects **robots.txt**, on the company website and on the job board provider's API. A website that disallows crawling is reported as `robots_blocked` and is not charged; a job board whose API disallows crawlers (SmartRecruiters, for example) is reported with its URL but not read.
- It does not use LinkedIn, Indeed or other job portals. Only the company's own website and its own job board are read.
- It does not guess. A careers page that shows its jobs only after JavaScript runs (Workday and some custom sites) gives `hiring_status: "unknown"` with the board's URL, never an invented number.

### Input

The simplest input is a list of websites:

```json
{
  "websites": ["https://www.figma.com", "monzo.com", "https://jobs.lever.co/acme"],
  "jobTitleKeywords": ["sales", "salesforce"]
}
```

Or point the Actor at a dataset, for example the output of a **Google Maps Scraper** run:

```json
{
  "datasetId": "YOUR_DATASET_ID",
  "includeJobs": false
}
```

The website field is detected automatically (`website`, `url`, `domain`, `companyWebsite`, …) or you can name it in **Website field in the dataset**. A Google Maps listing link is never taken for the website; items without a website are skipped. Fields such as `title`, `placeId`, `address`, `city` and `countryCode` are copied into `source_item`, so you can join the results back to your list.

| Field | What it does |
|---|---|
| `websites` | Company websites or job board URLs, one per line |
| `datasetId`, `datasetUrlField` | Import websites from a dataset |
| `jobTitleKeywords` | Words counted in job titles and departments (`keyword_matches`, `keyword_match` signal) |
| `includeJobs` | Return the job list (on by default); off gives a compact row with counts and signals only |
| `maxJobsPerCompany` | Caps the job list in a row (default 100); counts and signals always cover all jobs |
| `maxPagesPerWebsite` | Pages read on the company website to find the careers page (default 4) |
| `maxConcurrency` | Companies checked in parallel (default 20) |
| `proxyConfiguration` | Optional proxy for the company websites |

### Output

One row per company. Example (shortened):

```json
{
  "input_url": "https://www.acme-software.co.uk/",
  "domain": "acme-software.co.uk",
  "status": "ok",
  "company_name": "Acme Software",
  "is_hiring": true,
  "hiring_status": "hiring",
  "careers_url": "https://www.acme-software.co.uk/careers",
  "ats_provider": "Greenhouse",
  "ats_board_url": "https://job-boards.greenhouse.io/acmesoftware",
  "jobs_source": "ats_api",
  "open_jobs_count": 3,
  "jobs_posted_last_30_days": 2,
  "newest_job_posted_at": "2026-09-25T10:00:00.000Z",
  "job_functions": { "sales": 2, "engineering": 1 },
  "seniority_levels": { "director": 1, "mid": 1, "senior": 1 },
  "leadership_roles_open": 1,
  "remote_jobs": 1,
  "hiring_countries": [],
  "top_locations": [{ "location": "London", "jobs": 1 }],
  "keyword_matches": { "sales": 2 },
  "signals": ["hiring", "hiring_sales", "hiring_engineering", "hiring_leadership", "remote_roles", "keyword_match"],
  "hiring_score": 62,
  "jobs": [
    {
      "title": "Head of Sales, EMEA",
      "department": "Sales",
      "location": "London",
      "remote": null,
      "posted_at": "2026-09-25T10:00:00.000Z",
      "url": "https://job-boards.greenhouse.io/acmesoftware/jobs/1",
      "function": "sales",
      "seniority": "director"
    }
  ],
  "source_item": null,
  "scraped_at": "2026-10-01T00:00:00.000Z"
}
```

#### Hiring status

| `hiring_status` | `is_hiring` | Meaning |
|---|---|---|
| `hiring` | `true` | At least one open job was found |
| `not_hiring` | `false` | The job board was read and is empty, or the careers page says there are no openings |
| `unknown` | `null` | A careers page or job board exists, but its jobs could not be read (JavaScript-only board, or a provider without an open feed) |
| `no_careers_page_found` | `null` | The website links no careers page and has none at `/careers` or `/jobs` |

`jobs_source` tells you where the jobs came from: `ats_api` (the job board's feed, complete), `structured_data` (schema.org on the careers page) or `careers_page` (job ad links on the careers page).

#### Signals and score

| Signal | When |
|---|---|
| `hiring` | at least one open job |
| `hiring_<function>` | a job in that function: sales, marketing, engineering, data, customer\_success, finance, hr, operations, it, product, design, legal |
| `hiring_leadership` | a director, VP or C-level role is open |
| `high_volume_hiring` | 10 or more open jobs |
| `recent_postings` | 3 or more jobs posted in the last 30 days |
| `remote_roles` | at least one remote job |
| `multi_country_hiring` | jobs in two or more countries |
| `keyword_match` | one of your keywords appears in a job title or department |

`hiring_score` (0–100) is the sum of four parts, all computed from the jobs in the same row so you can check it:

- **volume**: 15 × log2(1 + open jobs), at most 45 (1 job = 15, 7 jobs = 45)
- **freshness**: 6 per job posted in the last 30 days, at most 30
- **leadership**: 15 if a director, VP or C-level role is open
- **breadth**: 2.5 per distinct job function, at most 10

#### Status values

| `status` | Meaning | Charged |
|---|---|---|
| `ok` | The website (or job board) was read | yes |
| `robots_blocked` | The site's robots.txt disallows crawling | no |
| `blocked` | The site refuses automated requests (HTTP 401/403/429) | no |
| `not_a_company_website` | A social profile, directory or marketplace page, not the company's own site | no |
| `unreachable` / `http_error` / `not_html` / `invalid_url` / `error` | The website could not be read | no |

### Pricing

This Actor uses **pay per event** pricing: you pay for results, not for compute time.

- **Company checked**: charged once per company whose website or job board answered (`status: ok`), whatever the result.
- **Hiring company found**: charged once per company that is hiring (`is_hiring: true`), whatever the number of jobs.

Websites that could not be read are listed with the reason, **free of charge**. The job list never changes the price. The current prices are on the **Pricing** tab.

The Actor respects your **maximum cost per run**: when the limit is reached it stops before starting another company, and it never returns hiring details it could not charge for.

### Use it with other Actors

- **Google Maps Scraper → Company Hiring Signals**: find the businesses in a niche and area, then see which of them are hiring. Start this Actor with the Maps run's `datasetId`.
- **Website Contacts Scraper + Company Hiring Signals**: run both on the same dataset and join on `input_url` or `source_item.placeId`: the hiring companies, with their phone numbers, emails and decision makers.

You can chain the runs automatically with an Apify **integration** (start this Actor when the previous run succeeds).

### Where your list can come from

- **Google Maps Scraper**: set `datasetId` to its run's dataset. This Actor never searches Google Maps itself; it works on the dataset you bring.
- **A CSV file or spreadsheet**: paste the website column into `websites`, one per line.
- **Your CRM**: export the accounts and paste the website column, or push the rows into an Apify dataset with the API and set `datasetId` to it. `datasetUrlField` names the column when it is not one of the usual names.
- **Make, n8n, Zapier or Clay**: start the Actor through the Apify API (Apify also offers ready-made modules for some of these tools), pass `websites` in the input and read the run's dataset as the output.

### Tips

- **Large lists**: 1,000 companies take a few minutes. Turn off `includeJobs` if you only need the signals; the rows get much smaller.
- **Repeat runs** (weekly, monthly) on the same list show who started hiring: compare `open_jobs_count` and `newest_job_posted_at` between runs, or schedule the Actor and send the rows with `recent_postings` to your CRM.
- **Blocked websites**: if many of your websites end as `blocked`, try a proxy in the input. Job board feeds never need one.
- **Resuming**: if a run is migrated by the platform, it continues where it stopped without checking or charging the finished companies again.

### Is it legal?

The Actor reads only pages and job board feeds that companies publish openly for job seekers, respects robots.txt and does not bypass any login or CAPTCHA. Job postings are company information, not personal data. You are responsible for how you use the results, including the terms of the websites you process and the rules for B2B outreach in your country.

### Limitations

- Careers pages and job boards that render their jobs only with JavaScript (Workday, some custom careers sites) give `unknown` with the board's URL.
- Job function and seniority are assigned from the job title (and department) with multilingual keyword rules. Unusual titles fall into `other` / `mid`.
- The job list on a careers page without a job board feed or structured data is recognised from link text and may miss jobs listed in an unusual layout.

### Feedback

Found a company where the result is wrong or incomplete? Open an issue on the **Issues** tab with the URL. Real examples are the fastest way to improve detection.

### Need it done for you?

Want this connected to your CRM or workflow, or adapted to your market? Tribloc, the team behind this Actor, builds these setups. Get in touch at [tribloc.co.uk](https://tribloc.co.uk/).

# Actor input Schema

## `websites` (type: `array`):

Company websites, one per line. A bare domain (acme.com) works too. You can also paste a job board URL (jobs.lever.co/acme, boards.greenhouse.io/acme); it is read directly. Each company is checked once, even if it appears several times.

## `datasetId` (type: `string`):

ID of a dataset whose items contain a website field, for example the default dataset of a Google Maps Scraper or Website Contacts Scraper run. Fields like title, placeId, address and countryCode are copied into `source_item`, so you can join the rows back. Pick the dataset (or paste its ID); the Actor is given read access to that one dataset only.

## `datasetUrlField` (type: `string`):

Name of the field that holds the website URL. Leave empty to try the usual names: website, url, websiteUrl, domain, companyWebsite, homepage.

## `jobTitleKeywords` (type: `array`):

Words to look for in job titles and departments, e.g. "sales", "salesforce", "warehouse". Matches are counted per keyword in `keyword_matches` and add the `keyword_match` signal. Up to 50 keywords.

## `includeJobs` (type: `boolean`):

Return the open jobs (title, department, location, remote, posted date, function, seniority, link). Turn off for a compact row with only the counts and signals; the counts are computed from all jobs either way.

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

Caps the length of the job list in a row (the counts and signals still cover all jobs). The job list never changes the price.

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

Pages read on the company's own website to find its careers page, homepage included. The crawler goes for careers and jobs links first; 3 to 5 is enough for almost every website.

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

How many companies are checked at the same time. Each single website and each job board provider is always requested politely, one request at a time.

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

Optional, for the company websites only (job board APIs are always requested directly). Use a proxy only if many of your websites return blocked or HTTP errors.

## Actor input object example

```json
{
  "websites": [
    "https://www.figma.com",
    "https://www.monzo.com",
    "https://www.mailerlite.com"
  ],
  "includeJobs": true,
  "maxJobsPerCompany": 100,
  "maxPagesPerWebsite": 4,
  "maxConcurrency": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One row per company: hiring or not, open jobs, score, signals, careers page and job board.

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

Complete rows: every job with function, seniority, location and link, all counts and signals.

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

Companies processed by status, hiring / not hiring / unknown, job board providers, charged events, requests.

# 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 = {
    "websites": [
        "https://www.figma.com",
        "https://www.monzo.com",
        "https://www.mailerlite.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("truswen/company-hiring-signals").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 = { "websites": [
        "https://www.figma.com",
        "https://www.monzo.com",
        "https://www.mailerlite.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("truswen/company-hiring-signals").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 '{
  "websites": [
    "https://www.figma.com",
    "https://www.monzo.com",
    "https://www.mailerlite.com"
  ]
}' |
apify call truswen/company-hiring-signals --silent --output-dataset

```

## MCP server setup

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

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/PsRvA1cyj8Pz6tv1A/builds/2rnclwiW5Xo2i0dau/openapi.json
