# EU Career Site Jobs API: Greenhouse, Lever, Recruitee & more (`benelux-signals/eu-career-site-jobs-api`) Actor

Job postings straight from company career sites: Greenhouse, Lever, Recruitee, Workable, Personio, Ashby, SmartRecruiters. Paste career URLs (ATS auto-detected) or use the Benelux/EU tech seed list. Filter by keyword, country, remote, date. From $0.001/job.

- **URL**: https://apify.com/benelux-signals/eu-career-site-jobs-api.md
- **Developed by:** [Benelux Signals](https://apify.com/benelux-signals) (community)
- **Categories:** Jobs, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

## EU Career Site Jobs API: Greenhouse, Lever, Recruitee, Personio & Ashby Jobs Scraper

**Get job postings straight from company career sites for $0.0015 per job.** Paste career-page URLs or board slugs, or pick one of the built-in lists of 60 verified European tech companies. The Actor reads the public job-board feeds behind **Greenhouse, Lever, Recruitee, Workable, Personio, Ashby and SmartRecruiters** and returns one clean, normalised and deduplicated record per job, with title, company, location, country, remote/hybrid/onsite, department, employment type, posting date, apply link, salary (when published) and the full description as plain text.

There are no logins, no proxies and no browser. The data comes from the same official JSON/XML endpoints that power the companies' own career pages, so it is fast and fresh. Companies that cannot be read show up as a free error row and are **never charged**.

### Who is this job scraper for?

- **Recruiters, staffing and sourcing agencies** who track which companies are hiring for which roles in the Netherlands, Belgium, Germany or the rest of Europe.
- **Sales and business-development teams** who use hiring as a buying signal (a company hiring 10 data engineers needs data tools).
- **Job boards, niche job sites and newsletters** that want direct-from-employer jobs (no reposts, no aggregator noise) on a schedule.
- **Labour-market researchers and HR analytics teams** who need structured vacancy data per company, department and country.
- **AI agents and MCP clients**: every input has a safe default, so a bare call with just `companies` works.

### What does the EU Career Site Jobs API do?

1. **Detects the ATS** from whatever you paste:
   - board URLs: `job-boards.greenhouse.io/adyen`, `jobs.lever.co/spotify`, `fastned.recruitee.com`, `apply.workable.com/viva`, `thermondo.jobs.personio.de`, `jobs.ashbyhq.com/mollie`, `careers.smartrecruiters.com/KPN`
   - a company's own career page (`www.bunq.com/careers`): the Actor looks for the ATS embed or link in the page, and also tries the Recruitee custom-domain API
   - `ats:slug` (for example `greenhouse:adyen`) or just a bare slug (`mollie`): the Actor probes the 7 ATSes in turn
2. **Downloads the full job board** from the public endpoint in one or a few requests per company.
3. **Normalises every job** to the same fields: country codes (ISO 3166), city, workplace type, employment type (`full-time`, `part-time`, `contract`, `internship`, `temporary`), ISO dates, salary min/max/currency/period, plain-text description (HTML converted, e-mail addresses removed).
4. **Filters** on keywords (title or also description), excluded keywords, countries (including `EU`, `Benelux`, `DACH`), location text, remote/hybrid/onsite, department, employment type and posting age.
5. **Deduplicates** across the whole run (same job id, or the same company + title + location + department posted twice) and sorts each company's jobs newest first.

#### Built-in seed lists (verified October 2026)

| ATS | Companies | Examples |
|---|---|---|
| Greenhouse | 12 | Adyen, Catawiki, Bird, Elastic, GetYourGuide, N26, Celonis, SumUp, Doctolib |
| Lever | 7 | Spotify, Pigment, Aircall, Malt, Swile, Agicap, Contentsquare |
| Recruitee | 11 | Fastned, bunq, Channable, Greenchoice, Vandebron, Nmbrs, Funda, Tony's Chocolonely, Payconiq |
| Workable | 5 | Viva, Skroutz, Epignosis, Orfium, Blueground |
| Personio | 6 | 1KOMMA5°, Thermondo, Hornetsecurity, codecentric, tonies, ottonova |
| Ashby | 14 | Mollie, Miro, Bitvavo, Polarsteps, n8n, DeepL, Babbel, Pleo, Synthesia, ElevenLabs, Qonto |
| SmartRecruiters | 5 | KPN, Delivery Hero, Continental, Ubisoft, Sodexo |

Pick one or more lists in **Built-in seed lists** (or `"all"`), combine them with your own companies, and use the filters to narrow down.

### How to use it

1. Paste company career URLs or slugs into **Company career sites** (one per line), and/or choose a **built-in seed list**.
2. Optional: set **Keywords**, **Countries** (`NL`, `Germany`, `EU`, `Benelux`…), **Workplace** (`remote`, `remote-or-hybrid`…) or **Posted within days**.
3. Set **Max results**. The default is 200, and this is also your cost cap.
4. Click **Start**. Open the **Jobs overview** table, or export to CSV, Excel, JSON or XML.

#### Input example

```json
{
  "companies": [
    "https://job-boards.greenhouse.io/adyen",
    "https://fastned.recruitee.com/",
    "ashby:mollie",
    "https://www.bunq.com/careers"
  ],
  "seedLists": ["recruitee"],
  "keywords": ["engineer", "developer", "data"],
  "countries": ["Benelux"],
  "workplace": "any",
  "postedWithinDays": 30,
  "includeDescription": true,
  "maxResults": 200
}
```

| Field | Default | What it does |
|---|---|---|
| `companies` | 3 examples (Catawiki, Fastned, Mollie) | Career-site URLs, board URLs, `ats:slug` or bare slugs, one per line. The examples are dropped automatically when you choose a seed list. |
| `seedLists` | none | Built-in verified company lists per ATS, or `all` (60 companies). |
| `keywords` | none | Keep jobs whose title contains any of these words (case-insensitive). |
| `searchInDescription` | false | Also match keywords in the job description. |
| `excludeKeywords` | none | Drop jobs whose title contains any of these words (for example `senior`, `stage`). |
| `countries` | any | ISO codes or names. Groups: `EU`/`Europe`, `Benelux`, `DACH`. Remote jobs open to "Europe" match European countries. |
| `locations` | any | Free-text match on the location (for example `Amsterdam`, `Berlin`). |
| `workplace` | `any` | `any`, `remote`, `remote-or-hybrid`, `hybrid` or `onsite`. |
| `departments` | any | Match on department or team name. |
| `employmentTypes` | any | `full-time`, `part-time`, `contract`, `internship`, `temporary`, `other`. |
| `postedWithinDays` | 0 (any) | Only jobs posted or updated in the last N days. |
| `includeDescription` | true | Full plain-text description. Turn off for faster, smaller results. |
| `maxResults` | 200 | Maximum jobs saved (and charged) per run. |
| `maxResultsPerCompany` | 0 (no limit) | Cap per company, so one big employer does not fill the run. |
| `maxConcurrency` | 4 | Companies fetched in parallel (1–10). |

#### Output example

One record per job (description shortened):

```json
{
  "status": "ok",
  "charged": true,
  "jobId": "recruitee:greenchoice:2347856",
  "ats": "recruitee",
  "companySlug": "greenchoice",
  "company": "Greenchoice",
  "title": "Klantenservice Medewerker Nijmegen",
  "location": "Nijmegen, Gelderland, Nederland",
  "city": "Nijmegen",
  "country": "NL",
  "countryName": "Netherlands",
  "countries": ["NL"],
  "regions": [],
  "remote": false,
  "workplaceType": "hybrid",
  "department": "Service & Operations",
  "team": null,
  "employmentType": "part-time",
  "employmentTypeRaw": "parttime_fixed_term",
  "seniority": "mid_level",
  "postedAt": "2026-10-15T11:24:37.000Z",
  "updatedAt": "2026-10-15T11:24:37.000Z",
  "applyUrl": "https://werkenbij-int.greenchoice.nl/o/klantenservice-medewerker-nijmegen-5/c/new",
  "jobUrl": "https://werkenbij-int.greenchoice.nl/o/klantenservice-medewerker-nijmegen-5",
  "salaryMin": 2650,
  "salaryMax": 2750,
  "salaryCurrency": "EUR",
  "salaryPeriod": "month",
  "salaryText": "EUR 2,650 - 2,750 per month",
  "description": "Als Klantenservice Medewerker help je onze klanten…",
  "input": "recruitee:greenchoice",
  "source": "seed",
  "scrapedAt": "2026-10-25T09:45:24.953Z",
  "error": null
}
```

**`status` values:**

- `ok`: a job posting. Charged.
- `error`: a company could not be read (no public board for that slug, the career page has no supported ATS, invalid input line, network error). Not charged, and the `error` field says why.
- `no_results`: the run worked but nothing matched your filters. Not charged, and `error` says which filter removed how many jobs.

A run summary is saved as `RUN_SUMMARY` in the key-value store: jobs saved, charged, `chargeLimitReached`, `maxResultsReached`, per-company job counts and ATS, companies not processed, duplicates removed, and how many jobs each filter removed.

#### Which fields does each ATS fill?

| Field | Greenhouse | Lever | Recruitee | Workable | Personio | Ashby | SmartRecruiters |
|---|---|---|---|---|---|---|---|
| Department | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Employment type | when set | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Workplace (remote/hybrid) | from text | ✓ | ✓ | ✓ | from text | ✓ | ✓ |
| Salary | – | when published | when published | – | – | when published | – |
| Posting date | ✓ (updated) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |

### Speed

Most boards are one request. All 60 built-in companies (about 5,500 jobs) take about 15 seconds without descriptions. SmartRecruiters needs one extra request per job for the description, so large SmartRecruiters boards with `includeDescription` take longer (about 1 second per 4 jobs).

### Pricing

This Actor uses **pay per event**: **$0.0015 per job** on the Free plan, cheaper on paid plans, with platform usage included. Error rows and `no_results` rows are free.

| Event | Free plan | Bronze | Silver | Gold and up |
|---|---|---|---|---|
| Job posting | $0.0015 | $0.0013 | $0.0011 | $0.001 |
| Actor start (per run) | $0.005 | | | |

#### Cost table (Free plan prices)

| What you collect | Jobs | Cost |
|---|---|---|
| A quick check of one company | 25 | $0.043 |
| Default run | 200 | $0.305 |
| Weekly snapshot of the 60 built-in companies | 5,500 | $8.26 |
| 10,000 jobs | 10,000 | $15.005 |

**Bill-shock protection:**

- A run never saves more than `maxResults` jobs (default 200, so at most **$0.305 per run** on the Free plan). `RUN_SUMMARY.maxResultsReached` tells you when the cap was hit.
- It stops cleanly at your *maximum cost per run*: `RUN_SUMMARY.chargeLimitReached` is set and companies that were skipped are listed in `companiesNotProcessed`.
- Use `maxResultsPerCompany` so one large employer cannot use up the whole budget.
- Errors, `no_results` rows, duplicates and filtered-out jobs are never charged.

### FAQ

#### How do I find a company's ATS slug?

Open the company's careers page and click a job. The URL usually shows the ATS: `job-boards.greenhouse.io/<slug>`, `jobs.lever.co/<slug>`, `<slug>.recruitee.com`, `apply.workable.com/<slug>`, `<slug>.jobs.personio.de`, `jobs.ashbyhq.com/<slug>` or `jobs.smartrecruiters.com/<slug>`. You can also just paste the careers page URL and let the Actor find it.

#### Why did a company return an error?

The `error` field says why. Usually the company uses an ATS that is not supported (Workday, SuccessFactors, Taleo, their own system), the slug is wrong, or the board is private. Error rows are free.

#### Is the data live?

Yes. Every run reads the job boards at that moment. Schedule a task daily or weekly to track new postings; `jobId` is stable, so you can diff runs.

#### How accurate is the country?

Lever, Ashby, Recruitee, Workable and SmartRecruiters give structured locations. Greenhouse and Personio give free text, which is matched against country names, codes and more than 700 cities. Small towns without a country in the text can have `country: null`; the original `location` is always included.

#### Can I use it from an AI agent or automation?

Yes. Call it via the Apify API, the Apify MCP server, Make, Zapier, n8n or Google Sheets. Every field has a default.

### Is it legal?

The Actor only reads public job-board endpoints that companies publish so their vacancies can be shown and shared. It does not log in, does not bypass any protection and removes e-mail addresses from descriptions. Job postings are business information, but descriptions can be copyrighted: if you republish them, link back to the `applyUrl` and respect each company's terms.

Greenhouse, Lever, Recruitee, Workable, Personio, Ashby and SmartRecruiters are trademarks of their respective owners. This Actor is an independent tool by benelux-signals and is not affiliated with, endorsed by or sponsored by any of these companies or by the employers whose jobs it reads.

### Related actors by benelux-signals

- **EU Tender Radar**: new public tenders from TED and TenderNed.
- **Benelux Trade Fair Exhibitors**: exhibitor lists from Dutch and Belgian trade fairs.
- **Bulk PageSpeed & SEO Audit**: Core Web Vitals, Lighthouse and on-page SEO for many URLs.

# Actor input Schema

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

One company per line: a career-site / job-board URL (ATS auto-detected), "ats:slug" (e.g. recruitee:fastned) or a bare board slug (we probe every supported ATS). Supported: boards.greenhouse.io / job-boards.greenhouse.io, jobs.lever.co, \*.recruitee.com (and Recruitee custom domains), apply.workable.com, \*.jobs.personio.de/.com, jobs.ashbyhq.com, careers.smartrecruiters.com. Custom career pages are scanned for an embedded ATS. The 3 example companies are dropped automatically when you pick a seed list.

## `seedLists` (type: `array`):

Add our verified list of Benelux/EU tech employers for these ATSes (e.g. Adyen, Catawiki, Mollie, Miro, bunq, Fastned, Channable, KPN, N26, Spotify, DeepL, Thermondo …). Leave empty to use only your own companies. Tip: combine with Max results per company.

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

Keep jobs whose title contains at least one of these words (case-insensitive substring, e.g. "engineer", "data", "marketing"). Empty = all jobs.

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

Match keywords against the description too, not only the title. Broader but less precise.

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

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

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

Keep jobs located in these countries: ISO codes (NL, BE, DE) or names (Netherlands, Belgium). Special values: EU / Europe (all European countries), Benelux, DACH. With a European country, "Remote – Europe/EMEA" jobs are included too. Countries come from the ATS data and the location text (heuristic).

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

Keep jobs whose location text contains any of these strings (e.g. "Amsterdam", "Rotterdam", "Remote").

## `workplace` (type: `string`):

Filter by workplace type (from the ATS or detected in the location, e.g. "Remote").

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

Keep jobs whose department or team contains any of these strings (e.g. "Engineering", "Product", "Data").

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

Keep only these normalised employment types. Jobs without a type are dropped when this is set.

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

Only jobs published in the last N days (0 = any date). Jobs without a publish date are dropped when set.

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

Add the full job description as plain text (personal e-mail addresses removed). Turn off for smaller, faster exports.

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

Maximum number of jobs saved in this run (across all companies). This is also your cost cap: max cost = Max results × the per-job price + $0.005 start.

## `maxResultsPerCompany` (type: `integer`):

Cap per company (newest jobs first), so one large employer doesn't fill the whole run. 0 = no per-company cap.

## `maxConcurrency` (type: `integer`):

How many career sites are read at the same time.

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/adyen",
    "https://jobs.lever.co/spotify",
    "recruitee:bunq",
    "https://apply.workable.com/skroutz/",
    "https://thermondo.jobs.personio.de/",
    "https://jobs.ashbyhq.com/n8n",
    "https://careers.smartrecruiters.com/KPN"
  ],
  "seedLists": [
    "recruitee",
    "ashby"
  ],
  "keywords": [
    "engineer",
    "developer"
  ],
  "searchInDescription": false,
  "excludeKeywords": [
    "intern",
    "werkstudent"
  ],
  "countries": [
    "NL",
    "BE"
  ],
  "locations": [
    "Amsterdam",
    "Utrecht"
  ],
  "workplace": "any",
  "departments": [
    "Engineering",
    "Product"
  ],
  "employmentTypes": [
    "full-time"
  ],
  "postedWithinDays": 14,
  "includeDescription": true,
  "maxResults": 200,
  "maxResultsPerCompany": 25,
  "maxConcurrency": 4
}
```

# Actor output Schema

## `overview` (type: `string`):

All job postings in the 'overview' table view (title, company, location, country, workplace, department, employment type, posted date, salary, apply URL).

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

JSON summary (jobs saved, charged count, per-company stats, filtered-out counts, duplicates removed, chargeLimitReached and maxResultsReached flags) stored under RUN_SUMMARY.

# 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/catawiki",
        "https://fastned.recruitee.com/",
        "https://jobs.ashbyhq.com/mollie"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("benelux-signals/eu-career-site-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/catawiki",
        "https://fastned.recruitee.com/",
        "https://jobs.ashbyhq.com/mollie",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("benelux-signals/eu-career-site-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/catawiki",
    "https://fastned.recruitee.com/",
    "https://jobs.ashbyhq.com/mollie"
  ]
}' |
apify call benelux-signals/eu-career-site-jobs-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,benelux-signals/eu-career-site-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/cx6YaXBGiIMreUoEh/builds/pYxtWdze1rFOte0dK/openapi.json
