# Lever & Greenhouse Jobs Lookup & New-Job Alerts (`stego-bot/ats-jobs`) Actor

Get open jobs from any company's Lever or Greenhouse job board: give names, careers URLs or board links and the Actor finds the board. Filter by date, location, remote, department, keyword and salary. New-job alerts. Official public Lever and Greenhouse APIs, clean JSON.

- **URL**: https://apify.com/stego-bot/ats-jobs.md
- **Developed by:** [Stego](https://apify.com/stego-bot) (community)
- **Categories:** Jobs, Lead generation, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 job returneds

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

## Lever & Greenhouse Jobs Lookup: open jobs for any company on Lever or Greenhouse, as clean JSON

Give it company names, careers page URLs or Lever / Greenhouse board links. It finds each company's **Lever** or **Greenhouse** job board and returns its **open jobs** as clean, flat JSON: title, company, location, remote / hybrid / on-site, department, team, commitment, posted date, salary (where the company publishes it) and a direct job link. Filter by posted date, location, remote, department, keyword and salary, or run it on a schedule to get **only new jobs since your last run**.

It reads only two official public, read-only APIs:

- **Lever's Postings API** (the same JSON feed each company's Lever careers page uses), whose docs say published postings "may be scraped by third parties";
- **Greenhouse's Job Board API** (`boards-api.greenhouse.io`), whose docs say "Job Board data is publicly available, so authentication is not required for any GET endpoints".

No login, no API key, no browser and no crawling: for Lever, one API call per board (plus a check that the list is complete) and one read of each board's Lever page for its `<title>` (the company name); for Greenhouse, one API call per board (the API returns the full list with a total). The Actor never loads Greenhouse web pages, only that API. If you give a careers page URL, that one page is read once, if its robots.txt allows. A typical run takes seconds.

**Lever and Greenhouse only.** Companies on other applicant tracking systems (Ashby, Workday, SmartRecruiters…) are not read; their links and tokens get status `unsupportedAts` and cost nothing beyond the run start.

### What it does

- **Finds the board.** Each entry can be:
  - a company name (`Zoox`, `Palantir Technologies`), which is matched against likely Greenhouse and Lever board names (Lever global and EU). A guessed board is used only when **the board's own name matches what you typed** (the Greenhouse board name or the Lever page title, ignoring Inc/Ltd/GmbH/Technologies…). If the same name exists on both, the matching one is used and the entry's `warning` names the other, so you can enter that token instead. Otherwise it's skipped with status `uncertainMatch` and a suggestion, and nothing is charged. For example, `Palantir` is used because the board `lever:palantir` is titled "Palantir Technologies", but a name whose likeliest board has a different title (say, `Acme Robotics` guessing a board titled "Totally Different Staffing") is skipped as `uncertainMatch`. Set `allowUncertainMatches` to include such boards;
  - a careers page URL (`https://www.example.com/careers/`). The Actor reads that one page once (if robots.txt allows) and uses **every** Lever or Greenhouse board it links to (up to 5). Otherwise it guesses from the domain, with the same name check. A careers page that only links to another ATS gets `unsupportedAts`;
  - a board URL: Lever (`https://jobs.lever.co/spotify`, `jobs.eu.lever.co/...`) or Greenhouse (`https://job-boards.greenhouse.io/<token>`, `boards.greenhouse.io/<token>`, an embed link with `?for=<token>`, or a `boards-api.greenhouse.io/v1/boards/<token>` link). Greenhouse URLs are only parsed for the board token; the Actor then reads that board through the Job Board API;
  - an explicit token: `lever:spotify`, `lever-eu:<site>` for Lever's EU instance, or `greenhouse:<token>`. This is the fastest and most reliable option.
