# Career Site Scraper (`automation-lab/multi-ats-career-site-jobs-api`) Actor

Export normalized public Greenhouse and Lever career site jobs for recruiting feeds, monitoring, and analysis.

- **URL**: https://apify.com/automation-lab/multi-ats-career-site-jobs-api.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Career Site Scraper

Export normalized public **Greenhouse and Lever career site jobs** into one stable dataset.

Give the Actor one or more employer career-board URLs and receive job identity, title, company, location, team, employment type, timestamps, description, job URL, application URL, and native source identifiers.

The Actor is designed for recurring recruiting feeds, job monitoring, workforce research, and downstream APIs.

It uses the public Greenhouse and Lever job-board APIs directly.

No login, browser, or proxy is required.

### What does Career Site Scraper do?

Career Site Scraper:

- accepts public Greenhouse board URLs;
- accepts public Lever site URLs;
- detects the ATS from each URL;
- fetches the current public jobs;
- normalizes both ATS formats into one schema;
- deduplicates jobs by ATS and native source ID;
- applies keyword, location, team, and employment-type filters;
- limits output across all supplied boards;
- saves jobs to the default Apify dataset;
- returns direct job and application links.

This focused two-ATS contract is useful when a pipeline needs predictable job identity and consistent fields rather than broad web crawling.

### Who is it for?

#### Recruiting operations teams

Refresh employer job feeds on a schedule without maintaining separate Greenhouse and Lever clients.

#### Talent intelligence teams

Track hiring by company, location, or department and compare dataset snapshots over time.

#### Job boards and HR products

Import public employer postings into a normalized ingestion queue.

#### Sales and market researchers

Use openings as hiring signals for company research, territory planning, or account prioritization.

#### Developers and data engineers

Call one Actor API and receive the same output shape from two common ATS platforms.

### Supported career sites

| ATS | Accepted URL examples | Data route |
| --- | --- | --- |
| Greenhouse | `https://boards.greenhouse.io/airbnb` | Public Greenhouse Boards API |
| Greenhouse | `https://job-boards.greenhouse.io/airbnb` | Public Greenhouse Boards API |
| Greenhouse | `https://boards-api.greenhouse.io/v1/boards/airbnb/jobs` | Public Greenhouse Boards API |
| Lever | `https://jobs.lever.co/veo` | Public Lever postings API |
| Lever | `https://api.lever.co/v0/postings/veo` | Public Lever postings API |

The Actor does not claim to scrape arbitrary career-page software.

Use a supported public board URL, not a company's generic home page.

### What job data can you extract?

| Field | Meaning |
| --- | --- |
| `recordId` | Stable ATS-prefixed identity such as `greenhouse:123456` |
| `ats` | `greenhouse` or `lever` |
| `sourceId` | Native ATS job identifier |
| `sourceBoard` | Board token or site handle |
| `sourceUrl` | Original career-board URL from the input |
| `title` | Published job title |
| `company` | Board company name or supplied override |
| `location` | Published location, when available |
| `team` | Greenhouse department or Lever team |
| `employmentType` | Published commitment or employment type |
| `workplaceType` | Remote, hybrid, or onsite value when available |
| `publishedAt` | Source publication time when available |
| `updatedAt` | Source update time when available |
| `description` | Normalized plain-text description when enabled |
| `jobUrl` | Hosted public job page |
| `applicationUrl` | Direct application URL |
| `scrapedAt` | Time the record was normalized |

Missing optional source values are returned as `null`.

### Get started

1. Open the Actor input.
2. Add one or more public Greenhouse or Lever career-board URLs.
3. Optionally add a keyword or structured filters.
4. Choose the maximum number of jobs.
5. Click **Start**.
6. Open the **Jobs** dataset view.
7. Export the records as JSON, CSV, Excel, XML, or RSS.

The prefilled Airbnb Greenhouse URL is a working small-start example.

