# Job Postings Scraper: Company Hiring Signals (`agents001/company-hiring-signals-feed`) Actor

Returns open job postings and hiring velocity per company from public Greenhouse, Lever and Ashby job boards: title, department, location, remote, salary range, posted date, plus new and closed roles since your last run.

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

## Pricing

from $2.00 / 1,000 job records

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

## Job Postings Scraper: Company Hiring Signals

Get every open job at the companies you care about, as clean structured data: **one record per job** with title, department, team, location(s), remote flag, employment type, posted date, apply link and the salary range where the company publishes one. Add an optional **hiring summary per company**: open roles, roles by department, roles posted in the last 7 and 30 days, hiring velocity, and how many roles opened or closed since your last run.

It reads each company's own public job board on **Greenhouse, Lever or Ashby**, the three applicant tracking systems most fast-growing companies use. Give it a list of companies, schedule it daily, and switch on `onlyNew` to get just the jobs that appeared since the previous run.

### What you get

**Job records** (`recordType: "job"`), one per open role:

| Field | Example |
|---|---|
| `company`, `companyKey`, `ats` | `Ramp`, `ashby:ramp`, `ashby` |
| `jobId`, `title` | `b7514711-...`, `Senior Backend Engineer` |
| `department`, `team` | `Engineering`, `Backend` |
| `location`, `locations`, `country` | `New York, NY (HQ)`, all listed places, `USA` |
| `remote`, `workplaceType` | `true`, `hybrid` |
| `employmentType` | `full-time`, `part-time`, `contract`, `temporary` or `internship` |
| `postedAt`, `updatedAt`, `ageDays` | ISO dates in UTC, days since posting |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryInterval`, `salaryText` | `204000`, `280000`, `USD`, `year`, `USD 204,000 - 280,000 per year` |
| `jobUrl`, `applyUrl` | Links to the job page and application form |
| `isNew`, `firstSeenAt` | Whether the job is new since the previous run with the same input |

**Company summaries** (`recordType: "companySummary"`), one per company:

| Field | Meaning |
|---|---|
| `openRoles`, `matchingRoles`, `rolesOutput` | All open roles, roles that match your filters, job records returned |
| `rolesByDepartment`, `topDepartments`, `topLocations` | Where the company is hiring |
| `postedLast7Days`, `postedLast30Days`, `postedLast90Days` | Recent posting activity |
| `hiringVelocityPerWeek` | Roles posted per week, averaged over the last 30 days |
| `medianRoleAgeDays` | How long roles have typically been open |
| `remoteRoles`, `remoteShare`, `rolesWithSalary` | Remote and pay transparency mix |
| `newRolesSinceLastRun`, `closedRolesSinceLastRun`, `netChangeSinceLastRun` | Change since the previous run with the same input (empty on the first run) |

The Output tab has three views: **Jobs**, **Company summaries** and **Salaries**.

### Who it is for

- **Sales and business development teams** who treat hiring as a buying signal: a company opening five finance roles is a likely buyer of finance software.
- **Recruiters and staffing firms** tracking which companies are growing and in which functions.
- **Investors and analysts** watching headcount growth, team mix and expansion into new cities.
- **Job seekers** who want one daily list of new roles at their target companies.

### Quick start

1. Click **Start** with the example input. You get the open roles at five example companies plus a summary for each.
2. Replace the list with your own companies. Any of these formats works:
   - `greenhouse:stripe`, `lever:spotify`, `lever-eu:<site>`, `ashby:ramp` (fastest and most reliable)
   - A hosted job board link: `https://boards.greenhouse.io/figma`, `https://jobs.lever.co/zoox`, `https://jobs.ashbyhq.com/linear`
   - A company careers page: `https://www.figma.com/careers/` (the Actor looks for the job board behind it)
   - Just a name: `openai` (the Actor tries Greenhouse, Ashby and Lever in turn)
3. Add filters if you only care about some roles, for example department `Sales`, location `London`, or posted in the last 7 days.
4. Schedule it daily with **Only jobs not seen in previous runs** switched on, and connect the dataset to email, Slack, Google Sheets or your CRM with an Apify integration or webhook.

### Input

| Field | What it does | Default |
|---|---|---|
| `companies` | List of companies (formats above). Empty runs the example list | example list |
| `departmentKeywords` | Department or team contains any of these words | all |
| `titleKeywords` | Job title contains any of these words | all |
| `excludeTitleKeywords` | Drop jobs whose title contains any of these words | none |
| `locations` | Any listed location or country contains any of these words | all |
| `remote` | `any`, `remoteOnly` or `excludeRemote` | `any` |
| `postedWithinDays` | Only jobs first published in the last N days. `0` means any age | 0 |
| `onlyNew` | Only return jobs not returned by a previous run with the same companies and filters | false |
| `includeCompanySummary` | Add one summary record per company | true |
| `includeSalary` | Add published salary ranges | true |
| `maxJobsPerCompany` | At most this many jobs per company, newest first. `0` means no limit | 0 |
| `maxItems` | At most this many job records in total. `0` means no limit | 1000 |