- **Returns every open job, never a silently cut list.** For Lever, the Actor reads the full list and then asks for the page after the end, to prove there's nothing more. For Greenhouse, it compares the list with the total the API reports and asks once more if they differ. If a list can't be confirmed complete, that company is flagged `complete: false` in the run's OUTPUT and the status message says so.
- **Whole-word keyword matching.** `AI` matches "AI Engineer" but not "Maintain"; `art` doesn't match "Department". Add `*` at the end for a prefix match (`engineer*`). A `*` anywhere else (`*engineer`, `eng*neer`) is rejected with a clear message rather than silently matching nothing.
- **Each board once.** Entries that resolve to the same board (e.g. `Spotify`, `https://jobs.lever.co/spotify` and `lever:spotify`, or `https://boards.greenhouse.io/acme` and `greenhouse:acme`) are read and charged once. Every entry you typed still appears in OUTPUT; the later ones get status `duplicate`, with `sameAs` naming the entry that was actually read and the same `boardName`.
- **Fair sharing of `maxResults`.** Each company first gets up to an equal share. What's left at the end goes to the companies that still have matches, smallest first, so small companies get all their jobs and big ones split the rest evenly. Example: `maxResults` 1000 over companies with 46, 80, 139, 195 and two with 2,000+ matches gives 46 / 80 / 139 / 195 / 270 / 270. Companies that were cut off are listed in the status message and in OUTPUT (`notReturned`), and the message says whether `maxResults`, `maxResultsPerCompany` or your max total charge was the limit.
- **Agent-friendly.** It takes one call with a list of companies and gives back flat JSON with one date format (`YYYY-MM-DDTHH:MM:SSZ`, UTC). A per-company summary in OUTPUT shows found, not found, failed and skipped companies, so an agent knows exactly what happened.
- **New-job alerts.** `onlyNew` remembers what it has returned (in a named key-value store), so a daily schedule returns only new postings. The first run takes a baseline and returns nothing. A company you add later gets its own baseline, so you're never billed for a backlog you didn't ask for. Each batch of up to 100 jobs is saved to the alert memory *before* it is pushed and charged, so a crashed, killed or timed-out run never re-delivers or re-charges a job. The trade-off: if the run dies in the middle of pushing a batch, that batch may be lost. Those jobs count as seen, so they aren't delivered (or charged) on the next run either.
- **Flat memory.** Boards are streamed: at most 2 full job lists are in memory at once, and each company's results are pushed and freed before the next one is read. In a local test with big Lever boards (900 postings each, about 7 KB of description per posting), 31 boards peaked at about 180–230 MB (up to 5,000 results with HTML descriptions) and 60 boards in alert mode at about 200 MB, under the 512 MB default. A local test with 20 synthetic Greenhouse boards of 3,000 short postings each plus 5 Lever boards peaked at about 130 MB. Jobs held back for the end-of-run fair share are kept in a compressed temporary file, not in memory, so a large `maxResults` is filled in full: 31 big Lever boards plus 30 small ones with `maxResults` 25,000 and HTML descriptions returned all 25,000 jobs and peaked at about 185 MB.

### Who it is for

- **Recruiters and sourcers:** watch target companies for new roles in a department or city.
- **Sales and BD teams:** hiring is a buying signal. Get a daily feed of companies opening roles like "Data Engineer" or "Head of Security".
- **Job-board and newsletter builders:** pull clean jobs from many employers with salary and remote flags.
- **AI agents:** answer "Is company X hiring for Y?" or "What remote engineering roles does Z have?" in a single call.

### Input

Only `companies` is required. Everything else is optional.

| Field | Description |
|---|---|
| `companies` | Up to 500 names, careers URLs, Lever / Greenhouse board URLs or `lever:` / `lever-eu:` / `greenhouse:` tokens. |
| `allowUncertainMatches` | Default off. Include guessed boards whose own name doesn't match what you typed. |
| `keywords` / `keywordMode` / `searchIn` | Whole-word keywords; `any` (default) or `all`; `title` (default) or `titleAndDescription`. Each keyword must contain a letter or digit. Note that some companies put the same boilerplate in every description, so description search can match every job. |
| `excludeKeywords` | Drop jobs whose title or description contains any of these, e.g. `Senior`, `Intern`. |
| `locations` / `excludeLocations` | Whole-word match on the job's location and all of its listed locations, e.g. `London`, `Germany`, `Remote`. |
| `remote` | `any` (default), `only` or `exclude`. |
| `departments` | Whole-word match on the department (Greenhouse departments, or the Lever department and team). |
| `postedWithinDays` | Only jobs first published in the last N days (0 = all open jobs). |
| `onlyWithSalary` | Only jobs with a published pay range. |
| `minSalary` + `salaryCurrency` | Keep jobs whose range reaches at least this yearly amount in this ISO currency. Hourly and monthly ranges are annualized. |
| `maxResults` | Max jobs per run, across all companies (default 100, newest first per company). |
| `maxResultsPerCompany` | Optional per-company cap (0 = none). |
| `includeDescription` / `descriptionFormat` / `descriptionMaxLength` | Full description, off by default. `text` or `html`, with optional truncation. |
| `includeContactDetails` | Off by default. When off, emails and phone numbers inside descriptions are replaced with `[email removed]` / `[phone removed]`. |
| `onlyNew` / `stateKey` / `baselineOnFirstRun` / `stateStoreName` | Alert mode (see below). Use a `stateKey` per saved search. Without one, the key is derived from the filters **and the list of company entries you typed** (normalised, order and repeats ignored), so a company that is briefly unreachable doesn't switch the run to a new memory. |