### Input parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `startUrls` | array | required | One to 50 Greenhouse or Lever board URLs |
| `query` | string | empty | Text required in searchable job fields |
| `locations` | string array | empty | Keep locations containing any supplied value |
| `teams` | string array | empty | Keep departments or teams containing any supplied value |
| `employmentTypes` | string array | empty | Keep employment types containing any supplied value |
| `maxItems` | integer | `1000` | Maximum jobs saved across every source |
| `includeDescription` | boolean | `true` | Include normalized plain-text descriptions |

Filters are case-insensitive substring matches.

All active filters apply equally to Greenhouse and Lever records.

A source object may include `companyName` when the board handle is not the desired display name.

### Example: search Airbnb engineering jobs

```json
{
  "startUrls": [
    { "url": "https://boards.greenhouse.io/airbnb" }
  ],
  "query": "engineering",
  "maxItems": 5,
  "includeDescription": false
}
```

This input returned five current typed Greenhouse records in the final local test.

### Example: export Palantir jobs in London

```json
{
  "startUrls": [
    {
      "url": "https://jobs.lever.co/palantir",
      "companyName": "Palantir"
    }
  ],
  "locations": ["London"],
  "maxItems": 5,
  "includeDescription": false
}
```

This source-specific input returned five current Lever records in the final local test.

### Example: build a recurring multi-ATS engineering feed

```json
{
  "startUrls": [
    { "url": "https://jobs.lever.co/veo", "companyName": "Veo" },
    { "url": "https://boards.greenhouse.io/airbnb" }
  ],
  "teams": ["Engineering"],
  "maxItems": 10,
  "includeDescription": false
}
```

Schedule this input to refresh one normalized dataset from both ATS platforms.

The final local test returned ten records and exercised both Lever and Greenhouse.

### Output example

A current local run produced this shape:

```json
{
  "recordId": "greenhouse:8074765",
  "ats": "greenhouse",
  "sourceId": "8074765",
  "sourceBoard": "airbnb",
  "sourceUrl": "https://boards.greenhouse.io/airbnb",
  "title": "Engineering Manager, Cloud & Data Security",
  "company": "Airbnb",
  "location": "United States",
  "team": "Software Engineering",
  "employmentType": null,
  "workplaceType": null,
  "publishedAt": "2025-01-15T12:00:00.000Z",
  "updatedAt": "2025-01-16T12:00:00.000Z",
  "description": null,
  "jobUrl": "https://boards.greenhouse.io/airbnb/jobs/8074765",
  "applicationUrl": "https://boards.greenhouse.io/airbnb/jobs/8074765",
  "scrapedAt": "2025-01-16T13:00:00.000Z"
}
```

Timestamps in the example illustrate the ISO format.

Live source timestamps and URLs vary with each posting.

### How much does it cost to export career site jobs?

Career Site Scraper uses pay-per-event pricing:

- one `start` event is charged when a run begins;
- one `item` event is charged for each normalized job saved;
- rejected, duplicate, failed, and filtered-out jobs do not incur an item charge.

The one-time start event costs **$0.005** on every plan. Per-job prices are:

| Tier | Price per saved job |
| --- | ---: |
| Free | $0.0011201 |
| Bronze | $0.000974 |
| Silver | $0.00075972 |
| Gold | $0.0005844 |
| Platinum | $0.0003896 |
| Diamond | $0.00027272 |

Bronze-tier examples, including the start event:

| Saved jobs | Total price |
| ---: | ---: |
| 1 | $0.005974 |
| 10 | $0.01474 |
| 100 | $0.10240 |

Use a small `maxItems` value to test a new board before scheduling a larger feed.

### Scheduling and change monitoring

Apify schedules can run the same input hourly, daily, or weekly.

For change monitoring:

1. create a Task with stable source URLs and filters;
2. schedule the Task;
3. export or store each dataset snapshot;
4. compare records by `recordId`;
5. treat unseen IDs as new openings;
6. treat missing IDs as potentially closed openings;
7. compare `updatedAt` where the ATS supplies it.

The Actor returns snapshots.

It does not maintain historical state or send alerts itself.

