# Jobs Feed for Lever Career Sites – AU & NZ ready (`kuro-solutions/ats-jobs-feed`) Actor

Live job postings from employers' public Lever career sites. Normalised salary, location, country, remote and seniority fields, AU & NZ preset, monitor mode for new jobs only. Email addresses and phone numbers are removed. Unofficial tool: not affiliated with or endorsed by Lever.

- **URL**: https://apify.com/kuro-solutions/ats-jobs-feed.md
- **Developed by:** [Kuro Solutions](https://apify.com/kuro-solutions) (community)
- **Categories:** Jobs, Automation, Lead generation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Jobs Feed for Lever Career Sites – AU & NZ ready

Get clean, normalised job postings **straight from employers' own Lever career sites**, using the public postings endpoint that Lever provides for published jobs. No job-board scraping, no logins, no proxies, no browser.

**Unofficial tool. Not affiliated with, endorsed by or sponsored by Lever (Employ Inc.). "Lever" is a trademark of its owner and is used here only to say which job boards this Actor can read.**

### What it does

- Takes a list of Lever career sites (`https://jobs.lever.co/kogan`, `lever:deputy`, …) or a built-in **AU & NZ preset** (6 employer boards).
- Calls Lever's public postings JSON endpoint for each board, normalises every job to one schema, and filters by **country** and **title keywords**.
- **Monitor mode** (`onlyNew`): remembers job IDs across runs and returns (and charges for) only jobs it has not delivered before. Schedule it daily for a "new jobs" alert.
- One broken or missing board never fails the run; see `RUN_SUMMARY` in the key-value store.

### Supported career sites

| System | Input formats |
|---|---|
| Lever (US & EU) | `https://jobs.lever.co/{slug}`, `https://jobs.eu.lever.co/{slug}`, `lever:{slug}`, `lever-eu:{slug}` |

Other applicant tracking systems are not supported in this version and are skipped with a warning.

### Input

| Field | Default | Notes |
|---|---|---|
| `companies` | – | Lever career-site URLs or `lever:slug` |
| `preset` | `none` | `au_nz_tech` adds 6 AU/NZ employer boards on Lever |
| `countries` | – | ISO-2 codes, e.g. `AU`, `NZ`. Empty = all countries |
| `keepUnknownRemote` | `false` | Keep remote jobs with no detectable country |
| `keywords` | – | Title must contain at least one (case-insensitive) |
| `includeDescription` | `false` | Adds `descriptionText` and `descriptionHtml`. Email addresses and phone numbers are always removed |
| `onlyNew` | `false` | Monitor mode |
| `monitorStoreName` | `ats-jobs-feed-seen` | Use a different name per saved search |
| `maxJobs` | `500` | Hard cap on results (and so on cost) |

Example input:

```json
{
  "companies": ["https://jobs.lever.co/kogan", "lever:deputy"],
  "countries": ["AU", "NZ"],
  "keywords": ["engineer", "data"],
  "onlyNew": true,
  "maxJobs": 500
}
```

### Output (one item per job)

`id` (`lever:slug:jobId`, stable), `ats`, `boardSlug`, `company`, `jobId`, `title`, `department`, `team`, `locationText`, `allLocations`, `country`, `countries`, `workplaceType` (onsite/hybrid/remote), `employmentType`, `seniority` (heuristic from title), `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryText`, `salarySource` (`ats_field` or `description`), `postedAt`, `updatedAt`, `url`, `applyUrl`, `scrapedAt`, optional `descriptionText`/`descriptionHtml`.

Example item (descriptions off):

```json
{
  "id": "lever:deputy:73af4076-6528-4e07-ac5d-5cfcc215d0a5",
  "ats": "lever", "boardSlug": "deputy", "company": "deputy",
  "title": "Account Executive", "department": "Sales", "team": "Sales Mid Market",
  "locationText": "Sydney", "country": "AU", "workplaceType": "hybrid",
  "seniority": "mid_or_unspecified", "salaryMin": null,
  "url": "https://jobs.lever.co/deputy/73af4076-6528-4e07-ac5d-5cfcc215d0a5",
  "applyUrl": "https://jobs.lever.co/deputy/73af4076-6528-4e07-ac5d-5cfcc215d0a5/apply"
}
```

Notes: `seniority` is a title heuristic and can be wrong (for example "Assistant Category Manager" is tagged `manager`). Salary is only filled when the posting gives it or its text has an explicit currency marker; most Australian ads do not publish pay, so expect it to be empty for most jobs. `company` is the board slug, not a verified display name.

### Pricing

Pay per event: you pay per job returned. In monitor mode you only pay for new jobs. Set a maximum charge per run in the run options and the Actor stops when it is reached.

### Data source, privacy and responsible use

- **Source:** only the public, unauthenticated postings endpoint of Lever (`api.lever.co` / `api.eu.lever.co`) for jobs in the published state. Lever's own documentation says published postings are publicly viewable and "may be scraped by third parties". No login, no private or internal postings, no candidate data.
- **Politeness:** before the first request to a host the Actor reads that host's `robots.txt`, refuses any path it disallows, and keeps at least one second (or the host's `Crawl-delay`, if larger) between requests. It identifies itself with a descriptive User-Agent and backs off on HTTP 429/5xx.
- **Job text is the employer's content.** By default it is not included. If you turn it on, keep the link back to the job (`url`, `applyUrl`) and do not present the text as your own or republish it wholesale.
- **Personal data:** the Actor never outputs recruiter or hiring-manager contact details. Email addresses, `mailto:`/`tel:` links and phone numbers are removed from every description field, always. If you combine the output with other data about identifiable people, you are responsible for privacy-law compliance (for example the Australian Privacy Act).
- **Acceptable use:** do not use the output to spam employers or candidates, to impersonate an employer, to mislead people into thinking a job is offered by someone other than the employer, to train AI models (Lever's job pages reserve AI-training rights), or in breach of Lever's or the employer's terms. You are responsible for how you use the data.
- **Takedown:** if an employer or Lever asks for a board to be excluded, tell the author via the Actor's Issues tab and it will be removed.

### Local development

```bash
npm install
npm test                                        # unit tests
test/run-local.sh test/input-au-preset.json     # simulated pay-per-event run, no account needed
```

`run-local.sh` sets `ACTOR_TEST_PAY_PER_EVENT=true` and writes simulated charges to `storage/datasets/charging_log`. The SDK's local simulation prices every event at $1 for budget checks, so local `maxTotalChargeUsd` tests stop earlier than on the platform.

# Actor input Schema

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

One per line. Paste a Lever careers URL (https://jobs.lever.co/kogan) or use the short form lever:slug (EU boards: lever-eu:slug). Only Lever is supported in this version.

## `preset` (type: `string`):

Add a curated list of employers with Australian or New Zealand roles on public job boards. Combine with your own list above.

## `countries` (type: `array`):

ISO country codes, e.g. AU, NZ, US, GB. Leave empty to keep every country. Remote jobs with no country are kept only if 'Keep remote jobs with unknown country' is on.

## `keepUnknownRemote` (type: `boolean`):

Some postings just say 'Remote'. Keep them even when a country filter is set.

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

Keep jobs whose title contains any of these words (case-insensitive). Empty = all titles.

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

Adds the employer's job description as plain text and HTML. Email addresses and phone numbers are always removed. Descriptions are the employer's own content: link back to the job URL and do not republish them as your own. Bigger output, same price.

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

Remembers job IDs in a named key-value store, so scheduled runs return (and charge for) new jobs only.

## `monitorStoreName` (type: `string`):

Use different names to run independent monitors.

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

Stop after this many jobs (0 = no limit). Also capped by your run's maximum cost.

## Actor input object example

```json
{
  "companies": [
    "https://jobs.lever.co/kogan",
    "lever:deputy"
  ],
  "preset": "none",
  "countries": [
    "AU",
    "NZ"
  ],
  "keepUnknownRemote": false,
  "includeDescription": false,
  "onlyNew": false,
  "monitorStoreName": "ats-jobs-feed-seen",
  "maxJobs": 500
}
```

# Actor output Schema

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

All job postings from this run, one item per job.

# 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://jobs.lever.co/kogan",
        "lever:deputy"
    ],
    "countries": [
        "AU",
        "NZ"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kuro-solutions/ats-jobs-feed").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://jobs.lever.co/kogan",
        "lever:deputy",
    ],
    "countries": [
        "AU",
        "NZ",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("kuro-solutions/ats-jobs-feed").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://jobs.lever.co/kogan",
    "lever:deputy"
  ],
  "countries": [
    "AU",
    "NZ"
  ]
}' |
apify call kuro-solutions/ats-jobs-feed --silent --output-dataset

```

## MCP server setup

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

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/pVWd37hEOPEiI9pF9/builds/lemt0Ux5dNU9kjwU9/openapi.json
