# Hiring Signals: Company Job Boards & New Jobs Tracker (`watchmere/hiring-signals`) Actor

Paste company websites, get every open job from their Greenhouse, Lever, Ashby, SmartRecruiters or Workday job board, plus new and closed jobs since your last run. One summary row per company with job counts by department and location.

- **URL**: https://apify.com/watchmere/hiring-signals.md
- **Developed by:** [Watchmere .](https://apify.com/watchmere) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.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

### What does Job Board Scraper do?

Paste a list of **company websites** and get back **every open job** each company is advertising, read straight
from its public job board, plus **what changed since your last run**: new jobs and closed jobs.

It works with five widely used job boards (applicant tracking systems):

- **Greenhouse**, **Lever**, **Ashby** and **SmartRecruiters**, through their official public job-board APIs
- **Workday** career sites (\*.myworkdayjobs.com), through the same public data the career site itself loads

You don't need to know which one a company uses. Give it `stripe.com` and the tool looks at the company's website
and careers pages for a link to its job board, and if there isn't one, tries the company's name on Greenhouse, Lever
and Ashby, keeping a board only when there's strong evidence it's the same company. Guessing is deliberately
cautious, so some boards are missed; **pasting the job board link always works.**

You get **one summary row per company** (open jobs, new jobs, closed jobs, jobs by department and location, remote
jobs) and **one row per job** (title, department, team, locations, remote/hybrid/on-site, employment type, dates, pay
when the company publishes it, link, and optionally the description).

- No login, no API keys, no browser
- Export to CSV, Excel, JSON or Google Sheets, or call it from your own code, Zapier, Make, n8n or Clay

### Who is it for?

- **Sales teams:** "this company just opened 12 engineering roles in Berlin" is a buying signal. Track your target
  accounts weekly and act on the companies that are growing
- **Recruiters and agencies:** see which companies are hiring for the roles you place, as soon as your next
  scheduled run sees the new jobs
- **Investors and analysts:** hiring by department and location is an early look at a company's plans
- **Job seekers and researchers:** one clean table of open roles across many companies

### How to use it

1. Paste your **companies**, one per line: a domain (`acme.com`), a careers page
   (`https://www.acme.com/careers`) or a job board link (`https://jobs.lever.co/acme`).
2. Leave **Show what changed since the last check** on.
3. Click **Start**. Each company takes a few seconds.
4. Open the **Companies** view for the summary, **Jobs** for every job, or **New and closed jobs**.

To monitor, open **Schedules** and run it weekly (or daily) with the same companies. The first run records each
board; every later run marks jobs as `new` or `unchanged` and adds a `closed` row for every job that disappeared.

### Input example

```json
{
  "companies": ["stripe.com", "https://www.figma.com/careers/", "https://jobs.lever.co/palantir",
                "https://jobs.smartrecruiters.com/Ubisoft2", "https://adobe.wd5.myworkdayjobs.com/external_experienced"],
  "guessBoards": true,
  "includeDescriptions": false,
  "maxJobsPerCompany": 1000,
  "compareWithPrevious": true,
  "historyStoreName": "hiring-signals-history"
}
```

### Output example

Each company gets one row with `rowType: "company"` (shortened):

```json
{
  "rowType": "company",
  "input": "figma.com",
  "companyDomain": "figma.com",
  "companyName": "Figma",
  "ats": "greenhouse",
  "boardUrl": "https://job-boards.greenhouse.io/figma",
  "foundBy": "website",
  "status": "ok",
  "openJobs": 140,
  "newJobs": 2,
  "closedJobs": 2,
  "remoteJobs": 0,
  "readInFull": true,
  "jobsByDepartment": { "Sales": 45, "Engineering": 25, "Early Career": 11 },
  "jobsByLocation": { "San Francisco, CA": 91, "New York, NY": 88, "United States": 73 },
  "note": null,
  "checkedAt": "2026-10-19T13:51:02Z"
}
```

and each job one row with `rowType: "job"`:

```json
{
  "rowType": "job",
  "input": "linear.app",
  "companyName": "Linear",
  "ats": "ashby",
  "jobId": "d3bc1ced-3ce4-4086-a050-555055dbb1ff",
  "title": "Senior / Staff Fullstack Engineer",
  "department": "Product",
  "team": "Engineering",
  "locations": ["Europe"],
  "workplaceType": "remote",
  "employmentType": "Full-time",
  "postedAt": "2021-04-27T20:13:45Z",
  "url": "https://jobs.ashbyhq.com/linear/d3bc1ced-3ce4-4086-a050-555055dbb1ff",
  "compensation": null,
  "change": "new",
  "firstSeenAt": "2026-10-19T13:51:02Z",
  "checkedAt": "2026-10-19T13:51:02Z"
}
```

A job that disappeared since the last check gets a row with `"change": "closed"`, its title, department, locations,
link and the date it was first seen.

| Field | Meaning |
|---|---|
| rowType | `company` (one summary per input) or `job` (one per open or closed job). Filter on it in a spreadsheet |
| input | What you entered |
| companyDomain / companyName | The company's domain, and its name from the job board (Greenhouse, SmartRecruiters, the Lever or Ashby board page) or its website. Workday doesn't publish a name |
| ats / boardUrl | `greenhouse`, `lever`, `ashby`, `smartrecruiters` or `workday`, and the board's public page |
| foundBy | `input` (you gave the board link), `website` (linked from the company's site) or `guess` (found by name, with evidence; the note says which) |
| status | `ok` (board read), `no-board` (none found), `failed` (a board was found but couldn't be read), `duplicate` (same board as an earlier input), `invalid` (a line that isn't a website or board link; the rest of the run carries on) or `skipped` (over 2,000 companies) |
| openJobs / newJobs / closedJobs | Counts for this check. New and closed need a previous check of the same board |
| jobsByDepartment / jobsByLocation | Open jobs per department (or team) and per location, top 15 |
| remoteJobs | Open jobs marked remote |
| readInFull | `false` if the board has more jobs than **Max jobs per company**, or a Workday site lists 2,000 or more (closed jobs aren't reported then) |
| note | What was checked and anything worth knowing, in plain words |
| jobId, title, department, team, locations | As the board lists them |
| workplaceType | `remote`, `hybrid` or `onsite` when the board says so (Greenhouse: only when the location says remote or hybrid) |
| employmentType | Full-time, Part-time, Contract, Internship... when the board says so |
| postedAt / updatedAt | When the job was published / last updated, if the board gives it. Workday gives only "posted N days ago", so its date is a day (`2026-10-16`), and empty for "30+ days ago" |
| url | The public job page |
| compensation | `{min, max, currency, interval, summary}` only when the company publishes pay in the board's job list (Ashby and Lever boards can) |
| descriptionText | Plain-text description (only with **Include job descriptions**) |
| change | `first-seen` (first check of this board), `new`, `unchanged` or `closed` |
| firstSeenAt | When this tool first saw the job on this board |

The run's **SUMMARY** record (Output tab) lists every company with the same counts, plus run totals.

### How the job board is found

1. **A board link you give** is used as is.
2. **The company's website:** the tool loads the homepage, follows links that say careers, jobs, join us and
   similar (and tries /careers, /jobs, /company/careers and /about/careers), at most 6 pages per company, and looks
   for links, embedded boards and scripts pointing at Greenhouse, Lever, Ashby, SmartRecruiters or Workday. A linked
   board is used only if it's the company's own: its job links point to the company's site, or its address or name
   matches the website or the domain, its job ids appear on the website, or its descriptions mention the domain. A
   fund's website linking to a portfolio company's board doesn't count. A board whose job pages are on another
   website is never used, except the company's own careers domain: its name plus inc, hq, careers, jobs, team or
   corp (`redditinc.com` for reddit.com, `instacart.careers`). Anything else, such as `deltalabs.io` for delta.com,
   is treated as another company.
3. **By name** (optional, on by default, deliberately cautious): if the website has no board link (many companies
   show jobs on their own pages), it tries the company's name on Greenhouse, Lever and Ashby. The name tried comes
   from the domain, so a board with that name proves nothing by itself. A guessed board is kept only if **its job
   links point to the company's own domain** (or a subdomain of it), or **the board's name is exactly the website's
   own name** apart from legal endings (Inc, LLC, Ltd, GmbH, Corp, Co): "Acme Inc." matches a website called
   "ACME", but "Acme Group", "Delta Labs" or "Zeta Technologies" don't match "Acme", "Delta" or "Zeta". If the
   website can't be read (bot protection), only job links on the company's domain count. When in doubt the result
   is `no-board` (free) rather than a wrong board, and the note says what was checked. This misses some real
   boards (for example a company whose jobs are on a careers domain and whose board name differs from its
   website's); **paste the board link** (`https://job-boards.greenhouse.io/acme`) and it's always used. One case
   can still go wrong: two different companies with exactly the same name (say a German and an American "Acme")
   whose job links stay on the job board. Check `companyName` and the note on each guessed row, and paste the board
   link when it matters.

Websites on hosting platforms are treated as sites of their own: `bakery.vercel.app`, `acme.notion.site`,
`acme.github.io` or `acme.webflow.io` is never mistaken for Vercel, Notion, GitHub or Webflow (while the platforms'
own sites, such as `carrd.co`, are companies like any other).

Workday and SmartRecruiters boards can't be found by name; give their link if the website doesn't link to them.
Other job boards (Workable, iCIMS, Recruitee, Teamtailor...) aren't supported yet: the company row says `no-board`
and what was checked. Companies without a board are free.

### New and closed jobs

With **Show what changed since the last check** on, the tool keeps each board's open job ids and the date each was
first seen in a key-value store in your own Apify account (default name `hiring-signals-history`). On the next run:

- a job that wasn't there last time is `new`
- a job that was there and still is: `unchanged` (with its original `firstSeenAt`)
- a job that's gone gets a `closed` row, but only after **a second read of the board** a few seconds later confirms
  it's gone, so a glitch in one answer never shows up as a closed job

Closed jobs are only reported when the whole board was read both times. If a board has more jobs than **Max jobs
per company**, its missing jobs are kept and checked again next time, and jobs the last check couldn't have seen are
marked `first-seen` rather than `new`. Use a different **History name** per project to keep histories apart.

### How much does it cost?

You pay **per company checked**: a company whose job board was found and read, whether it has 500 open jobs or
none. See the **Pricing** tab for the rate. **Everything else is free:** job rows, closed-job rows, companies where
no job board was found, boards that couldn't be read, and duplicates.

Set a **maximum cost per run** in the run options and the tool stops cleanly when it gets there, without saving
companies it couldn't charge for. If Apify moves a run to another server, it carries on after the last company it
saved instead of starting over, and never charges a company twice.

### Good to know

- **Speed:** most companies take 1–8 seconds, several at a time. In our tests, 8 companies with 1,835 open jobs
  between them took under 30 seconds, and 16 well-known companies with 5,263 open jobs took 101 seconds, most of it
  one Workday site: Workday is read one request at a time with pauses (20 jobs per request), so 1,000 Workday jobs
  take about 90 seconds.
- **What it found in our tests** (21 well-known companies given as plain domains, plus Palantir): linked from the
  company's own website: Figma, Notion, Ramp, Linear, Mistral AI, GitLab, Vercel, Anthropic (Greenhouse or Ashby)
  and Palantir (Lever); by name, each with its evidence in the note: Stripe, Airbnb and Datadog (job links on their
  own domains), Cloudflare, Discord, Spotify and Bitpanda (board name is the website's name; Bitpanda is a
  Greenhouse EU board). Not found and not charged: OpenAI and Reddit (their websites blocked us, so a board with
  their name couldn't be confirmed), Instacart (its board's job pages are on instacart.careers and its website
  doesn't link to the board), Salesforce (Workday, not linked from its pages), HubSpot and Shopify. For a company
  like OpenAI, Reddit or Instacart, give the board link instead.
- **Descriptions:** Greenhouse, Lever and Ashby send them with the job list. SmartRecruiters and Workday need one
  request per job, so for them descriptions are fetched for up to 300 jobs per company per run, new jobs first.
  Descriptions are plain text, up to 10,000 characters. Contact details are removed: email addresses (also
  written as `name(at)company.de`), phone numbers (international, national and US formats, and numbers after
  "Tel", "Phone", "Mobil"...), LinkedIn and XING profile links, and sentences or blocks that name a contact
  person, such as "Ihr Ansprechpartner: Max Mustermann …", "Ansprechpartner\*in", "Talent Partner: …" or "Reach out
  to Jane Doe". Dates, years, salary ranges and phrases like "be the point of contact for clients" or "reach out to
  Google Cloud partners" are kept. This is best-effort: a name mentioned anywhere else in the text can't always be
  detected.
- **Workday** has no documented public API; the tool reads the same data the career site loads in your browser,
  politely (one request at a time per site). Workday reports at most 2,000 jobs per site, so bigger sites aren't
  read in full.
- **SmartRecruiters** answers "no jobs" for a company id it doesn't know. When a SmartRecruiters board has never had
  jobs in your history, a "no jobs" answer is reported as `no-board` and not charged.
- **Greenhouse EU boards** (job-boards.eu.greenhouse.io) are read through the same public API and linked on their
  EU address.
- **Proxy:** Apify Proxy is used only to load company websites; the job boards' APIs are called directly.

### FAQ

**Is this legal?** The tool reads public job postings that companies publish so that anyone can see them, through
the job boards' public APIs (Greenhouse Job Board API, Lever Postings API, Ashby Job Posting API, SmartRecruiters
Posting API) and Workday's public career sites. It doesn't log in and doesn't collect people: no recruiter or
hiring-manager fields, and contact details are removed from descriptions (see above). Job descriptions are
company-written texts; check that your use of them is allowed, and don't republish them as your own job board
without the companies' permission.

**Why did a company get `no-board`?** Its website doesn't link to a supported job board, it uses another job board,
or its website refused our requests (some sites block automated visitors). Give the board link directly, or check
the `note` and `pagesChecked`.

**Something isn't working or you'd like a feature?** Open an issue on the Issues tab.

# Actor input Schema

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

One per line: a company website or domain (stripe.com), its careers page (https://www.figma.com/careers/), or its job board link (https://jobs.lever.co/palantir, https://boards.greenhouse.io/acme, https://jobs.ashbyhq.com/ramp, https://jobs.smartrecruiters.com/Acme, https://acme.wd5.myworkdayjobs.com/External). Up to 2,000 per run.

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

Many companies show jobs on their own site without linking to the job board. When no board link is found, the tool tries the company's name (from its domain) on Greenhouse, Lever and Ashby, and keeps a board only when there's evidence it's the same company: the board's name matches, its job links point to the company's domain, its job ids appear on the company's website, or its job descriptions mention the domain. The company row says why.

## `includeDescriptions` (type: `boolean`):

Adds each job's description as plain text (up to 10,000 characters, with email addresses and phone numbers removed). SmartRecruiters and Workday need one extra request per job, so for them descriptions are fetched for up to 300 jobs per company per run, new jobs first.

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

Stops reading a company's board after this many jobs (at most 5,000). Keep it above the board's size if you want closed jobs reported: they're only reported when the whole board was read.

## `compareWithPrevious` (type: `boolean`):

Remembers each board's open job ids and when each was first seen, in a key-value store in your own Apify account (named below). Each job row then says new or unchanged, and jobs that disappeared since the last check are added as closed rows, after a second read of the board confirms they're gone.

## `historyStoreName` (type: `string`):

Name of the key-value store that remembers jobs between runs. Use a different name per project or client to keep their histories apart. Letters, digits and hyphens.

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

Used only to load company websites when looking for their job board. The job boards' own APIs are called directly. The default works for most people.

## Actor input object example

```json
{
  "companies": [
    "figma.com",
    "notion.com",
    "https://jobs.lever.co/spotify"
  ],
  "guessBoards": true,
  "includeDescriptions": false,
  "maxJobsPerCompany": 1000,
  "compareWithPrevious": true,
  "historyStoreName": "hiring-signals-history",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# 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": [
        "figma.com",
        "notion.com",
        "https://jobs.lever.co/spotify"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("watchmere/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 = {
    "companies": [
        "figma.com",
        "notion.com",
        "https://jobs.lever.co/spotify",
    ],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("watchmere/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 '{
  "companies": [
    "figma.com",
    "notion.com",
    "https://jobs.lever.co/spotify"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call watchmere/hiring-signals --silent --output-dataset

```

## MCP server setup

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