Use webhooks, Make, Zapier, or your own data warehouse for downstream notifications.

### Integration patterns

#### Recruiting feed

Send dataset items to an ATS ingestion service or internal search index.

#### Hiring-signal monitoring

Aggregate counts by company, team, and location, then compare scheduled snapshots.

#### Data warehouse refresh

Load JSON or CSV exports into BigQuery, Snowflake, PostgreSQL, or object storage.

#### Spreadsheet workflow

Connect the default dataset to Google Sheets through an Apify integration.

#### Webhook workflow

Trigger a webhook when the run succeeds, then fetch the default dataset by run ID.

### Run with the Apify API using cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~multi-ats-career-site-jobs-api/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "startUrls": [{"url": "https://jobs.lever.co/veo", "companyName": "Veo"}],
    "teams": ["Engineering"],
    "maxItems": 10,
    "includeDescription": false
  }'
```

Poll the returned run ID or use the synchronous dataset endpoint for bounded workflows.

### Run with JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

const run = await client.actor('automation-lab/multi-ats-career-site-jobs-api').call({
  startUrls: [{ url: 'https://boards.greenhouse.io/airbnb' }],
  query: 'engineering',
  maxItems: 25,
  includeDescription: false,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Run with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("automation-lab/multi-ats-career-site-jobs-api").call(
    run_input={
        "startUrls": [{"url": "https://jobs.lever.co/palantir", "companyName": "Palantir"}],
        "locations": ["London"],
        "maxItems": 25,
        "includeDescription": False,
    }
)
items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
```

### Use with MCP and AI agents

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/multi-ats-career-site-jobs-api"
```

#### Claude Desktop

Use this MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/multi-ats-career-site-jobs-api"
    }
  }
}
```

#### Cursor

Add the same HTTP MCP server URL in **Cursor Settings → MCP**.

#### VS Code

Add the same HTTP MCP server URL to your VS Code MCP server configuration.

Example MCP prompts:

- “Use the Career Site Scraper MCP tool to export engineering roles from this Airbnb Greenhouse board.”
- “Call the Actor through MCP and find Palantir jobs in London from its Lever site.”
- “Use MCP to build one normalized engineering feed from these Greenhouse and Lever URLs.”

### Reliability and failure behavior

Requests use a 30-second timeout.

Transient HTTP statuses and network errors receive bounded retries with backoff.

Deterministic unsupported URLs and missing boards are not retried blindly.

If one source fails, other valid sources continue.

If every source fails, the run fails with a non-zero status and the source errors appear in logs.

A valid source with no matching jobs completes normally with an empty dataset.

No automatic browser, proxy, or residential fallback is enabled.

### Limits and tips

- Supply no more than 50 career-board URLs per run.
- Set `maxItems` between 1 and 10,000.
- Start small when testing an unfamiliar board.
- Disable `includeDescription` when compact records are enough.
- Use `companyName` for Lever handles that are not readable company names.
- Use `recordId` as the stable snapshot comparison key.
- Expect source fields to be `null` when the employer did not publish them.
- Board APIs can change or rate-limit requests.
- The Actor covers current public jobs, not expired-job history.
- The Actor does not submit applications.

### Responsible use and legality

This Actor accesses public job-board endpoints without login.

You are responsible for ensuring your collection and downstream use comply with applicable laws, contracts, privacy requirements, and source terms.

Do not use job data for unlawful discrimination, spam, impersonation, or deceptive recruiting.

Avoid republishing descriptions in ways that violate employer rights.

Collect only the fields needed for a legitimate purpose and apply appropriate retention controls.

### Troubleshooting

#### Why is my URL rejected?

Confirm it is a public `boards.greenhouse.io`, `job-boards.greenhouse.io`, `boards-api.greenhouse.io`, `jobs.lever.co`, or `api.lever.co` URL.

Generic company career pages and unsupported ATS platforms fail closed.

#### Why did a board return HTTP 404?

The board handle may be misspelled, retired, private, or migrated to another ATS.

