# ATS Jobs Scraper: Greenhouse, Lever, Ashby, Workable & more (`flets_sv/ats-jobs-feed`) Actor

Get every open job from Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee and Personio boards as clean JSON. Salary, location, remote, alerts.

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

## Pricing

$1.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

## ATS Jobs Scraper: Greenhouse, Lever, Ashby, Workable & more

### Use cases

- Build a niche job board or aggregator without maintaining scrapers for each applicant tracking system
- Track hiring at competitors, startups or target accounts for sales and recruiting outreach
- Salary benchmarking and remote-job market research from structured salary and location fields
- Daily alerts when a company opens or closes roles (monitor mode)

### English

#### What it does

Gets every open job from company job boards that use an applicant tracking
system (ATS) with a public API: Greenhouse, Lever, Ashby, Workable,
SmartRecruiters, Recruitee and Personio. You give it company tokens or careers
page URLs, and it returns one clean, normalized JSON item per job: title,
department, location, remote flag, contract type, salary range, dates, apply
link and, optionally, the full description as plain text.

The ATS is detected automatically from the URL, so you can mix providers in
the same run. Monitor mode remembers what it saw in the previous run and only
outputs new, changed or closed jobs, which makes it a good fit for scheduled
job alerts.

#### Input

- **Companies** (`empresas`): one per line, as `ats:token` (for example
  `greenhouse:airbnb`, `lever:palantir`, `ashby:ramp`) or as a careers page URL.
- **Keywords in title** (`palabrasClave`): keeps jobs whose title contains any
  of these words. Case and accent insensitive. Empty means all jobs.
- **Locations** (`ubicaciones`): keeps jobs whose location contains any of these
  texts. Empty means all jobs.
- **Departments** (`departamentos`): keeps only these departments or teams.
- **Remote only** (`soloRemoto`): keeps only jobs flagged or described as remote.
- **Posted in the last N days** (`publicadasDesdeDias`): 0 means no limit.
- **Include full description** (`incluirDescripcion`): adds the description as text.
- **Monitor mode** (`modoVigilancia`): outputs only new, changed or closed jobs
  compared with the previous run.
- **Max jobs** (`maxOfertas`): hard limit per run, to protect your budget.
- **Concurrency** (`concurrencia`): parallel requests. Keep it low to be polite
  with the source.

#### Output

Each dataset item is one job with these fields: `id`, `ats`, `empresa`,
`titulo`, `departamento`, `ubicacion`, `pais`, `remoto`, `tipoContrato`,
`salarioMin`, `salarioMax`, `moneda`, `periodoSalario`, `publicadoEn`,
`actualizadoEn`, `url`, `urlSolicitud`, `descripcionTexto`, `cambio`, `error`
and `comprobadoEn`.

In monitor mode, `cambio` is `nuevo` (new job), `modificado` (changed since the
last run) or `cerrada` (the job has disappeared; it is reported but not charged).

#### Pricing

You pay per job returned in the dataset (pay per event, event `job`, 0.001 USD
per job). Closed-job reports in monitor mode are not charged, but they count
towards `maxOfertas`. If `maxOfertas` cuts the changes of a company, its state
is not saved, so the changes are reported on the next run.

#### Limits

Each run is capped by `maxOfertas`. Requests per ATS are limited, and the
concurrency is set by `concurrencia`. Only public job board data is read; no
login or token from the source is needed.

#### Integrations (Make, Zapier, Google Sheets)

- **Make**: add the Apify module "Run an Actor" (Actor `Flets_sv/ats-jobs-feed`), paste the input JSON, chain "Get Dataset Items" and map the fields to Google Sheets, Slack, Airtable or your CRM.
- **Zapier**: the Apify app offers "Run Actor" and "Fetch Dataset Items". Add a Schedule trigger and send each row to Gmail, Slack or Notion.
- **Google Sheets**: enable the official Google Sheets integration in the Actor's Integrations tab to append every run to a sheet, or call `https://api.apify.com/v2/acts/Flets_sv~ats-jobs-feed/run-sync-get-dataset-items?format=csv` with POST and your input from Apps Script (`UrlFetchApp`).
- **API and webhooks**: start the Actor from the REST API, use Schedules for recurring runs and add a webhook to notify your system when a run finishes.

Input to paste into Make or Zapier:

```json
{"empresas":["greenhouse:airbnb","lever:palantir","ashby:ramp"],"palabrasClave":[],"ubicaciones":[],"soloRemoto":false,"departamentos":[],"publicadasDesdeDias":0,"incluirDescripcion":true,"modoVigilancia":false,"maxOfertas":50,"concurrencia":5}
```

#### FAQ

**Do I need an API key from the job boards?** No. Only the public job board
endpoints are used.

**Why do I get no output on the second run in monitor mode?** Nothing changed
since the previous run, so nothing is emitted or charged.

**Can I test it without spending credits?** Yes. Run the dry-run script in
`scripts/en-seco.mjs`, which uses a local fixture and no network.

### Español

#### Qué hace