Example input for a daily sales-signal feed:

```json
{
  "companies": ["greenhouse:stripe", "ashby:ramp", "lever:zoox", "https://jobs.ashbyhq.com/notion"],
  "departmentKeywords": ["Finance", "Accounting"],
  "locations": ["New York", "London", "Remote"],
  "onlyNew": true,
  "includeCompanySummary": true
}
```

### Output example

```json
{
  "company": "Ramp",
  "companyKey": "ashby:ramp",
  "ats": "ashby",
  "boardToken": "ramp",
  "jobId": "b7514711-8a29-4bd2-a97a-967be14e336f",
  "title": "Senior Backend Engineer | Token Spend Management",
  "department": "Engineering",
  "team": "Backend",
  "location": "New York, NY (HQ)",
  "locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)"],
  "country": "USA",
  "remote": true,
  "workplaceType": "hybrid",
  "employmentType": "full-time",
  "employmentTypeRaw": "FullTime",
  "postedAt": "2026-10-20T13:40:20Z",
  "updatedAt": null,
  "ageDays": 0,
  "salaryMin": 204000,
  "salaryMax": 280000,
  "salaryCurrency": "USD",
  "salaryInterval": "year",
  "salaryText": "USD 204,000 - 280,000 per year",
  "jobUrl": "https://jobs.ashbyhq.com/ramp/b7514711-8a29-4bd2-a97a-967be14e336f",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/b7514711-8a29-4bd2-a97a-967be14e336f/application",
  "isNew": true,
  "firstSeenAt": "2026-10-20T14:11:22Z",
  "source": "api.ashbyhq.com",
  "recordType": "job",
  "scrapedAt": "2026-10-20T14:11:22Z",
  "schemaVersion": 1
}
```

```json
{
  "recordType": "companySummary",
  "company": "Ramp",
  "companyKey": "ashby:ramp",
  "boardUrl": "https://jobs.ashbyhq.com/ramp",
  "openRoles": 168,
  "matchingRoles": 168,
  "rolesOutput": 168,
  "topDepartments": "Sales (67), Engineering (38), People & Talent (15), Marketing (8), Product (7)",
  "remoteShare": 0.899,
  "rolesWithSalary": 161,
  "postedLast7Days": 15,
  "postedLast30Days": 51,
  "hiringVelocityPerWeek": 11.9,
  "medianRoleAgeDays": 49,
  "newRolesSinceLastRun": null,
  "closedRolesSinceLastRun": null
}
```

A run report is saved in the key-value store as `RUN_SUMMARY`: records returned, companies read, and any company that could not be found.

### Pricing

This Actor uses **pay per event** pricing:

- **$3 per 1,000 job records** (`job-record` event, $0.003 each)
- **$0.005 per company summary** (`company-summary` event)

You only pay for records saved to your dataset, so filters, `onlyNew` and `maxItems` keep costs down. For example, a daily `onlyNew` run over 50 companies returns only the jobs posted since the previous day plus 50 summaries: $0.25 for the summaries plus $0.003 per new job. Set a maximum cost per run in the run options and the Actor stops cleanly when it is reached.

### How "only new jobs" works

When `onlyNew` is on, the Actor remembers which job ids it has already returned for this exact input (companies plus filters) in a key-value store in your account called `hiring-signals-feed-state`. The next run only returns jobs that are not on that list. The first run returns everything that matches. Changing the companies or filters starts a fresh history, and you can keep separate histories by changing **History store name**. The store holds job ids and counts only.

The company summary's "since last run" numbers use the same history, so they work whether `onlyNew` is on or off.

### Data source and privacy

All data comes from the official public job board endpoints that Greenhouse, Lever and Ashby provide so companies can show their open roles on their own careers sites. The Actor reads only published, publicly listed jobs and uses no login.

The Actor does **not** collect personal data. It never returns job descriptions, recruiter or hiring manager names, contact details, or custom job fields (some companies use those to name recruiting teams). Company and job information is all it outputs.

### FAQ

**My company is not found. What can I do?**
Open the company's careers page, click a job, and look at the link. `boards.greenhouse.io/<name>` or `job-boards.greenhouse.io/<name>` means `greenhouse:<name>`, `jobs.lever.co/<name>` means `lever:<name>`, and `jobs.ashbyhq.com/<name>` means `ashby:<name>`. Companies on other systems (Workday, SmartRecruiters, iCIMS and so on) are not supported yet.

