# ATS Jobs Scraper: Greenhouse, Lever, Ashby, Workday (`grit-77/ats-jobs-scraper`) Actor

ATS jobs scraper for public company career boards, including Greenhouse, Lever and Ashby. Export job titles, locations, descriptions and application links as consistent JSON rows.

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

## Pricing

$1.20 / 1,000 job postings

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

## ATS Jobs Scraper - Greenhouse, Lever, Ashby, Workable, SmartRecruiters (+ Workday)

ATS jobs scraper returns public job postings from supported company career boards. ATS jobs API rows include job titles, locations, descriptions, and application links when supplied by the board.

### What it returns

One row per job, the same fields for every platform:

| Field | Type | Example value |
|---|---|---|
| `company` | string | `"Ramp"` |
| `ats` | string | `"ashby"` |
| `job_id` | string | `"34413f8d-26bf-4bbc-8ade-eb309a0e2245"` |
| `title` | string | `"Security Engineer, Cloud"` |
| `department` | string | `"Engineering"` |
| `team` | string | `"Backend"` |
| `location` | string | `"New York, NY (HQ)"` |
| `locations` | array | `["New York, NY (HQ)", "Remote (Canada)", "Remote (US)", "Miami, FL"]` |
| `remote` | boolean | `true` |
| `workplace_type` | string | `"hybrid"` |
| `employment_type` | string | `"Full-time"` |
| `salary_min` | integer | `211400` |
| `salary_max` | integer | `290600` |
| `salary_currency` | string | `"USD"` |
| `salary_interval` | string | `"year"` |
| `posted_at` | string | `"2026-04-07T17:12:35.753000Z"` |
| `updated_at` | string | `"2026-09-25T20:45:00Z"` |
| `apply_url` | string | `"https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application"` |
| `description_text` | string | `"ABOUT RAMP\n\nRamp is building the smart infrastructure for finance teams, embedded in the transaction flow of every dollar a business spends. We automate how over $200B in annualized spend flows in and out of 70,000+ companies: authorizing payments, flagging risk, categorizing spend, and closing books.\n\nThe problems are high-stakes, data-dense, and unforgiving.\n\nWe hire people with high agency and high urgency. We look for slope over intercept. We care less about where you trained and more about  [... truncated in sample file only]"` |
| `description_html` | null | `null` |
| `source_url` | string | `"https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245"` |
| `error` | null | `null` |

### Input example

```json
{
  "companies": [
    "https://jobs.ashbyhq.com/ramp"
  ],
  "maxItems": 10
}
```

Use the input schema for the remaining filters and defaults.

### Output example

This is a selected-field excerpt of the first object in [sample_output.json](sample_output.json).

```json
{
  "company": "Ramp",
  "ats": "ashby",
  "job_id": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Security Engineer, Cloud",
  "department": "Engineering",
  "team": "Backend",
  "location": "New York, NY (HQ)",
  "locations": [
    "New York, NY (HQ)",
    "Remote (Canada)",
    "Remote (US)",
    "Miami, FL"
  ],
  "remote": true,
  "workplace_type": "hybrid",
  "employment_type": "Full-time",
  "salary_min": 211400,
  "salary_max": 290600,
  "salary_currency": "USD"
}
```

### Pricing

Pay per event: **$0.0012 per job** returned (event `job`), no start fee. Error rows are free. You set a maximum spend per run and the actor stops when it is reached. Reasoning against the current Store competitors is in `PRICING.md`.

### Speed (measured, local run, 2026-10-05)

- 100 jobs with descriptions from Greenhouse + Lever + Ashby (3 boards): 2.6 s
- 100 jobs with descriptions from a Workday site (NVIDIA): 24.4 s (one detail request per job)
- 18 jobs from 6 boards on 6 platforms: 4.3 s

### Limits and honest notes

