# Job Scraper by Company — Greenhouse, Lever, Ashby ATS + Salary (`yabanana99/ats-job-scraper`) Actor

Every open role straight from a company's own ATS. Give it company domains — it finds the board itself. One schema across Greenhouse, Lever and Ashby: title, location, remote flag, department, employment type and published salary. No proxies, no anti-bot.

- **URL**: https://apify.com/yabanana99/ats-job-scraper.md
- **Developed by:** [Daniele Giovane](https://apify.com/yabanana99) (community)
- **Categories:** Jobs, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 job scrapeds

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/platform/actors/running/actors-in-store#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

## Job Scraper by Company — Greenhouse, Lever, Ashby

Give it a list of companies. Get back every open role they are advertising,
in one schema, with salary where the employer published it.

```json
{ "companies": ["stripe.com", "ramp.com", "vercel.com"] }
```

That is the whole input. **You do not have to know which applicant tracking
system each company uses, or what its board is called** — which is the part
that makes every other tool in this category unusable on a real list of
companies.

### Why this does not break

Most job data is scraped from job boards, which is a permanent fight against
sites that do not want to be read: proxies, rotating fingerprints, and a
product that quietly starts returning nothing the week the target changes its
defences.

This reads the **source** instead — the endpoint the employer's own ATS
publishes so that their careers page works. An employer pays Greenhouse
precisely so that their openings are easy to fetch. There is no anti-bot to
get past, no login, no proxy, and no incentive for any of that to appear.

### What you get, per role

| | |
|---|---|
| **Identity** | company, `company_domain`, ATS, board, stable `job_id`, title, URL, apply URL |
| **Classification** | department, team, `employment_type` normalised to `full_time` / `part_time` / `contract` / `internship` / `temporary` |
| **Place** | the board's original text *and* the individual places split out, country, `is_remote`, `workplace_type` (`remote` / `hybrid` / `on_site`) |
| **Pay** | `salary_min`, `salary_max`, currency, period, and `salary_source` so you know whether the figure is a field the employer filled in or something read out of prose |
| **Time** | `posted_at`, `updated_at` |
| **Text** | full description, on request |

`job_id` is the ATS's own identifier and is stable between runs, so it is the
right key for an incremental sync.

#### One schema, ten dialects

Every system says the same things differently, and the differences are exactly
where a job dataset goes wrong:

- Lever publishes dates as epoch milliseconds and everyone else uses ISO 8601.
  Left alone, one column is sometimes a number and sometimes a date, and you
  cannot filter on it.
- Ashby puts equity and salary in the same list. Read the first element instead
  of the salary one and you publish an "equity percentage" as a wage.
- Greenhouse serves descriptions as HTML that has itself been HTML-escaped.
  Strip the tags before decoding and you get a description full of visible
  markup.

All three are handled, and all three are pinned by tests against captured
payloads so they stay handled.

### Filters

`title_keywords`, `locations`, `departments`, `remote_only`,
`with_salary_only`. Filtered-out roles are **not charged** — you pay for rows
you receive.

`remote_only` keeps only roles the board actually marked remote. A role whose
remoteness the employer never stated is excluded rather than guessed at:
charging you for a maybe is how a filter becomes worthless.

### Pricing

| Event | Price |
|---|---|
| Job scraped | **$0.004** per role delivered |
| Actor start | $0.005 per run, charged by the platform |

Nothing is charged for a board that failed, a company whose board could not be
found, or a row your own filters removed. Those are our costs.

A run over 50 companies pulling 20 roles each is 1,000 roles — $4.00.

### Two things worth knowing before you run it

**Descriptions are off by default.** They make the payload about thirteen times
larger: measured on a real 564-role board, 0.3 MB without them and 4.0 MB with.
Turn `include_description` on only when you need the text.

**`max_jobs` is shared fairly between boards.** Ask for 100 roles across 10
companies and you get roughly 10 from each, not 100 from whichever board
answered first. A board with three openings hands its unused share to the
others.

### Which systems

Greenhouse, Lever and Ashby today — the three that between them cover most
startup and scale-up hiring, and the three whose public endpoints are stable
enough to promise. Ashby and Lever publish structured salary; Greenhouse does
not publish it at all, so `with_salary_only` removes every Greenhouse role.

Workday, SmartRecruiters, Personio, Teamtailor, Recruitee, Workable and
BambooHR all expose usable public endpoints and are next. If you need one of
them, say so in an issue and it moves up.

### Limits

- 500 companies and 500 boards per run; 50,000 roles per run.
- Roles are what the board publishes. A company that lists nothing publishes
  nothing, and no amount of scraping invents a job.
- This returns **job postings**, which are company data, not personal data.

### Legal

The data is fetched from endpoints each ATS vendor publishes without
authentication for the purpose of distributing their customers' job adverts.
No login is used, no access control is circumvented, and no personal data is
collected or resold.

# Actor input Schema

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

The companies you want jobs from, as domains — <code>stripe.com</code>, <code>ramp.com</code>. <b>You do not need to know which applicant tracking system they use.</b> Their careers page is read to find the board, and when a company hides it (Stripe does) the board name is derived from the domain and confirmed against the vendor's API before anything is returned. Up to 500 per run.

## `boards` (type: `array`):

Boards you already know, either as <code>system:token</code> (<code>greenhouse:gitlab</code>, <code>lever:leverdemo</code>, <code>ashby:ramp</code>) or as a pasted board URL (<code>https://jobs.ashbyhq.com/Linear</code>). Leave <b>Company domains</b> empty and this alone is used; fill both and they are merged.

## `title_keywords` (type: `array`):

Keep only roles whose title contains one of these words, case-insensitively. Empty means every role. Example: <code>engineer</code>, <code>designer</code>.

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

Keep only roles whose location mentions one of these. Matched against both the board's original text and the individual places, so <code>NYC</code> finds a role posted as <code>SF, NYC, SEA, CHI</code>.

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

Keep only roles in a matching department or team.

## `remote_only` (type: `boolean`):

Keep only roles the board itself marks remote. Roles whose remoteness the board does not state are excluded rather than guessed at — you are not charged for them.

## `with_salary_only` (type: `boolean`):

Keep only roles that publish pay. Ashby and Lever publish a structured salary range; Greenhouse does not publish one at all, so this filter removes every Greenhouse role.

## `include_description` (type: `boolean`):

Add the posting text to each row. <b>Off by default because it makes the response about thirteen times larger</b> — measured on a real 564-role board: 0.3 MB without descriptions, 4.0 MB with them. Turn it on only if you need the text.

## `max_jobs` (type: `integer`):

Stop after this many roles have been delivered. The budget is shared fairly between boards, so 100 roles across 10 companies gives you roughly 10 from each — not 100 from whichever answered first. Maximum 50000 per run.

## `concurrency` (type: `integer`):

How many boards are fetched at once.

## Actor input object example

```json
{
  "companies": [
    "stripe.com",
    "ramp.com",
    "vercel.com"
  ],
  "boards": [
    "greenhouse:gitlab",
    "ashby:ramp",
    "lever:leverdemo"
  ],
  "title_keywords": [],
  "locations": [],
  "departments": [],
  "remote_only": false,
  "with_salary_only": false,
  "include_description": false,
  "max_jobs": 100,
  "concurrency": 5
}
```

# Actor output Schema

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

One record per open role: company, ATS, stable job id, title, location, remote flag, department, employment type, published salary and dates.

## `jobsCsv` (type: `string`):

The same records flattened to CSV, for spreadsheets and ATS imports.

## `overview` (type: `string`):

Browse the roles in the Apify Console.

# 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": [
        "stripe.com",
        "ramp.com",
        "vercel.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("yabanana99/ats-job-scraper").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": [
        "stripe.com",
        "ramp.com",
        "vercel.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("yabanana99/ats-job-scraper").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": [
    "stripe.com",
    "ramp.com",
    "vercel.com"
  ]
}' |
apify call yabanana99/ats-job-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/TTe7kqqzhYl0qf6wt/builds/4MIFLXrgbggtYBNAN/openapi.json
