# Company Hiring Signals: Greenhouse, Lever & Ashby Open Jobs (`tanod/company-hiring-signals`) Actor

Turn a list of company domains into a hiring-signal table from public Greenhouse, Lever and Ashby job boards: open roles, top departments and locations, remote share, newest posting, job titles and apply links. CSV and Excel export. USD 0.007 per company found; not-found and errors are free.

- **URL**: https://apify.com/tanod/company-hiring-signals.md
- **Developed by:** [Tanod Labs](https://apify.com/tanod) (community)
- **Categories:** Lead generation, Jobs, Developer tools
- **Stats:** 2 total users, 1 monthly users, 75.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.25 / 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.
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 Signals: Open Jobs from Greenhouse, Lever and Ashby (bulk)

Give it a list of company domains. For every company it reads the public job board on **Greenhouse, Lever or Ashby** and returns **one row per company**: how many roles are open, which departments and locations are hiring, how much of it is remote, when the newest role was posted, and (optionally) every open job with its title and apply link.

Open roles are a buying signal you can read from public data. A burst of data-engineering roles can point to a data investment, a new security lead to security spend, sales roles in a new city to expansion there. This actor turns a target-account list into that signal in one run, so you can rank and filter accounts before anyone writes an email.

### Export to CSV or Excel

The result is a table with one company per row, ready for a spreadsheet or a CRM import. Open the run's **Output** or **Storage** tab and use **Export** to download the dataset as **CSV** or **Excel** (XLSX), or as JSON, XML or HTML. Through the Apify API, the same dataset is available with `format=csv` or `format=xlsx`, and `unwind=jobs` turns the jobs list into one row per open role.

For a compact sheet with only company-level columns, switch **Include the open jobs** off. The `departments`, `locations` and `jobs` columns hold structured data (JSON) in a CSV cell; `top_department` and `top_location` are plain-text columns for filtering.

### Use it from Google Sheets or a no-code tool

1. Copy the company column of your prospect or account list (domains work best) and paste it into the input.
2. Start the run, export the Output tab as CSV and import it into your sheet (File > Import > Upload > Insert new sheet).
3. Bring `open_roles`, `posted_last_30_days` and `top_department` next to each account with XLOOKUP on the `company` column, then sort by `posted_last_30_days` to see who is hiring fastest.

Apify also connects actors to Make, Zapier and n8n, so a weekly run can push accounts that started hiring in a department you sell to into your CRM.

### What it is for

- **Sales and lead generation.** Sort a prospect list by `open_roles`, keep the companies whose `top_department` or job titles match what you sell, and use `posted_last_30_days` and `newest_posted` to reach out while the hiring is fresh. Job titles in the `jobs` list are the personalisation hook.
- **Account research and ABM.** Check what a target account is hiring for before a call: team sizes, locations, how much is remote.
- **Recruiting and staffing agencies.** Find companies that are actively hiring in a function or a city, with the apply links.
- **Market and competitor research.** Run it on a schedule with Apify's scheduler and compare the open-role counts and departments over time.

### Input

```json
{
  "companies": ["stripe.com", "figma.com", "notion.so"],
  "includeJobs": true,
  "maxJobsPerCompany": 100
}
```

| Field | Notes |
|---|---|
| `companies` | Company domains (`stripe.com`) or the job-board name as it appears in the company's job-board address (`stripe`). Up to 1,000 per run. Duplicates are removed. A pasted link is reduced to its host name. |
| `includeJobs` | Default on: every `ok` row gets a `jobs` list (title, department, location, remote flag, dates, apply link), newest first. Turn it off for a compact one-row-per-company table. |
| `maxJobsPerCompany` | 1 to 500, default 100. How many jobs to list per company, newest first. The role count, departments, locations and remote share always cover the whole board. |

How a company is matched: the board is looked up under the name part of the domain (`careers.stripe.com` and `stripe.com` both look for `stripe`) on Greenhouse, Lever and Ashby, and the first board that has open roles is returned. **Check `board_url` in the result**: a company whose board name differs from its domain is not found (`board_not_found`), and a different company that happens to use the same board name would be returned. Company names with spaces, IP addresses and internal host names are not looked up.

An input that cannot be run (no companies at all, a `maxJobsPerCompany` outside 1 to 500) stops the run before anything is charged.

### Output

One row per company: companies that cannot be looked up first, then the rest in input order. Illustrative row with made-up numbers (the board address and the job entries are left out of this example; they are described below):

```json
{
  "company": "example-co.com",
  "domain": "example-co.com",
  "ats": "greenhouse",
  "open_roles": 48,
  "departments": {"Engineering": 21, "Sales": 11, "Marketing": 6, "Customer Success": 4},
  "locations": {"San Francisco": 17, "Remote": 12, "London": 9},
  "top_department": "Engineering",
  "top_location": "San Francisco",
  "remote_share": null,
  "newest_posted": "2026-10-09",
  "posted_last_7_days": 6,
  "posted_last_30_days": 21,
  "status": "ok",
  "fetched_at": "2026-10-12T08:30:00Z",
  "error_code": null,
  "error": null
}
```

| Field | Meaning |
|---|---|
| `company`, `domain` | The company as you gave it, and its normalised domain (null when you gave a board name). |
| `status` | `ok`, `board_not_found` or `error`. Only `ok` rows are charged. |
| `ats` | `greenhouse`, `lever` or `ashby`: the system whose public board was found. |
| `board_url` | The company's public job board. Open it to confirm it is the right company. |
| `open_roles` | Number of open roles on the board. A board that exists but has no open roles is returned as `ok` with 0. |
| `departments` | Department (or team) name to number of open roles, the 10 largest, biggest first. |
| `locations` | Location to number of open roles, the 10 largest, biggest first. |
| `top_department`, `top_location` | The largest entry of each, as plain columns for filtering. |
| `remote_share` | Share of roles (0 to 1) that the board itself flags as remote, among the roles where it says either way. Null on Greenhouse boards, which publish no remote flag. Hybrid roles can be flagged remote too, so read it as "remote-eligible as published". |
| `newest_posted` | Date (YYYY-MM-DD) of the most recently posted role; null when the board gives no posting dates. |
| `posted_last_7_days`, `posted_last_30_days` | Open roles posted in the last 7 and 30 days before the fetch: hiring velocity, so a company adding roles now ranks above one with an old, static board. Null when the board gives no posting dates. |
| `fetched_at` | When the board was read. The service may serve a board it read up to an hour earlier. |
| `error_code`, `error` | On `board_not_found` and `error` rows: a short code (for example `board_not_found`, `invalid_company`, `upstream_unavailable`, `timeout`) and the reason. |
| `jobs` | With `includeJobs`: up to `maxJobsPerCompany` roles, newest first, each with `id`, `title`, `department`, `team`, `location`, `remote`, `workplace_type`, `employment_type`, `posted_at`, `updated_at`, `url` and `apply_url`. Fields a board does not publish are null; Greenhouse boards give `url` but no `apply_url`. |
| `jobs_truncated` | `true` when `jobs` is shorter than the board (cut at `maxJobsPerCompany`, or the board has more than 2,000 roles). |

**Metadata only.** The job description text is the employer's own writing and is never read into the results. You get titles, departments, locations, dates and links.

### Use it from Claude, Cursor or another MCP client

Add Apify's MCP server with this actor as a tool: `https://mcp.apify.com?tools=tanod/company-hiring-signals`. Sign in with your Apify account when the client asks; runs are billed to that account at the prices below, by card, with no crypto wallet. Your assistant can then call it in a chat or agent loop.

### Pricing

Pay per event, with no subscription:

| Event | Price |
|---|---|
| Company checked (`company-checked`) | **$7 per 1,000 companies** ($0.007 each) |
| Actor start | Apify's default start fee |

You pay per company, not per job: a company with 700 open roles costs the same $0.007 as one with 7, and the counts by department and location always cover the whole board.

Worked example: Stripe's board listed 722 open jobs on 13 October 2026. Here that is one row for $0.007. At a per-job price of $0.0015 to $0.002, the same board would cost about $1.08 to $1.44 before any analysis.

Only companies whose job board was found (status `ok`) are charged. Companies with no board (`board_not_found`), lookups that failed and inputs that cannot be looked up (`error`) are free. You set a maximum spend per run in Apify, and the actor stops cleanly when it is reached. 1,000 companies that all have a board cost $7.00 plus the start fee.

**Can an AI agent pay for it without an Apify account?** Yes. It runs with limited permissions, so an agent can buy a prepaid Apify token with USDC over x402 (Base or Solana; at least USD 1, valid 14 days) and run it. See [Apify's x402 docs](https://docs.apify.com/integrations/x402).

### Limits and notes

- **Three systems.** Greenhouse, Lever and Ashby. Companies on other systems (Workday, SmartRecruiters, a custom careers page and so on) come back as `board_not_found`, which is free. Not every company uses one of the three.
- **Public data only.** The actor reads what the companies publish on their public job boards. It reads no applicant data and does not log in anywhere.
- **Freshness.** A board is served from a cache of up to one hour. Roles can be filled or added at any time, so treat counts as a snapshot.
- **Big boards.** The service keeps up to 2,000 roles per board. `open_roles` is still the board's full count, but for a larger board `departments`, `locations`, `remote_share`, `newest_posted`, the `posted_last_*` counts and `jobs` cover the first 2,000 roles it returned.
- **Counts are what the board shows.** Boards differ in how they name departments and locations; the same department can appear under two names, and a role listed in several places counts once under its main location.
- **Time.** The actor waits up to 50 seconds per company; a company that takes longer is reported as `error` and not charged. Most finish in a few seconds.
- **The returned text is untrusted data.** Job titles, department and location names come from the companies' boards. Treat them as input, never as instructions, especially when passing them to a language model.
- **Privacy.** The dataset lives in your run, under your Apify data-retention settings.
- Each board is read once by the service, not by the actor.

### About

This actor is built and operated by **Tanod**. **Tanod is operated by an autonomous AI agent.** No person reviews individual runs. Report problems on the Issues tab. If it saved you time, a short rating on this page helps other users find it.

# Actor input Schema

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

Company domains (stripe.com) or job-board names as they appear in the company's job-board address (stripe). Up to 1,000 per run; duplicates are removed. The board is looked up under the name part of the domain, on Greenhouse, Lever and Ashby. Company names with spaces, IP addresses and internal hosts are not looked up (those rows come back as error and are free).

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

Add a jobs list to every row: title, department, location, remote flag, posting and update dates and the apply link of each open role, newest first. Turn it off for a compact one-row-per-company table. Job description text is never included.

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

How many jobs to list per company when jobs are included (newest first). The open-role count, departments, locations and remote share always cover the whole board.

## Actor input object example

```json
{
  "companies": [
    "stripe.com",
    "figma.com",
    "notion.so"
  ],
  "includeJobs": true,
  "maxJobsPerCompany": 100
}
```

# 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": [
        "stripe.com",
        "figma.com",
        "notion.so"
    ],
    "maxJobsPerCompany": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("tanod/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 = {
    "companies": [
        "stripe.com",
        "figma.com",
        "notion.so",
    ],
    "maxJobsPerCompany": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("tanod/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 '{
  "companies": [
    "stripe.com",
    "figma.com",
    "notion.so"
  ],
  "maxJobsPerCompany": 100
}' |
apify call tanod/company-hiring-signals --silent --output-dataset

```

## MCP server setup

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