**Why are department or employment type empty for some companies?**
They are only filled when the company fills them on its job board. Ashby and Lever boards usually have both. Greenhouse boards always have a department but rarely an employment type.

**Why is salary empty for many jobs?**
Salary is only included when the company publishes it on the job board, which is common for US roles because of pay transparency laws and less common elsewhere.

**What does `remote: true` mean?**
The job can be done remotely from at least one of its listed locations. A hybrid job with a "Remote (US)" option is `remote: true` with `workplaceType: "hybrid"`.

**How fresh is the data?**
Live. Each run reads the job boards directly, so jobs appear as soon as the company publishes them.

**Something looks wrong. What should I do?**
Open an issue on the Actor's Issues tab with the run link and the company entry.

### Disclaimer

This Actor is independent and is not affiliated with, endorsed by or sponsored by Greenhouse, Lever, Ashby or any company whose jobs it returns. Product and company names are used only to describe compatibility and belong to their owners. Job data belongs to the companies that publish it; check the original posting before acting on it.

# Actor input Schema

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

One entry per company. Use 'greenhouse:<board>', 'lever:<site>', 'lever-eu:<site>' or 'ashby:<name>' when you know the job board. You can also paste a hosted job board URL (boards.greenhouse.io/..., jobs.lever.co/..., jobs.ashbyhq.com/...), a company careers page URL, or just a board name like 'stripe' and the Actor will try to detect the job board. Leave empty to run the example list.

## `departmentKeywords` (type: `array`):

Only jobs whose department or team contains any of these words, for example 'Engineering', 'Sales', 'Finance'. Case-insensitive.

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

Only jobs whose title contains any of these words or phrases, for example 'Account Executive', 'Data', 'Director'. Case-insensitive.

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

Drop jobs whose title contains any of these words, for example 'Intern'. Case-insensitive.

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

Only jobs where any listed location (or the country code where the job board publishes one) contains any of these words, for example 'London', 'Germany', 'Remote'. Case-insensitive.

## `remote` (type: `string`):

Keep all jobs, only remote jobs, or only jobs that are not remote. Hybrid jobs count as not remote.

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

Only jobs first published within this many days. 0 means any age.

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

Remember which jobs were already returned for this exact input and only return jobs that are new since then. The first run returns everything that matches. Ideal for scheduled daily runs. Changing the companies or filters starts a fresh history.

## `includeCompanySummary` (type: `boolean`):

Add one summary record per company: open roles, roles by department, remote share, roles posted in the last 7 and 30 days, hiring velocity and the change since the previous run.

## `includeSalary` (type: `boolean`):

Add the salary range when the company publishes one on its job board.

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

Return at most this many jobs per company, newest first. 0 means no limit. The company summary still counts every open role.

## `maxItems` (type: `integer`):

Stop returning job records after this many in total. 0 means no limit. Company summaries are not counted. You pay per job record returned.

## `stateStoreName` (type: `string`):

Name of the key-value store in your account that keeps the list of jobs already seen. Each different input gets its own record inside it. Change it only to keep separate histories.

## `requestDelaySecs` (type: `number`):

Minimum seconds between requests to the same job board host. Keep this polite.

## Actor input object example

```json
{
  "companies": [
    "greenhouse:figma",
    "greenhouse:discord",
    "lever:spotify",
    "ashby:linear",
    "ashby:ramp"
  ],
  "remote": "any",
  "postedWithinDays": 0,
  "onlyNew": false,
  "includeCompanySummary": true,
  "includeSalary": true,
  "maxJobsPerCompany": 0,
  "maxItems": 1000,
  "stateStoreName": "hiring-signals-feed-state",
  "requestDelaySecs": 0.25
}
```

# Actor output Schema

## `jobs` (type: `string`):

No description

## `companySummaries` (type: `string`):

No description

## `runSummary` (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": [
        "greenhouse:figma",
        "greenhouse:discord",
        "lever:spotify",
        "ashby:linear",
        "ashby:ramp"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("agents001/company-hiring-signals-feed").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": [
        "greenhouse:figma",
        "greenhouse:discord",
        "lever:spotify",
        "ashby:linear",
        "ashby:ramp",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("agents001/company-hiring-signals-feed").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": [
    "greenhouse:figma",
    "greenhouse:discord",
    "lever:spotify",
    "ashby:linear",
    "ashby:ramp"
  ]
}' |
apify call agents001/company-hiring-signals-feed --silent --output-dataset

```

## MCP server setup

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

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/e8qMzTxOuZCRjBOaw/builds/V9BbOun5rrLNadjpa/openapi.json
