# Job Listings Scraper | Greenhouse Lever Ashby | No API Key (`adored_maze/job-listings-api`) Actor

Scrape job postings straight from company career boards on Greenhouse, Lever, Ashby, SmartRecruiters, Rippling, Workable and Personio. Returns one normalized schema with parsed salary range, seniority and remote/hybrid/onsite. No API key, no proxy, n

- **URL**: https://apify.com/adored\_maze/job-listings-api.md
- **Developed by:** [Bizking Jang](https://apify.com/adored_maze) (community)
- **Categories:** Jobs, Lead generation, Automation
- **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.

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 Listings Scraper — Greenhouse, Lever, Ashby & more

Scrape job postings directly from company career boards on **Greenhouse, Lever, Ashby, SmartRecruiters, Rippling, Workable and Personio**, and get them back in **one normalized schema** with parsed salary range, seniority level and remote/hybrid/onsite classification.

**No API key. No proxy. No login. No cookies.** These are the companies' own public job-board endpoints, so nothing here breaks when a scraper gets blocked.

### Greenhouse job board scraper

Point it at `greenhouse:stripe` or paste `https://boards.greenhouse.io/stripe` and get every open role with offices, departments, salary bands and posting dates.

### Lever, Ashby and SmartRecruiters job scraper

Same call, same output shape. Mixing providers in one run is the point — you should not have to care which ATS a company happens to use.

### Job listings API for AI agents

Small, fast, single-purpose. Call it per company, on a schedule or from an agent, and get JSON back.

***

### Why this beats a generic job scraper

| | This Actor | Typical job scraper |
|---|---|---|
| Source | Company's **official ATS endpoint** | Scraped HTML off an aggregator |
| Blocked by anti-bot | **Never** — no proxy needed | Frequently |
| Providers in one schema | **7** | 1 |
| Salary parsed to numbers | **Yes** (min, max, currency, period) | Raw text, if at all |
| Seniority classified | **Yes** (8 levels) | No |
| Remote / hybrid / onsite | **Yes, precision-first** | Keyword guess on description |
| Candidate personal data | **None, by design** | Sometimes |
| Redistributes employer's copyrighted text | **No, by default** | Usually, in bulk |
| Charges on failure | **No** | Usually |

**On the remote flag specifically:** almost every posting's boilerplate says something like *"we support remote work"*, so classifiers that read the description mark half the board remote. This one reads only the title, the location field and the provider's own remote flag. You get fewer remote hits and you can trust them.

***

### What you get per job

```json
{
  "source": "greenhouse",
  "board": "stripe",
  "job_id": "7922618",
  "title": "Account Executive, Existing Business, Platforms",
  "company": "Stripe",
  "department": "Global Operations",
  "team": null,
  "location": "New York, NY",
  "locations": ["US-PERM"],
  "country": null,
  "employment_type": null,
  "workplace_type": "hybrid",
  "is_remote": false,
  "seniority": "mid",
  "apply_url": "https://stripe.com/jobs/search?gh_jid=7922618",
  "posted_at": "2026-06-02T16:49:53-04:00",
  "updated_at": "2026-08-06T12:10:06-04:00",
  "salary_min": 182208,
  "salary_max": 236580,
  "salary_currency": "USD",
  "salary_period": "yearly",
  "salary_raw": "$182,208 - $236,580",
  "first_seen_at": "2026-07-14T09:02:00+00:00",
  "last_seen_at": "2026-08-09T18:29:02+00:00",
  "is_new": false,
  "days_open": 26
}
```

`seniority` is one of `internship`, `junior`, `mid`, `senior`, `staff`, `lead`, `principal`, `executive`.
`workplace_type` is one of `remote`, `hybrid`, `onsite`, or `null` when the posting genuinely does not say.

### Input

```json
{
  "boards": ["greenhouse:stripe", "ashby:ramp", "lever:plaid"],
  "keywords": ["engineer", "data"],
  "excludeKeywords": ["intern"],
  "locations": ["New York", "Remote"],
  "seniorities": ["senior", "staff", "principal"],
  "remoteOnly": true,
  "postedAfter": "2026-07-01",
  "descriptionMode": "none",
  "trackHistory": true,
  "onlyNew": false,
  "reportClosed": false,
  "maxItems": 500
}
```

Only `boards` is required. Each entry is `provider:token` or the board URL — both work.

| Provider | Board format | Where the token comes from |
|---|---|---|
| greenhouse | `greenhouse:stripe` | `boards.greenhouse.io/**stripe**` |
| lever | `lever:plaid` | `jobs.lever.co/**plaid**` |
| ashby | `ashby:ramp` | `jobs.ashbyhq.com/**ramp**` |
| smartrecruiters | `smartrecruiters:Visa` | `jobs.smartrecruiters.com/**Visa**` |
| rippling | `rippling:rippling` | `ats.rippling.com/**rippling**` |
| workable | `workable:loom` | `apply.workable.com/**loom**` |
| personio | `personio:demo` | `**demo**.jobs.personio.de` |

### Pricing

| Event | Price |
|---|---|
| `job` — one normalized posting returned | **$10.00 / 1,000** |
| `board` — one career board fetched | **$0.02** |

`maxItems` is a hard cost ceiling. Filters are applied **before** billing, so you are not charged for jobs you filtered out, and **a board that fails or returns nothing costs you nothing beyond the board fetch.**

### On job description text

The factual fields — title, company, department, location, salary, dates, apply URL — are facts, and facts are not copyrightable. The description body is prose the employer wrote and holds copyright in.

So `descriptionMode` defaults to **`none`**: you get every factual field and a link to the posting, and no redistributed text. Set it to `excerpt` for the first 300 characters when you need to judge relevance, or `full` if your particular use is licensed or fair.

Keyword filtering always searches the full description regardless of this setting — filtering on text that is never handed back raises no redistribution question. So `descriptionMode: "none"` costs you nothing in search quality.

### New-jobs feed and closed-role feed

Every posting carries `first_seen_at`, `last_seen_at`, `is_new` and `days_open`, tracked per board across runs. Set `onlyNew: true` and the run becomes a pure feed of roles that opened since you last checked. Set `reportClosed: true` and you also get a row for every posting that has disappeared, marked `status: "closed"`.

History covers **every posting the board returned**, not only the ones that pass your filters or your `maxItems` — so narrowing a search never makes unrelated roles look new next run.

Why it matters: a static list of open roles is worth one purchase. "What opened this week, what closed" is worth a subscription, and it is the hiring signal buyers actually act on — a company opening five infrastructure roles is buying infrastructure, and a role quietly closing says something too.

### Use cases

Job boards and aggregators that need clean, first-party listings. Recruiting and ATS tools tracking who is hiring. Labour-market and compensation research. Competitive intelligence — watching a competitor's headcount plan appear in public before it is announced. Sales teams using hiring signals ("they just opened 5 data-engineer roles") as intent data. AI agents answering questions about open roles.

### Use with AI agents

The Actor is callable over the Apify API and through MCP, and it is enabled for agentic payments. Because it is scoped to one job — take boards, return jobs — it works well as a tool an agent calls repeatedly rather than a crawl a human launches once.

### Troubleshooting

**`board not found`** — the token is wrong. Open the company's careers page and take the slug straight out of the URL; the table above shows where it sits for each provider.

**A company returns nothing** — some boards genuinely have zero open roles, and some companies use a provider not listed here. Run with no filters first to confirm before assuming a bug.

**No salary on most jobs** — most postings don't publish one. The parser is deliberately conservative and returns nothing rather than guessing; roughly 1 in 8 postings in the US carries a range, far more in states with pay-transparency laws.

**Descriptions missing** — by default they are not returned at all; see "On job description text". Even with `descriptionMode: "full"`, SmartRecruiters and Rippling expose descriptions only on their per-job endpoints, not the list endpoint. Titles, locations, departments, salary and apply URLs are complete for every provider.

### FAQ

**Is scraping job postings legal?** These are the companies' own public job-board API endpoints, published so that job boards and aggregators can read them. No login, no paywall, no anti-bot circumvention. Job postings are corporate publications, not personal data.

**Do you return any candidate or recruiter personal data?** No. By design, this Actor returns only postings. It never touches applicants, résumés, or recruiter contact details.

**What about the employers' copyright in their postings?** Facts about a posting are not copyrightable and are always returned. The description prose is, so it is withheld unless you explicitly ask for it. See "On job description text" above.

**How fresh is the data?** Live. Every run hits the ATS at request time — there is no cache and no stored index.

**Can I monitor boards on a schedule?** Yes — that is what it is built for. Use `onlyNew` rather than `postedAfter`: it compares against what the board actually had last run, so it catches reposts and undated listings that a date filter misses.

### Legal

Not affiliated with, endorsed by, or sponsored by Greenhouse, Lever, Ashby, SmartRecruiters, Rippling, Workable or Personio. All trademarks belong to their respective owners. You are responsible for how you use the data you collect.

# Actor input Schema

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

One entry per company board, as provider:token (e.g. greenhouse:stripe, ashby:ramp, lever:plaid) or the board URL itself (e.g. https://boards.greenhouse.io/stripe). Providers: greenhouse, lever, ashby, smartrecruiters, rippling, workable, personio.

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

Keep only jobs whose title, team, location or description contains at least one of these terms. Leave empty for all jobs.

## `excludeKeywords` (type: `array`):

Drop any job matching one of these terms.

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

Keep only jobs whose location matches one of these (substring match, e.g. "New York", "London", "Remote").

## `seniorities` (type: `array`):

Keep only these levels: internship, junior, mid, senior, staff, lead, principal, executive.

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

Keep only postings classified as remote. Classification uses the title, location and the provider's own remote flag — never description boilerplate, so this is precise rather than generous.

## `postedAfter` (type: `string`):

YYYY-MM-DD. Drops postings published before this date.

## `descriptionMode` (type: `string`):

One of: none (default - factual fields only, nothing redistributed), excerpt (first 300 characters, for judging relevance), full (the complete description; the employer holds copyright in that text, so only choose this if your use is licensed or fair). Keyword filtering always searches the full text regardless of this setting.

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

Stop after this many jobs. Billing is per job returned, so this is your cost ceiling. 0 = no limit.

## `trackHistory` (type: `boolean`):

Remember which postings each board had last run, so every job carries first\_seen\_at, last\_seen\_at, is\_new and days\_open. This is what makes a scheduled run worth more than a one-off pull.

## `onlyNew` (type: `boolean`):

Return only postings that were not present in the previous run of the same board. Turns the run into a pure new-jobs feed.

## `reportClosed` (type: `boolean`):

Also emit a row for every posting present last run that has since disappeared, marked status=closed. A role closing is a hiring signal in its own right.

## Actor input object example

```json
{
  "boards": [
    "greenhouse:stripe",
    "ashby:ramp",
    "greenhouse:airbnb"
  ],
  "keywords": [],
  "excludeKeywords": [],
  "locations": [],
  "seniorities": [],
  "remoteOnly": false,
  "descriptionMode": "none",
  "maxItems": 0,
  "trackHistory": true,
  "onlyNew": false,
  "reportClosed": false
}
```

# 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 = {
    "boards": [
        "greenhouse:stripe",
        "ashby:ramp",
        "greenhouse:airbnb"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("adored_maze/job-listings-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 = { "boards": [
        "greenhouse:stripe",
        "ashby:ramp",
        "greenhouse:airbnb",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("adored_maze/job-listings-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 '{
  "boards": [
    "greenhouse:stripe",
    "ashby:ramp",
    "greenhouse:airbnb"
  ]
}' |
apify call adored_maze/job-listings-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,adored_maze/job-listings-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/MqHoXmwhBj9hJXxaQ/builds/54xKaratX3tKPjPDf/openapi.json
