# Ghost Job Detector: Find Fake, Stale and Closed Job Postings (`kodabloomllc/ghost-job-detector`) Actor

Checks Indeed job postings for signs they aren't really being filled: closed on the employer's own site, posted for months, re-posted, no pay range where the law requires one, employer hidden. Every risk score comes with the reasons.

- **URL**: https://apify.com/kodabloomllc/ghost-job-detector.md
- **Developed by:** [KodaBloom LLC](https://apify.com/kodabloomllc) (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 job 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

## Ghost Job Detector: Find Fake, Stale and Closed Job Postings

Some job postings stay up long after the job is filled, or were never meant to be filled at all. This actor checks job postings from Indeed scraper results and gives each one a **ghost risk** score from 0 to 100, with the reasons spelled out.

### What it checks

| Signal | Points | How it is checked |
|---|---|---|
| **Closed on the employer's own site** | 60 | Opens the job's application link (Greenhouse, Lever, Ashby, Workday, Dayforce and others). If the employer's page is gone or says the job is closed while the job board still lists it, that is a zombie posting. |
| Posted a long time ago | 10 to 35 | 30+, 60+, 120+ and 365+ days since the job was first published |
| Re-posted | 10 | First published long before it was refreshed on the job board |
| Same job posted many times | 10 | Same employer and title 3+ times in your search |
| No pay range where the law requires one | 10 | CA, CO, WA, NY, IL, MD, MN, HI, VT, MA, NJ and DC require pay ranges for most employers |
| Employer not named | 10 | No employer name, or "our client", "confidential company" |
| Very short description | 5 | Under 150 words |
| "Talent pool" language | 15 | "talent pipeline", "future openings", "evergreen role", "no current opening" and similar |

Risk levels: **high** 60+, **medium** 30 to 59, **low** under 30.

The employer-page check reads only text people can see on the page. Many career sites carry "Page not found" inside their code on every page, so a naive check calls open jobs closed. Phrases like "open until the position is filled" are not counted as closed either. Ashby career pages are checked through Ashby's public job-board API.

### Example

On 300 real Indeed postings (data scientist in San Francisco, remote software engineer, customer service in Sacramento):

- **5 high risk**: all 5 were confirmed closed on the employer's own site (the page returns "not found") while still listed on Indeed. Four came from one company and had been up 68 days.
- **71 medium risk**: mostly postings up for over 180 days with no pay range in California.
- **224 low risk**.
- 182 employer pages were confirmed open; 113 could not be checked (applies on Indeed, or the employer site blocks automated visits).

### How to use it

1. Run an Indeed scraper. Best: **[Indeed Jobs Scraper by valig](https://apify.com/valig/indeed-jobs-scraper)**, which includes both the original publish date and the date on Indeed. [Indeed Scraper by misceres](https://apify.com/misceres/indeed-scraper) works too.
2. Open this actor and pick that run's dataset under **Indeed scraper results**.
3. Optional: set **Minimum ghost risk** to 30 or 60 for a short list.

### Output

```json
{
  "title": "Customer Service Manager - US",
  "company": "Eco Global",
  "location": "Sacramento, CA",
  "ghost_risk": 90,
  "risk_level": "high",
  "why": "employer page gone (HTTP 404); first posted 68 days ago; no pay range, though CA requires one for most employers",
  "posted_days_ago": 68,
  "first_published_days_ago": 68,
  "employer_page": "closed",
  "same_job_postings": 1,
  "salary_listed": false,
  "description_words": 374,
  "job_url": "https://www.indeed.com/viewjob?jk=...",
  "apply_url": "https://jobs.dayforcehcm.com/..."
}
```

`employer_page` is `closed`, `open` or `unknown`.

### Good to know

- A high score means "look closer before you spend time applying", not proof. Some long-running postings are real (large employers hiring for the same role all year).
- Pay-range laws have employer-size thresholds and exceptions, so a missing range is a signal, not a legal finding.
- Remote jobs without a state are not checked for pay-range laws.

### Pricing

You pay per job checked. Run it with no input to score a small built-in demo sample for free.

Made by KodaBloom LLC. Questions: info@kodabloom.com

# Actor input Schema

## `datasetIds` (type: `array`):

Pick one or more datasets from an Indeed scraper run. Supported: valig/indeed-jobs-scraper (best: it has both publish dates) and misceres/indeed-scraper.

## `jobs` (type: `array`):

Job objects in either supported scraper's format.

## `checkEmployerPages` (type: `boolean`):

Open each job's application link on the employer's own site (Greenhouse, Lever, Ashby, Workday and others) to see whether the job is still open there. This is the strongest ghost-job signal.

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

Only output jobs with at least this risk score (0 to 100). Every job is still checked and counted.

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

Stop after this many (0 = no limit).

## Actor input object example

```json
{
  "jobs": [],
  "checkEmployerPages": true,
  "minRisk": 0,
  "maxJobs": 0
}
```

# Actor output Schema

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

One row per job posting, highest ghost risk first, with the reasons.

# 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 = {
    "jobs": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("kodabloomllc/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 = { "jobs": [] }

# Run the Actor and wait for it to finish
run = client.actor("kodabloomllc/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 '{
  "jobs": []
}' |
apify call kodabloomllc/ghost-job-detector --silent --output-dataset

```

## MCP server setup

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