# Greenhouse, Lever & Ashby Jobs API - Salary & Remote (`sipki/ats-jobs-api`) Actor

Job postings from any company's Greenhouse, Lever or Ashby board in one clean schema: salary range, remote flag, department, posting date and apply URL. Paste board or careers-page URLs. Keyword, location, remote and date filters, plus 'only new since last run'. $2 per 1,000 jobs.

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

## Pricing

$2.00 / 1,000 job postings

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

## Greenhouse, Lever & Ashby Jobs API - one schema, salary and remote flag

**Job postings from Greenhouse, Lever and Ashby boards in one normalized schema - $2 per 1,000 jobs, no start fee.**

Paste job-board URLs, careers-page URLs or board tokens. Get one row per job with title, locations, remote flag, department, salary range, posting date and apply URL - the same fields whichever ATS the company uses. Filter by keyword, location, remote and date, and schedule it with "only new since last run" to get (and pay for) only fresh postings.

**Try it now:** press **Start** with the prefilled input (Discord on Greenhouse, Spotify on Lever, Linear on Ashby, 20 jobs each). It returns up to 60 jobs in seconds and costs at most $0.12. Apify's free plan credit covers a few thousand jobs.

Built for job aggregators, recruiters and sales teams tracking hiring signals, and analysts who need clean job data without writing a parser per ATS.

### Sources

The Actor reads job postings for a list of companies from the **public job-board APIs documented by** three applicant tracking systems (Greenhouse, Lever, Ashby) for reading published job postings:

| ATS | Public API endpoint | Requests per company |
|---|---|---|
| Greenhouse | `boards-api.greenhouse.io/v1/boards/{token}/jobs?content=true` | 1 |
| Lever | `api.lever.co/v0/postings/{company}?mode=json` | 1 (per 500 jobs) |
| Ashby | `api.ashbyhq.com/posting-api/job-board/{name}?includeCompensation=true` | 1 |

These are the public job-board APIs the vendors document for reading published postings; the actor reads only job postings that companies have made public, with no login, no browser and no proxy. You are responsible for using the data in line with the vendors' terms and applicable law. Workday and other sources are not supported.

### What you get

- **One normalized schema for all three ATSs**: company, title, location(s), remote flag, workplace type, department, team, employment type, salary min/max/currency/interval, posting date, job URL, apply URL, requisition ID, and the description as plain text and/or HTML.
- **Salary**: taken from the ATS when it publishes structured pay (Ashby compensation, Lever `salaryRange`). Otherwise it is parsed from the pay-transparency range in the description (for example "$139,200 - $235,200 USD"). `salary_source` tells you which one you got.
- **Paste whatever you have**: a job-board URL, a company careers-page URL (the embedded ATS is detected; for JavaScript-only pages the board name is guessed from the domain, and the actor only checks that a board with that name exists, not that it belongs to the company; domains it cannot read safely, such as co.uk or com.au names it does not recognise, are refused instead of guessed), `ats:token`, or a bare token.
- **Descriptions are optional**: set `includeDescriptions` to `false` (or `descriptionFormat` to `none`) to get only structured fields.
- **Filters**: keywords (title, optionally description), exclude keywords, locations, remote only, departments, employment types, and `postedAfter` (a date or "7 days" / "24 hours").
- **Deduplication** inside a run: the same board given twice, or the same job reached through two inputs, is returned once.
- **Only new since last run**: the actor remembers job IDs in a named key-value store in your account. A scheduled run returns, and charges for, only jobs it has not returned before. You can keep several watch-lists by using a different `stateKey` for each.

### Example input

```json
{
  "companies": [
    "https://job-boards.greenhouse.io/discord",
    "lever:spotify",
    "https://linear.app/careers"
  ],
  "keywords": ["engineer", "developer"],
  "excludeKeywords": ["intern"],
  "remoteOnly": true,
  "postedAfter": "30 days",
  "descriptionFormat": "text",
  "includeDescriptions": true,
  "onlyNewSinceLastRun": false
}
```

### Example output (one item)

```json
{
  "ats": "greenhouse",
  "company": "GitLab",
  "company_token": "gitlab",
  "job_id": "8738179002",
  "title": "Senior Software Engineer, Security Factory: Code Security",
  "location": "Remote, Canada; Remote, United States",
  "locations": [
    "Canada",
    "United States of America"
  ],
  "remote": true,
  "workplace_type": null,
  "department": "Sec Engineering",
  "team": null,
  "employment_type": null,
  "salary_min": 139200.0,
  "salary_max": 235200.0,
  "salary_currency": "USD",
  "salary_interval": "year",
  "salary_source": "description",
  "posted_at": "2026-10-10T00:31:32Z",
  "posted_at_precision": "exact",
  "updated_at": "2026-10-15T19:24:46Z",
  "url": "https://job-boards.greenhouse.io/gitlab/jobs/8738179002",
  "apply_url": "https://job-boards.greenhouse.io/gitlab/jobs/8738179002",
  "requisition_id": "6860",
  "description_text": "GitLab is the intelligent orchestration platform for DevSecOps. GitLab enables organizatio…",
  "fetched_at": "2026-10-15T22:07:42Z"
}
```