Open the supplied career-board URL and verify the current handle.

#### Why is the dataset empty?

The board may have no open jobs, or your keyword and structured filters may exclude every posting.

Remove filters and run with a small limit to inspect the source.

#### Why is a field null?

Greenhouse and Lever do not require employers to publish every normalized field.

Null values preserve this source truth rather than inventing data.

#### Why did only some sources finish?

The Actor isolates source failures.

Inspect logs for the failed URL while retaining useful records from successful boards.

### Related Automation Lab Actors

- [Multi-ATS Company Jobs Scraper](https://apify.com/automation-lab/multi-ats-jobs-scraper) — choose this when you also need Workday, SmartRecruiters, or Ashby.
- [Greenhouse Jobs Scraper](https://apify.com/automation-lab/greenhouse-jobs-scraper) — choose this for Greenhouse-only workflows and Greenhouse-specific fields.
- [LinkedIn Jobs Scraper](https://apify.com/automation-lab/linkedin-jobs-scraper) — choose this for LinkedIn job discovery rather than employer-hosted board feeds.

Career Site Scraper is the focused choice for one normalized Greenhouse-and-Lever API contract.

### FAQ

#### Does this Actor need an API key for Greenhouse or Lever?

No. It uses the public board APIs exposed for public postings.

#### Does it need a proxy?

No. The current implementation uses direct HTTP only and has no automatic proxy mode.

#### Can I process multiple companies?

Yes. Add up to 50 supported board URLs and use `maxItems` as the combined output limit.

#### Can I filter by several locations or teams?

Yes. A job is kept when its field contains any value in the corresponding filter list.

Filters across different fields are combined, so a job must satisfy each non-empty filter category.

#### Can I monitor new and removed jobs?

Yes, by scheduling snapshot runs and comparing the stable `recordId` field downstream.

The Actor itself does not persist history.

#### Are descriptions HTML?

No. When enabled, descriptions are normalized to plain text for consistent downstream use.

#### Are filtered jobs charged?

No item event is charged for filtered, duplicate, rejected, or failed records.

#### Can it scrape Workday or Ashby?

Not in this Actor.

Use the related Multi-ATS Company Jobs Scraper when those ATS platforms are required.

# Actor input Schema

## `startUrls` (type: `array`):

Public Greenhouse or Lever board URLs, for example https://boards.greenhouse.io/airbnb or https://jobs.lever.co/veo. Add an optional companyName to override the name inferred from the board.

## `query` (type: `string`):

Case-insensitive text that must occur in the job title, company, location, team, employment type, or description.

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

Keep jobs whose location contains any of these values (case-insensitive). Leave empty for every location.

## `teams` (type: `array`):

Keep jobs whose Greenhouse department or Lever team contains any of these values.

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

Keep jobs whose employment type contains any supplied value, such as full-time or contract.

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

Maximum number of matching normalized jobs saved across all career sites.

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

Include normalized plain-text job descriptions. Disable to reduce dataset size.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://boards.greenhouse.io/airbnb"
    }
  ],
  "query": "",
  "locations": [],
  "teams": [],
  "employmentTypes": [],
  "maxItems": 10,
  "includeDescription": true
}
```

# Actor output Schema

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

Normalized jobs in the overview dataset view.

# 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 = {
    "startUrls": [
        {
            "url": "https://boards.greenhouse.io/airbnb"
        }
    ],
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/multi-ats-career-site-jobs-api").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 = {
    "startUrls": [{ "url": "https://boards.greenhouse.io/airbnb" }],
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/multi-ats-career-site-jobs-api").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 '{
  "startUrls": [
    {
      "url": "https://boards.greenhouse.io/airbnb"
    }
  ],
  "maxItems": 10
}' |
apify call automation-lab/multi-ats-career-site-jobs-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/multi-ats-career-site-jobs-api"
        }
    }
}

```

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/XEpVNgJHzk138cB0H/builds/yDw8x3pS2FbgTxTat/openapi.json
