# Lever Jobs Scraper (`humble-echidna/lever-jobs`) Actor

Type company names or paste Lever job-board URLs (US and EU) and get every open job from those companies' Lever career pages: full descriptions, salary where published, one clean format. Filter by keyword, place, department, salary or date; get only new, changed or closed jobs since your last run.

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

## Pricing

from $2.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

### What does Lever Jobs Scraper do?

**Lever Jobs Scraper** takes a list of **companies that hire on Lever** and returns **every open job**
they post there, in **one clean, consistent format**, fetched live from each company's own Lever job board
through Lever's public job-board API. No login.

It is **not** a job-board search: it doesn't search Indeed, LinkedIn, or all of Lever. Lever doesn't publish a directory of the companies that use it, so you list the companies, and you get every job each of them has open, the moment it's listed.

**Three ways to name a company**, one per line, mixed freely:

| You type | Example | How it's found |
|---|---|---|
| **Company name** | `Outreach` | Looked up on Lever only. It's used only when the board's own company name agrees with what you typed. |
| **Lever board URL** | `https://jobs.lever.co/spotify`, `https://jobs.eu.lever.co/<company>` (Lever's EU boards), or the short form `lever:spotify` | Used as is. Links to a single job work too. |
| **Careers page** | `https://example.com/careers` | Scanned for a link to the company's Lever board. Works when the page links it directly; many big-company pages load jobs with JavaScript, and then the name or the Lever URL works better. |

**Try it in one click:** the input comes pre-filled with 2 example companies, one typed as a name and one as a URL: Outreach and Spotify. That's about 105 jobs, or roughly $0.21 (105 × $0.002 per job, plus $0.00005 for the run start). **Then replace them with the companies you actually want.**

### Monitor new, changed and closed Lever jobs

Lever Jobs Scraper can watch a list of companies and give you only what happened since its last run: new jobs,
and optionally the jobs that closed or changed. Set it up once:

1. **Save a task.** Fill in **Companies** (and any filters), turn on **Only jobs new since my last run**
   (`onlyNewJobs: true` in the API), then click **Save as a new task** at the top right of the actor's page. The first
   run returns every current job that matches; later runs return only jobs posted since.
   For a full job-change feed, also turn on **Also report closed and changed jobs** (`includeClosedAndChanged: true`):
   each row then has a `changeType` of `new`, `changed` (title, location, department, employment type or salary
   changed; `changedFields` shows old and new) or `closed` (taken off the company's Lever board; the row keeps its
   last known title, location and link). Useful for spotting ghost jobs and filled roles.
2. **Schedule it.** In Apify Console, open **Schedules**, click **Create new**, pick the task and set how often it runs
   (for example every day at 07:00, cron `0 7 * * *`).
3. **Send the new jobs where you work,** from the task's **Integrations** tab:
   - **Slack:** a message in the channel you choose when a run succeeds (you pick the events and can edit the
     message).
   - **Email:** *Send results email via Gmail* sends the run's results, with the dataset attached.
   - **Webhook:** a POST to your URL on the `ACTOR.RUN.SUCCEEDED` event. The payload's `resource.defaultDatasetId`
     points to the new jobs: `GET https://api.apify.com/v2/datasets/<defaultDatasetId>/items?format=json` with your
     API token.
   - **Make, Zapier or n8n:** each has an Apify step that starts on a finished run and reads its dataset.

A run that finds nothing new (or changed or closed) returns 0 jobs and costs only the $0.00005 start fee. Slack and email are sent for every
successful run, including a quiet one; to hear only about new jobs, use the webhook or Make/Zapier/n8n and skip runs
whose dataset is empty. Add the **run failed** event too, so a failed run doesn't go unnoticed. Keep the task's
filters fixed: the memory is per company and per filter combination, so changing them starts a fresh search.

### What data does Lever Jobs Scraper return?

| Field | Example | Notes |
|---|---|---|
| `title` | `Customer Advocacy Manager` | |
| `companyName`, `company` | `Outreach`, `outreach` | The board's display name (taken from the board page, since Lever's feed leaves it out) and its Lever address. |
| `department`, `team` | `Marketing` | `null` when the board doesn't group jobs. |
| `location`, `locations`, `country` | `United States`, `US` | As the company lists them. |
| `workplaceType`, `remote` | `remote`, `true` | `remote`, `hybrid` or `onsite`; `null` when the job doesn't say. |
| `employmentType` | `FULL_TIME` | One of `FULL_TIME`, `PART_TIME`, `CONTRACT`, `TEMPORARY`, `INTERNSHIP`, `VOLUNTEER`, or `null`. |
| `postedAt`, `updatedAt` | `2026-02-16T17:59:45.944000Z` | ISO 8601 UTC. |
| `url`, `applyUrl` | `https://jobs.lever.co/outreach/5becd4e1-...` | The job page and its application form. |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` | `116994`, `167135`, `USD`, `YEAR` | Lever's own salary range, else the pay range in the job text; `null` when there is none. |
| `salaryText`, `salarySource` | `$116,994 - $167,135 USD`, `description` | `ats` from Lever's field, `description` from the job text (with the text it was read from). |
| `description` | `About Outreach...` | Plain text; switch off **Include full job description** to leave it out. |
| `id` | `lever:outreach:5becd4e1-...` | Stable across runs, for deduplication. |
| `changeType` | `new`, `changed`, `closed` | Only with **Also report closed and changed jobs**; the fields below too. |
| `changedFields` | `{"title": {"old": "Engineer", "new": "Senior Engineer"}}` | For `changed` jobs: each field that changed, old and new. `null` otherwise. |
| `closedDetectedAt`, `firstSeenAt` | `2026-10-02T07:00:00Z` | When a run found the job gone, and when a run first returned it (`null` for jobs returned before version 1.1). |

Every job has the same fields; the full record is under [Output](#output).

### How much does it cost to scrape Lever jobs?

You pay per job returned: **$2.00 per 1,000 jobs**, plus $0.00005 each time a run starts. Filters are applied before
you're charged, so you only pay for jobs you keep. A company that isn't found, fails or has no matching jobs costs
nothing, and a repeat run with nothing new costs only the start fee.

- **The example below:** 107 jobs × $0.002 = $0.214, plus the $0.00005 start fee; the repeat run $0.00005.
- **Closed and changed jobs** are rows like any job, $0.002 each, and only when you turn that option on.
- **A month, for example:** 50 companies with 2,000 open jobs between them, checked daily: the first run is
  2,000 × $0.002 = $4.00; if 30 new jobs a day are posted after that, 870 × $0.002 = $1.74: **about $5.74**, plus
  30 starts ($0.0015).
- **Caps:** **Max jobs per run** in the input, and **Maximum cost per run** in the run options. It stops cleanly at
  whichever comes first, and stops fetching as soon as the limit is covered, so a capped run is also a fast one.

### How to scrape Lever jobs

1. Open the actor and type your companies into **Companies**, one per line: names, Lever board URLs (US or EU) or careers
   pages.
2. Optionally add **Title keywords**, **Locations**, **Departments**, **Employment types**, **Minimum salary** or **Posted within (days)** to keep only the jobs you want.
3. Click **Start**.
4. Open the **Output** tab and export the jobs as JSON, CSV or Excel, or read them through the API.
5. To keep watching the same companies, save it as a task and schedule it (see *Monitor new, changed and closed Lever jobs* above).

Each run's log and the `RUN_STATS` record show what every name or page was matched to, and anything that couldn't be used, with the reason. A company on another hiring platform is skipped with a clear message and the rest of the run continues; for those, use [Company Career Page Jobs Scraper](https://apify.com/humble-echidna/ats-jobs), which covers Lever and 5 other platforms with the same output format.

### Example: two companies, then a daily re-check

The pre-filled input, with **Only jobs new since my last run** turned on:

```json
{"companies": ["Outreach", "https://jobs.lever.co/spotify"], "onlyNewJobs": true, "includeDescription": true}
```

It returned 107 jobs. One of them (real output from a local run on 2026-09-25; `description` shortened):

```json
{
  "id": "lever:spotify:1bbaf909-5ff3-4ed6-87ca-f7ff007a169c",
  "platform": "lever",
  "company": "spotify",
  "companyName": "Spotify",
  "title": "Data Scientist - Music Mission",
  "department": "Data and Analytics",
  "team": "Music",
  "location": "New York, NY",
  "country": "US",
  "workplaceType": "remote",
  "employmentType": "FULL_TIME",
  "postedAt": "2026-06-22T17:14:00.858000Z",
  "url": "https://jobs.lever.co/spotify/1bbaf909-5ff3-4ed6-87ca-f7ff007a169c",
  "salaryMin": 116994.0,
  "salaryMax": 167135.0,
  "salaryCurrency": "USD",
  "salaryPeriod": "YEAR",
  "salaryText": "$116,994 - $167,135 USD",
  "salarySource": "description",
  "description": "The Music Mission enables music creators to grow, engage, and monetize their fan bases on Spotify. C..."
}
```

Run again straight after, the same input returned **0 jobs**, so only the start fee was charged.

### Input

| Field | What it does |
|---|---|
| **Companies** | One per line: a company name, a Lever board URL (US or EU, or `lever:slug`), or a careers page that links to its Lever board. |
| Title keywords / Exclude keywords | Keep or drop jobs by words in the title (case-insensitive). |
| Locations | Keep jobs whose location or country contains any of these. Add `remote` to include remote jobs. |
| Remote jobs only | Only jobs marked remote. |
| Posted within (days) | Only recently published jobs. |
| Departments | Keep jobs whose department or team contains any of these (case-insensitive). |
| Employment types | Only jobs of these types (`FULL_TIME`, `CONTRACT`, ...). Jobs with no stated type are left out while it's set. |
| Minimum salary, currency, per | Only jobs whose pay reaches this amount at the top of their range, in this currency (default USD) and period (default a year). Currencies are never converted. |
| Keep jobs with no comparable salary | With a minimum salary: keep (default) or drop jobs whose pay isn't published or can't be compared. |
| Only jobs new since my last run | First run returns everything that matches; later runs return only jobs posted since. Remembered in your own Apify storage, per company and per filter combination. |
| Also report closed and changed jobs | Later runs also return jobs you were given before that have since closed or changed, each marked with `changeType`. Implies the option above. |
| Include full job description | Plain-text description (on by default). |
| Max jobs per run | Cap the total number of jobs across all companies. |
| Max jobs per company | Cap results per company. |

```json
{
  "companies": [
    "Outreach",
    "https://jobs.lever.co/spotify",
    "lever:palantir"
  ],
  "keywords": [
    "engineer"
  ],
  "locations": [
    "London",
    "remote"
  ],
  "postedWithinDays": 30
}
```

### Output

Every job has the same fields. Fields Lever doesn't publish for a job are `null`.

```json
{
  "id": "lever:outreach:5becd4e1-3474-4f36-b5dd-4b2cd0eb1179",
  "platform": "lever",
  "company": "outreach",
  "companyName": "Outreach",
  "title": "Customer Advocacy Manager",
  "department": "Marketing",
  "team": "Marketing",
  "location": "United States",
  "locations": [
    "United States"
  ],
  "country": "US",
  "remote": true,
  "workplaceType": "remote",
  "employmentType": "FULL_TIME",
  "postedAt": "2026-02-16T17:59:45.944000Z",
  "updatedAt": null,
  "url": "https://jobs.lever.co/outreach/5becd4e1-3474-4f36-b5dd-4b2cd0eb1179",
  "applyUrl": "https://jobs.lever.co/outreach/5becd4e1-3474-4f36-b5dd-4b2cd0eb1179/apply",
  "salaryMin": 70000,
  "salaryMax": 110000,
  "salaryCurrency": "USD",
  "salaryPeriod": "YEAR",
  "salaryText": null,
  "salarySource": "ats",
  "description": "About Outreach\n \nOutreach, founded in 2014, is the only complete agentic AI platform for…",
  "scrapedAt": "2026-09-24T15:58:30.437639Z"
}
```

`id` is stable across runs (`platform:company:jobId`), so you can use it to deduplicate.

### Run it on a schedule, or from your own code

- **Schedule:** save the input as a task and add it to a schedule, as in *Monitor new, changed and closed Lever jobs* above.
- **API:** fetch a task's latest results with
  `GET https://api.apify.com/v2/actor-tasks/<task id>/runs/last/dataset/items?status=SUCCEEDED&format=csv` and your
  API token (`format` can also be `json` or `xlsx`).
