# ATS Job Boards - live postings from Greenhouse and Lever (`vital_tuxedo/ats-job-boards`) Actor

Every open role on a company's own job board, from the public APIs Greenhouse and Lever publish for employers to embed on their careers page. One row per posting: title, location, department, apply URL, posted date. No login, no API key. Ashby is not read - its robots.txt refuses us.

- **URL**: https://apify.com/vital\_tuxedo/ats-job-boards.md
- **Developed by:** [Stephen Psaradellis](https://apify.com/vital_tuxedo) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## ATS Job Boards — live postings from Greenhouse and Lever

Every open role on a company's **own** job board, read from the public APIs
Greenhouse and Lever publish so that employers can embed their postings on
their careers page. One dataset row per posting: title, location, department,
commitment, apply URL, posted date, and optionally the full description.

No login. No API key. No scraping around a wall — these are the endpoints the
company's own careers page calls.

### What you give it

A list of boards, one per line:

```
greenhouse:vercel
greenhouse:figma
lever:leverdemo
```

The token is the last path segment of the company's public board URL —
`job-boards.greenhouse.io/`**`vercel`** or `jobs.lever.co/`**`leverdemo`**. You
can paste the whole URL and it will take the token off the end. A bare name
with no prefix is tried on Greenhouse.

| Setting | What it does |
|---|---|
| **Job boards** | The boards to read, in order |
| **Include the full description** | Adds the posting's HTML body. Costs no extra request. On Greenhouse it also fills in **department** and offices, which the plain jobs endpoint does not return |
| **Max boards** | Stop after this many boards (0 = unbounded) |
| **Max postings** | Stop after this many postings (0 = unbounded) |
| **Request timeout** | Seconds before a board read is retried, then recorded as an error |

### What you get

One row per posting:

```json
{
  "ats": "greenhouse",
  "board": "vercel",
  "job_id": "6136160004",
  "title": "Account Executive, Commercial",
  "location": "Hybrid - London",
  "department": "Account Executive",
  "commitment": "",
  "workplace": "",
  "absolute_url": "https://job-boards.greenhouse.io/vercel/jobs/6136160004",
  "posted_at": "2026-08-18T22:06:19.000Z",
  "read_at": "2026-09-07T02:55:44.979Z"
}
```

A `RUN_SUMMARY` record in the key-value store says how many boards were asked,
how many answered, how many were empty, how many 404ed, how many postings were
pushed, and what stopped the run if a cap did. **A board with no open roles is
reported as empty, not dropped** — "this company is not hiring right now" is an
answer, and you paid a request for it either way.

### Pricing

Pay per event, and **there is no charge for starting the actor**:

| Event | Price | When |
|---|---|---|
| Job board read | $0.01 | One board fetched, whether or not it has open roles |
| Job posting row | $0.004 | One posting pushed. A posting seen twice in a run is charged once |

Reading 20 boards with 40 roles each costs $0.20 + $3.20 = **$3.40**.

### What it does not do

- It does not submit applications. Both ATSes have an application endpoint;
  this actor never calls it.
- It does not read **Ashby**, although Ashby boards are in the same family.
  `api.ashbyhq.com` answers a `robots.txt` request with `401`, and this
  project treats a refusal as a refusal.
- It does not invent fields. Where an ATS returns nothing for `department`,
  `commitment` or `workplace`, the column is empty rather than guessed.
- It does not enrich rows from anywhere else. What you get is what the
  employer published.

### Where the data comes from

| ATS | Endpoint | robots.txt |
|---|---|---|
| Greenhouse | `boards-api.greenhouse.io/v1/boards/{token}/jobs` | `Disallow: /embed/` only. Greenhouse's docs: *"Job Board data is publicly available, so authentication is not required for any GET endpoints."* |
| Lever | `api.lever.co/v0/postings/{company}?mode=json` | `Allow: /`, `Crawl-delay: 1` — honoured as a real one-second gap between Lever reads |

Both were fetched and checked under this actor's own user agent before it was
built, and the responses are kept with their SHA-256 digests.

# Actor input Schema

## `boards` (type: `array`):

The boards to read, one per line, as 'greenhouse:token' or 'lever:company'. The token is the last path segment of the company's public board URL - job-boards.greenhouse.io/TOKEN or jobs.lever.co/COMPANY. A bare name with no prefix is tried on Greenhouse.

## `includeContent` (type: `boolean`):

Fetch each posting's full HTML description. It costs no extra request on either ATS. On Greenhouse it also fills in department and offices, which the plain jobs endpoint does not return - leave this off and the department column is blank for Greenhouse rows.

## `maxBoards` (type: `integer`):

Stop after this many boards have been read. 0 means unbounded. Each board read is one board-read event, charged whether or not the board has any open roles - an empty board is an answer.

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

Stop after this many postings have been pushed. 0 means unbounded. Each unique posting is one job-row event.

## `timeoutSecs` (type: `integer`):

A request that does not answer within this many seconds is retried once, then recorded as an error against that board.

## Actor input object example

```json
{
  "boards": [
    "greenhouse:vercel",
    "greenhouse:figma",
    "lever:leverdemo"
  ],
  "includeContent": false,
  "maxBoards": 1000,
  "maxJobs": 50000,
  "timeoutSecs": 20
}
```

# Actor output Schema

## `postings` (type: `string`):

Every open posting found, one row each, with its board, title, location, apply URL and posted date.

## `runSummary` (type: `string`):

Counts for the run: boards asked, boards that answered, boards that errored or 404ed, postings pushed, and what stopped the run if a cap did.

# 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 = {
    "boards": [
        "greenhouse:vercel",
        "greenhouse:figma",
        "lever:leverdemo"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("vital_tuxedo/ats-job-boards").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 = { "boards": [
        "greenhouse:vercel",
        "greenhouse:figma",
        "lever:leverdemo",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("vital_tuxedo/ats-job-boards").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 '{
  "boards": [
    "greenhouse:vercel",
    "greenhouse:figma",
    "lever:leverdemo"
  ]
}' |
apify call vital_tuxedo/ats-job-boards --silent --output-dataset

```

## MCP server setup

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

```

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/uDza5PxC349w3y5Vd/builds/KZZ1WdfNg15tgUydF/openapi.json
