# Company Jobs Scraper: Greenhouse, Lever, Ashby and more (`djk12/company-jobs-multi-ats`) Actor

Live job listings straight from company career pages on Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee and Workable. One clean schema, filters, salary and new-job tracking. No login.

- **URL**: https://apify.com/djk12/company-jobs-multi-ats.md
- **Developed by:** [Dylan Knaup](https://apify.com/djk12) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 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

## Company Jobs Scraper: Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee and Workable

Get the current job openings of any company that hosts its career page on **Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee or Workable**, in one clean format, straight from the employer. No login, no cookies, no proxies.

Give it a list of companies, get back one row per open job with title, department, location, remote flag, salary range (when the company publishes one), publish date, apply link and the full description.

### Why use this Actor

- **Straight from the employer.** Jobs are read live from the career page the company itself runs. Nothing is a day-old copy, and every row links to the company's own apply page. No LinkedIn or Indeed reposts, no ghost listings.
- **One format for six systems.** Greenhouse, Lever, Ashby and the others all name their fields differently. Here every job has the same fields, so your spreadsheet, database or AI agent needs only one parser.
- **Pay only for jobs you get.** Pay per event: a tiny start fee plus a price per job in your dataset. Companies that cannot be found cost you nothing.
- **Built for monitoring.** Switch on *Only jobs not seen in an earlier run*, put the Actor on a schedule, and every run returns only the newly posted jobs.
- **Official public job board APIs.** The six systems each publish their job board data for exactly this purpose (company career pages and job aggregators use them). No scraping of protected pages and no personal data: only job postings.

### What can you do with it

- Build a **job board or newsletter** for a niche (remote Python jobs, climate tech, fintech in Berlin).
- **Sales and recruiting leads:** find companies that are hiring for a role you sell into, the day the job is posted.
- **Competitor and market tracking:** watch how many engineers, salespeople or data scientists a competitor opens, and where.
- **Salary research:** pay ranges that companies publish (many Ashby and some Lever and Greenhouse boards do) in a numeric, comparable format.
- **Feed AI agents** with fresh, structured, deduplicated postings.

### How to use it

1. Open the **Input** tab and add companies. A career page link is the most reliable way:

   | System | Link looks like |
   |---|---|
   | Greenhouse | `https://job-boards.greenhouse.io/anthropic` or `https://boards.greenhouse.io/stripe` |
   | Lever | `https://jobs.lever.co/palantir` |
   | Ashby | `https://jobs.ashbyhq.com/ramp` |
   | SmartRecruiters | `https://jobs.smartrecruiters.com/BoschGroup` |
   | Recruitee | `https://bunq.recruitee.com` |
   | Workable | `https://apply.workable.com/huggingface` |

   You can also write `ats:name`, for example `lever:palantir` or `greenhouse:stripe`. This is the way to go when a company's career page lives on its own domain (for example `careers.example.com`) but the jobs are hosted by one of these systems: look at the apply link of any job to see which system and name it uses.
   A plain name such as `ramp` also works: the Actor tries the six systems in turn and uses the first one that has jobs. If the name exists in several systems, use the link or `ats:name` form to be sure.
2. Optionally add filters (job title words, location, department, remote only, posted within N days).
3. Click **Start**. Results appear in the **Output** tab and can be downloaded as JSON, CSV, Excel, XML or HTML, or read through the API.

#### Input example

```json
{
    "companies": [
        "https://jobs.ashbyhq.com/ramp",
        "greenhouse:stripe",
        "lever:palantir",
        "https://apply.workable.com/huggingface"
    ],
    "titleKeywords": ["engineer", "developer"],
    "excludeTitleKeywords": ["intern"],
    "remoteOnly": true,
    "postedWithinDays": 60,
    "maxJobsPerCompany": 25,
    "descriptionFormat": "text"
}
```

#### All input options

| Field | What it does |
|---|---|
| `companies` | Required. Career page links, `ats:name` or plain names. |
| `titleKeywords` | Keep jobs whose title contains any of these words. |
| `excludeTitleKeywords` | Drop jobs whose title contains any of these words. |
| `locations` | Keep jobs whose location, city or country text contains any of these words. |
| `departments` | Keep jobs whose department or team contains any of these words. |
| `remoteOnly` | Keep only jobs the company flags as remote (this includes jobs that list a remote option next to an office location). |
| `postedWithinDays` | Keep jobs first published in the last N days. Jobs without a publish date are dropped when this is set. |
| `descriptionFormat` | `text` (default), `html`, `both` or `none`. `none` is fastest and smallest. |
| `maxJobsPerCompany` | Newest jobs first. Default 1000. |
| `maxJobsTotal` | Hard stop for the whole run. Default 5000. |
| `onlyNewSinceLastRun` | Return only jobs not reported in an earlier run (see below). |
| `stateStoreName` | Name of the memory used for new job tracking. Use one name per watch list. |
| `maxConcurrency` | Companies read in parallel (default 3). |

All word filters are not case sensitive and match anywhere in the text. Different filters combine with AND, words inside one filter combine with OR.

### Output

One dataset row per job, the same fields for every system. Real example (description shortened):

```json
{
    "id": "29a5da77-37f0-402a-92bc-072778e77ed8",
    "ats": "ashby",
    "company": "ramp",
    "companyName": "ramp",
    "title": "Software Engineer, Forward Deployed",
    "department": "Engineering",
    "team": "Engineering",
    "employmentType": "Full-time",
    "workplaceType": "hybrid",
    "isRemote": true,
    "location": "New York, NY (HQ)",
    "locations": ["New York, NY (HQ)", "San Francisco, CA"],
    "country": "USA",
    "city": "New York City",
    "postedAt": "2026-10-22T19:20:16Z",
    "updatedAt": null,
    "url": "https://jobs.ashbyhq.com/ramp/29a5da77-37f0-402a-92bc-072778e77ed8",
    "applyUrl": "https://jobs.ashbyhq.com/ramp/29a5da77-37f0-402a-92bc-072778e77ed8/application",
    "salaryMin": 168000,
    "salaryMax": 285000,
    "salaryCurrency": "USD",
    "salaryInterval": "year",
    "salaryText": "$168K - $285K",
    "description": "About Ramp  Ramp is building the smart infrastructure for finance teams...",
    "scrapedAt": "2026-11-02T06:45:54Z"
}
```

Also stored in the run: a `RUN_SUMMARY` record that lists, for every company you entered, which system was used, how many jobs were found, matched and returned, and the reason for any company that could not be read (not found, unsupported link, temporary error).

#### What each system provides

Not every system publishes every field. Empty means the company's system does not provide it, not that the Actor missed it.

| Field | Greenhouse | Lever | Ashby | SmartRecruiters | Recruitee | Workable |
|---|---|---|---|---|---|---|
| Title, location, link, description | yes | yes | yes | yes | yes | yes |
| Publish date | yes | yes | yes | yes | yes | yes (date only) |
| Last update | yes | no | no | no | yes | no |
| Department | yes | yes | yes | yes | yes | yes |
| Team | no | yes | yes | no | no | no |
| Employment type | sometimes | yes | yes | yes | yes | yes |
| Remote or hybrid | remote when the location says so | yes | yes | yes | yes | remote |
| Salary range | no | when the company fills it in | yes, when the company publishes it | no | when the company fills it in | no |
| Company display name | yes | no (name is used) | no (name is used) | yes | yes | yes |

### Track new jobs automatically

1. Turn on **Only jobs not seen in an earlier run**.
2. Pick a **Memory name** for the watch list (default `company-jobs-seen`).
3. Create a **Schedule** (hourly, daily or weekly) for the Actor with this input.

The first run returns every matching job and remembers them. Every later run returns only jobs that were not reported before, each with `isNew: true`. Jobs that disappear from the career page drop out of the memory, so a job that is closed and later reposted is reported again. The memory lives in a key-value store in your own Apify account. Change the filters and you may see jobs again that earlier runs filtered out; use a new memory name for a new watch list.

Connect the dataset to Google Sheets, Slack, Zapier, Make or a webhook with Apify integrations to get a notification for each new job.

### Pricing

Pay per event: you are charged for the jobs that land in your dataset and a very small fee for starting the run. Companies that cannot be found, links that cannot be understood and jobs removed by your filters are free. Set a **maximum cost per run** in the run options to cap spending: the Actor stops when it is reached.

See the **Pricing** tab for the current price per job. Platform usage is included in the event prices.

### Use it from code

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("<YOUR_USERNAME>/company-jobs-multi-ats").call(run_input={
    "companies": ["greenhouse:stripe", "https://jobs.ashbyhq.com/ramp"],
    "titleKeywords": ["data"],
    "postedWithinDays": 14,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["companyName"], job["title"], job["url"])
```

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

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('<YOUR_USERNAME>/company-jobs-multi-ats').call({
    companies: ['greenhouse:stripe', 'https://jobs.ashbyhq.com/ramp'],
    titleKeywords: ['data'],
    postedWithinDays: 14,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

The Actor also works as a tool for AI agents through the Apify MCP server: describe the companies and filters and the agent gets structured jobs back.

### FAQ

**Which companies are covered?** Any company that hosts its jobs on one of the six systems. Many startups and tech companies do (for example Stripe and Canonical on Greenhouse, Palantir on Lever, Ramp and OpenAI on Ashby, Ubisoft on SmartRecruiters, bunq on Recruitee, Hugging Face on Workable). The Actor does **not** search for companies for you: you give it the companies.

**How do I find a company's system and name?** Open the company's careers page and click a job. The link of the apply page shows the system (`greenhouse.io`, `lever.co`, `ashbyhq.com`, `smartrecruiters.com`, `recruitee.com`, `apply.workable.com`) and the company name used by that system.

**What about Workday, iCIMS, Taleo, BambooHR, Personio?** Not supported in this version.

**Is it legal?** The Actor reads only the public job board interfaces that these vendors provide so that job postings can be shown and shared outside their own pages. It collects job postings, not personal data about candidates or employees, and does not log in anywhere. You are responsible for how you use the data; check the terms of the employer and the countries you operate in, especially if you republish descriptions.

**How fresh is the data?** Live at the moment of the run. The Actor keeps no database.

**A company shows "not-found".** The name is not the one used by the system, or the company moved to another system. Use the link of a job's apply page.

**How long does a run take?** A few seconds for a handful of companies. Companies with several thousand jobs take a little longer. SmartRecruiters needs one extra request per job to get descriptions, so a board of about 300 jobs takes roughly 20 seconds; use `descriptionFormat: none` to skip that.

**Can I get closed jobs?** No. Only jobs that are open at the time of the run.

### Support

Found a company that is read wrongly, or want another system added? Open an issue in the **Issues** tab with the career page link and we will look at it.

# Changelog

This Actor's version history is a separate document: https://apify.com/djk12/company-jobs-multi-ats/changelog.md

# Actor input Schema

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

One entry per company. Accepted forms: a career page link (<code>https://job-boards.greenhouse.io/anthropic</code>, <code>https://jobs.lever.co/palantir</code>, <code>https://jobs.ashbyhq.com/ramp</code>, <code>https://jobs.smartrecruiters.com/BoschGroup</code>, <code>https://bunq.recruitee.com</code>, <code>https://apply.workable.com/huggingface</code>), an explicit <code>ats:name</code> such as <code>greenhouse:stripe</code>, or just a name such as <code>ramp</code> (the Actor then tries each supported ATS in turn and uses the first one that has jobs).

## `titleKeywords` (type: `array`):

Keep only jobs whose title contains at least one of these words (not case sensitive). Leave empty to keep all.

## `excludeTitleKeywords` (type: `array`):

Drop jobs whose title contains any of these words, for example <code>intern</code> or <code>director</code>.

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

Keep only jobs whose location, city or country text contains one of these words, for example <code>Germany</code>, <code>London</code> or <code>Remote</code>.

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

For example <code>Engineering</code> or <code>Sales</code>.

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

Keep only jobs that the company marks as remote.

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

Keep only jobs first published in the last N days. Jobs without a publish date are dropped when this is set.

## `descriptionFormat` (type: `string`):

Plain text is best for spreadsheets and AI tools. Choose HTML if you want to keep the original formatting. 'None' is fastest and gives the smallest output.

## `maxJobsPerCompany` (type: `integer`):

Newest jobs are kept first.

## `maxJobsTotal` (type: `integer`):

Hard stop for the whole run. Also set a maximum cost per run in the run options if you want a cost cap.

## `onlyNewSinceLastRun` (type: `boolean`):

Turn this on and schedule the Actor to get a feed of new postings. The first run returns every matching job and remembers them; later runs return only additions. Each row gets <code>isNew: true</code>. The memory is stored in your own account.

## `stateStoreName` (type: `string`):

Name of the key-value store that remembers which jobs were already reported. Use a different name for each separate watch list. Lowercase letters, digits and hyphens only.

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

Lower this if you read many very large career pages and see memory errors.

## Actor input object example

```json
{
  "companies": [
    "https://job-boards.greenhouse.io/canonical",
    "lever:palantir"
  ],
  "remoteOnly": false,
  "descriptionFormat": "text",
  "maxJobsPerCompany": 1000,
  "maxJobsTotal": 50,
  "onlyNewSinceLastRun": false,
  "stateStoreName": "company-jobs-seen",
  "maxConcurrency": 3
}
```

# Actor output Schema

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

One row per job posting, in the same format for every ATS.

## `summary` (type: `string`):

Per-company report: which ATS was used, how many jobs were found and matched, and any company that could not be read.

# 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://job-boards.greenhouse.io/canonical",
        "lever:palantir"
    ],
    "maxJobsTotal": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("djk12/company-jobs-multi-ats").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://job-boards.greenhouse.io/canonical",
        "lever:palantir",
    ],
    "maxJobsTotal": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("djk12/company-jobs-multi-ats").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://job-boards.greenhouse.io/canonical",
    "lever:palantir"
  ],
  "maxJobsTotal": 50
}' |
apify call djk12/company-jobs-multi-ats --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,djk12/company-jobs-multi-ats"
        }
    }
}
```

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/rfBUf6GX2lNeUKrmi/builds/2AZk1TH76Nae7ADNj/openapi.json