A run summary with the status of every board (ok/error, fetched, matched, pushed, how the board was detected) is saved to the key-value store as `RUN_SUMMARY`. A board that fails does not stop the run.

### Pricing

**Pay per event: $0.002 per job returned** (`job`). There is no start fee and no charge for boards that fail or for jobs removed by filters. Your run's maximum-charge setting is respected exactly, and the actor stops at that limit.

### Measured performance

One local run, 16 October 2026: 3 boards (Greenhouse, a company careers page with an embedded board, Lever), 5 jobs per board requested, descriptions off: 3 of 3 boards ok, 15 jobs returned, 1.3 s of run time (the Lever 1 request per second limit dominates). Larger boards and descriptions take longer; no other figures are claimed.

### Limits and honest notes

- Greenhouse boards rarely publish employment type. Fields a board does not publish are `null`.
- A domain guess (for a careers page with no detectable ATS embed) can hit a board with the same name that belongs to another company. Check `detected_by: "domain_guess"` in `RUN_SUMMARY`, or pass the board URL directly.
- Only public job boards are read. Personal data of applicants is never touched. Job postings may contain names of recruiters or hiring managers; handle them in line with applicable data-protection law.
- **Rate limits:** requests to `api.lever.co` and `api.eu.lever.co` are limited to 1 per second per host (their robots.txt says `Crawl-delay: 1`), for the whole run, however many boards you pass. Other hosts are limited too (careers pages: 1 per second).
- **Company careers pages:** when you give a company website instead of a board URL, the Actor first reads that site's robots.txt. If it disallows the page, cannot be read, or the site answers 401/403/429, the Actor stops for that entry with `robots_disallowed` / `site_refused`: no retry, no workaround, no proxy, no domain guess. Board URLs and tokens go straight to the vendors' public job-board APIs, which are meant for this use.
- Every row has `detected_by` (`url`, `explicit`, `token_probe`, `page_html` or `domain_guess`). Treat `domain_guess` rows as unverified.
- Internal and private network addresses (private ranges, loopback, link-local, cloud-metadata) are refused, including after redirects.
- The user agent is `SipkiATSJobs/1.0 (+https://apify.com/sipki/ats-jobs-api)`. It is never changed or rotated.

### FAQ

**Which ATS does a company use?** Paste its careers page URL; the actor finds it. You can also use the board URL from any "Apply" link.
**Can I monitor companies daily?** Yes. Schedule the actor with `onlyNewSinceLastRun: true`, and each run returns, and charges for, only new postings.
**Can AI agents use it?** Yes. The input is a plain list, the output is flat JSON, and the actor runs without a browser.

### How to use

1. Paste companies into **Companies / job boards**: a board URL (`https://job-boards.greenhouse.io/stripe`), a careers page (`https://linear.app/careers`), `ats:token` (`lever:spotify`) or a bare token.
2. Optional: keywords, locations, remote only, `postedAfter` ("7 days").
3. Press **Start**. Export the dataset as JSON, CSV or Excel, or call the Actor from the Apify API, a schedule, an integration or the Apify MCP server.

### Who uses it

- **Job boards and aggregators** - backfill and refresh postings from company boards in one schema.
- **Recruiters and sales teams** - watch target companies for new roles (hiring signals).
- **Analysts** - salary ranges, remote share and hiring by department across companies.

### More questions

**How much does a typical run cost?** You pay $0.002 per job returned: 500 jobs cost $1.00. Jobs removed by your filters and boards that fail are free.
**Does it use proxies or a browser?** No. It reads the public job-board APIs the vendors document, so runs are fast and cheap.
**Which ATS are not supported?** Workday, iCIMS, SmartRecruiters and others are not supported; only Greenhouse, Lever and Ashby.

### Not affiliated

Not affiliated with, endorsed or sponsored by Greenhouse Software, Inc., Lever (Employ Inc.) or Ashby. Names are used only to identify the public APIs the Actor reads. All trademarks belong to their owners.

### Content rights

Job descriptions are written and owned by the employers who published them. The Actor gives you the text for search, filtering and internal analysis. Republishing full descriptions may require the employer's permission. Set `includeDescriptions` to `false` if you only need structured fields.