#### Example inputs

Is Spotify hiring remote engineers?

```json
{ "companies": ["lever:spotify"], "keywords": ["engineer*"], "remote": "only" }
```

New jobs in the last 7 days at several companies, with salary:

```json
{ "companies": ["Zoox", "https://jobs.lever.co/palantir", "greenhouse:<token>"],
  "postedWithinDays": 7, "onlyWithSalary": true, "maxResults": 500 }
```

Daily alert for business roles in London (schedule it daily):

```json
{ "companies": ["Palantir", "lever:spotify"], "departments": ["Business"], "locations": ["London"],
  "onlyNew": true, "stateKey": "london-business" }
```

### Output

One item per job (a Lever job shown; Greenhouse jobs have the same fields):

```json
{
  "jobId": "f02fb195-ba10-40bd-a926-0318a34a7fae",
  "title": "Staff Product Designer - Platform Design",
  "company": "Spotify",
  "ats": "lever",
  "boardToken": "spotify",
  "location": "London",
  "allLocations": ["London", "Stockholm"],
  "remote": true,
  "workplaceType": "remote",
  "department": "Design and User Experience",
  "team": "Platform",
  "employmentType": "Permanent",
  "postedAt": "2026-10-21T17:27:06Z",
  "updatedAt": null,
  "url": "https://jobs.lever.co/spotify/f02fb195-ba10-40bd-a926-0318a34a7fae",
  "applyUrl": "https://jobs.lever.co/spotify/f02fb195-ba10-40bd-a926-0318a34a7fae/apply",
  "requisitionId": null,
  "language": null,
  "country": "GB",
  "leverInstance": "global",
  "salaryMin": null,
  "salaryMax": null,
  "salaryCurrency": null,
  "salaryInterval": null,
  "salaryText": null,
  "salaryNote": null,
  "salaryRanges": [],
  "description": null,
  "input": "lever:spotify",
  "scrapedAt": "2026-10-21T21:52:29Z",
  "matchConfidence": "high",
  "boardName": "Spotify"
}
```

Notes on fields:

