# ATS Finder - Which ATS a Company Uses & Its Job Board URL (`neverempty/ats-board-finder`) Actor

Which applicant tracking system does a company use, and what is their job board called? Give a domain, get the ATS (Greenhouse, Lever, Ashby, Workable), the board name, and how many roles are open - confirmed by the company's own site, never by a name that merely matches.

- **URL**: https://apify.com/neverempty/ats-board-finder.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Jobs, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 confirmed job boards

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## ATS Finder - Which ATS a Company Uses & Its Job Board URL

Give a company domain. Get back **which ATS runs that company's job board, under what board name, and how many roles are open right now**.

**"What applicant tracking system does this company use?"** and **"what is their Greenhouse / Lever / Ashby / Workable board called?"** are questions people answer by hand, one company at a time, because every other job-board Actor already expects you to know the board name (`stripe`, `spotify`, `monzo`). This finds it.

Feed it a list of domains from your CRM and you get a directory: ATS, board name, board URL, and how many roles are open right now.

***

### What makes an answer trustworthy here

A board named `example` exists on Greenhouse with 21 open jobs. It has nothing to do with `example.com`. Guessing the board name from the domain and reporting a hit would have produced exactly that lie - **measured on 2026-08-28, that is a real board with real jobs.**

So this Actor separates two different claims, in two different columns:

