# Expired Jobs API for Career Site Job Listing API (`jobscrawler/expired-jobs-api-for-career-site-job-listing-api`) Actor

Track observed removals of public Greenhouse and Ashby job ads without an API key. Requires a genuine prior observation, two complete current collections and detail HTTP 404/410. Native board IDs; no claim of a filled vacancy or exact source expiry time.

- **URL**: https://apify.com/jobscrawler/expired-jobs-api-for-career-site-job-listing-api.md
- **Developed by:** [Jobs Scraper](https://apify.com/jobscrawler) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 52.6% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.99 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

### What does this Actor do?

Track **public ATS advertisement removals without a vendor API key**. The public mode compares genuinely observed Greenhouse and Ashby posting IDs with current complete collections and verifies exact detail status. It emits an event only when a previously observed listing is missing from two matching complete ID collections and its detail returns HTTP 404 or 410.

The result means that the advertisement is no longer publicly available. It does not establish that the employer filled the vacancy, and it does not invent an exact source expiration timestamp. Native IDs and board coverage differ from the original Fantastic.jobs service. The original authorized vendor mode remains available through sourceMode=fantastic; it is never a fallback for public mode.

### Sources and defaults

The [Ashby public Posting API](https://developers.ashbyhq.com/docs/public-job-posting-api) and [Greenhouse Job Board API](https://developers.greenhouse.io/job-board.html) provide public job collections. Empty source input selects the OpenAI Ashby board and Duolingo Greenhouse board.

The bootstrap file contains 21 historical identities from actual live API runs captured on 23–24 October 2026, with capture paths and SHA-256 provenance. These identities are comparison inputs. They are never returned as static results: each emitted event requires new current collection/detail checks. The Actor learns all current IDs for selected boards on every successful run. A new board initially establishes its own baseline.

### Input and operation

1. Select sourceMode=public.
2. Keep the starter boards or supply up to ten supported board URLs.
3. Choose a maximum result count and observation window.
4. Run the Actor. Inspect SOURCE_METADATA and RUN_SUMMARY alongside the dataset.
5. Schedule subsequent checks against the same named observation store.

Example input:

```json
{
  "sourceMode": "public",
  "startUrls": [
    {
      "url": "https://jobs.ashbyhq.com/openai"
    },
    {
      "url": "https://boards.greenhouse.io/duolingo"
    }
  ],
  "maxItems": 10,
  "timeRange": "1d",
  "stateStoreName": "jobscrawler-expired-ats-public-v1"
}
```

See the Input tab for all limits. timeRange filters when this tracker first confirmed a removal; it is not an exact publisher expiry date. 1w and 1m alias 7d and 30d. searchQuery filters the last genuinely observed title. New boards and quiet windows can produce zero events successfully. maxItems is a cap, not a promise that ten jobs expired.

The named KVS belongs to the running Apify user account. Run related workflows sequentially against the same state store. Give independent workflows distinct store names. Source failures and changing/incomplete collections fail without committing partial observation state. Source denial, server errors, redirects or a guessed URL alone never produce a removal event. Directly reachable detail pages are excluded.

### Output

You can download the dataset in JSON, HTML, CSV or Excel. The following two anonymized records illustrate the contract only; they are not runtime evidence.

```json
[
  {
    "recordId": "ashby/example/00000000-0000-4000-8000-000000000001",
    "jobId": "00000000-0000-4000-8000-000000000001",
    "jobTitle": "Example Engineer",
    "jobTitleObservation": "last_observed_public_title",
    "expirationStatus": "no_longer_publicly_available",
    "lastSeenAt": "2026-01-01T10:00:00.000Z",
    "firstObservedMissingAt": "2026-01-02T10:00:00.000Z",
    "exactExpirationTimeKnown": false,
    "sourceVacancyFilledStatusKnown": false,
    "dataMode": "live_public_ats_comparison"
  },
  {
    "recordId": "greenhouse/example/1234567",
    "jobId": "1234567",
    "jobTitle": "Example Analyst",
    "expirationStatus": "no_longer_publicly_available",
    "lastSeenAt": "2026-01-01T11:00:00.000Z",
    "firstObservedMissingAt": "2026-01-02T11:00:00.000Z",
    "exactExpirationTimeKnown": false,
    "dataMode": "live_public_ats_comparison"
  }
]
```

Real records also contain the current source URLs, provider, company handle, detail status, both complete collection hashes, verification timestamps and the provenance of the earlier observation. Missing source properties are omitted. The title is explicitly historical.

### Costs, limitations and support

Public mode requires no Jooble, Exa, Fantastic.jobs or other vendor API key. Normal Apify compute, storage and any published event pricing still apply. Requests are bounded, sequential and spaced; no proxy identity rotation is used. The client stops on denied routes or quota/server errors. The provider's collection is the authority for currently published IDs.

This is selected-board observation history, not a global historical vendor feed or a guarantee about a vacancy's hiring status. Removing and later publishing a job again clears its current removal event when the native ID reappears. Input errors, malformed collections, corrupt state and request-budget limits produce diagnostics rather than fabricated rows. Contact the Actor maintainer with the run ID and sanitized diagnostic code when a supported source changes.

# Changelog

This Actor's version history is a separate document: https://apify.com/jobscrawler/expired-jobs-api-for-career-site-job-listing-api/changelog.md

# Actor input Schema

## `sourceMode` (type: `string`):

Public mode uses native IDs on selected boards; original global vendor coverage and IDs are separate.

## `startUrls` (type: `array`):

Up to ten public boards. New boards establish genuine baselines before emitting removals. Empty targets use OpenAI and Duolingo.

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

A sparse observation window can return fewer or zero real records; no padding.

## `timeRange` (type: `string`):

Public mode filters the first observed removal time. 1w/1m alias 7d/30d. Exact source expiry time is unknown.

## `searchQuery` (type: `string`):

Optional substring of the actual previously observed title.

## `offset` (type: `integer`):

Skip matching verified records.

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

Persistent KVS in the running user account. Run related jobs sequentially; use distinct names for independent workflows. Prefix jobscrawler-expired-ats- is required.

## `maxMissingChecks` (type: `integer`):

Fail without committing partial state if the limit is exceeded.

## `maxRequests` (type: `integer`):

Includes collection, confirmation, robots and detail requests.

## `requestTimeoutSecs` (type: `integer`):

Bounds headers and response body time.

## `sampleMode` (type: `boolean`):

Must remain false. Historical real observations are comparison inputs, never static output.

## `fantasticJobsApiKey` (type: `string`):

Existing Fantastic mode only. Public mode reads and requires no vendor key.

## Actor input object example

```json
{
  "sourceMode": "public",
  "startUrls": [
    {
      "url": "https://jobs.ashbyhq.com/openai"
    },
    {
      "url": "https://boards.greenhouse.io/duolingo"
    }
  ],
  "maxItems": 10,
  "timeRange": "1d",
  "offset": 0,
  "stateStoreName": "jobscrawler-expired-ats-public-v1",
  "maxMissingChecks": 40,
  "maxRequests": 100,
  "requestTimeoutSecs": 30,
  "sampleMode": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing normalized non-null records plus complete cleaned rawData.

## `runSummary` (type: `string`):

Machine-readable status, data provenance mode, counts, timing, and null validation.

## `files` (type: `string`):

Key-value store containing source metadata and any sanitized error diagnostic.

# 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 = {
    "startUrls": [
        {
            "url": "https://jobs.ashbyhq.com/openai"
        },
        {
            "url": "https://boards.greenhouse.io/duolingo"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("jobscrawler/expired-jobs-api-for-career-site-job-listing-api").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 = { "startUrls": [
        { "url": "https://jobs.ashbyhq.com/openai" },
        { "url": "https://boards.greenhouse.io/duolingo" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("jobscrawler/expired-jobs-api-for-career-site-job-listing-api").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 '{
  "startUrls": [
    {
      "url": "https://jobs.ashbyhq.com/openai"
    },
    {
      "url": "https://boards.greenhouse.io/duolingo"
    }
  ]
}' |
apify call jobscrawler/expired-jobs-api-for-career-site-job-listing-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jobscrawler/expired-jobs-api-for-career-site-job-listing-api"
        }
    }
}
```

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/nU5bjbFa4w5PVs0tW/builds/jOfkGLifAxcseqEAS/openapi.json