- Public data only: whatever a company publishes on its own job board. No login, no CAPTCHA solving, no applicant or recruiter personal data.
- Company-hosted career pages (a company's own domain in front of Greenhouse, for example) cannot be detected from the URL: pass `greenhouse:<token>`.
- Salary is only present when the company publishes it through the API. Workable and SmartRecruiters publish none; Lever and Greenhouse only if the board returns pay ranges (not seen in the test boards). Nothing is guessed from description text.
- Workday: the list gives a relative date ("Posted 3 Days Ago", "30+ Days Ago" gives no date). With `includeDescription` on, the exact start date from the job detail is used. Workday salary, department and team are not returned.
- `remote` on Greenhouse and Workday is inferred from the word "Remote" in the location or title; on the other platforms it comes from the platform's own field.
- Lever boards on the EU datacenter are found automatically. Some well-known companies have left their ATS (e.g. Netflix no longer has a public Lever board) and return a "not found" error row.
- A company that does not exist or has no board returns an error row (`error` field, free) and the run continues.

### FAQ

**What limits the output?** The maxItems input caps output rows; its schema maximum is 100,000. Source availability and errors may yield fewer rows.

**Does it need a proxy, login, or API key?** The actor calls public ATS job-board endpoints without a company login or proxy input.

**How is it priced?** The pay-per-event proposal in PRICING.md charges job rows as described there. Check the published Store settings for the active price and platform charges.

**What happens on rate limits?** The HTTP client retries throttling and transient server failures, using Retry-After when supplied. A source can still reject requests.

**How fresh is the data?** Jobs reflect public boards at run time. posted_at and updated_at are source fields and may be absent.

**Are all jobs guaranteed to have salary data?** No. Salary fields remain empty when the source board omits them.

### Use with AI agents / MCP

An AI agent can call a published actor through the Apify MCP server or Apify API, pass the JSON input, then read the run’s default dataset. Check the actor’s published identifier and permissions in your Apify account.

Every run returns plain JSON, so an agent can call it directly. Through the Apify MCP server (`https://mcp.apify.com`) add this actor as a tool and give the model a prompt such as: "Find remote backend engineering jobs posted in the last 7 days at stripe, ramp and spotify". The agent supplies `companies`, `keywords: ["backend"]`, `remoteOnly: true`, `postedWithinDays: 7` and gets normalised rows it can rank, summarise or load into a CRM. Keep `maxItems` low in agent loops so cost stays bounded.

# Actor input Schema

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

Career-page URLs, for example https://boards.greenhouse.io/stripe, https://jobs.lever.co/spotify, https://jobs.ashbyhq.com/ramp, https://apply.workable.com/huggingface, https://jobs.smartrecruiters.com/BoschGroup or https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite. The platform is detected from the URL. A bare token such as `stripe` is probed on the five token-based platforms; use a prefix such as `greenhouse:stripe` or `lever:spotify` to be exact (also needed for company-hosted career pages).

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

Keep jobs whose title, department, team or description contains ANY of these words (case-insensitive).

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

Keep jobs whose location contains ANY of these strings, e.g. `London`, `Germany`.

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

Keep only jobs the platform marks as remote (Greenhouse and Workday: 'Remote' in the location or title).

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

Keep jobs whose department or team contains ANY of these strings, e.g. `Engineering`.

## `employmentTypes` (type: `array`):

Keep jobs whose normalised employment type contains ANY of these: Full-time, Part-time, Contract, Intern, Temporary.

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

Keep jobs published in the last N days. 0 = no limit. Jobs without a publish date are dropped when this is set.

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

Add the plain-text job description. On Workday and SmartRecruiters this costs one extra request per job (slower); turn it off for fast listings.

## `includeHtml` (type: `boolean`):

Also return the original HTML description.

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

Stop after this many jobs in total (you are billed per job returned).

## `maxItemsPerCompany` (type: `integer`):

Cap per company so one big board does not use the whole budget. 0 = no per-company cap.

## `maxConcurrency` (type: `integer`):

How many company boards are fetched at the same time.

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp"
  ],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "includeDescription": true,
  "includeHtml": false,
  "maxItems": 100,
  "maxItemsPerCompany": 0,
  "maxConcurrency": 5
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset with all result rows

# 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": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("grit-77/ats-jobs-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": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("grit-77/ats-jobs-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": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp"
  ]
}' |
apify call grit-77/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,grit-77/ats-jobs-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/Y8QkHjxCgXO4HKNdZ/builds/rEZs2DSRhLa89kHr2/openapi.json