| Column | Question it answers |
|---|---|
| `verified` | Does a live board with this name exist on this ATS right now? (checked against the ATS's own API) |
| `confidence` | Is it **this company's** board? |

`confidence` is one of:

- **`confirmed-by-board-links-back`** - the board's job postings link to the company's own domain
- **`confirmed-by-job-posted-on-company-site`** - a job link on the company's site resolves to a job that really is on that board
- **`confirmed-by-board-name-matching-domain`** - the company site links a board whose name is the company's own domain name
- **`confirmed-by-sole-board-link-on-company-site`** - the company site links exactly one board, and nothing suggests it belongs to anyone else
- **`linked-on-company-site-but-not-attributable`** - the board is linked from the site, but the page also lists other companies' jobs
- **`name-match-only`** - the board name simply matches the domain and nothing else backs it up

`rowType` follows from that:

| `rowType` | `ok` | Meaning | Charged? |
|---|---|---|---|
| `board-found` | `true` | Confirmed to be this company's board | **Yes** |
| `unconfirmed-link` | `true` | Linked from the site, but the page lists other companies' boards too - a lead | No |
| `name-match` | `true` | A live board of that name exists, nothing else backs it up - a lead | No |
| `no-board-found` | `true` | Nothing could be confirmed for this domain | No |
| `unavailable` | `false` | The domain was unusable, or the ATS APIs could not be reached | No |

#### Why `unconfirmed-link` exists

A link on a company's page is **not** proof the board is theirs. Investor portfolios, accelerators, universities and partner pages all link job boards they do not own.

Measured on 2026-08-28: `a16z.com` links the job boards of **eight portfolio companies** (Figma, xAI, Anduril and others) from its own jobs page. Treating "it was linked from their site" as ownership would report all eight as a16z's boards - and charge for them. This Actor returns a16z's own board as `board-found` and the other eight as free `unconfirmed-link` leads, naming them rather than hiding them.

The same rule applies to job links: when a page references jobs from more than one board, none of those references are accepted as proof of ownership.

**You are charged only for `board-found` rows.** Leads, misses and errors are free.

***

### How it looks up a board

1. **Guess from the domain** (`stripe.com` → `stripe`) and ask all four ATS APIs whether that board exists. Costs nothing to the company's own site.
2. **Read the company's own pages** (home page, then `/careers`, `/jobs`, `/careers/jobs`, `/company/careers`, ...) and look for a link to a Greenhouse, Lever, Ashby or Workable board - or to an individual job on one. Careers links are followed one hop, including to a separate careers domain when it carries the company's name. **`robots.txt` is fetched for every host that is about to be read and obeyed**, a host that refuses once is not asked again, and the page budget is a hard cap.
3. **Check every candidate against the ATS's own API.** A link on a page is a claim, not a fact - only boards the ATS confirms are returned, with their live open-role count.

***

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `domains` | array | `stripe.com`, `monzo.com`, `ramp.com`, `lyst.com` | Company domains, up to 500. Full URLs work; only the host is used; duplicates are collapsed. |
| `tryDomainNameFirst` | boolean | `true` | Probe the ATS APIs with the name guessed from the domain |
| `scanCareerPages` | boolean | `true` | Read the company's own pages to confirm ownership |
| `maxPagesPerSite` | integer | `4` | How many pages of the company's site to read before giving up |
| `respectRobots` | boolean | `true` | Read `robots.txt` first and skip anything it disallows |

### Output columns

`rowType`, `ok`, `reason`, `domain`, `ats`, `boardSlug`, `boardUrl`, `openJobs`, `verified`, `foundVia`, `confidence`, `evidenceUrl`, `checkedAt`

Every row has all 13 columns, including the misses - so the table never changes shape.

`boardSlug` is exactly what you feed into a job-scraping Actor afterwards; `boardUrl` is the page a human can open.

***

### Measured, not estimated

Run on 2026-08-28 with the shipped build:

Twelve domains, one run, 44 seconds:

| Domain | Result |
|---|---|
| `stripe.com` | Confirmed - Greenhouse `stripe`, 580 open; the board's jobs link back to stripe.com |
| `monzo.com` | Confirmed - Greenhouse `monzo`, 66 open; the site links a board named after the company |
| `ramp.com` | Confirmed - Ashby `ramp`, 137 open; a job on the site is on that board |
| `lyst.com` | Confirmed - Workable `lyst`, 8 open; same |
| `notion.so` | Confirmed - Ashby `notion`, 135 open; same |
| `linear.app` | Confirmed - Ashby `linear`, 29 open; same |
| `a16z.com` | Confirmed - Greenhouse `a16z`, 16 open, **plus 8 portfolio boards returned as free `unconfirmed-link` leads** |
| `gitlab.com`, `cloudflare.com`, `discord.com`, `posthog.com` | Lead only (`name-match`). These boards are real, but those careers pages are rendered by JavaScript, so nothing in the HTML proves ownership. **Not charged.** |
| `example.com` | Lead only (`name-match`). A Greenhouse board called `example` exists with 21 open jobs and has nothing to do with this domain. **Not charged.** |

**7 of 12 domains confirmed and charged; 5 returned as free leads.** Four of those five have a real board this Actor could not prove - that is the honest cost of not guessing, and you pay nothing for it.

Test suite: **73 checks** across two files, plus **29 deliberate defects injected into the source, all 29 caught by the tests**. The whole per-domain decision - robots handling, the page budget, and every ownership rule - is driven by fakes so each branch is exercised.

***

### Next step: pull the jobs

`boardSlug` is exactly the input the companion Actor takes. Once you know a company's board, **[ATS Jobs API](https://apify.com/neverempty/ats-jobs-api)** returns every open role from Greenhouse, Lever, Ashby and Workable in one identical 29-column schema - with the same rule that an empty board, a missing board and a failed fetch are three different answers.

```jsonc
// this Actor  ->  { "ats": "Greenhouse", "boardSlug": "monzo", "openJobs": 66 }
// then        ->  { "boards": ["greenhouse:monzo"] }
```

### Questions this answers

- **Which ATS does a company use?** - `ats` column, checked against that ATS's own API.
- **What is their board called?** - `boardSlug`, the identifier every job-board tool asks for.
- **Are they hiring at all right now?** - `openJobs`, live at the moment of the run.
- **Is this really their board, or just a name that matches?** - `confidence`, and you are only charged when it is really theirs.

### Typical uses

- Build a target list of companies and hand the `boardSlug` values straight to a job-scraping Actor
- Track which companies are hiring at all - `openJobs` is live at the moment of the run
- Enrich a CRM with a careers-page URL that is actually correct
- Find out which ATS a set of companies uses, for competitive or vendor research

### Notes and limits

- Only publicly published job boards are read. Nothing requires a login.
- Companies whose careers page is rendered entirely by JavaScript, or whose root domain does not lead to a careers page at all, may not expose a board link in the HTML. A genuine board then comes back as `name-match` - reported as a lead rather than upgraded by guesswork. Measured hit rate on the twelve domains above: **7 confirmed, 5 leads.**
- Duplicate domains in your input (`stripe.com` and `www.stripe.com`) are collapsed to one lookup, so you are never charged twice for the same company.
- Greenhouse boards on the EU data-residency hosts (`job-boards.eu.greenhouse.io`) are not covered yet.
- Only Greenhouse, Lever, Ashby and Workable are covered today. Others come back as `no-board-found`, which is stated plainly rather than implied.

### Other tools by NeverEmpty

Every NeverEmpty Actor follows the same rule: it never returns an empty result to mean two different things, and it only charges for rows that actually carry an answer.

- **[ats-jobs-api](https://apify.com/neverempty/ats-jobs-api)** - every open job from a Greenhouse, Lever, Ashby or Workable board, one schema
- **[website-contacts](https://apify.com/neverempty/website-contacts)** - emails, phones and social profiles from any website, no invented addresses
- **[domain-lookup](https://apify.com/neverempty/domain-lookup)** - WHOIS, DNS, MX, SPF, DMARC and SSL expiry for a list of domains

### Support

Open an issue on the **Issues** tab with the domain in question - it will be reproduced against the live sources before anything is changed.

# Actor input Schema

## `domains` (type: `array`):

Company domains to look up, for example "stripe.com". A full URL works too - only the host is used. Duplicates (with and without www) are collapsed so you are never charged twice for the same company. Up to 500 domains per run. Every domain produces at least one row, including the ones where no board is found.

## `tryDomainNameFirst` (type: `boolean`):

Probe the four ATS APIs with the board name guessed from the domain (stripe.com -> "stripe"). This costs no request to the company's own site. A hit alone is reported as name-match, never as a confirmed board.

## `scanCareerPages` (type: `boolean`):

Fetch the company's home and careers pages and look for a link to a Greenhouse, Lever, Ashby or Workable board. This is what turns a guess into a confirmed board.

## `maxPagesPerSite` (type: `integer`):

Hard cap on how many pages of the company's own site are read before giving up, including any careers page followed from the home page. Scanning stops as soon as a board link is found, and a host that refuses a request is not asked again.

## `respectRobots` (type: `boolean`):

Read robots.txt first and skip any page it disallows for this crawler. Leave on unless you have the site owner's permission.

## Actor input object example

```json
{
  "domains": [
    "stripe.com",
    "monzo.com",
    "ramp.com",
    "lyst.com"
  ],
  "tryDomainNameFirst": true,
  "scanCareerPages": true,
  "maxPagesPerSite": 4,
  "respectRobots": true
}
```

# Actor output Schema

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

All rows produced by this run.

# 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 = {
    "domains": [
        "stripe.com",
        "monzo.com",
        "ramp.com",
        "lyst.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/ats-board-finder").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 = { "domains": [
        "stripe.com",
        "monzo.com",
        "ramp.com",
        "lyst.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/ats-board-finder").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 '{
  "domains": [
    "stripe.com",
    "monzo.com",
    "ramp.com",
    "lyst.com"
  ]
}' |
apify call neverempty/ats-board-finder --silent --output-dataset

```

## MCP server setup

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

```

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/RrlXaoTR0dqKsSezc/builds/cMw3FkwxwYXMkM53U/openapi.json
