# Job Postings & Hiring Signals (`dataio/job-postings-hiring-signals`) Actor

Every open role at any company, across Greenhouse, Ashby, Lever, SmartRecruiters and Workable — one schema, with normalised function and seniority, country resolved from the city, and days each role has been open.

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

## Pricing

$3.00 / 1,000 job postings

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Job Postings & Hiring Signals

Every open role at any company — across the five applicant tracking
systems that publish their job boards openly, in **one schema**.

A company publishes its openings on Greenhouse, Ashby, Lever,
SmartRecruiters or Workable. Each exposes a public board, and each does it
in a completely different shape: the title is `title` in one and `text` in
another, the date is an ISO string here and a millisecond timestamp there,
the location is a plain string in one and a six-field object in the next.

And nobody knows off-hand which system a given company uses.

No API key. No account.

***

### Genuinely international, by construction

This is not a US dataset with a few foreign rows. Measured on a single run
across 8 companies — 1,805 postings, **103 countries, 183 cities**:

| Country | Postings |
|---|---|
| United Kingdom | 212 |
| United States | 185 |
| Singapore | 141 |
| Australia | 140 |
| Malaysia | 116 |
| Indonesia | 87 |
| Ireland | 65 |
| Vietnam | 58 |
| India | 56 |
| Germany | 43 |

A company in Tokyo, Berlin, São Paulo or Kuala Lumpur publishes in the
same five places as one in San Francisco. One Actor reaches all of them —
which covers more of the world than a separate scraper per country ever
would.

***

### The five things this does that the sources do not

**1. It works out which system a company uses.**

You supply a company identifier; the Actor tries all five boards and keeps
whichever answers. Measured on 36 well-known companies: **75% detected**,
7,550 postings. The rest run Workday, SuccessFactors or their own site,
and are reported as not found rather than silently skipped.

**2. One schema for five incompatible structures.**

Same columns whichever board a role came from, with `ats` recording the
origin so nothing is hidden.

**3. Days each role has been open.**

The hiring signal. A role open for six months is one the company cannot
fill — a real, persistent need. No source gives this; they give a date.
Measured: median 36 days, but **19% have been open more than 90 days**,
and the longest in the sample was 1,059 days.

**4. Normalised function and seniority.**

Greenhouse returns `8611 Security Analytics`, internal cost-centre codes
included. SmartRecruiters already returns `Engineering`. All five are
mapped onto one vocabulary, and department codes are stripped.

Seniority is read from the title when the source does not give it.
**Manager is kept separate from Staff** — they are two different career
tracks, and merging them put 28% of a sample into "Staff".

**5. Country resolved from the city.**

Greenhouse often returns `Bengaluru` with no country at all, which left
`country` empty on a quarter of rows and made country filtering useless.
Cities are resolved against a table covering every continent, and country
names are normalised so `US` and `United States` stop counting as two
countries. Coverage: **74.3% → 90.5%**.

***

### Field coverage

Measured on 1,805 postings from 8 companies across all five systems.

| Field | Coverage |
|---|---|
| `company`, `title`, `jobId`, `location`, `city` | 100% |
| `function`, `seniority`, `daysOpen`, `publishedAt` | 100% |
| `jobUrl`, `applyUrl` | 100% |
| `country` | 90.5% |
| `requisitionId` | 76.5% |
| `department` | 62.6% |
| `description` | 61.9% |
| `employmentType` | 60.6% |
| `countryCode` | 37.4% |

`description` and `countryCode` are only carried by some of the five
systems; they are left empty rather than filled with a guess.

***

### What it is for

- **B2B prospecting.** A company hiring twelve backend engineers is
  buying infrastructure. Hiring sales reps means it is expanding. The
  postings say so before any announcement does.
- **Market entry timing.** `country` and `city` across a competitor's
  whole board show where they are opening, quarter by quarter.
- **Recruitment.** Roles open past 90 days are the ones agencies get
  called about.
- **Competitive intelligence.** Team names and departments reveal an org
  chart the company never published.
- **Compensation and talent research.** Function and seniority are
  comparable across companies and countries.

Three dataset views ship with the Actor: **Postings**, **Hard-to-fill
roles** and **Global footprint**.

***

### Limits

Stated plainly, because they affect what you can conclude.

- **Roughly a quarter of companies are not reachable.** Workday,
  SuccessFactors, Taleo and bespoke career sites are not covered. The
  status message names the companies that were not found, so an empty
  result is never mistaken for "not hiring".
- **You supply the identifier, not the company name.** It is the slug in
  the careers URL — `stripe` in `boards.greenhouse.io/stripe`. Pasting the
  full URL works. A company name that is not the slug will not resolve.
- **`daysOpen` is time since first published, not time unfilled.** Some
  boards re-publish a role rather than update it, which resets the clock;
  others leave evergreen listings open permanently. Treat a very old date
  as "long-running requisition", not as a precise vacancy duration.
- **Function and seniority are inferred.** They are good enough to
  segment thousands of rows, not to adjudicate a single one. A title like
  "Risk Operations Associate" under a "User Policy Operations" department
  is a genuine judgement call.
- **The city table covers where qualified hiring concentrates**, not every
  town. An unlisted city leaves `country` empty rather than guessed.
- **A posting is not a hire.** Boards carry cancelled, evergreen and
  pipeline roles. Volume is a signal of intent, not of headcount.
- **Public boards only.** Confidential and internal-only roles never
  appear, by design.

***

### Sources

Public job board APIs of
[Greenhouse](https://boards-api.greenhouse.io),
[Ashby](https://api.ashbyhq.com),
[Lever](https://api.lever.co),
[SmartRecruiters](https://api.smartrecruiters.com) and
[Workable](https://apply.workable.com) — the same endpoints that power the
careers pages companies publish deliberately. No authentication. This
Actor is not affiliated with any of them.

# Actor input Schema

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

Company identifiers, one per line — the slug from the careers board URL, for example stripe from boards.greenhouse.io/stripe. You can paste the full URL instead and it will be cleaned up. The Actor works out which system each company uses.

## `ats` (type: `string`):

Leave on automatic unless you already know. Automatic tries all five and keeps whichever answers.

## `functions` (type: `array`):

Normalised job function. Leave empty for all. Measured split on a 1,805-job sample: Engineering 31%, Sales 17%, Marketing 10%, Operations 9%.

## `countries` (type: `array`):

Country names or codes, for example Singapore, India, Germany, US. Matched against country, location and country code.

## `titleKeyword` (type: `string`):

Keeps only jobs whose title contains this text, for example platform, account executive, designer.

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

Around 20% of postings in a typical run are remote.

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

Recent postings are the freshest signal. Median posting age is 36 days, but 19% have been open for more than 90 days.

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

One row per posting. The longest-open roles are kept first, since those are the strongest hiring signal.

## Actor input object example

```json
{
  "companies": [
    "stripe",
    "notion",
    "grab"
  ],
  "ats": "auto",
  "remoteOnly": false,
  "maxItems": 1000
}
```

# Actor output Schema

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

Every row produced by the run, in JSON.

# 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",
        "notion",
        "grab"
    ],
    "maxItems": 1000
};

// Run the Actor and wait for it to finish
const run = await client.actor("dataio/job-postings-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",
        "notion",
        "grab",
    ],
    "maxItems": 1000,
}

# Run the Actor and wait for it to finish
run = client.actor("dataio/job-postings-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",
    "notion",
    "grab"
  ],
  "maxItems": 1000
}' |
apify call dataio/job-postings-hiring-signals --silent --output-dataset

```

## MCP server setup

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