# Live ATS Jobs Search & Posting Verification - MCP & API (`alex_fodo/ats-jobs-mcp`) Actor

Search selected Greenhouse, Lever and Ashby employer boards live. Verify posting IDs in source feeds; get Remote/Hybrid distinctions and evidence. No LLM key.

- **URL**: https://apify.com/alex_fodo/ats-jobs-mcp.md
- **Developed by:** [AGENT DOJO](https://apify.com/alex_fodo) (community)
- **Categories:** Jobs, Developer tools, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 completed live job queries

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

## Live ATS Jobs Search & Posting Verification — MCP & API

Give an AI assistant current employer-published job data. Search selected **Greenhouse, Lever and Ashby** boards, or check whether supplied posting IDs are still present in their official public feeds. Results include source URLs, evidence paths, observation times and explicit coverage gaps.

No LLM, provider key, n8n account, company login or proxy is needed. The Actor performs deterministic source extraction. Your AI client can interpret the results using its own model.

### Try it

Leave **Operation = Fictional demo** and press **Start**. The built-in example finds one fictional fully remote automation role mentioning n8n. It makes no network requests and produces **zero paid query events**. The result is clearly marked `isDemo: true`.

For a live example, set `mode: "search"`, enable `useExampleBoards`, and set `maxJobs: 10`. This reads public boards for Anthropic, Palantir and Notion; it is a normal paid query. These examples do not imply affiliation with those companies.

### What it solves

- **Fresh source data:** read the employer's official public API at request time, rather than assuming a search-engine result is current.
- **Remote vs Hybrid:** use explicit `workplaceType`. Ashby's `isRemote=true` can mean Remote **or Hybrid**, so that flag alone never qualifies a role as fully remote. Greenhouse location text remains separate from structured workplace evidence.
- **Posting verification:** match IDs in source data. A generic HTTP 200 page is not treated as proof that an individual role is listed.
- **Auditable keyword matches:** literal words/phrases, short normalized excerpts, and JSON source paths. No invented relevance or hiring-intent scores.
- **Known unknowns:** blocked, empty, failed and incomplete feeds do not turn into closed-job claims.

This is a per-company tool, **not a worldwide job search database**. You supply hosted board URLs or opt into the example boards. No background index, monitoring schedule or recurring server is created.

### Search example

```json
{
  "mode": "search",
  "boardUrls": ["https://jobs.ashbyhq.com/notion"],
  "keywords": ["engineer"],
  "keywordMode": "any",
  "searchDescription": false,
  "remoteOnly": true,
  "maxJobs": 20
}
```

Search accepts **1–5 board URLs**. Supported hosted domains: `boards.greenhouse.io`, `job-boards.greenhouse.io`, `jobs.lever.co`, `jobs.eu.lever.co`, `jobs.ashbyhq.com`. Custom company career sites and Greenhouse EU hosts are outside this version's scope.

Up to 15 literal keywords, 60 characters each. `any` matches any keyword; `all` requires every one. Keywords default to title-only matching. Enable `searchDescription` to inspect description text too. `AI` does not match the middle of `mail`; this is word/phrase matching, not semantic AI search.

`remoteOnly` requires explicit structured **Remote**. Hybrid and unknown types are excluded, including Greenhouse rows whose location text says Remote without a structured workplace field. This does not determine country, visa or work-from-anywhere eligibility.

Up to 100 matched jobs are returned. The summary reports all observed matches and whether output was truncated. Ordering prioritizes title keyword matches, then stable source IDs; it is not a candidate-fit ranking.

### Verification example

```json
{
  "mode": "verify",
  "jobUrls": ["https://job-boards.greenhouse.io/YOUR_BOARD/jobs/YOUR_NUMERIC_ID"]
}
```

Replace the example with a real posting URL. Supply **1–20 URLs from at most five boards**. Queries/grouping use official APIs, not the supplied HTML page. Tracking parameters are dropped for identity; repeated posting URLs are marked `duplicate` and are not re-fetched individually.

| Status | Meaning |
| --- | --- |
| `listed` | Matching ID exists in the source's listed public postings |
| `direct_link_only` | Ashby exposes the posting with `isListed=false` |
| `visible_listing_unspecified` | Posting exists in Ashby API, but listing visibility was not supplied |
| `not_in_public_feed` | ID absent from a complete, successfully read, **nonempty** feed |
| `unknown` | Source cannot establish presence or absence |
| `duplicate` | Same canonical posting already checked in this query |

Absence from a feed is not proof of permanent closure or that someone was hired. Presence is not proof of genuine hiring intent. This does not label roles as “ghost jobs.”

### Use from an AI assistant via MCP

The Actor is available through **Apify's hosted MCP gateway**:

[Connect this Actor through MCP](https://mcp.apify.com/?tools=alex_fodo/ats-jobs-mcp)

Configure that URL in a supported remote MCP client using your own Apify authentication. Alternatively, load this Actor by name through Apify's `call-actor` tool. It runs a finite job and exits; **there is no standalone Standby server or idle-time service to maintain**.

Example request to your assistant:

> Search these employer board URLs for automation or n8n mentions. Return titles, locations, exact source evidence and URLs. Distinguish Remote from Hybrid and unknown workplace types. Verify selected posting links before I use them. Do not assume a company wants outsourcing or that a published role proves actual hiring intent.

The assistant's own model/subscription cost is separate. No model is called by this Actor.

### Outputs

**Dataset:** job results, verification records and explicit diagnostics. Inspect `recordType` before processing rows.

Job fields include `provider`, `board`, `jobId`, `jobKey`, `title`, `locations`, `workplaceType`, `workplaceTypeSource`, `jobUrl`, `sourceJobUrl`, `sourceApiUrl`, `sourcePath`, `observedAt`, `matchedKeywords` and `matchEvidence`. Full job descriptions are not copied; matched body text produces short normalized excerpts. Salary text is returned only where directly provided in supported structured source fields, without inference or annual/currency conversion.

Dates preserve their source meaning: Greenhouse `first_published` and Ashby's **last** publication time appear with `publishedAtMeaning`; Lever creation time is `createdAt`, not an invented posting date. `observedAt` records this check.

**Key-value store:** `OUTPUT` contains the full report, per-source requests, errors, coverage, limitations and billing. `SUMMARY` contains counts and event totals. A healthy search with no keyword matches returns an explicit `no_matches` diagnostic, not a fabricated job.

### Pricing

**$0.01 per completed live query**, including Apify platform usage. One search covers up to five boards; one verification batch covers up to 20 URLs / five boards. There is **no per-job, separate source or model fee**. Repeated successful queries are billed again.

A search is billable when at least one nonempty source feed was fully read, even if no job matches the filters. Verification is billable when at least one unique posting has source-backed presence or absence evidence. Partially successful batches retain source diagnostics and still count as one query. Fictional demo, invalid input and entirely unusable evidence produce zero query events. Upstream rate limits/availability are not guaranteed.

Own-account development tests use your account's usual Apify credits. Other users invoke and pay for the Actor under their own accounts. No external provider API charges are used by this application.

### Bounds and data handling

- Source response cap 10 MB; 5,000 postings per board; at most ten Lever pages (250 requested per page). A capped/incomplete feed cannot establish absence.
- Source request timeout up to 15 seconds; total source-fetch budget 45 seconds. No retries, redirects, logins, applications, browser rendering or anti-bot bypass.
- Application logs omit source bodies. Normal Apify input/output storage and account retention apply. Only public job-posting endpoints are read; no applicant profiles, resumes or private ATS records are accessed.
- Employer labels/snippets/links are untrusted data. API availability and schemas can change. Review your rights to reuse upstream job content; the code's licence does not license employer content.

Source documentation: [Greenhouse Job Board API](https://developers.greenhouse.io/job-board.html), [Lever Postings API](https://github.com/lever/postings-api), [Ashby public postings API](https://developers.ashbyhq.com/docs/public-job-posting-api). Independent product, not affiliated with those providers or example employers.

### Development

Node.js 22+. `npm ci`, `npm test`, `apify run` for the fictional demo. `npm run test:live` reads the three public example boards and verifies a currently observed posting from each, without Apify runs or model calls. Live results are time-specific; fixture tests provide deterministic failure/edge-case coverage.

# Actor input Schema

## `mode` (type: `string`):

Demo uses fictional data and zero query events. Search reads selected employer boards; verify checks supplied posting IDs in their source feeds.

## `boardUrls` (type: `array`):

1–5 hosted board URLs. Greenhouse global, Lever global/EU, Ashby. Required for search unless example boards are explicitly enabled.

## `useExampleBoards` (type: `boolean`):

Explicitly search Anthropic (Greenhouse), Palantir (Lever), and Notion (Ashby), replacing boardUrls. Live search; normal query pricing applies. Ignored in demo/verify.

## `jobUrls` (type: `array`):

1–20 supported hosted posting URLs, at most 5 distinct boards. Only official APIs are requested; input web pages are not fetched.

## `keywords` (type: `array`):

Up to 15 non-empty keywords (max 60 characters each). Whole normalized words/phrases; no semantic AI matching. Empty returns all listed jobs in search mode.

## `keywordMode` (type: `string`):

Match any keyword or require all. Applies to search only.

## `searchDescription` (type: `boolean`):

Also search employer description text, with short evidence excerpts. Otherwise only titles match. Applies to search; may increase transfer/processing.

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

Require a structured Remote workplace type. Hybrid and unknown types are excluded; Greenhouse location text alone does not establish Remote. No country/visa eligibility is inferred.

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

Return at most 1–100 matched jobs (default 50). Summary reports truncation. Does not turn a partial feed into a complete one; verification uses up to 20 supplied URLs.

## Actor input object example

```json
{
  "mode": "demo",
  "boardUrls": [],
  "useExampleBoards": false,
  "jobUrls": [],
  "keywords": [],
  "keywordMode": "any",
  "searchDescription": false,
  "remoteOnly": false,
  "maxJobs": 50
}
```

# Actor output Schema

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

No description

## `report` (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 = {
    "mode": "demo"
};

// Run the Actor and wait for it to finish
const run = await client.actor("alex_fodo/ats-jobs-mcp").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 = { "mode": "demo" }

# Run the Actor and wait for it to finish
run = client.actor("alex_fodo/ats-jobs-mcp").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 '{
  "mode": "demo"
}' |
apify call alex_fodo/ats-jobs-mcp --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,alex_fodo/ats-jobs-mcp"
        }
    }
}
```

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/TKsG3Ec6fgPHxwkwK/builds/OBvlQu8cp14vESxUK/openapi.json