Obtiene todas las ofertas abiertas de los portales de empleo de empresas que
usan un sistema de seguimiento de candidatos (ATS) con API pública: Greenhouse,
Lever, Ashby, Workable, SmartRecruiters, Recruitee y Personio. Le indicas los
identificadores de empresa o las URL de las páginas de empleo, y devuelve un
elemento JSON limpio y normalizado por oferta: título, departamento, ubicación,
indicador de remoto, tipo de contrato, rango salarial, fechas, enlace para
postularse y, si quieres, la descripción completa en texto plano.

El ATS se detecta automáticamente a partir de la URL, así que puedes mezclar
proveedores en la misma ejecución. El modo vigilancia recuerda lo que vio en la
ejecución anterior y solo emite las ofertas nuevas, modificadas o cerradas, lo
que lo hace adecuado para alertas de empleo programadas.

#### Entrada

- **Empresas** (`empresas`): una por línea, como `ats:token` (por ejemplo
  `greenhouse:airbnb`, `lever:palantir`, `ashby:ramp`) o como URL de la página
  de empleo.
- **Palabras clave en el título** (`palabrasClave`): conserva las ofertas cuyo
  título contiene alguna de estas palabras. No distingue mayúsculas ni tildes.
  Vacío significa todas.
- **Ubicaciones** (`ubicaciones`): conserva las ofertas cuya ubicación contiene
  alguno de estos textos. Vacío significa todas.
- **Departamentos** (`departamentos`): conserva solo estos departamentos o equipos.
- **Solo remoto** (`soloRemoto`): conserva solo las ofertas marcadas o descritas
  como remotas.
- **Publicadas en los últimos N días** (`publicadasDesdeDias`): 0 significa sin límite.
- **Incluir descripción completa** (`incluirDescripcion`): añade la descripción en texto.
- **Modo vigilancia** (`modoVigilancia`): emite solo las ofertas nuevas, modificadas
  o cerradas respecto a la ejecución anterior.
- **Máximo de ofertas** (`maxOfertas`): límite estricto por ejecución, para
  proteger tu presupuesto.
- **Concurrencia** (`concurrencia`): peticiones en paralelo. Mantenla baja para
  no sobrecargar la fuente.

#### Salida

Cada elemento del dataset es una oferta con estos campos: `id`, `ats`, `empresa`,
`titulo`, `departamento`, `ubicacion`, `pais`, `remoto`, `tipoContrato`,
`salarioMin`, `salarioMax`, `moneda`, `periodoSalario`, `publicadoEn`,
`actualizadoEn`, `url`, `urlSolicitud`, `descripcionTexto`, `cambio`, `error`
y `comprobadoEn`.

En modo vigilancia, `cambio` vale `nuevo` (oferta nueva), `modificado` (cambió
desde la última ejecución) o `cerrada` (la oferta ha desaparecido; se informa
pero no se cobra).

#### Precios

Pagas por cada oferta devuelta en el dataset (pago por evento, evento `job`,
0,001 USD por oferta). Los avisos de oferta cerrada en modo vigilancia no se
cobran, pero cuentan para `maxOfertas`. Si `maxOfertas` corta los cambios de una
empresa, su estado no se guarda, así que los cambios se informan en la siguiente
ejecución.

#### Límites

Cada ejecución está limitada por `maxOfertas`. Las peticiones por ATS están
limitadas, y la concurrencia la fija `concurrencia`. Solo se leen datos públicos
de los portales de empleo; no hace falta ningún inicio de sesión ni token de la fuente.

#### Integraciones (Make, Zapier, Google Sheets)

- **Make**: añade el módulo de Apify «Run an Actor» (Actor `Flets_sv/ats-jobs-feed`), pega el input JSON y encadena «Get Dataset Items»; después mapea los campos a Google Sheets, Slack, Airtable o tu CRM.
- **Zapier**: la app de Apify ofrece «Run Actor» y «Fetch Dataset Items». Ponle un disparador de horario y envía cada fila a Gmail, Slack o Notion.
- **Google Sheets**: en la pestaña Integrations del Actor activa la integración oficial con Google Sheets para añadir cada ejecución a una hoja, o llama desde Apps Script (`UrlFetchApp`) a `https://api.apify.com/v2/acts/Flets_sv~ats-jobs-feed/run-sync-get-dataset-items?format=csv` con POST y tu input.
- **API y webhooks**: lanza el Actor por API REST, programa ejecuciones (Schedules) y avisa a tu sistema con un webhook al terminar.

Ejemplo de input para pegar en Make o Zapier:

```json
{"empresas":["greenhouse:airbnb","lever:palantir","ashby:ramp"],"palabrasClave":[],"ubicaciones":[],"soloRemoto":false,"departamentos":[],"publicadasDesdeDias":0,"incluirDescripcion":true,"modoVigilancia":false,"maxOfertas":50,"concurrencia":5}
```

#### Preguntas frecuentes

**¿Necesito una clave de API de los portales de empleo?** No. Solo se usan los
endpoints públicos de cada portal.

**¿Por qué no obtengo resultados en la segunda ejecución del modo vigilancia?**
Desde la ejecución anterior no ha cambiado nada, así que no se emite ni se cobra nada.