- **Webhooks and automation:** a webhook when a run succeeds, or Make, Zapier or n8n through Apify's integrations.

#### Can I use Lever Jobs Scraper from an AI agent (MCP)?

Yes. Connect the agent to Apify's MCP server (`https://mcp.apify.com`) and add `humble-echidna/lever-jobs` as a
tool; the agent passes the same input JSON as above. To keep answers small, have it set `includeDescription: false`
and a **Max jobs per run** (`maxJobs`).

### Who it's for

Niche job boards and aggregators that list jobs from a known set of employers, recruiters and sales teams watching
target companies' hiring (hiring signals), job seekers with a shortlist of companies, and salary and market
research (pay ranges as numbers, with currency and period). The recurring job: keep a list of companies that hire on
Lever, run it daily with **Only jobs new since my last run**, and get each new posting once.

### Why this one?

- **Just type the company name.** "Outreach" finds Outreach's Lever board, checked against the board's own company name, so a different company that happens to use that name as its board address is rejected.
- **US and EU boards.** Lever runs a separate EU instance (`jobs.eu.lever.co`); both work.
- **Company names filled in.** Lever's job feed leaves out the company's display name; it's taken from the board page, so `companyName` is filled.
- **Reliable.** A broken or unreachable company never affects the others in your run. A malformed job record is skipped instead of failing its company. Rate limits are handled automatically. The run log and the `RUN_STATS` record say exactly which company had a problem and why.
- **Clean, consistent fields.** Employment type is one of `FULL_TIME`, `PART_TIME`, `CONTRACT`, `TEMPORARY`, `INTERNSHIP`, `VOLUNTEER` and workplace type one of `remote`, `hybrid` or `onsite` (`null` when the job doesn't say); dates are ISO 8601 UTC. The format is identical to our other job scrapers, so results from several platforms can go in one table.
- **Salary where it's published.** Lever's own salary range is used when the company fills it in. Otherwise pay is extracted from pay-transparency text in the description ("$150,000 - $200,000 USD", "$28.50/hr"), and `salarySource` says which. Extraction is conservative: bonuses, stipends and funding amounts are ignored.
- **Fast and live.** It reads Lever's public job-board API, the same data the company's own careers page shows, during your run: no login, no browser, no proxies.

### Limits

- It reads only the companies you list; it can't search all of Lever (see the FAQ).
- Lever only: a company on another platform is reported, not fetched.
- A careers page that loads its jobs with JavaScript can't be scanned; type the company name or the Lever URL
  instead.

### FAQ

#### Can I get every job on Lever, from every company?

No. This actor fetches the companies you list. Lever doesn't publish a directory of its customers, so no scraper can list "all Lever jobs" from Lever itself. Use this one when you know which companies you want to track and need their complete, fresh listings.

#### Why wasn't a company found?

The run log and `RUN_STATS` (`unresolved`) say why, and the rest of the run continues. Common reasons: it hires on another platform, its careers page loads jobs with JavaScript (type the name instead), or it has no open jobs right now.

#### What if it matched the wrong company?

Names are only matched when the Lever board's own company name agrees with what you typed, but two companies can share a name. Paste the exact Lever URL to pin it down; `company` and `companyName` on every job show which board it came from.

#### How does "Only jobs new since my last run" know about my last run?

It stores the IDs of the jobs it returned in a named key-value store (`lever-jobs-seen`) in **your** Apify account, which persists between runs. Each company and filter combination has its own memory, so a daily "engineer" alert and a weekly "sales" alert on the same company don't interfere. Changing the filters starts a fresh search, and so does deleting that store. Jobs held back by **Max jobs per company** are not marked as seen, so they come through on a later run.

#### How are closed jobs detected?

With **Also report closed and changed jobs** on, the same memory also keeps each returned job's title, locations, department, employment type, salary text and link. On the next run, a job that's no longer on the company's Lever board comes back once as `changeType: "closed"`, with those last known fields and `closedDetectedAt`; a job whose tracked fields differ comes back once as `changeType: "changed"`, with `changedFields` (old and new). Only jobs you were already given are tracked, so a search with filters sees closures and changes of its own jobs. A job only counts as closed when its board was read cleanly: if it fails, is blocked, times out or has records that don't parse, or suddenly lists nothing or far fewer jobs, its closed-job check is skipped for that run and the log and `RUN_STATS` (`closureChecksHeld`) say why. A sudden drop that the next run sees again is reported then; one that recovers never is.

#### How do the department, employment type and salary filters work?

**Departments** keeps jobs whose department or team contains any of your words, ignoring case ("engineering" matches "Product Engineering"). **Employment types** keeps jobs of the types you pick. **Minimum salary** keeps jobs whose pay range reaches your amount at its top, in the currency and period you choose: pay per hour, day, week or month is compared as a full-time year (2,080 hours, 260 days, 52 weeks, 12 months), and amounts are never converted between currencies. All three run before you're charged, so a job they leave out costs nothing, and each is part of the search that **Only jobs new since my last run** remembers.

A filter can only use what the board publishes. A job with no department or team, or no stated employment type, is left out while that filter is set. A job with no comparable pay (none published, another currency, or no period) is kept unless you turn off **Keep jobs with no comparable salary**. The log and `RUN_STATS` (`boards` → `notes`) say per company how many jobs that affected. On 3 boards checked on 2026-09-25 (Outreach, Spotify, Palantir; 430 jobs), 23 had Lever's own salary range and 254 more stated pay in the job text: 277 (64%), mostly US dollars a year. Lever states an employment type for almost every job (425 of the 430).

#### Why does a company that used to work now fail or return fewer jobs?

Lever can change its public feed without notice, so a site change is the most likely cause. The run log names the company and what didn't parse, and every other company in the run is unaffected. Please open an issue with the company so it can be looked at.

#### What if some of my companies use another platform?

A job-board URL from another platform is skipped with a message saying which platform it is, and a name that isn't found on this platform is reported as not found; the rest of the run continues. [Company Career Page Jobs Scraper](https://apify.com/humble-echidna/ats-jobs) reads Greenhouse, Lever, Ashby, Recruitee, Personio and Teamtailor in one run, with this exact output format.

#### Is it legal to scrape Lever jobs?

It reads the public job-board data each company publishes on Lever for its own careers page. It collects job postings only: no recruiter names, emails, or other personal data. It identifies itself honestly (User-Agent `HumbleEchidnaApify`) and follows Lever's robots.txt: anything the site disallows is never requested, and the company is reported with the reason instead of being skipped silently; crawl delays are respected. Lever asks automated clients to wait 1 second between requests to each of its sites, so large runs take a little longer.

#### Which addresses does it request?

Only public web addresses on the standard web ports (80 and 443). A careers
page on another port, or on a domain that points at a private network, is refused with the reason, and a careers
domain that doesn't exist is reported as such. Each site's robots.txt is read once per run; a site that asks for
more than 30 seconds between requests is reported rather than fetched slowly.

### Related actors

| Actor | Use it when |
|---|---|
| [Company Career Page Jobs Scraper](https://apify.com/humble-echidna/ats-jobs) | Your list mixes platforms: one run for Greenhouse, Lever, Ashby, Recruitee, Personio and Teamtailor, with this same output format. |
| [Greenhouse Jobs Scraper](https://apify.com/humble-echidna/greenhouse-jobs) | The same, for companies hiring on Greenhouse. |
| [Ashby Jobs Scraper](https://apify.com/humble-echidna/ashby-jobs) | The same, for companies hiring on Ashby. |
| [Remote Jobs Scraper](https://apify.com/humble-echidna/remote-jobs) | You want remote jobs from We Work Remotely and Remote OK, in the same format. |
| [Dataset Transformer: Filter, Dedupe, Convert](https://apify.com/humble-echidna/dataset-transform) | You want to filter the jobs and export CSV for your board. |

### Feedback and support

Found a bug, or need a field that isn't here? Open an issue on the **Issues** tab with the input you used.

### Versions

Current version: **1.1**. See the Changelog tab for what changed in each version.

# Changelog

This Actor's version history is a separate document: https://apify.com/humble-echidna/lever-jobs/changelog.md

# Actor input Schema

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

One company per line, in any of three forms: a company name (e.g. Outreach), its Lever job-board URL (e.g. https://jobs.lever.co/spotify, EU boards on jobs.eu.lever.co too, or the short form lever:spotify), or its careers page if that page links to its Lever board. Names are looked up on Lever only. The list starts with 2 examples; replace them with your own.

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

Keep only jobs whose title contains any of these words (case-insensitive). Leave empty for all jobs.

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

Drop jobs whose title contains any of these words, e.g. intern, senior.

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

Keep only jobs whose location or country contains any of these, e.g. London, Germany, US. Add "remote" to also keep remote jobs.

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

Keep only jobs the ATS marks as remote.

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

Keep only jobs published in the last N days. Leave empty for all.

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

Keep only jobs whose department or team contains any of these (case-insensitive), e.g. Engineering, Sales. Jobs whose board names no department or team for them are left out while this is set; the run log says how many per company. Leave empty for all departments.

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

Keep only jobs of these types. Jobs whose board states no type for them are left out while this is set; the run log says how many per company. Leave empty for all types.

## `minSalary` (type: `integer`):

Keep only jobs whose pay reaches this amount (the top of the job's range), in the currency and period below. Pay comes from the ATS's own salary field, or else from pay-transparency text in the description, which is then read even if "Include full job description" is off. Most jobs publish no pay: see "Keep jobs with no comparable salary". Leave empty for no salary filter.

## `minSalaryCurrency` (type: `string`):

Three-letter currency code of the minimum salary, e.g. USD, EUR, GBP, CAD. Amounts are never converted between currencies: a job paid in another currency counts as having no comparable salary.

## `minSalaryPeriod` (type: `string`):

The period of `minSalary`: `YEAR` (default), `MONTH`, `WEEK`, `DAY` or `HOUR`. Pay stated per another period is compared as a full-time year of 2,080 hours, 260 days, 52 weeks or 12 months; a job whose pay has no period counts as having no comparable salary.

## `keepJobsWithoutSalary` (type: `boolean`):

Only with a minimum salary. On: jobs that publish no pay, or pay in another currency or with no period, are kept (their salary fields show what is known). Off: only jobs known to pay at least the minimum are returned.

## `onlyNewJobs` (type: `boolean`):

The first run returns all matching jobs; later runs return only jobs posted since. The actor remembers what it already returned in a storage in your own Apify account, separately for each company and each combination of filters, so different searches never hide each other's jobs. Changing the filters starts a fresh search. Use it with a schedule for job alerts or hiring-signal monitoring.

## `includeClosedAndChanged` (type: `boolean`):

Turns this into a job-change feed: besides new jobs, each run also returns jobs returned before that have since closed (changeType `closed`, last known title, location and link) or changed title, location, department, employment type or salary (changeType `changed`, changedFields old -> new). Each such row is charged like a job. Implies `onlyNewJobs`. A board that fails, is blocked or suddenly shrinks isn't checked for closures that run.

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

Adds the plain-text job description. Turn off for faster, smaller results.

## `maxJobs` (type: `integer`):

Stop after this many jobs in total, across all companies. Leave empty for no limit. The run also stops cleanly at the maximum cost per run you set in the run options, whichever comes first.

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

Stop after this many matching jobs per company. Leave empty for no limit.

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

How many companies are fetched at the same time, 1 to 20 (default 5). The default suits almost every run; lower it if a career site starts rate-limiting.

## Actor input object example

```json
{
  "companies": [
    "Outreach",
    "https://jobs.lever.co/spotify"
  ],
  "remoteOnly": false,
  "minSalaryCurrency": "USD",
  "minSalaryPeriod": "YEAR",
  "keepJobsWithoutSalary": true,
  "onlyNewJobs": false,
  "includeClosedAndChanged": false,
  "includeDescription": true,
  "maxConcurrency": 5
}
```

# Actor output Schema

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

No description

## `runStats` (type: `string`):

No description

# 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": [
        "Outreach",
        "https://jobs.lever.co/spotify"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("humble-echidna/lever-jobs").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": [
        "Outreach",
        "https://jobs.lever.co/spotify",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("humble-echidna/lever-jobs").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": [
    "Outreach",
    "https://jobs.lever.co/spotify"
  ]
}' |
apify call humble-echidna/lever-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,humble-echidna/lever-jobs"
        }
    }
}
```

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/d1qlSpDF0D6dPjDZt/builds/wd55sf6tG7iyiJGXC/openapi.json