- `ats` is `lever` or `greenhouse`.
- `postedAt` is Lever's `createdAt` or Greenhouse's `first_published`. `updatedAt` is Greenhouse's `updated_at` (Lever doesn't publish one, so it's `null`). `requisitionId` and `language` come from Greenhouse (`null` for Lever).
- `remote` and `workplaceType`: Lever publishes a workplace type (remote, hybrid or onsite); Greenhouse doesn't. When a posting has none, a job counts as remote when its location, offices or title say "Remote", "anywhere" or "work from home" (hybrid likewise); otherwise `workplaceType` is `null` (not stated).
- `department` and `team` are Lever's department and team; for Greenhouse, `department` joins the job's departments and `team` is `null`. `employmentType` is Lever's commitment (e.g. "Permanent", "Full-time"); Greenhouse doesn't publish one (`null`).
- `leverInstance` is `global` (jobs.lever.co) or `eu` (jobs.eu.lever.co), and `null` for Greenhouse. `country` is Lever's country code (`null` for Greenhouse).
- `matchConfidence` is `high` for board URLs, tokens and careers-page links, and for guesses whose board name matches. It's `medium` only with `allowUncertainMatches`. `boardName` is the board's own display name (the Greenhouse board name or the Lever page title). "high" means the names match, not that it's certainly the company you meant (e.g. two companies called "Mercury"); use a board URL or token when it matters.
- `salary*` comes only from structured pay data: Lever's salary range (with its own period: year, month, week, day or hour) or Greenhouse's pay transparency ranges (in cents, converted to whole units). Greenhouse ranges carry no period, so they count as yearly unless the range title says hourly or monthly; an obviously mislabelled "yearly" range is fixed and explained in `salaryNote` (a maximum of 200 or less is treated as hourly, one under 1,000 as entered in thousands). When a job has several ranges (e.g. pay zones), `salaryMin`/`salaryMax` span the ranges that share the first range's currency and period, and `salaryRanges` lists them all. Salaries that appear only in the description text aren't parsed, and many large employers publish no structured pay, so `onlyWithSalary`/`minSalary` drop all their jobs.
- `company` is Greenhouse's `company_name` or the Lever page title (e.g. "Palantir Technologies"). If that can't be read, it's the name you typed, or the board token.

The **Overview**, **Salary** and **Details** dataset views give you ready-made tables. The run's `OUTPUT` record lists each company with its status (`ok`, `notFound`, `unsupportedAts`, `uncertainMatch`, `duplicate`, `error`, `skipped`), how it was detected (`token`, `boardUrl`, `careersPage`, `nameGuess`, `domainGuess` or `cache`), the board it read (`boardRef`, e.g. `lever:spotify`, `lever-eu:xentral` or `greenhouse:<token>`, and for Lever `leverInstance`: `global` or `eu`) and the counts of jobs listed, matched and returned. Copy `boardRef` into `companies` to skip the lookup next time.

### Alerts (only new jobs)

1. Set `onlyNew: true` and a `stateKey`, then schedule the Actor daily.
2. The first run remembers today's matching jobs and returns nothing; you pay only the run start.
3. Each later run returns only jobs that haven't been returned before. If `maxResults` or your max charge cuts a run short, the rest come on the next run. A job is never returned or charged twice, even if a run crashes. If a run dies in the middle of pushing a batch, though, that batch (up to 100 jobs) may never be delivered, because it was already marked as seen.
4. Changing the filters for a state key takes a fresh baseline. Jobs already returned stay remembered. Detected boards are cached in the alert memory, so later alert runs skip detection.
5. The state store (default `ats-jobs-state`) is shared by all runs and tasks in your account. Use a different `stateKey` per saved search.

### Pricing

Pay per event: a small fee per run start plus a fee per job returned. Not-found companies, baseline runs and filtered-out jobs cost nothing beyond the run start. Set **Max total charge** on a run to cap spending. The run-start fee counts toward the cap, so the cap minus the start fee is what's left for jobs. The Actor stops at the cap, says so in the status message and, in alert mode, keeps the rest for the next run.

### Data sources, terms and limits