### Acceptable use

This Actor is a tool. You decide which sites, companies or files it processes and what you do with the results, and you are responsible for that use. Use it only where you have the right to access the data, and in line with applicable law (including data-protection, anti-spam and copyright law) and the terms of the sources. Do not use it to send unsolicited messages, to build lists for spam, to bypass blocks or access controls, or to place excessive load on any site. If a site owner asks us to stop, we may exclude that site. Sipki, LLC does not receive or store your results; they stay in your Apify storage.

### No warranty

The Actor and its output are provided "as is", without warranty of accuracy, completeness or fitness for a purpose. Verify results before relying on them. Liability of Sipki, LLC is limited as set out in the Apify Standard Actor Contract that governs your use of this Actor.

### Personal data

Results may contain personal data (for example, a recruiter's name in a job description). For such data you are the controller and Sipki, LLC acts only as your processor under section 5 of the Apify Standard Actor Contract. You need a lawful basis for your processing and must handle data-subject requests. To request deletion of anything related to you, open an Issue on this Actor's page.

### For site owners and employers (opt-out)

The Actor identifies itself as `SipkiATSJobs/1.0 (+https://apify.com/sipki/ats-jobs-api)` and follows robots.txt for company websites. To have your domain or job board excluded, or to report a problem, open an Issue on this Actor's page: your domain will be added to the block list within 3 business days, and every run checks that list.

# Actor input Schema

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

One per line. Accepted: a job-board URL (https://job-boards.greenhouse.io/stripe, https://jobs.lever.co/spotify, https://jobs.ashbyhq.com/openai), a company careers-page URL (the embedded ATS is detected automatically), 'ats:token' (greenhouse:stripe, lever:spotify, ashby:ramp), or a bare token (probed on Greenhouse, then Lever, then Ashby). Up to 500.

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

Keep jobs whose title contains at least one of these words/phrases (whole-word, case-insensitive). Empty = all jobs.

## `searchInDescription` (type: `boolean`):

Match keywords in the job description too, not only in the title.

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

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

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

Keep jobs whose location text contains one of these strings (case-insensitive), e.g. 'New York', 'London', 'Germany'. The word 'remote' here also keeps all remote jobs.

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

Keep only jobs flagged remote by the ATS (Ashby/Lever workplace type) or whose location says Remote/Anywhere.

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

Keep jobs whose department or team contains one of these strings, e.g. 'engineering', 'sales'.

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

full_time, part_time, contract, temporary, internship. Greenhouse boards rarely publish this field; jobs without it are dropped when this filter is set.

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

Only jobs published after this moment: a date (2026-10-01), a datetime, or relative ('24 hours', '7 days', '2 weeks').

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

Remember returned job IDs in a named key-value store in your account and return only jobs not seen in previous runs with the same state key. Ideal for scheduled monitoring: you pay only for new jobs.

## `stateKey` (type: `string`):

Name of the memory used by 'Only new since last run'. Use different keys for different watch-lists.

## `includeDescriptions` (type: `boolean`):

Job descriptions are written and owned by the employers. Turn this off to receive only structured fields (title, location, department, salary, dates, URLs). Salary found in a description text is still extracted.

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

Description format. 'none' returns smaller items.

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

Newest jobs first.

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

Hard cap for the whole run (also caps your cost).

## Actor input object example

```json
{
  "companies": [
    "https://job-boards.greenhouse.io/discord",
    "lever:spotify",
    "https://jobs.ashbyhq.com/linear"
  ],
  "searchInDescription": false,
  "remoteOnly": false,
  "onlyNewSinceLastRun": false,
  "stateKey": "default",
  "includeDescriptions": true,
  "descriptionFormat": "text",
  "maxJobsPerCompany": 20,
  "maxJobs": 10000
}
```

# Actor output Schema

## `results` (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": [
        "https://job-boards.greenhouse.io/discord",
        "lever:spotify",
        "https://jobs.ashbyhq.com/linear"
    ],
    "maxJobsPerCompany": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("sipki/ats-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 = {
    "companies": [
        "https://job-boards.greenhouse.io/discord",
        "lever:spotify",
        "https://jobs.ashbyhq.com/linear",
    ],
    "maxJobsPerCompany": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("sipki/ats-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 '{
  "companies": [
    "https://job-boards.greenhouse.io/discord",
    "lever:spotify",
    "https://jobs.ashbyhq.com/linear"
  ],
  "maxJobsPerCompany": 20
}' |
apify call sipki/ats-jobs-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sipki/ats-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/HQx1FmP9YNZTyVa3I/builds/4tWKd9ZYYBBbIjiEx/openapi.json