**¿Puedo probarlo sin gastar créditos?** Sí. Ejecuta el script de prueba en seco
`scripts/en-seco.mjs`, que usa un fixture local y no hace peticiones de red.

### Ejemplos de uso / Usage examples

#### Example output (real run, 2026-11-05)

First rows returned by a real run of this Actor on 2026-11-05 (shortened columns, values truncated):

| empresa | titulo | departamento | ubicacion | remoto | salarioMin | salarioMax | url |
|---|---|---|---|---|---|---|---|
| airbnb | AirCover Specialist, Claims Experience | Community Support | Canada | True | 58000 | 68000 | https://careers.airbnb.com/positions/8306554?gh_jid=8306554 |
| airbnb | AirCover Specialist, Claims Experience | Community Support | Japan | - | - | - | https://careers.airbnb.com/positions/8345532?gh_jid=8345532 |
| airbnb | Associate Legal Counsel, Japan | Legal | Tokyo, Japan | False | - | - | https://careers.airbnb.com/positions/7789554?gh_jid=7789554 |

#### Copy-paste inputs

**1. All open jobs at three companies**

```json
{
  "empresas": [
    "greenhouse:airbnb",
    "lever:palantir",
    "ashby:ramp"
  ],
  "maxOfertas": 100
}
```

**2. Remote engineering jobs posted in the last 7 days**

```json
{
  "empresas": [
    "greenhouse:airbnb"
  ],
  "palabrasClave": [
    "engineer"
  ],
  "soloRemoto": true,
  "publicadasDesdeDias": 7,
  "maxOfertas": 50
}
```

#### Call it from your code (Apify API)

```bash
curl -X POST "https://api.apify.com/v2/acts/Flets_sv~ats-jobs-feed/run-sync-get-dataset-items?token=YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"empresas": ["greenhouse:airbnb", "lever:palantir", "ashby:ramp"], "maxOfertas": 100}'
```

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_API_TOKEN")
run = client.actor("Flets_sv/ats-jobs-feed").call(run_input={"empresas": ["greenhouse:airbnb", "lever:palantir", "ashby:ramp"], "maxOfertas": 100})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

No code? Use the Apify modules for **Make** and **Zapier** (run the Actor, then fetch dataset items), or the **Google Sheets** integration in the Actor's Integrations tab to append each run to a sheet.

# Actor input Schema

## `empresas` (type: `array`):

One per line. Format `ats:token` (e.g. `greenhouse:airbnb`, `lever:palantir`, `ashby:ramp`, `workable:huggingface`, `smartrecruiters:BoschGroup`, `recruitee:bunq`, `personio:acme`) or just the careers page URL; the ATS is detected automatically.

## `palabrasClave` (type: `array`):

Keep only jobs whose title contains any of these words (case and accent insensitive). Empty = all.

## `ubicaciones` (type: `array`):

Keep only jobs whose location contains any of these texts (e.g. `Remote`, `London`, `Spain`). Empty = all.

## `soloRemoto` (type: `boolean`):

Keep only jobs flagged or described as remote.

## `departamentos` (type: `array`):

Keep only these departments/teams (e.g. `Engineering`, `Sales`). Empty = all.

## `publicadasDesdeDias` (type: `integer`):

Only jobs published or updated in the last N days (when the ATS gives the date). 0 = no limit.

## `incluirDescripcion` (type: `boolean`):

Add the job description as plain text (bigger dataset).

## `modoVigilancia` (type: `boolean`):

Remember what was seen in previous runs (key-value store) and output only new or changed jobs (also reports jobs that were closed). Use it with a schedule for alerts.

## `maxOfertas` (type: `integer`):

Hard limit of jobs per run. Protects your budget.

## `concurrencia` (type: `integer`):

Parallel requests. Keep it low to be polite with the source.

## Actor input object example

```json
{
  "empresas": [
    "greenhouse:airbnb",
    "lever:palantir",
    "ashby:ramp"
  ],
  "palabrasClave": [],
  "ubicaciones": [],
  "soloRemoto": false,
  "departamentos": [],
  "publicadasDesdeDias": 0,
  "incluirDescripcion": true,
  "modoVigilancia": false,
  "maxOfertas": 50,
  "concurrencia": 5
}
```

# Actor output Schema

## `resultados` (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 = {
    "empresas": [
        "greenhouse:airbnb",
        "lever:palantir",
        "ashby:ramp"
    ],
    "palabrasClave": [],
    "ubicaciones": [],
    "departamentos": [],
    "maxOfertas": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("flets_sv/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 = {
    "empresas": [
        "greenhouse:airbnb",
        "lever:palantir",
        "ashby:ramp",
    ],
    "palabrasClave": [],
    "ubicaciones": [],
    "departamentos": [],
    "maxOfertas": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("flets_sv/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 '{
  "empresas": [
    "greenhouse:airbnb",
    "lever:palantir",
    "ashby:ramp"
  ],
  "palabrasClave": [],
  "ubicaciones": [],
  "departamentos": [],
  "maxOfertas": 50
}' |
apify call flets_sv/ats-jobs-feed --silent --output-dataset

```

## MCP server setup

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