# Career Site Jobs: ATS Job Postings API for Greenhouse, Lever (`jtpalms/ats-jobs-index`) Actor

Job postings from 17,000+ company career sites, read from the official job-board APIs of Greenhouse, Ashby, Lever, Workable, Teamtailor and other ATS platforms, indexed twice a day. Filter by keyword, location, remote, department, job type, posted date, company, domain or pay. USD 3 per 1,000 jobs.

- **URL**: https://apify.com/jtpalms/ats-jobs-index.md
- **Developed by:** [JT Palms](https://apify.com/jtpalms) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Features:** Limited permissions
- **Stats:** 133 total users, 82 monthly users, 99.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 job returneds

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

## Career Site Jobs: ATS job postings API for thousands of company career sites

Search job postings published directly on company career sites, straight from the official public job-board APIs of the applicant tracking systems (ATS) that companies use to publish them: Greenhouse, Ashby, Lever, Workable, Recruitee, Personio, Breezy, Teamtailor, Gem and Pinpoint. The index is rebuilt twice a day, so each run returns current job listings in one normalized format, without scraping each company yourself.

- **400,000+ open jobs from 17,000+ company job boards** (October 2026), growing as more job boards are added.
- **USD 3 per 1,000 jobs.** You pay only for jobs in your results. Filtered-out jobs are free.
- **Filters you can rely on.** Every filter is checked against the full live index before each release: it must change the results, every job it returns must really match it, and filters used together must return exactly the jobs that match all of them.

### What people use it for

- **Job boards and newsletters.** Fill a niche board or weekly email with fresh roles filtered by title and location, each linking to the employer's own posting.
- **Job alerts on a schedule.** Run daily with **Jobs from: Last 24 hours** and your keywords. Each new job arrives once.
- **Sales signals.** See which companies are hiring for a function, for example data engineers or first sales hires, and reach them while the need is live.
- **Recruiting and labour-market research.** Track openings by company, department, location and remote status over time.
- **AI agents and RAG.** Clean JSON with plain-text descriptions, ready for matching and summarising.

### Filters

Filters combine with AND: a job must pass every filter you set. Values inside one filter combine with OR: Countries `CA, US` keeps jobs in either.

| Filter | What it matches |
|---|---|
| **Job title keywords** | The job title. Whole words with common endings: `engineer` finds Engineers and Engineering, `intern` finds Internship but not International or Internal. End a word with `*` for any ending (`analy*` finds Analyst and Analytics). Wrap in slashes for a regular expression: `/data (scientist\|engineer)/`. |
| **Exclude keywords** | Drops jobs whose title or department matches, the same way. |
| **Keywords in title or description** | The title and the full description, for example `python`, `kubernetes` or `visa sponsorship`. Works even when descriptions are left out of the results. |
| **Departments** | The department or team the employer gave on the job board, for example `Engineering` or `Customer Success`. About 93% of jobs name one. |
| **Teams** | A team read from the title, with the department as fallback (list below). |
| **Locations** | Whole words in the location text, so `US` does not match Austin. Country names (`Canada`, `United States`, `USA`, `UK`, `Germany`) and US state or Canadian province names (`California`, `Ontario`) also match jobs the index placed there, so `Canada` finds "Toronto, ON". Region tags such as `Europe` or `EMEA` match where the job board gives them. `Remote`, `Hybrid` and `On-site` match the job's workplace type. |
| **Countries**, **US states and Canadian provinces** | ISO codes the index reads from each location. About 94% of jobs get a country. |
| **Remote only** | Jobs the job board or location marks as remote. |
| **Companies** | A name matches whole words of the company or job board name (`stripe` finds Stripe; `meta` does not find Metal or Nametag). A domain such as `stripe.com` matches the company website where the index knows it (about a quarter of jobs), a company or board named exactly after the domain, or a company name that starts with it (`anduril.com` finds Anduril Industries). |
| **Job board platforms** | Greenhouse, Ashby, Lever, Workable, Recruitee, Personio, Breezy, Teamtailor, Gem, Pinpoint. |
| **Posted within (days)**, **Posted on or after** | The job board's own posting date. A date (`2026-09-25`) or a period (`7 days`, `2 weeks`). Jobs without a posting date (Pinpoint boards publish none, about 1.5% of jobs) are left out when either is set, because their age is unknown. |
| **Seniority** | Read from the title: intern, entry, mid, senior, lead, staff-principal, manager, director, vp, c-level. |
| **Employment type** | Full time, part time, contract, temporary or internship, as the job board states it. About 46% of jobs (most Greenhouse and Teamtailor boards) do not state it: add **Not stated** to keep them. For internships on any board, use Seniority `intern`. |
| **Only jobs with a pay range** | Jobs with a minimum, a maximum or both, from a job board field or the description. About a third of jobs. |
| **Minimum yearly pay** | The bottom of the range converted to yearly, in the job's own currency. |

**When a run returns nothing**, the log shows how many jobs each filter keeps on its own, so you can see which one to loosen. The run's `SUMMARY` record lists the same counts, and how many jobs each filter removed. The log also shows the most a run can cost before it starts, from **Max jobs** and the price per job.

### Sample output

A real row from the index (description shortened):

```json
{
  "title": "Sales & Service Consultant, Lansing MI",
  "companyName": "A1 Garage Door Service",
  "company": "a1 garage door service",
  "board": "a1 garage door service",
  "ats": "ashby",
  "source": "ashby",
  "jobId": "3fb5ac01-8cdd-4564-9031-9c6b87a2fb36",
  "indexId": "ashby:a1 garage door service:3fb5ac01-8cdd-4564-9031-9c6b87a2fb36",
  "department": "Sales",
  "departmentPath": [],
  "team": "Sales",
  "function": "sales",
  "seniority": "mid",
  "employmentType": "full_time",
  "location": "Lansing",
  "locations": ["Lansing"],
  "countries": ["US"],
  "states": ["US-MI"],
  "regions": [],
  "workplaceType": "onsite",
  "isRemote": false,
  "compensation": { "min": 90000, "max": 150000, "currency": "USD", "interval": "year", "summary": "$90K - $150K, Offers Commission", "source": "job board" },
  "hasSalary": true,
  "salaryYearlyMin": 90000,
  "postedAt": "2026-09-30T16:41:53.654Z",
  "updatedAt": null,
  "firstSeenAt": "2026-09-30T21:10:07.114Z",
  "lastSeenAt": "2026-09-30T21:10:07.114Z",
  "jobUrl": "https://jobs.ashbyhq.com/a1%20garage%20door%20service/3fb5ac01-8cdd-4564-9031-9c6b87a2fb36",
  "applyUrl": "https://jobs.ashbyhq.com/a1%20garage%20door%20service/3fb5ac01-8cdd-4564-9031-9c6b87a2fb36/application",
  "companyWebsite": "a1garage.com",
  "descriptionText": "A Career Where Sales Meet Skilled Work..."
}
```

Every row has the same fields in the same order, so CSV and Excel exports line up. A value the job board does not give is `null` (or `[]` for lists).

| Field | Meaning |
|---|---|
| `title`, `companyName`, `jobUrl`, `applyUrl` | The job, the employer's display name, and links to the employer's own posting and application form. |
| `company`, `board`, `ats`, `source` | The job board's name on its platform and the platform it comes from. |
| `jobId`, `indexId` | The platform's job ID, and a stable ID in this index (`platform:board:job`). |
| `department`, `departmentPath`, `team` | As the employer named them on the job board. |
| `function`, `seniority` | Team and level read from the title (lists below). |
| `employmentType` | `full_time`, `part_time`, `contract`, `temporary`, `internship`, or `null` when the board does not state it. |
| `location`, `locations` | Location text as published. `countries`, `states` (US and Canada) and `regions` are read from it. |
| `workplaceType`, `isRemote` | `remote`, `hybrid` or `onsite` where the board says so; `isRemote` is true when the board or the location text marks the job remote. |
| `compensation`, `hasSalary`, `salaryYearlyMin` | Pay range (`min`, `max`, `currency`, `interval`, `summary`, and `source`: `job board` or `description`), whether there is one, and its bottom converted to yearly. |
| `postedAt`, `updatedAt` | Dates from the job board. |
| `firstSeenAt`, `lastSeenAt` | When the index first and last saw the job open. |
| `companyWebsite` | The company's website where the index knows it. |
| `descriptionText` | Plain-text description, up to 5,000 characters (turn off **Include job description** to leave it out). |

**Teams** (`function`): sales, customer, data-ai, engineering, design, product, marketing, people, legal, finance, healthcare, education, operations, trades (technicians, drivers, maintenance), retail (stores, food, hotels, personal services) or other. **Seniority**: intern, entry, mid, senior, lead, staff-principal, manager, director, vp or c-level. Place data from GeoNames (CC BY 4.0).

### How to use it

1. Choose **Jobs from**:

   - **Latest index update**: new jobs from the newest update.
   - **Last 24 hours**, **7 days** or **14 days**: new jobs from that window.
   - **All open jobs**: a full backfill.

   A job is new the first time the index sees it. When a company's job board is newly added to the index, only its jobs posted in the last 3 days count as new, so scheduled alerts don't fill up with older postings. The older ones are still in **All open jobs**.
2. Add the filters you want (table above).
3. Set **Max jobs**. The newest jobs come first.
4. Run it once, or on a schedule. For a daily feed with no duplicates, schedule it every day with **Last 24 hours**. Then export as CSV, JSON or Excel, or connect it to Make, Zapier, n8n, Google Sheets or your own code.

### Pricing

| What | Price |
|---|---|
| Job in your results | USD 0.003 (USD 3 per 1,000). Lower on Apify's higher plans |
| Jobs removed by your filters | Free |
| Run start (Apify's standard start event) | USD 0.00005 per run at the default 1 GB of memory |

For example, a daily alert returning 50 jobs costs about USD 0.15. A 5,000-job backfill costs USD 15. **Max jobs** caps each run, and you can also set a maximum cost per run in the run options; the actor stops cleanly at either limit and keeps the jobs saved so far.

### Where the data comes from

Every job is read from the employer's official job board through the platform's public job-board API. These are the same feeds companies use to show jobs on their own careers pages.

- **Fields added by the index:** `ats`, `board`, `firstSeenAt`, `lastSeenAt`, `indexId`, `function`, `seniority`, `countries`, `states`, `regions`, `salaryYearlyMin` and `hasSalary`.
- **Closed jobs** drop out of the index on the next update.
- **Excluded platforms:** job boards whose terms forbid automated access (for example SmartRecruiters and BambooHR) are not included.

### Limits

- **Coverage** is the companies in the index, not every company on the web. The count grows as more job boards are discovered.
- **Refresh:** about twice a day. A job posted minutes ago may not be in the index yet.
- **Salary, employment type and posting date:** only when the employer publishes them.
- **Places, teams and seniority** are read from text, so a few jobs are placed or labelled wrongly. Open an issue with the job URL and it gets fixed.

### Related actors

- [Hiring Signals: Companies Hiring by Role, Team & Location](https://apify.com/JTPalms/hiring-signals): Find companies hiring right now, one row per company: open roles by team, seniority and location, roles posted in the last 7 and 30...
- [Career Page Jobs Scraper: Auto-Detects the Job Board (ATS)](https://apify.com/JTPalms/career-page-jobs): Give company websites or careers pages and get every open job.
- [Pay Transparency Checker: Job Ads Missing Salary (US, Canada)](https://apify.com/JTPalms/pay-transparency-checker): Check job postings from thousands of company career sites against pay-transparency laws in Ontario, BC, PEI, New Brunswick and 17 US...
- [Company Enrichment From Domain: Tech Stack, Socials, Hiring](https://apify.com/JTPalms/company-profile): Company enrichment from a list of website domains: company name, description, official social links, tech stack (CMS, ecommerce,...

### FAQ

**Is it legal?** The index uses only the public job-board APIs that employers publish so their jobs get seen. It does not log in or scrape personal profiles. Email addresses and phone numbers that appear inside job descriptions (a recruiter's contact details, for example) are removed before the jobs are published; the job link still leads to the employer's own posting.

**Can I get one specific company's jobs right now?** Use the single-platform scrapers from the same developer (Greenhouse, Lever, Ashby, Workable, Recruitee, Personio, Breezy), or the Career Page Jobs actor, which finds a company's job board from its website.

**Something missing or wrong?** Open an issue with the company or job URL. Job board requests are welcome in issues too.

# Actor input Schema

## `timeRange` (type: `string`):

Which jobs to search. latest: new jobs in the most recent index update. 24h, 7d, 14d: new jobs in that window. all: every open job (backfill). Run on a schedule with the matching window to get each new job once.

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

Keep jobs whose title matches any of these. Whole words with common endings: engineer finds Engineers and Engineering, intern finds Internship but not International. End a word with \* for any ending (analy\* finds Analyst and Analytics). Wrap in slashes for a regular expression, for example /data (scientist|engineer)/.

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

Drop jobs whose title or department matches any of these, matched the same way as Job title keywords. For example intern drops internships but keeps International Sales.

## `descriptionKeywords` (type: `array`):

Keep jobs whose title or description mentions any of these, for example python, kubernetes or visa sponsorship. Matched the same way as Job title keywords. Combines with Job title keywords (both must match when both are set). Searches the full description even when Include job description is off.

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

Keep jobs whose department or team, as the employer named it on the job board, matches any of these, for example Engineering, Customer Success or Finance. Matched the same way as Job title keywords. About 9 in 10 jobs name a department.

## `functions` (type: `array`):

Keep jobs in these teams, read from the job title with the department as fallback. Leave empty for every team.

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

Keep jobs whose location matches any of these, for example Toronto, New York, Germany, California, Europe or Remote. Text matches whole words (US does not match Austin). Country names (United States, USA, UK, Germany, Canada) and US state or Canadian province names (California, Ontario) also match jobs the index placed there, so Canada finds Toronto, ON. Remote, Hybrid and On-site match the job's workplace type.

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

Keep jobs in any of these countries, read from the job's location (Toronto, ON counts as Canada). Jobs with no clear country, such as plain 'Remote', are left out when this is set.

## `states` (type: `array`):

Keep jobs in any of these states or provinces, read from the location (San Francisco, CA counts as California; Toronto counts as Ontario).

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

Keep only jobs the job board or location marks as remote.

## `ats` (type: `array`):

Limit to these job board platforms. Leave empty for all platforms in the index.

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

Keep jobs from these companies. A name matches whole words of the company or job board name (stripe finds Stripe, meta does not find Metabase). A domain such as stripe.com matches the company website where the index knows it, or a company or board named exactly stripe.

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

Keep jobs the employer posted within this many days, by the job board's own posting date. Jobs without a posting date (Pinpoint boards publish none) are left out when this is set. Leave empty for no limit.

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

Keep jobs the employer posted on or after this date (YYYY-MM-DD), or within a period such as 7 days or 2 weeks. Uses the job board's own posting date; jobs without one (Pinpoint boards publish none) are left out when this is set.

## `seniority` (type: `array`):

Keep jobs at these levels, read from the job title. mid is anything not marked otherwise.

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

Keep jobs of these types, as stated by the job board. Greenhouse and Teamtailor boards usually do not state it (nearly half of all jobs): add Not stated to keep those. For internships on any board, the Seniority filter Intern reads the job title instead.

## `requireSalary` (type: `boolean`):

Keep only jobs with a pay range (a minimum, a maximum or both), either published by the employer as a field or found in the job description.

## `minSalaryYearly` (type: `integer`):

Keep only jobs whose pay range starts at or above this yearly amount (hourly and monthly pay are converted to yearly). Compared as a number in the job's own currency. Jobs without a pay range are dropped when this is set.

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

Include the plain-text job description (up to 5,000 characters) in each row. Turning it off makes a full backfill much faster. Keywords in title or description still search descriptions either way.

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

Stop after this many jobs. Newest first.

## Actor input object example

```json
{
  "timeRange": "24h",
  "keywords": [
    "engineer",
    "developer"
  ],
  "remoteOnly": false,
  "requireSalary": false,
  "includeDescription": true,
  "maxJobs": 100
}
```

# Actor output Schema

## `results` (type: `string`):

All output rows in the default dataset (JSON, CSV, Excel via the format parameter).

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

Counts and per-input status for this run.

# 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 = {
    "keywords": [
        "engineer",
        "developer"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("jtpalms/ats-jobs-index").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 = { "keywords": [
        "engineer",
        "developer",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("jtpalms/ats-jobs-index").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 '{
  "keywords": [
    "engineer",
    "developer"
  ]
}' |
apify call jtpalms/ats-jobs-index --silent --output-dataset

```

## MCP server setup

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

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/FJEgfK5eUMS5UUvhV/builds/n545Uc9XHRd1McBVL/openapi.json
