# Ghost Job Detector: Is This Job Real? Greenhouse, Lever, Ashby (`robotninja/ghost-job-detector`) Actor

Ghost risk score (0–100) with plain reasons for every job ad on Greenhouse, Lever and Ashby boards: days open, reposts, refreshed ads, talent pools. Built on our own daily job-ad history.

- **URL**: https://apify.com/robotninja/ghost-job-detector.md
- **Developed by:** [Fam Nguyen](https://apify.com/robotninja) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 scored jobs

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

## Ghost Job Detector: Is This Job Real?

Get a **ghost risk score (0–100) with plain reasons** for every job ad on **Greenhouse, Lever and Ashby** job boards.

A ghost job is a job ad with no real plan to hire: left open for months or years, reposted again and again, or kept online "just in case". In Q2 2026 about **1 in 5 job ads** on Greenhouse clients' boards showed no hiring activity (WSJ). Ontario banned them in January 2026, and several US states are working on rules.

This Actor tells you, per job, **how likely it is to be a ghost job and why**.

### Example result

| Risk | Level | Company | Job | Why |
|---|---|---|---|---|
| 75 | high | ExampleCorp | Senior Data Engineer | Same role open 164 days (this ad posted 5 days ago); Reposted 2 times with a new ID; Company's openings barely changed in 90 days |
| 60 | high | ExampleCorp | Frontend Engineer | Open 1,012 days (more than 2 years) |
| 0 | low | ExampleCorp | Account Executive | No ghost signals found |

Every row also has the official job link, the posting date, days open, the number of reposts, and a disclaimer.

### How the score works

Simple, transparent rules, no black box:

| Signal | Points |
|---|---|
| Open more than 60 / 90 / 180 days | +20 / +30 / +40 |
| Open more than 1 year / 2 years | +50 / +60 |
| Reposted with a new ID (same requisition number, or same title + location) | +15 per repost, max +30 |
| Old ad recently "refreshed" (Greenhouse; not counted when the company updated its whole board on one day) | +5 |
| Title says talent pool, general or open application | +15 |
| The company's openings barely changed in 90 days | +15 |

Score is capped at 100. **0–29 low, 30–59 unclear, 60–100 high.**

**Why reposts count:** a reposted ad looks brand new on job sites. We keep a **daily history** of every tracked board, so we see when the same role disappears and comes back with a new ID, and we count the days from the first time we saw the role.

### How to use it

- **Check specific companies:** paste their job board links, for example `https://job-boards.greenhouse.io/vercel`, `https://jobs.lever.co/company` or `https://jobs.ashbyhq.com/company`. You get all their jobs.
- **Get the riskiest jobs across all tracked companies:** leave the boards empty and set **Minimum ghost risk** to 60. You get at most 3 jobs per company (change this with **Maximum jobs per company**).
- **Filter** by job title or location (for example `engineer`, `London`, `Remote`).
- **Talent pools** ("Don't see a role for you?", "General application") are left out by default, because the title already says they are not a real opening. Switch on **Include talent pools** to get them.

Boards we don't track yet are read live (days open and refreshed signals work immediately) and are **added to our daily history**, so their repost signals build up over time.

### Who uses it

- **Job boards and aggregators:** hide stale or ghost listings.
- **Job-seeker apps and AI job agents:** only apply to real jobs.
- **Sales and recruiting teams:** a real hire usually means a real budget.
- **Researchers and journalists:** ghost-job numbers per company or sector.
- **HR teams:** check how your own ads look from the outside.

### Pricing

You pay **per job returned** (see the price on this page). Use **Maximum jobs** and **Minimum ghost risk** to control what you pay. Jobs that are filtered out cost nothing.

### Data and privacy

- Only job ads that companies publish themselves, read through the **official public job-board APIs** of Greenhouse, Lever and Ashby, within their robots.txt rules and at a polite rate.
- **No personal data:** no recruiter names, no emails, nothing about applicants.
- History started on **12 October 2026** and grows every day. The `historyDays` field shows how much history a board has.

### Good to know

- **This is a risk estimate from public signals, not proof that a job is fake.** Some roles are legitimately always open (for example, large sales or support teams). Use the reasons, not just the number.
- Lever and Ashby don't show "last updated", so the "refreshed" signal only works for Greenhouse.
- Repost and company signals need history. Boards we started tracking recently mostly score on days open.

### Output fields

`ghostRisk`, `riskLevel`, `reasons`, `reasonsText`, `company`, `board`, `ats`, `jobId`, `title`, `location`, `department`, `postedAt`, `firstSeenByClock`, `daysOpen`, `roleDaysOpen`, `reposts`, `refreshed`, `evergreen`, `historyDays`, `source` (`history` or `live`), `url`, `checkedAt`, `disclaimer`.

# Actor input Schema

## `boards` (type: `array`):

Job boards to check, for example https://job-boards.greenhouse.io/vercel, https://jobs.lever.co/company or https://jobs.ashbyhq.com/company. Leave empty to get the highest-risk jobs from all companies we track.

## `minRisk` (type: `integer`):

Only return jobs with at least this score. 0 = all jobs, 30 = unclear and high, 60 = high risk only.

## `includeEvergreen` (type: `boolean`):

Also return ads that say openly they are a talent pool or general application (for example "Don't see a role for you?"). Off by default, because these are not real openings and everyone can see that from the title.

## `maxPerCompany` (type: `integer`):

Only used when you leave the job boards empty (all tracked companies). Shows at most this many jobs per company, so one big company doesn't fill the whole list. 0 = no limit.

## `titleContains` (type: `string`):

Optional, for example engineer or sales.

## `locationContains` (type: `string`):

Optional, for example London, Remote or Netherlands.

## `maxJobs` (type: `integer`):

Stop after this many jobs (highest risk first). You pay per job returned.

## Actor input object example

```json
{
  "boards": [
    "https://job-boards.greenhouse.io/vercel",
    "https://jobs.ashbyhq.com/Ashby"
  ],
  "minRisk": 0,
  "includeEvergreen": false,
  "maxPerCompany": 3,
  "maxJobs": 1000
}
```

# 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 = {
    "boards": [
        "https://job-boards.greenhouse.io/vercel",
        "https://jobs.ashbyhq.com/Ashby"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("robotninja/ghost-job-detector").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 = { "boards": [
        "https://job-boards.greenhouse.io/vercel",
        "https://jobs.ashbyhq.com/Ashby",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("robotninja/ghost-job-detector").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 '{
  "boards": [
    "https://job-boards.greenhouse.io/vercel",
    "https://jobs.ashbyhq.com/Ashby"
  ]
}' |
apify call robotninja/ghost-job-detector --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,robotninja/ghost-job-detector"
        }
    }
}
```

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/KaFnpazgD8c5aOel3/builds/fQ8IrFycRoqiDQjDD/openapi.json