- **Lever Postings API** (`api.lever.co`, `api.eu.lever.co`). Lever's docs say: "all job postings in the published state are publicly viewable. These jobs may be scraped by third parties." The company name comes from the `<title>` of the company's Lever page (`jobs.lever.co/<site>`), read once per board.
- **Greenhouse Job Board API** (`boards-api.greenhouse.io`, GET only). Greenhouse's docs say: "Job Board data is publicly available, so authentication is not required for any GET endpoints." Its robots.txt disallows only `/embed/`, not the `/v1/` API. We found no Greenhouse terms that restrict this use (Greenhouse's own user agreement covers its My Greenhouse candidate product, and its subscription agreement covers its customers). That is our reading, not an endorsement by Greenhouse. The Actor contacts **no other Greenhouse host**: board and job page URLs are only parsed for their token, and redirects or robots.txt hops to any other `greenhouse.io` host are never followed.
- **Other ATS vendors are not supported.** Ashby, Workday and other ATS links or tokens (e.g. `ashby:acme`, `https://jobs.ashbyhq.com/acme`) are reported as `unsupportedAts`. The Actor never sends a request to Ashby hosts, not even a robots.txt check: a careers site that redirects there is reported as `unsupportedAts` without following the redirect.
- The Actor is polite: at most 1 request per second to each Lever host (their robots.txt sets `Crawl-delay: 1`) and to the Greenhouse Job Board API, and about 3 per second to a company's own careers site, with a clear User-Agent. It retries 429 and 5xx responses with backoff and honours `Retry-After` (seconds or HTTP date) for up to 5 minutes. If a host asks for a longer wait, that host is not contacted again for the rest of the run, and its remaining companies are reported as errors.
- Careers page URLs: the Actor fetches only the one page you gave (following up to 5 redirects), once, and only if each host's robots.txt allows it. robots.txt redirects are followed by hand (up to 5 hops), never to another ATS vendor or to Greenhouse. A robots.txt that returns a server error, can't be reached or is over 2 MB counts as "disallowed" (RFC 9309); the Actor then guesses from the domain instead.
- Job postings are employer content. If you republish them, link to the original posting (`url`) and follow each employer's terms.
- **Personal data:** postings rarely contain personal data, but descriptions sometimes include a recruiter's email or phone number. Descriptions are off by default, and when they're on, emails and phone numbers are removed unless you turn on `includeContactDetails`. That covers US numbers, international `+`/`00` numbers, national formats such as UK `020 7946 0958`, DE `030 1234567` / `030/1234567` and FR `01 23 45 67 89`, `tel:` links, and numbers right after words like "phone", "call" or "WhatsApp". Salary figures are kept. People's **names** in descriptions are not removed. If you do, you're responsible for having a lawful basis (e.g. under GDPR) to process them.
- Name detection is a best guess. Only boards whose own name matches what you typed are used by default, but a company whose board token differs from its name can be missed (status `notFound`, sometimes with a "did you mean" suggestion). Two different companies can also share a name. For anything important, use the board URL or an explicit token.
- Some Lever and Greenhouse boards belong to staffing agencies; for those, `company` is the agency.

### FAQ

**Why didn't my company show up?** It may use another ATS (Ashby, Workday, SmartRecruiters, …), which this Actor doesn't read, or a board token that differs from its name. Open its careers page, click a job, and if the link is on jobs.lever.co or greenhouse.io, paste that board URL.

**How fresh is the data?** It's live: each run reads the Lever and Greenhouse APIs at that moment.

**Can I search all Lever or Greenhouse jobs at once?** No. Both APIs work per company, so you need to list the companies you want.

# Actor input Schema

## `companies` (type: `array`):

