# MCP Server for AI Agents: Multi-ATS Career Jobs Search (`mutualistic_housing/multi-ats-career-jobs-mcp`) Actor

Standby MCP Server (/mcp) and JSON API for AI agents to search live public job postings across 71 companies + any public career board URL on Greenhouse, Lever, Ashby, SmartRecruiters, and Workable with normalized USD salary, remote/hybrid filters, and incremental deduplication.

- **URL**: https://apify.com/mutualistic_housing/multi-ats-career-jobs-mcp.md
- **Developed by:** [Mutualistic Labs](https://apify.com/mutualistic_housing) (community)
- **Categories:** AI, Jobs, Developer tools
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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

## Standby MCP Server for AI Agents: Multi-ATS Career Jobs Search

An always-on **Model Context Protocol (MCP) server for AI agents** (Claude Desktop, Cursor, Windsurf, OpenAI Agents, LangChain)
to search live **public job postings** across 71 built-in companies + any public career board URL on five applicant tracking systems:
Greenhouse, Lever, Ashby, SmartRecruiters and Workable. Results are normalized (USD salary, remote/hybrid filters,
incremental deduplication). Only official, unauthenticated public job-board JSON APIs are used: no scraping, no logins, no proxies.

The Actor runs in two modes on a single codebase:

1. **Standby MCP mode** (primary): an MCP server at `/mcp` exposing the `search_jobs` and `list_supported_companies` tools to AI agents.
2. **Batch dataset mode**: provide input, get a clean, de-duplicated dataset.

### Quick start: MCP for AI agents

Standby URL: `https://mutualistic-housing--multi-ats-career-jobs-mcp.apify.actor/mcp` (authenticate with your Apify API token).

**Claude Desktop** (`claude_desktop_config.json`, via the `mcp-remote` bridge):

```json
{
  "mcpServers": {
    "ats-jobs": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "https://mutualistic-housing--multi-ats-career-jobs-mcp.apify.actor/mcp",
        "--header", "Authorization: Bearer <APIFY_API_TOKEN>"
      ]
    }
  }
}
```

**Cursor** (`.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "ats-jobs": {
      "url": "https://mutualistic-housing--multi-ats-career-jobs-mcp.apify.actor/mcp",
      "headers": { "Authorization": "Bearer <APIFY_API_TOKEN>" }
    }
  }
}
```

**Tool call examples** (JSON-RPC `tools/call`):

```json
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_jobs","arguments":{"query":"python engineer","remoteOnly":true,"minAnnualSalaryUsd":150000,"maxItems":10}}}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_supported_companies","arguments":{"atsProviders":["greenhouse","ashby"]}}}
```

Batch dataset mode is described under [Input](#input) below.

### Features

- Unified `NormalizedJob` schema across all providers (deterministic SHA-256 `uid`, UTC timestamps, workplace type, structured compensation).
- Built-in curated registry of 71 built-in companies + any public career board URL, or bring your own slugs / career URLs.
- Filters: keyword query, locations, remote only, posted within N hours, only new since a date, minimum annual USD salary, excluded job IDs.
- Clean plain-text descriptions (HTML stripped, entities decoded).
- Resilient fetching: timeouts, retry with backoff on 429/5xx, dead company slugs (404) skipped without failing the run.
- Hard spending caps: `maxItems` and `maxTotalChargeUsd` are never exceeded. Invalid input is rejected with `INVALID_INPUT_ZERO_CHARGE` before any network call or charge.

### Supported providers

| Provider | Public API used |
|---|---|
| Greenhouse | `https://boards-api.greenhouse.io/v1/boards/{slug}/jobs?content=true` |
| Lever | `https://api.lever.co/v0/postings/{slug}?mode=json` |
| Ashby | `https://api.ashbyhq.com/posting-api/job-board/{slug}?includeCompensation=true` |
| SmartRecruiters | `https://api.smartrecruiters.com/v1/companies/{slug}/postings` |
| Workable | `https://apply.workable.com/api/v1/widget/accounts/{slug}` |

Recognized career URL formats: `boards.greenhouse.io/{slug}`, `job-boards.greenhouse.io/{slug}`, `jobs.lever.co/{slug}`,
`jobs.ashbyhq.com/{slug}`, `jobs.smartrecruiters.com/{slug}`, `apply.workable.com/{slug}` (EU variants, `www.`, trailing paths and query strings are handled).
SmartRecruiters slugs are case-sensitive (e.g. `Visa`, `BoschGroup`).

### Input

| Field | Type | Description |
|---|---|---|
| `query` | string | All whitespace-separated tokens must appear in title, department, team or description. |
| `companySlugs` | string\[] | `provider:slug` (e.g. `greenhouse:stripe`) or bare slug (tried on every selected provider). |
| `careerUrls` | string\[] | Public board URLs; provider and slug are extracted automatically. |
| `atsProviders` | string\[] | Subset of `greenhouse`, `lever`, `ashby`, `smartrecruiters`, `workable`. Empty = all. |
| `locations` | string\[] | Keep jobs whose location contains any substring (case-insensitive). |
| `remoteOnly` | boolean | Only remote jobs. |
| `minAnnualSalaryUsd` | number | Annualized upper-bound USD salary threshold. Jobs without USD pay are excluded. |
| `postedWithinHours` | number | Published (or updated) within the last N hours. |
| `onlyNewSince` | string | ISO-8601 date/time; only jobs published after it (incremental runs). |
| `excludeJobIds` | string\[] | `uid` or `external_id` values to skip. |
| `includeDescription` | boolean | Include `description_plain` (default `true`). |
| `maxItems` | integer | 0 to 10000, default 100. |
| `maxTotalChargeUsd` | number | Hard budget cap in USD for the run. |

If both `companySlugs` and `careerUrls` are empty, the built-in registry is searched (filtered by `atsProviders`).

Example:

```json
{
  "query": "python engineer",
  "companySlugs": ["greenhouse:stripe", "ashby:openai", "lever:palantir"],
  "remoteOnly": true,
  "minAnnualSalaryUsd": 150000,
  "maxItems": 50
}
```

### Output

Each dataset item (snake_case):

| Field | Description |
|---|---|
| `uid` | SHA-256 of `provider:slug:external_id` |
| `ats_provider`, `company_slug`, `company_name`, `external_id` | Source identification |
| `title`, `department`, `team`, `employment_type` | Job attributes |
| `workplace_type` | `remote`, `hybrid`, `onsite` or `unspecified` |
| `locations`, `is_remote` | Locations list and remote flag |
| `published_at`, `updated_at` | ISO-8601 UTC (`YYYY-MM-DDTHH:MM:SSZ`) |
| `compensation` | `{min_amount, max_amount, currency, interval, raw_text}` or `null` |
| `apply_url`, `source_api_url` | Application link and API endpoint used |
| `description_plain` | Plain-text description (when enabled) |

### Standby MCP mode

When the Actor runs in Standby mode it serves HTTP on the Standby port:

- `GET /health` returns `200 {"status":"ok"}`.
- `POST /mcp` accepts JSON-RPC 2.0: `initialize`, `ping`, `tools/list`, `tools/call`.

Tools:

- **`search_jobs`**: same arguments as the batch input (camelCase; `maxItems` defaults to 20). Returns `structuredContent` with `jobs`, `count` and `charged`.
- **`list_supported_companies`**: lists the built-in registry, optionally filtered by `atsProviders`.

Example MCP client configuration (replace the token):

```json
{
  "mcpServers": {
    "ats-jobs": {
      "url": "https://mutualistic-housing--multi-ats-career-jobs-mcp.apify.actor/mcp",
      "headers": { "Authorization": "Bearer <APIFY_API_TOKEN>" }
    }
  }
}
```

Quick check:

```bash
curl -X POST "https://mutualistic-housing--multi-ats-career-jobs-mcp.apify.actor/mcp" \
  -H "Authorization: Bearer <APIFY_API_TOKEN>" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_jobs","arguments":{"query":"data scientist","maxItems":5}}}'
```

### Pricing

This Actor is currently **Free to use (`Pay for usage`)**: there is a **$0.00 Actor fee**. You only pay the standard Apify platform compute usage on your own Apify account.

The `maxItems` and `maxTotalChargeUsd` hard caps are enforced in code (`src/billing.py` and `.actor/pay_per_event.json`), so runs never exceed the limits you configure. Invalid input is never charged.

### Limitations

- **No descriptions for SmartRecruiters and Workable.** Their public list endpoints do not include job descriptions or compensation. For these providers, `query` matches only the title and metadata (department, team, location), and compensation/`minAnnualSalaryUsd` cannot be derived. Searching "python" will miss jobs whose title lacks the word.
- **EU boards.** Greenhouse and Lever EU career URLs (`boards.eu.greenhouse.io`, `job-boards.eu.greenhouse.io`, `jobs.eu.lever.co`) are detected as EU boards. Lever EU career URLs (`jobs.eu.lever.co`) are fetched from `api.eu.lever.co`. Greenhouse EU career URLs (`boards.eu.greenhouse.io`, `job-boards.eu.greenhouse.io`) are served via `boards-api.greenhouse.io` (the EU board API host does not resolve). The region is tracked per request/target; there is no process-global state, so an EU URL never affects later requests. Built-in registry entries are US-hosted; supply EU boards through `careerUrls`.
- **SmartRecruiters pagination cap.** Pagination stops after 10 pages of 100 (1000 postings per company). If a company has more, a warning is logged, the fetch result is flagged `truncated`, and the run/MCP `errors` list gets a `truncated_at_1000` entry with `severity: "warning"`; the remaining postings are not returned.
- **Pay-per-event limits.** The platform's charge limit is respected: when the run's max total charge is reached, items beyond the limit are not returned (neither pushed to the dataset nor returned by MCP `search_jobs`), in addition to the `maxItems` and `maxTotalChargeUsd` caps. The remaining platform charge capacity is read before every charge, in both batch and Standby mode (shared across all MCP calls of a run), so nothing is delivered once the limit is exhausted.

### Notes

- Only publicly available job postings are accessed. Respect each company's terms when reusing the data.
- Company slugs that do not exist are reported in the run log and skipped.

### Related MCP Servers by Mutualistic Labs

| Actor | Covers | Store | Standby MCP URL |
|---|---|---|---|
| Multi-ATS Career Jobs Search MCP | Greenhouse, Lever, Ashby, SmartRecruiters, Workable | https://apify.com/mutualistic_housing/multi-ats-career-jobs-mcp | https://mutualistic-housing--multi-ats-career-jobs-mcp.apify.actor/mcp |
| Google Ads Transparency & Ad Spy MCP | SearchCreatives, SearchSuggestions, `content.js` ad-copy & video metadata | https://apify.com/mutualistic_housing/google-ads-transparency-mcp | https://mutualistic-housing--google-ads-transparency-mcp.apify.actor/mcp |
| Multi-Engine Keyword & Autocomplete MCP | Google, YouTube, Amazon, Bing, DuckDuckGo, Brave + `google:suggestrelevance` & search intent | https://apify.com/mutualistic_housing/keyword-autocomplete-mcp | https://mutualistic-housing--keyword-autocomplete-mcp.apify.actor/mcp |
| Discourse Forum Search & Threads MCP | community.openai.com, discuss.huggingface.co, meta.discourse.org + any public Discourse | https://apify.com/mutualistic_housing/discourse-forum-mcp | https://mutualistic-housing--discourse-forum-mcp.apify.actor/mcp |
| Google Trends Realtime & Entities MCP | i0OFE realtime trends, trending/rss news, Knowledge Graph autocomplete entities | https://apify.com/mutualistic_housing/google-trends-realtime-mcp | https://mutualistic-housing--google-trends-realtime-mcp.apify.actor/mcp |

# Actor input Schema

## `query` (type: `string`):

Keywords; every whitespace-separated token must appear in the title, department, team or description (case-insensitive).

## `companySlugs` (type: `array`):

Company board slugs, either 'provider:slug' (e.g. greenhouse:stripe) or a bare slug (tried against every selected ATS provider). SmartRecruiters slugs are case-sensitive.

## `careerUrls` (type: `array`):

Public job board URLs, e.g. https://boards.greenhouse.io/stripe, https://jobs.lever.co/palantir, https://jobs.ashbyhq.com/notion, https://jobs.smartrecruiters.com/Visa, https://apply.workable.com/huggingface.

## `atsProviders` (type: `array`):

Restrict to these providers. Empty means all five.

## `locations` (type: `array`):

Keep jobs whose location contains any of these substrings (case-insensitive). Remote jobs also match 'remote'.

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

Return only remote jobs.

## `minAnnualSalaryUsd` (type: `number`):

Keep jobs whose annualized upper-bound USD salary is at least this value. Jobs without USD compensation are excluded.

## `postedWithinHours` (type: `number`):

Keep jobs published (or updated, if unpublished date is missing) within the last N hours.

## `onlyNewSince` (type: `string`):

ISO-8601 date or datetime (UTC assumed if no offset). Only jobs published after this moment are returned; useful for incremental runs.

## `excludeJobIds` (type: `array`):

Job uid or external_id values to skip (e.g. IDs returned by a previous run).

## `includeDescription` (type: `boolean`):

Include the plain-text job description in each result.

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

Maximum number of jobs to return (and be charged for). 0 returns nothing.

## `maxTotalChargeUsd` (type: `number`):

Hard spending cap for this run; results are truncated so the charge never exceeds it.

## Actor input object example

```json
{
  "query": "machine learning engineer",
  "companySlugs": [
    "greenhouse:stripe",
    "ashby:openai"
  ],
  "locations": [
    "Berlin",
    "Remote"
  ],
  "remoteOnly": false,
  "includeDescription": true,
  "maxItems": 100
}
```

# Actor output Schema

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

Dataset items containing normalized job postings across Greenhouse, Lever, Ashby, SmartRecruiters, and Workable.

# 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 = {
    "companySlugs": [
        "greenhouse:stripe",
        "ashby:openai"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mutualistic_housing/multi-ats-career-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 = { "companySlugs": [
        "greenhouse:stripe",
        "ashby:openai",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("mutualistic_housing/multi-ats-career-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 '{
  "companySlugs": [
    "greenhouse:stripe",
    "ashby:openai"
  ]
}' |
apify call mutualistic_housing/multi-ats-career-jobs-mcp --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mutualistic_housing/multi-ats-career-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/ai21y2sqsyVyQmf0W/builds/6mFJ734IfQLVkST3g/openapi.json
