# EU Jobs Scraper: Workday, Greenhouse, Lever & Teamtailor Jobs (`jordydev/eu-job-search`) Actor

Search live jobs from 6,500+ European employers' career sites on Workday, Greenhouse, Lever, Ashby, Workable, Recruitee, Personio and Teamtailor. No company list needed: filter by keyword, country (NL, DE, UK, DACH, Nordics), remote and date. New-jobs alerts. $8 per 1,000 jobs.

- **URL**: https://apify.com/jordydev/eu-job-search.md
- **Developed by:** [Jordy](https://apify.com/jordydev) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.00 / 1,000 jobs

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

## EU Jobs Scraper: Workday, Greenhouse, Lever & Teamtailor Career Sites

**Search live jobs from 6,500+ European employers' own career sites by keyword, country and date, with no company list needed, for $8 per 1,000 jobs.**

Type a job title, pick a country (or a group like `BENELUX`, `DACH`, `NORDICS`) and a date range, and get one clean, identical row per job from Workday, Greenhouse, Lever, Ashby, Workable, Recruitee, Personio and Teamtailor. A built-in, versioned directory of employers hiring in Europe tells the Actor where to look; every job is then read **live from the employer's own board during your run**, so nothing is days old and closed jobs aren't returned.

### What can it do?

- ✅ **Search mode, no company list**: keyword + country + date in, matching jobs out. The directory is ranked by how many jobs each employer has in your countries.
- ✅ **8 career-site platforms in one schema**: Workday, Greenhouse, Lever, Ashby, Workable, Recruitee, Personio and Teamtailor.
- ✅ **European focus**: country codes, names and groups (`EU`, `EUROPE`, `BENELUX`, `DACH`, `NORDICS`, `UKI`) plus `REMOTE`; multi-location jobs list every country.
- ✅ **Live data, straight from the employer**: no stale index, no reposts from job boards or agencies.
- ✅ **Filters**: keywords (title or description), excluded keywords, posted within N days or since a date, remote/hybrid, employment type, ATS platform, employer name.
- ✅ **Daily job alerts**: new-jobs-only mode returns only jobs you haven't had before.
- ✅ **Company mode**: bring your own list of boards (any supported board works, listed or not).
- ✅ **Exports**: JSON, CSV, Excel, HTML, plus `jobs.csv` and a `RUN_SUMMARY`. Works with schedules, webhooks, Zapier, Make, Slack and Google Sheets.

### Use cases

- **Recruiters and agencies**: source roles in NL, BE, DE, the UK and the Nordics the day they're posted.
- **Niche job boards and newsletters**: fill a "data jobs in Benelux" or "remote product jobs in Europe" board with direct employer links.
- **Sales and lead generation**: find European companies hiring for roles that signal a need for your product.
- **Labour-market research**: count openings by role, country, platform or employer.
- **Job seekers**: a daily alert for one role in one country.

### How to use it

1. Click **Try for free**. The form is pre-filled with `data engineer`, `NL`, last 7 days, 50 jobs.
2. Change **Keywords**, **Countries** and **Posted within (days)**.
3. Optional: limit to certain **ATS platforms**, narrow employers with **Employer name contains**, or switch on **Remote only**.
4. Click **Start**. Jobs appear in the **Output** tab; download them or use `jobs.csv` from the run's storage.
5. For a daily alert, switch on **Only new jobs since last run** with a feed name, save the input as a **task** and add a **schedule**.

### Example input

```json
{
  "keywords": ["data engineer"],
  "countries": ["NL"],
  "postedWithinDays": 7,
  "maxJobs": 50
}
```

More examples:

```json
{ "keywords": ["product manager", "product owner"], "countries": ["DACH"], "remoteOnly": true, "postedSince": "14 days", "ats": ["workday", "personio"] }
```

```json
{ "keywords": ["account executive"], "countries": ["NORDICS"], "onlyNewJobs": true, "stateName": "nordic-ae" }
```

```json
{ "companies": ["greenhouse:adyen", "lever:mollie", "workday:philips/wd3/jobs-and-careers"], "countries": ["NL"] }
```

| Field | Default | What it does |
|---|---|---|
| `keywords` | – | Any of these words in the title (whole words, accent-insensitive). `searchInDescription` also checks descriptions. |
| `countries` | – | ISO codes, country names or groups (`EU`, `EUROPE`, `BENELUX`, `DACH`, `NORDICS`, `UKI`) and `REMOTE`. |
| `postedWithinDays` / `postedSince` | – | Last N days, or since a date (`2026-10-01`) or relative time (`2 weeks`). |
| `companies` | – | Your own list of boards (`ats:slug`, board URL, careers-page URL or name); skips the directory. Workday is `workday:tenant/wdN/site`. |
| `ats` | all | Only these platforms. |
| `companyQuery` | – | Only employers whose name contains one of these words. |
| `maxCompanies` | 400 | Search mode: how many directory employers to check at most. |
| `maxJobs` | 200 | Stop after this many jobs (you only pay for jobs returned). |
| `remoteOnly`, `includeHybrid` | false, true | Remote filter. |
| `onlyNewJobs`, `stateName` | false | New-jobs feed across runs. |
| `descriptionFormat` | text | `text`, `html`, `both` or `none`. |

#### Call it from the API

```bash
curl -X POST "https://api.apify.com/v2/acts/jordydev~eu-job-search/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keywords":["data engineer"],"countries":["NL"],"postedWithinDays":7,"maxJobs":50}'
```

### Example output

Real rows from test runs on 28 Oct 2026 (search `data engineer`, NL, last 7 days; and an `engineer` search on Workday in DE/NL):

| company | ats | title | location | country | remote | posted |
|---|---|---|---|---|---|---|
| Xebia | recruitee | Data Engineer (Scala) | Amsterdam, Noord-Holland, Netherlands | NL | hybrid | 2026-10-27 |
| Flow Traders | greenhouse | Core Data Engineer | Amsterdam | NL | – | 2026-10-23 |
| Jobgether | lever | Senior Software Engineer - Data Engineer | Netherlands | NL | remote | 2026-10-27 |
| Sulzer | workday | Application Engineer - Automation & Digital Solutions (w/m/d) | Bonn | DE | – | 2026-10-26 |
| BD (Becton Dickinson) | workday | Staff Electrical Engineer | Automation, Systems & Sustaining Engineering | DEU Kelberg - Rowastrasse | DE | – | 2026-10-20 |

One row as JSON (description left out):

```json
{
  "jobId": "recruitee:xebiacareers:2798656",
  "ats": "recruitee",
  "company": "Xebia",
  "title": "Data Engineer (Scala)",
  "location": "Amsterdam, Noord-Holland, Netherlands",
  "countryCode": "NL",
  "countryCodes": ["NL"],
  "remoteType": "hybrid",
  "employmentType": "full-time",
  "postedAt": "2026-10-27T10:20:04.000Z",
  "jobUrl": "https://xebiacareers.recruitee.com/o/data-engineer-scala",
  "detectedVia": "directory"
}
```

Every row has the same fields for every platform: `jobId` (stable, `ats:slug:id`), `ats`, `company`, `title`, `department`, `team`, `location`, `city`, `region`, `countryCode`, `countryCodes`, `isRemote`, `remoteType`, `employmentType`, `seniority`, `salaryMin/Max/Currency/Period/Text/Source` (only when the employer publishes pay), `postedAt`, `updatedAt`, `applyUrl`, `jobUrl`, `descriptionText` / `descriptionHtml`, `isNew`, `firstSeenAt`, `detectedVia`, plus a run envelope (`tool`, `status`, `checkedAt`).

### How much does it cost?

Pay per event: you only pay for jobs returned, not for Apify compute.

| Event | Price | When it's charged |
|---|---|---|
| Job | **$0.008** ($8 per 1,000 jobs) | One job row in your dataset |
| Company checked (new-jobs mode) | **$0.0005** | New-jobs-only mode only: one board checked, also when it has no new jobs |
| Actor start | $0.00005 | Apify's standard fee per run start (per GB of memory) |

**Free**: directory lookups, employers checked in search mode without new-jobs mode, boards not found, unsupported sources and fetch errors.

Examples:

- 50 data-engineering jobs in the Netherlands = **$0.40**.
- Daily alert over 100 employers returning ~10 new jobs = 100 × $0.0005 + 10 × $0.008 ≈ **$0.13 a day**.

Your maximum cost per run is respected: the Actor checks the remaining budget before every row and stops cleanly at the cap.

### The employer directory

| | |
|---|---|
| Employers | 6,548 (version `1`, verified 28 Oct 2026) |
| By ATS | Personio 1,495, Greenhouse 1,165, Recruitee 908, Teamtailor 903, Workable 795, Ashby 777, Lever 415, Workday 90 |
| Top countries (employers with jobs there) | DE 2,563, GB 2,327, NL 892, FR 783, ES 657, PL 532, SE 507, IT 385, IE 342, CH 316, BE 315, PT 292, AT 258, DK 219 |

Each entry holds the employer name, ATS, board slug and its job count per country when verified. It only steers the search; job data always comes live from the board. Sources: the [ATS Company Directory by LastRound AI](https://github.com/fyrosofttech/lastroundai-hiring-data) (CC BY 4.0), board URLs from the public [Common Crawl](https://commoncrawl.org) URL index, and boards checked by hand. Every board was re-checked live before inclusion.

### Supported career sites

| ATS | How jobs are read |
|---|---|
| Workday | The JSON service behind each employer's public Workday career site (the same calls its job search page makes). Each tenant's `robots.txt` is checked first; sites that disallow access are skipped. |
| Teamtailor | The public `jobs.rss` feed on every Teamtailor careers site, after a `robots.txt` check. |
| Greenhouse | Public Job Board API ([docs](https://developers.greenhouse.io/job-board.html)) |
| Lever | Postings API, global and EU host ([docs](https://github.com/lever/postings-api)) |
| Ashby | Public Job Posting API ([docs](https://developers.ashbyhq.com/docs/public-job-posting-api)) |
| Workable | Public endpoint behind Workable's jobs widget |
| Recruitee | Careers Site API ([docs](https://docs.recruitee.com/reference/offers)); from 10 Feb 2027 a per-company token is required, so other companies' boards may stop working then |
| Personio | Public XML feed ([docs](https://developer.personio.de/reference/get_xml)) |
| SmartRecruiters, LinkedIn, Indeed | Not supported: their terms or API policies forbid large-scale extraction |

### FAQ

**Do I need a list of companies?**
No. Leave **Companies** empty and the built-in directory is used. Add companies only for specific employers.

**Is this legal? Does it respect robots.txt?**
Jobs are read from public endpoints that employers use to publish vacancies widely. `robots.txt` is honoured for Workday and Teamtailor (RFC 9309; user agent `eu-job-search`) and disallowed sites are skipped. Requests to one host are spaced by `requestDelayMs` (default 500 ms) with back-off on 429/5xx. Some platforms' general website terms restrict automated access, so check the terms for your own use case. Terms and robots rules were checked on 28 Oct 2026; this is a technical summary, not legal advice.

**Is personal data collected?**
No applicant data is ever read. E-mail addresses and phone numbers in job descriptions are removed (`[email removed]`, `[phone removed]`).

**Why did a search return fewer jobs than `maxJobs`?**
Only directory employers are searched, and only `maxCompanies` of them (best matches first). Raise `maxCompanies`, widen the countries (e.g. `BENELUX`) or the date range, or add your own `companies`.

**How fresh are the jobs?**
Live: each board is read during your run. The directory only decides which boards to read.

**Can I get every job from one Workday site?**
Yes: `companies: ["workday:tenant/wd3/site"]` with a high `maxJobs`. Without keywords every listing needs a detail request, so large sites take a while.

**Do I get salaries?**
Only when the employer publishes pay (Greenhouse pay ranges, Ashby compensation, Lever salary range, Personio/Recruitee fields, or a clear range in the text). Never guessed.

### Related Actors

More tools by the same developer, all pay-per-event with the same clean output style:

- [Greenhouse & Lever Jobs Scraper (ATS Job Feed)](https://apify.com/jordydev/ats-job-feed): every open job from your own list of companies on Greenhouse, Lever, Ashby, Workable, Recruitee and Personio.
- [Bulk PageSpeed Insights & Core Web Vitals Checker](https://apify.com/jordydev/pagespeed-bulk-audit): Lighthouse scores, LCP/CLS/INP and top fixes for hundreds of URLs, with CSV and PDF report.
- [Website Screenshot & HTML to PDF](https://apify.com/jordydev/screenshot-pdf): full-page PNG/JPEG screenshots and PDFs of any URL or raw HTML, cookie banners removed.
- [Broken Link Checker & SEO Site Audit (Site QA Crawler)](https://apify.com/jordydev/site-qa-crawler): broken links, redirects, SEO basics, WCAG accessibility and SSL expiry in one crawl, with a fix list and PDF.

***

Related searches: EU jobs API, Europe jobs scraper, Netherlands jobs scraper, Germany jobs API, UK jobs feed, Workday jobs scraper, Workday careers API, Teamtailor jobs scraper, Greenhouse jobs API, Lever jobs scraper, Personio jobs feed, career site jobs API, new job alerts, hiring signals, data engineer jobs Netherlands.

# Actor input Schema

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

Keep jobs whose title contains at least one of these words or phrases, e.g. "data engineer", "python" (whole words, case- and accent-insensitive). Empty = all jobs.

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

Where the job is. ISO codes (NL, DE, GB…), country names (Netherlands) or groups: EU, EUROPE, BENELUX, DACH, NORDICS, UKI. Add REMOTE to also keep fully remote jobs without a country. Multi-location jobs match if any location is in these countries.

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

Keep jobs published (or, if the ATS has no publish date, updated) in the last N days. 0 = no limit.

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

Leave empty to search the built-in directory of European employers (search mode). Or list your own: "ats:slug" (greenhouse:airbnb, lever:spotify, ashby:ramp, workable:huggingface, recruitee:bunq, personio:tado, teamtailor:career, workday:nvidia/wd5/NVIDIAExternalCareerSite), a job-board URL (https://philips.wd3.myworkdayjobs.com/jobs-and-careers), a careers-page URL that embeds a board, or a company name to auto-detect.

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

Limit search mode to employers on these applicant tracking systems. Empty = all.

## `companyQuery` (type: `string`):

Only search directory employers whose name contains this text. Separate alternatives with commas, e.g. "bank, insurance".

## `maxCompanies` (type: `integer`):

Search mode checks the directory employers with the most open jobs in your countries (weighted by local focus) first, and stops at this many (or earlier when Max jobs is reached). Checking employers is free: you pay per job returned.

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

Stop after this many jobs. This is your cost cap: you pay per job returned.

## `postedSince` (type: `string`):

Keep jobs posted on or after this date, e.g. 2026-10-01 or "7 days". Combined with "Posted within (days)" the stricter one wins.

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

Keep only jobs marked remote (by the ATS workplace field or a "Remote" location).

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

Drop jobs whose title contains any of these, e.g. "intern", "senior".

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

Search the full job description as well as the title.

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

Keep jobs whose location text contains one of these, e.g. "Amsterdam", "Berlin", "remote". Empty = anywhere.

## `includeHybrid` (type: `boolean`):

With "Remote jobs only", also keep hybrid jobs.

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

Keep only these types. Empty = all (including jobs with an unknown type).

## `remote` (type: `boolean`):

Same as "Remote jobs only".

## `onlyNewJobs` (type: `boolean`):

Remember which jobs were already returned (in a named key-value store) and return only new ones. The first run returns everything and sets the baseline. Schedule the Actor daily for a new-jobs feed.

## `newOnly` (type: `boolean`):

Same as "Only new jobs since the last run".

## `onlyNewSinceLastRun` (type: `boolean`):

Same as "Only new jobs" (kept for compatibility with the spec name). If either is on, new-jobs mode is on.

## `stateName` (type: `string`):

Separate memory per feed. Use a different name for each saved task with different companies or filters.

## `descriptionFormat` (type: `string`):

Recruiter e-mail addresses and phone numbers are removed in all formats.

## `maxJobsPerCompany` (type: `integer`):

0 = automatic: no limit with your own company list; in search mode max(5, Max jobs / 5) so results come from several employers.

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

0 = automatic (8 in search mode, 4 otherwise). Requests to the same host are always spaced out, whatever this is.

## `autoDetectAts` (type: `boolean`):

For company names and careers-page URLs, find the board by probing each supported ATS with name-based slugs, or by scanning the careers page for a board link. Off = only "ats:slug" and board URLs are accepted.

## `requestDelayMs` (type: `integer`):

Politeness: minimum gap between two requests to the same API host.

## `personioLanguage` (type: `string`):

Language code for Personio XML feeds (en, de, …). Empty = the board's default.

## `recruiteeToken` (type: `string`):

Only for a single company's own Recruitee board. Recruitee announced that its Careers Site API will need a token from 10 Feb 2027; until then it is not needed.

## Actor input object example

```json
{
  "keywords": [
    "data engineer"
  ],
  "countries": [
    "NL"
  ],
  "postedWithinDays": 7,
  "ats": [],
  "maxCompanies": 400,
  "maxJobs": 50,
  "remoteOnly": false,
  "excludeKeywords": [],
  "searchInDescription": false,
  "locations": [],
  "includeHybrid": false,
  "employmentTypes": [],
  "remote": false,
  "onlyNewJobs": false,
  "newOnly": false,
  "onlyNewSinceLastRun": false,
  "stateName": "default",
  "descriptionFormat": "text",
  "maxJobsPerCompany": 0,
  "maxConcurrency": 0,
  "autoDetectAts": true,
  "requestDelayMs": 500,
  "personioLanguage": "en"
}
```

# Actor output Schema

## `jobs` (type: `string`):

No description

## `csv` (type: `string`):

No description

## `summary` (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 = {
    "keywords": [
        "data engineer"
    ],
    "countries": [
        "NL"
    ],
    "postedWithinDays": 7,
    "maxJobs": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("jordydev/eu-job-search").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": ["data engineer"],
    "countries": ["NL"],
    "postedWithinDays": 7,
    "maxJobs": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("jordydev/eu-job-search").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": [
    "data engineer"
  ],
  "countries": [
    "NL"
  ],
  "postedWithinDays": 7,
  "maxJobs": 50
}' |
apify call jordydev/eu-job-search --silent --output-dataset

```

## MCP server setup

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

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/thM1HVul6jg4rdMa5/builds/Mv4wqqqVhnbacIQAK/openapi.json