One per line, up to 500. Each entry can be a company name (e.g. 'Spotify'), a careers page or a Lever / Greenhouse board URL (e.g. https://jobs.lever.co/spotify, https://jobs.eu.lever.co/<site>, https://job-boards.greenhouse.io/<token>, a company careers page that links to its board), or a token ('lever:spotify', 'lever-eu:<site>', 'greenhouse:<token>'). Names and careers URLs are matched against Lever and Greenhouse boards; board URLs and tokens are the most reliable. Greenhouse boards are read only through Greenhouse's public Job Board API. Ashby, Workday and other ATS links get status 'unsupportedAts'.

## `allowUncertainMatches` (type: `boolean`):

Off by default: when a company name or domain is guessed and the board's own name doesn't match what you typed (e.g. 'Apex' → a board named 'Apex Eye'), that board is skipped (status 'uncertainMatch', nothing charged) and suggested in OUTPUT. Turn on to include such boards; every job carries matchConfidence and boardName either way.

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

Whole-word, case-insensitive match: 'AI' matches 'AI Engineer' but not 'Maintain'. Phrases are matched as phrases. Add '*' at the end for a prefix match ('engineer*' also matches 'engineering'); '*' anywhere else is rejected. Leave empty for all jobs. An entry must contain a letter or digit ('*' alone is rejected).

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

Jobs containing ANY keyword, or ALL of them.

## `searchIn` (type: `string`):

Title-only is the most precise. 'Title and full description' also finds jobs that mention a skill in the body.

## `excludeKeywords` (type: `array`):

Drop jobs whose title or description contains any of these (whole-word), e.g. 'Senior', 'Intern'.

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

Keep jobs whose location (or any of their locations/offices) contains one of these as a whole word or phrase, e.g. 'London', 'New York', 'Germany', 'Remote'. Location text is free-form, exactly as each company writes it.

## `excludeLocations` (type: `array`):

Drop jobs whose location contains any of these (whole-word).

## `remote` (type: `string`):

Lever publishes a workplace type (remote / hybrid / on-site); Greenhouse doesn't. When a posting has none, a job counts as remote when its location, offices or title say 'Remote' (or 'anywhere', 'work from home').

## `departments` (type: `array`):

Keep jobs whose department (Greenhouse department, or Lever department or team) contains one of these as a whole word, e.g. 'Engineering', 'Sales'.

## `postedWithinDays` (type: `integer`):

Only jobs first published in the last N days (Lever 'createdAt', Greenhouse 'first_published'). 0 = all open jobs.

## `onlyWithSalary` (type: `boolean`):

Keep only jobs whose posting includes a pay range (many US postings do; most others don't).

## `minSalary` (type: `integer`):

Keep jobs whose published range reaches at least this yearly amount (hourly/monthly ranges are annualized: 2,080 h, 12 months). Needs a salary currency; jobs without a salary are dropped.

## `salaryCurrency` (type: `string`):

3-letter ISO 4217 code used with Minimum salary, e.g. USD, EUR, GBP.

## `maxResults` (type: `integer`):

Maximum jobs returned (and charged) per run across all companies. Each company's newest jobs come first.

## `maxResultsPerCompany` (type: `integer`):

Cap per company (0 = no cap), so one big employer doesn't use up Max results.

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

Add the full job description. Off by default to keep results small and fast.

## `descriptionFormat` (type: `string`):

Plain text (default) or the original HTML.

## `descriptionMaxLength` (type: `integer`):

Truncate descriptions to this many characters (0 = no limit).

## `includeContactDetails` (type: `boolean`):

Off by default: email addresses and phone numbers inside descriptions (often a recruiter's) are replaced with '\[email removed]' / '\[phone removed]'. Turn on only if you have a lawful basis to process that personal data.

## `onlyNew` (type: `boolean`):

Return only jobs not returned by a previous run with the same State key. Use with a schedule for new-job alerts.

## `stateKey` (type: `string`):

Name for this alert's memory, e.g. 'client-a-eng'. Recommended. Leave empty to derive one from the filters AND the company entries you typed (changing the company list then starts a new alert; a company that is briefly unreachable does not). The state store is shared by all runs and tasks in your account.

## `baselineOnFirstRun` (type: `boolean`):

On the first alert run (and for any company added later, or after you change filters) just remember today's matching jobs and return nothing, so you only pay for the run start. Turn off to receive (and pay for) the current jobs on the first run.

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

Named key-value store that keeps alert memory. Default 'ats-jobs-state'.

## Actor input object example

```json
{
  "companies": [
    "spotify",
    "https://jobs.lever.co/palantir"
  ],
  "allowUncertainMatches": false,
  "keywordMode": "any",
  "searchIn": "title",
  "remote": "any",
  "postedWithinDays": 0,
  "onlyWithSalary": false,
  "maxResults": 100,
  "maxResultsPerCompany": 0,
  "includeDescription": false,
  "descriptionFormat": "text",
  "descriptionMaxLength": 0,
  "includeContactDetails": false,
  "onlyNew": false,
  "baselineOnFirstRun": true
}
```

# Actor output Schema

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

Dataset with one item per matching job posting.

## `summary` (type: `string`):

OUTPUT record with result counts and per-company status (found, not found, other ATS, duplicates).

# 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 = {
    "companies": [
        "spotify",
        "https://jobs.lever.co/palantir"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("stego-bot/ats-jobs").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 = { "companies": [
        "spotify",
        "https://jobs.lever.co/palantir",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("stego-bot/ats-jobs").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 '{
  "companies": [
    "spotify",
    "https://jobs.lever.co/palantir"
  ]
}' |
apify call stego-bot/ats-jobs --silent --output-dataset

```

## MCP server setup

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

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/pXkzF11cePcgtPGYJ/builds/XDcdrvcKQueV4vUVd/openapi.json
