# Job Scraper for Greenhouse, Lever & Ashby (`miacx/job-scraper-greenhouse-lever-ashby`) Actor

Collect jobs from public Greenhouse, Lever and Ashby feeds or JobPosting JSON-LD. Get normalized job records, deduplicated by application URL.

- **URL**: https://apify.com/miacx/job-scraper-greenhouse-lever-ashby.md
- **Developed by:** [mia.cx](https://apify.com/miacx) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.30 / 1,000 jobs

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

## Job Scraper for Greenhouse, Lever & Ashby

Give your agent comparable job records without writing a parser for each careers site.
Send public ATS identifiers or careers URLs for normalized jobs, including structured salary data when available.
The Actor reads public feeds or JobPosting JSON-LD. It returns no job descriptions.
Each delivered Job costs $0.0003, plus $0.00005 per run start at default memory.

### Try it

```json
{ "inputs": ["lever:leverdemo"] }
```

An empty input `{}` runs this same public Lever demo board. No upstream API credentials are needed.

### Use from an agent

Add `https://mcp.apify.com?tools=miacx/job-scraper-greenhouse-lever-ashby` to your MCP client and sign in to Apify.
Call the dedicated Actor tool, then use `get-dataset-items` to read the completed run's dataset.
Or set `APIFY_TOKEN` to your Apify API token and call the API for JSON rows:

```sh
curl --fail-with-body 'https://api.apify.com/v2/actors/miacx~job-scraper-greenhouse-lever-ashby/run-sync-get-dataset-items' \
  -H "Authorization: Bearer $APIFY_TOKEN" -H 'Content-Type: application/json' \
  -d '{"inputs":["lever:leverdemo"]}'
```

### What you get

Read `status` first, then check for `applyUrl`. An `ok` job row is billable; an `ok` empty-board row is not.
The dataset contains unique jobs, deduplicated by application URL across the run, plus uncharged error and empty-board rows.
An ATS job URL requests its whole board. Other careers pages provide one discovery page or their own JobPosting JSON-LD.

This job row comes from the recorded Lever fixture. The fetch timestamp is fixed for the offline replay:

```json
{
  "status": "ok",
  "id": "ad208490-4052-4f91-a57e-433f2d1e484b",
  "title": "Customer Success Manager",
  "team": "Customer Success Team",
  "locations": ["Atlanta, Georgia"],
  "workMode": "hybrid",
  "employmentType": "full_time",
  "salaryMin": 85000,
  "salaryMax": 175000,
  "salaryCurrency": "USD",
  "salaryInterval": "year",
  "postedAt": "2026-08-31T18:12:19.703Z",
  "applyUrl": "https://jobs.lever.co/leverdemo/ad208490-4052-4f91-a57e-433f2d1e484b/apply",
  "ats": "lever",
  "source": "https://api.lever.co/v0/postings/leverdemo?mode=json",
  "fetchedAt": "2026-10-31T12:00:00.000Z"
}
```

Job rows contain no `input` or `message`. Status-only rows contain these fields and omit job fields.
Empty-board rows use `message: "Board contains no current jobs."`
Missing job values stay null; unknown locations are `[]`. Location strings retain source order with duplicates removed.
`workMode` comes from explicit fields or exact location labels, never general description text.
Salary uses the first supported structured base-salary range in its original currency and period, excluding bonus and equity.
`postedAt` uses a publication or creation date, never an update date.
`source` identifies the feed or page; `fetchedAt` records its fetch time.

The default key-value store also contains `OUTPUT`, an ordered per-input summary.
It preserves duplicate inputs and marks boards that could not be fully delivered with `BILLING_LIMIT`.

### Input

`inputs` is a list of 1–25 strings, each at most 2,048 characters.
Omitting it uses `["lever:leverdemo"]`; an empty list fails the run. Bare company names are invalid.
Use public HTTP(S) URLs or qualified identifiers. Identifier company slugs allow letters, digits, underscores and hyphens, up to 100 characters.

| Prefix                | Source                        | Coverage                                                                               |
| --------------------- | ----------------------------- | -------------------------------------------------------------------------------------- |
| `greenhouse:`         | Greenhouse public JSON        | Also recognizes EU and US board/job URLs.                                              |
| `lever:`, `lever-eu:` | Lever public JSON             | Also recognizes EU and US board/job URLs.                                              |
| `ashby:`              | Ashby public job-board API    | Includes available structured compensation.                                            |
| `workable:`           | Workable public account JSON  | Uses the public company feed.                                                          |
| `recruitee:`          | Recruitee Careers Site API    | Adapter follows the public contract; its fixture is synthetic.                         |
| `smartrecruiters:`    | SmartRecruiters postings JSON | Current recorded robots rules block generic crawlers; requests return `ROBOTS_DENIED`. |
| `personio:`           | Personio public XML           | Also recognizes `.de` and `.com` board URLs.                                           |

A careers page can select one linked ATS feed. A selected feed error returns directly without a JSON-LD fallback.
If no supported feed is found, the Actor reads JobPosting JSON-LD from that page and skips expired postings.
It does not crawl linked job-detail pages or render scripts.

### Pricing

| Event       | Price    | Counted unit                                                                                 |
| ----------- | -------- | -------------------------------------------------------------------------------------------- |
| Job         | $0.0003  | One normalized job delivered to the dataset, deduplicated by application URL across the run. |
| Actor start | $0.00005 | One run start at the default 256 MB memory.                                                  |

One hundred delivered jobs cost $0.03005 including startup at default memory.
Missing optional fields do not make a job free. Repeated inputs and shared application URLs incur no additional Job events.
Errors and empty-board status rows incur no Job events.
Delivered jobs remain billable if another board fails or a spending limit prevents the rest of a board's output.
The $0.00005 per run start at default memory applies even with no billable jobs.
Apify counts one startup event per GB of memory, with a minimum of one event.

### Limits and data freshness

- Up to 2,000 jobs per board, three concurrent inputs and 40 outbound requests per batch, including DNS, robots checks and retries.
- Responses allow 2 MiB each and 16 MiB across the batch. Large or changing paginated boards return explicit errors.
- The Actor shares duplicate work within a run and has no cross-run response cache. Each new run fetches the permitted sources.
- Requests respect robots.txt and crawl delays. Private-network destinations are blocked; access denials are not bypassed.
- No applicant records, job descriptions, browser rendering or runtime LLM calls are included.

The synchronous API waits up to 300 seconds. Use [asynchronous runs](https://docs.apify.com/api/v2/actor-run-post) when coordinating longer workflows.

### If a row fails

| Status                                                        | Next step                                                                                                              |
| ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `INVALID_INPUT`, `UNSUPPORTED_SOURCE`, `UNSAFE_URL`           | Supply a supported identifier or public careers URL.                                                                   |
| `ROBOTS_DENIED`                                               | Use a source that permits automated access.                                                                            |
| `ROBOTS_UNAVAILABLE`                                          | Retry when a usable robots policy is available.                                                                        |
| `NO_JOBS_FOUND`                                               | Check for a supported feed or current JobPosting JSON-LD; scripts are not rendered.                                    |
| `DNS_ERROR`, `NETWORK_ERROR`, `UPSTREAM_HTTP`, `RATE_LIMITED` | Read `message` and retry transient failures later.                                                                     |
| `RESPONSE_TOO_LARGE`, `JOB_LIMIT`, `REQUEST_LIMIT`            | Split the input batch; oversized individual boards remain unsupported.                                                 |
| `INVALID_RESPONSE`                                            | Retry a changing board, or retain the run ID when reporting malformed source data.                                     |
| `BILLING_LIMIT`                                               | Raise the spending limit. The dataset contains only delivered jobs; incomplete boards have no unpaid jobs in `OUTPUT`. |
| `INTERNAL_ERROR`                                              | Retain the run ID and `message` when reporting the failure.                                                            |

# Actor input Schema

## `inputs` (type: `array`):

1–25 public HTTP(S) careers URLs or ATS identifiers, each at most 2,048 characters. Prefixes: greenhouse, lever, lever-eu, ashby, workable, recruitee, smartrecruiters, personio. Example: \["lever:leverdemo"]. Omitted uses that example; \[] is invalid. Bare company names are invalid. ATS job URLs return the whole board, up to 2,000 jobs. Application URLs are deduplicated across the run; robots rules can block feeds.

## Actor input object example

```json
{
  "inputs": [
    "lever:leverdemo"
  ]
}
```

# Actor output Schema

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

One row per item, each with a status field.

# 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 = {
    "inputs": [
        "lever:leverdemo"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("miacx/job-scraper-greenhouse-lever-ashby").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 = { "inputs": ["lever:leverdemo"] }

# Run the Actor and wait for it to finish
run = client.actor("miacx/job-scraper-greenhouse-lever-ashby").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 '{
  "inputs": [
    "lever:leverdemo"
  ]
}' |
apify call miacx/job-scraper-greenhouse-lever-ashby --silent --output-dataset

```

## MCP server setup

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

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/jEw9c4d9cq1gZ79mA/builds/u7p9K2qoRzczUak3l/openapi.json
