# Job Postings Database — All ATS, Live Verified (`foxlabs/job-postings-database`) Actor

Search live jobs from tens of thousands of company career sites across 7 ATS platforms (Greenhouse, Lever, Ashby, SmartRecruiters, Teamtailor, Personio, BambooHR) in one query. Filter by title, company, location, remote and posted date. Every role is re-checked at the source before delivery.

- **URL**: https://apify.com/foxlabs/job-postings-database.md
- **Developed by:** [Berkan Kaplan](https://apify.com/foxlabs) (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

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

## Job Postings Database — All ATS, Live Verified 🚀

### 🔍 What is the Job Postings Database — and when should you use it?

Most job data on the market is either a scrape of one aggregator or a snapshot of a database
that was fresh yesterday. This actor searches **company career sites directly** — the boards
employers publish themselves on seven applicant tracking systems — and then **re-reads the
source before it hands you a row**. A role that was filled this morning is not in your output.

Use it when you need live hiring data you can act on:

- a job board or feed that must not show dead links
- a sales or recruiting signal ("this company is hiring three DevOps engineers")
- market research on titles, locations, remote share and departments

Not the right tool if you want aggregator listings (Indeed, LinkedIn) rather than the
employer's own posting, or if you need a platform outside the seven below.

### 🤖 Use with AI agents

The actor is agent-friendly in three ways:

- **One query, no company ids.** An agent asks for `titles: ["backend engineer"]`,
  `locations: ["Berlin"]`, `postedAfter: 7` and gets roles across every platform. It never
  has to know which ATS a company uses or what its board token is.
- **Honest empty results.** When nothing matches, the run returns a status row that names
  the filters that emptied it and charges nothing, so an agent can retry with a wider query
  instead of guessing why it got zero rows.
- **x402 / pay-per-event.** You pay per delivered role, so an agent loop costs what it uses.

```json
{
  "titles": ["devops", "site reliability"],
  "locations": ["Germany", "remote"],
  "remote": "only",
  "postedAfter": 14,
  "maxResults": 100
}
```

### 📋 Overview

| | |
|---|---|
| **Platforms** | Greenhouse, Lever, Ashby, SmartRecruiters, Teamtailor, Personio, BambooHR |
| **Company boards indexed** | 32,997 indexed, 20,380 currently carrying open roles (greenhouse 7,597 · bamboohr 8,833 · lever 5,235 · ashby 4,140 · personio 3,818 · smartrecruiters 1,783 · teamtailor 1,591) |
| **Roles in the index** | **445,722** (measured on the full refresh of 2026-10-20: greenhouse 185,617 · lever 70,317 · ashby 59,405 · smartrecruiters 49,921 · bamboohr 42,023 · personio 22,373 · teamtailor 16,066) |
| **Index freshness** | rebuilt daily (a full refresh takes ~14 minutes); every delivered row carries `indexedAt` and a live check |
| **Verification** | each matched role's board is re-read at query time |
| **Keys needed** | none — all seven sources are public employer boards |

### ✨ Features

- **Cross-ATS search in one query.** Title (any/all words, phrases), company, location,
  department, remote, posted-date window, platform.
- **Live verification.** Closed roles are dropped before delivery and never charged. One
  board read verifies every matched role of that company, so verification is cheap.
- **Full descriptions.** Delivered as text, not markup.
- **Freshest first.** Results are sorted newest-first, so a cap keeps the newest roles.
- **Per-company cap.** One employer with 392 open roles cannot fill your whole run.
- **Carried-over roles are flagged, not hidden.** If a board could not be read during the
  last index refresh, its roles stay searchable and arrive with `verified`/`stale` set, so a
  network hiccup never looks like a hiring freeze.
- **No silent empty runs.** An empty result says which filter emptied it.

### 🎬 Quick Start

```json
{
  "titles": ["backend engineer", "devops"],
  "locations": ["Berlin"],
  "maxResults": 200
}
```

Returns the newest matching open roles across all seven platforms, each verified live, with
company, location, department, remote flag, posted date, description and apply URL.

### 🚀 Getting Started (3 steps)

1. **Say what you are looking for.** A title or keyword is usually enough; everything else
   is optional. Leave `titles` empty to see the freshest roles across every platform.
2. **Narrow if you need to.** `locations`, `remote`, `departments`, `companies`,
   `postedAfter` (a date, or a number of days like `7`).
3. **Run it.** Results arrive newest-first. Check `verified` on each row: `true` means the
   role was still on the employer's board when it was delivered.

### 📥 Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `titles` | array | — | Words to match in the job title. A multi-word entry is matched as a phrase. |
| `titleMode` | `any` | `all` | `any` | Match at least one entry, or every entry. |
| `locations` | array | — | City, region or country text to match. |
| `remote` | `any` | `only` | `exclude` | `any` | Keep only remote roles, exclude them, or ignore the flag. |
| `companies` | array | — | Company name or board id. Use it to watch a named list of employers. |
| `departments` | array | — | Department or team text, e.g. `Engineering`. |
| `postedAfter` | string | — | `2026-10-01`, or a number of days (`7` = the last week). |
| `postedBefore` | string | — | `2026-10-15`. |
| `ats` | array | all seven | Limit to certain platforms. |
| `maxResults` | integer | `200` | How many roles to deliver. |
| `maxPerCompany` | integer | `0` | Cap per employer; `0` means no cap. |
| `verifyLive` | boolean | `true` | Re-read each role's board before delivery. |
| `includeDescription` | boolean | `true` | Attach the description text. |
| `includeStale` | boolean | `true` | Include roles carried over from an unreadable board. |

Word boundaries are respected: `ops` does not match `Operations`, and `go` does not match
`Google`. Accents and the Turkish dotless `ı` are folded, so `Köln` matches `koln`.

### 📤 Output

One row per open role, always the same 18 columns:

```json
{
  "jobId": "4457863009",
  "ats": "greenhouse",
  "companyName": "Corvias Corporate Services, LLC",
  "companyBoard": "corviascorporateservicesllc",
  "jobTitle": "Maintenance Manager",
  "department": "Facilities",
  "location": "Fort Sill, Oklahoma, United States",
  "isRemote": false,
  "employmentType": "Full-time",
  "postedAt": "2026-10-19",
  "updatedAt": "2026-10-19",
  "description": "At Corvias, you'll join a team …",
  "applyUrl": "https://job-boards.greenhouse.io/corviascorporateservicesllc/jobs/4457863009",
  "sourceUrl": "https://job-boards.greenhouse.io/corviascorporateservicesllc/jobs/4457863009",
  "verified": true,
  "verifiedAt": "2026-10-20T08:45:12.004Z",
  "indexedAt": "2026-10-20T08:43:21.870Z",
  "stale": false
}
```

**Measured fill rate** (delivered rows, mixed-platform sample):

| Field | Filled | Note |
|---|---|---|
| `jobTitle`, `location`, `applyUrl`, `sourceUrl`, `postedAt` | 100% | |
| `description` | 100% | average ~6,500 characters |
| `companyName` | 93% | Lever and BambooHR boards publish no company name; the board id is in `companyBoard` |
| `department` | 53% | not every employer fills it in |
| `employmentType` | 53% | same |

Dates: Personio and BambooHR publish **no posting date** — those rows carry `null` rather
than a guessed date, and a date filter sets them aside and tells you how many.

### 💼 Use cases

- **Job boards and feeds.** Build a niche board (remote data roles, Berlin engineering) whose
  links are never dead.
- **Sales signals.** A company hiring five salespeople has a budget; a company hiring its
  first DevOps engineer is about to buy tooling.
- **Recruiting intelligence.** Who is hiring for the role you place, where, and since when.
- **Market research.** Title mix, remote share, department spread across tens of thousands
  of employers.

### 🔗 Integration

```bash
curl -X POST "https://api.apify.com/v2/acts/foxlabs~job-postings-database/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"titles":["backend engineer"],"locations":["Berlin"],"maxResults":50}'
```

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("foxlabs/job-postings-database").call(run_input={
    "titles": ["backend engineer", "devops"],
    "locations": ["Berlin"],
    "postedAfter": 7,
    "maxResults": 100,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["jobTitle"], "—", job["companyName"], "—", job["applyUrl"])
```

Works with Make, Zapier, n8n and any HTTP client through the standard Apify API.

### 📊 Pricing

Pay per event: **$0.002 per delivered job posting**, plus the standard Actor start event.

- Roles that turned out to be closed are **not delivered and not charged**.
- Status rows (an empty result, an input error) are **free**.
- A 200-role run costs $0.40.

### ❓ FAQ

**Which platforms are covered?** Greenhouse, Lever, Ashby, SmartRecruiters, Teamtailor,
Personio and BambooHR. Workday is not in this version.

**How fresh is the data?** The index is rebuilt daily, and every delivered role is re-checked
against the employer's board at query time. `indexedAt` tells you when the index was built,
`verifiedAt` when the role was last confirmed open.

**What does `verified: false` mean?** The employer's board could not be read at that moment
(a timeout, or the platform answered with a page instead of data). The role is delivered
anyway, because an unreadable board is not proof the job closed — but it is flagged so you
can decide.

**How fast is a query?** Measured on the full 445,722-row index: a title+location query scanned the
whole index, matched 45 roles and verified every one of them against its board in **11.8 seconds**.

**Do I need an API key for the sources?** No. All seven are public employer job boards.

**Can I search one company?** Yes — put its name or board id in `companies`, and leave
`titles` empty to get all of its open roles.

**Why is `department` only filled half the time?** Because employers fill it in about half
the time. The actor never invents a value to make a column look full.

### 🐛 Troubleshooting

- **Zero rows.** Read the status row in the dataset: it names the filters that emptied the
  result. The usual causes are a title that is too specific, or a date window narrower than
  the hiring pace.
- **A date filter returned fewer rows than expected.** Personio and BambooHR roles are set
  aside when a date filter is used (no posting date at source). The status line reports the
  count.
- **A company you expect is missing.** It may use an ATS outside the seven, or a board that
  was unreadable during the last refresh. Pass it in `companies` to check.

### ⚠️ Trademark

Greenhouse, Lever, Ashby, SmartRecruiters, Teamtailor, Personio and BambooHR are trademarks
of their respective owners. This actor is not affiliated with, endorsed by or sponsored by
any of them. It reads the public job boards those platforms host for employers.

### ⚖️ Is it legal to scrape this data?

The actor reads **public job boards that employers publish to attract applicants** — three
of the seven sources are documented public APIs provided for exactly this purpose
(Greenhouse Job Board API, Lever postings API, Ashby posting API). No login is used, no
personal data about applicants is touched, and no paywalled or private content is accessed.
Job postings are business information, not personal data. You are responsible for how you
use the output in your jurisdiction.

### 🤝 Support & contact

Found a board that is missing, a platform you need, or a row that looks wrong? Open an issue
on the actor page. Reports with a company board id and the query you ran get fixed fastest.

### Changelog

#### 0.1 — 2026-10-20

First release: seven ATS platforms, 32,997 indexed company boards (445,722 open roles measured), live verification of every
delivered role, honest empty results, and `null` instead of a guessed date where the source
publishes none.

# Changelog

This Actor's version history is a separate document: https://apify.com/foxlabs/job-postings-database/changelog.md

# Actor input Schema

## `titles` (type: `array`):

Words to match in the job title. A multi-word entry is matched as a phrase. Leave empty to search every open role.

## `titleMode` (type: `string`):

any = the title matches at least one entry (default). all = it must match every entry.

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

City, region or country text to match, e.g. Berlin, Germany, remote-friendly office names.

## `remote` (type: `string`):

Whether to keep only remote roles, exclude them, or ignore the flag.

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

Company name or career-board id to match. Use this to watch a named list of companies.

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

Department or team text to match, e.g. Engineering, Sales.

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

A date (2026-10-01) or a number of days (7 = the last week). Personio and BambooHR publish no posting date, so their roles are set aside when this is used and the run says how many.

## `postedBefore` (type: `string`):

A date like 2026-10-15.

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

Limit the search to certain platforms. Empty searches all seven.

## `maxResults` (type: `integer`):

How many roles to deliver, newest first.

## `maxPerCompany` (type: `integer`):

Keeps one large employer from filling the whole run. 0 = no cap.

## `verifyLive` (type: `boolean`):

Re-read each matched role's board before delivering, so closed roles are dropped. Leave on unless you want maximum speed.

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

Attach the full job description text.

## `includeStale` (type: `boolean`):

Roles from boards that could not be read during the last index refresh. They are still verified live before delivery.

## Actor input object example

```json
{
  "titles": [
    "backend engineer",
    "devops"
  ],
  "titleMode": "any",
  "locations": [
    "Berlin"
  ],
  "remote": "any",
  "ats": [],
  "maxResults": 200,
  "maxPerCompany": 0,
  "verifyLive": true,
  "includeDescription": true,
  "includeStale": true
}
```

# Actor output Schema

## `dataset` (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 = {
    "titles": [
        "backend engineer",
        "devops"
    ],
    "locations": [
        "Berlin"
    ],
    "ats": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("foxlabs/job-postings-database").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 = {
    "titles": [
        "backend engineer",
        "devops",
    ],
    "locations": ["Berlin"],
    "ats": [],
}

# Run the Actor and wait for it to finish
run = client.actor("foxlabs/job-postings-database").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 '{
  "titles": [
    "backend engineer",
    "devops"
  ],
  "locations": [
    "Berlin"
  ],
  "ats": []
}' |
apify call foxlabs/job-postings-database --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,foxlabs/job-postings-database"
        }
    }
}
```

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/mZSQZZTr8hk57mhhR/builds/T3VzT6rrZVEP2Ozn4/openapi.json
