# EURES Job Vacancy Scraper (`crawlerbros/eures-job-vacancy-scraper`) Actor

Scrape live job vacancies from EURES, the European Union's official cross-border job mobility portal. Search 2M+ jobs across 31 EEA countries by keyword, country, sector, contract type, schedule, education level and more, or fetch full details for specific job IDs.

- **URL**: https://apify.com/crawlerbros/eures-job-vacancy-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Automation, Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

## EURES Job Vacancy Scraper

Scrape live job vacancies from **EURES** — the European Union's official job mobility
portal for cross-border employment across the European Economic Area. EURES lists
**millions of open positions** across **31 countries** (all EU member states plus
Iceland, Liechtenstein, Norway and Switzerland), making it one of the largest public
job boards in Europe.

Use this actor to search jobs by keyword, country, business sector, contract type,
work schedule, education level, required experience or language, or to fetch full
details (description, employer, application link, required experience, locations)
for specific job vacancy IDs.

No login, cookies, or API key required — this actor uses EURES's own public search
API, so results are always fresh and directly sourced from the platform.

### What data can I get?

For every job vacancy, the actor returns as much of the following as EURES publishes
for that listing:

- **Job details** — title, full description, publication date, last modified date,
  number of open posts, occupation classification (ESCO)
- **Location** — country/countries, NUTS region code(s), city (detail mode), postal code
- **Employer** — name, website, sector(s), description
- **Terms** — contract type (direct hire, temporary, internship, etc.), work
  schedule (full-time/part-time/flexible), EURES cross-border flag, remote-work
  allowed flag (detail mode, when disclosed)
- **Requirements** — required years of experience, required education level,
  required skills (ESCO), required driving licenses, working languages and
  per-language required proficiency level (detail mode)
- **Salary & benefits** (detail mode, when disclosed) — minimum/maximum offered
  salary, currency, pay period (yearly/monthly/hourly) or a reference figure,
  plus any free-text salary description, a list of named extra benefits
  (accommodation included, meals included, relocation assisted, travel
  expenses, medical/dental/vision/life insurance, retirement plan, company
  car, etc.) and a separate free-text description of non-salary perks
- **Application** — direct application URL and instructions, application deadline
- **Contact** (detail mode) — recruiter/contact name, email, phone number and
  postal address when the employer publishes them
- **Links** — the canonical EURES job vacancy page URL

### Input

| Field | Type | Description |
|---|---|---|
| `mode` | select | `search` (default) — keyword + filter search. `byIds` — fetch specific job vacancies by ID. |
| `searchQuery` | string | Free-text keyword, e.g. `developer`, `nurse`, `warehouse`. |
| `searchIn` | select | Where to match the keyword: everywhere (title + description + employer), job title only, job description only, or employer name only. |
| `jobIds` | array | Job vacancy IDs to fetch directly (mode=`byIds`). |
| `countries` | array (select) | Restrict to job locations in one or more of the 31 EEA countries. |
| `regionCodes` | array | Restrict to job locations in one or more EU NUTS region codes for finer-grained geographic filtering than `countries`, e.g. `deb` (Berlin, Germany) or `no0b` (Trondelag, Norway). Combines with `countries` as a union. Copy region codes from the `nutsRegionCodes` field of a previous result. |
| `sectors` | array (select) | Restrict to one or more NACE Rev.2 business sectors. |
| `positionOfferingCodes` | array (select) | Restrict to contract types (direct hire, temporary, internship, apprenticeship, etc.). |
| `positionScheduleCodes` | array (select) | Restrict to full-time, part-time or flexible schedules. |
| `educationLevels` | array (select) | Restrict to required education level (basic to doctoral). |
| `experienceLevels` | array (select) | Restrict to required-experience bands. |
| `languageSkills` | array (select) | Restrict to jobs requiring specific languages. |
| `benefitCodes` | array (select) | Restrict to jobs that explicitly advertise specific extra benefits (accommodation, meals, relocation, travel expenses, insurance types, retirement plan, company car, etc.). Most listings don't disclose this, so it narrows results significantly. |
| `occupationUris` | array | Restrict to jobs classified under specific ESCO occupation URIs (copy from a previous result's `occupationUris` field). |
| `skillUris` | array | Restrict to jobs requiring specific ESCO skill URIs (copy from a previous `mode=byIds` result's `requiredSkills` field). |
| `euresFlagOnly` | boolean | Only return jobs explicitly flagged for cross-border EURES candidates. |
| `publicationPeriod` | select | Only jobs published within the last day / 3 days / week / month. |
| `minNumberOfPosts` | integer | Only return vacancies offering at least this many open posts (find bulk-hiring vacancies). |
| `sortBy` | select | Relevance (best match) or most recent. "Most recent" orders by each listing's last-synced timestamp in EURES's index (`lastModifiedDate`), which is usually the same as `publicationDate` but can differ when a source employment service re-syncs an unchanged listing. Most-recent ordering only applies when `searchQuery` is empty; a keyword search is always ordered by relevance to keep results on-topic. |
| `maxItems` | integer | Maximum number of records to return (1–2000). |

### Output

One dataset item per job vacancy. Example fields (omit-empty — only fields with
real data are included):

```json
{
  "jobId": "MzQwNDYwIDU",
  "title": "Software Developer",
  "description": "We are looking for an experienced developer...",
  "publicationDate": "2026-08-10T09:05:00+00:00",
  "numberOfPosts": 1,
  "euresFlag": true,
  "countries": ["de"],
  "countryNames": ["Germany"],
  "nutsRegionCodes": ["DE300"],
  "positionScheduleCodes": ["fulltime"],
  "positionOfferingCode": "directhire",
  "employerName": "Acme GmbH",
  "employerWebsite": "https://acme.example",
  "employerSectorCodes": ["j"],
  "recordType": "jobVacancy",
  "sourceUrl": "https://europa.eu/eures/portal/jv-se/jv-details/MzQwNDYwIDU?lang=en",
  "scrapedAt": "2026-08-10T09:10:00+00:00"
}
```

Every job vacancy also includes `nutsRegionCodes` — the EU NUTS statistical
region code(s) covering the job's location(s), e.g. `DE300` (Berlin) — useful
for regional aggregation beyond country level. Feed these codes back into the
`regionCodes` input filter to search a specific region directly.

When using `mode=byIds`, additional fields become available: `documentId` (the
source employment service's own internal document/reference number, when
different from `reference`), `locations` (with
postal codes), `postalCodes`, `workingLanguages`, `requiredYearsOfExperience`
(or `requiredExperienceDescription` — a free-text summary of the experience
requirement — when the source feed only provides text instead of a number),
`requiredEducationLevelCode` (raw ISCED-based education level code), `requiredSkills`
(ESCO skill URIs), `requiredLanguageSkills` (per-language required/desired CEFR
level), `drivingLicensesRequired`, `employmentPeriod`, `employmentStartDate`,
`employmentEndDate`, `applicationUrl`, `applicationInstructions`,
`applicationDeadline`, `employerLegalId`, `employerSizeCode` (employer's
organisation size band, e.g. `microenterprise`, `smallenterprise`,
`medium-sizedenterprise`, `largeenterprise`, when disclosed), `remoteWorkAllowed`
(when the employer has explicitly indicated whether remote work is allowed),
`immediateStart` (whether the employer indicated the position starts immediately),
`salaryMin`/`salaryMax` (or `salaryReference` when only a single figure is
given), `salaryCurrency`, `salaryPeriod` (e.g. `year`, `month`, `hour`),
`salaryDescription` (free-text pay notes, when the employer discloses pay
information), `benefits` (a list of named extra benefits the employer
explicitly ticked, e.g. `"Accommodation included"`, `"Travel expenses"`,
`"Medical insurance"` — corresponds to the `benefitCodes` search filter),
`benefitsDescription` (free-text non-salary perks such as meal
vouchers, company car or remote-work days, when disclosed), `contentLanguage`
(the language code the returned `title`/`description` text is actually
written in), `travelRequired` (whether the role involves travel, when the
employer specified it), `travelPercentage` and `travelDescription` (extra
detail on travel expectations, when disclosed) and — when the
employer publishes them — `contactName`, `contactEmail`, `contactPhone`,
`contactAddress`.

### FAQ

**Do I need an API key or to log in to EURES?**
No. EURES's job search is fully public — the actor uses the same public search
endpoint that powers the EURES website, with no authentication required.

**How many jobs can I scrape?**
EURES lists millions of active vacancies at any time. Use `maxItems` to cap the
number of records, and narrow with `searchQuery`, `countries`, `sectors` or other
filters to target the jobs you need.

**Can I get jobs from a specific country only?**
Yes — set `countries` to one or more of the 31 supported EEA country codes.

**Can I filter by a specific city or region, not just a whole country?**
Yes — set `regionCodes` to one or more EU NUTS region codes (found in the
`nutsRegionCodes` field of any previous result), e.g. `deb` for Berlin. This
narrows results below country level and combines with `countries` as a union.

**Can I filter by contract type or work schedule?**
Yes — use `positionOfferingCodes` (direct hire, temporary, internship, etc.) and
`positionScheduleCodes` (full-time, part-time, flexible).

**How do I get full job details including the direct application link?**
Run `mode=search` first to collect `jobId` values, then run `mode=byIds` with those
IDs to fetch full details including `applicationUrl` and `applicationDeadline`.

**Is the data up to date?**
Yes — every run queries EURES live; nothing is cached or scraped from a snapshot.

**Can I find vacancies that are hiring for many positions at once?**
Yes — set `minNumberOfPosts` to only return vacancies advertising at least that
many open posts (useful for finding bulk-hiring campaigns).

**Do results include a recruiter's contact details?**
When the employer publishes them, `mode=byIds` results include `contactName`,
`contactEmail`, `contactPhone` and `contactAddress` alongside the direct
`applicationUrl`.

**Can I tell if a job allows remote work?**
When the employer has explicitly indicated it, `mode=byIds` results include a
`remoteWorkAllowed` boolean field.

**Does the actor return salary information?**
When the employer discloses it, `mode=byIds` results include `salaryMin`/`salaryMax`
(or `salaryReference` for a single reference figure), `salaryCurrency` and
`salaryPeriod` (yearly, monthly or hourly). Not every listing publishes a salary —
EURES only exposes it when the source employer/job board provides it.

**Can I search only job descriptions or only employer names?**
Yes — set `searchIn` to `DESCRIPTION` to match only within the job description
text, or `EMPLOYER` to match only against the employer/company name (useful for
finding every open vacancy at a specific company).

**Why does `sortBy=MOST_RECENT` sometimes return a job with an older `publicationDate` before one that's newer?**
"Most recent" orders results by each listing's last-synced timestamp in EURES's own
index (mirrored in the output as `lastModifiedDate`), not by `publicationDate`.
National employment services periodically re-sync their feeds, which can refresh a
listing's sync timestamp without it being a genuinely new posting. `lastModifiedDate`
itself is always strictly newest-first under this sort order.

**Can I find jobs that offer specific benefits, like relocation or accommodation?**
Yes — set `benefitCodes` to one or more benefit types (accommodation included,
meals included, relocation assisted, travel expenses, medical/dental/vision/life
insurance, retirement plan, company car, etc.). Note that most EURES listings
don't disclose extra-benefit information at all, so this filter can narrow
results significantly; leave it empty to include all jobs regardless of
disclosed benefits. Matching benefits appear in `mode=byIds` results as the
`benefits` field.

**Does the actor say whether a job requires travel?**
When the employer specified it, `mode=byIds` results include a `travelRequired`
boolean, plus `travelPercentage` and a free-text `travelDescription` when the
employer gave more detail. Most listings don't disclose this.

**Can I filter by ESCO occupation or skill?**
Yes. Every job vacancy is classified against the EU's ESCO taxonomy. Run a search
first, copy an occupation URI from a result's `occupationUris` field (or a skill URI
from a `mode=byIds` result's `requiredSkills` field), then pass it into `occupationUris`
or `skillUris` on a follow-up run to find similar vacancies.

# Actor input Schema

## `mode` (type: `string`):

What to fetch.

## `searchQuery` (type: `string`):

Free-text keyword to search for (mode=search). Matches job title, description and employer name depending on `searchIn`.

## `searchIn` (type: `string`):

Where the search query is matched.

## `jobIds` (type: `array`):

EURES job vacancy IDs, as found in the `id` field of search results or in a job's detail URL, e.g. `MzQwNDYwIDU`.

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

Restrict results to job locations in these countries. Leave empty for all EEA countries.

## `regionCodes` (type: `array`):

Restrict results to job locations in these EU NUTS region codes (finer-grained than `countries`), e.g. `deb` for Berlin (Germany) or `no0b` for Trondelag (Norway). Find a region's code in the `nutsRegionCodes` output field of a previous run, or on the EURES advanced-search location picker. Combines with `countries` as an OR (union of matches).

## `sectors` (type: `array`):

Restrict results to these NACE Rev.2 business sectors.

## `positionOfferingCodes` (type: `array`):

Restrict results to these contract/position offering types.

## `positionScheduleCodes` (type: `array`):

Restrict results to these work schedules.

## `educationLevels` (type: `array`):

Restrict results to these required education levels.

## `experienceLevels` (type: `array`):

Restrict results to these required-experience bands.

## `languageSkills` (type: `array`):

Restrict results to jobs requiring at least basic proficiency in these languages.

## `occupationUris` (type: `array`):

Restrict results to jobs classified under these ESCO occupation URIs. Copy values from the `occupationUris` field of a previous search result, e.g. `http://data.europa.eu/esco/occupation/f2b15a0e-e65a-438a-affb-29b9d50b77d1`.

## `skillUris` (type: `array`):

Restrict results to jobs requiring these ESCO skill URIs. Copy values from the `requiredSkills` field of a previous `mode=byIds` result, e.g. `http://data.europa.eu/esco/skill/ccd0a1d9-afda-43d9-b901-96344886e14d`.

## `benefitCodes` (type: `array`):

Restrict results to jobs that explicitly advertise these extra (non-salary) benefits. Note: most EURES listings do not disclose extra-benefit information at all, so this filter narrows results significantly -- leave empty to include all jobs regardless of disclosed benefits.

## `euresFlagOnly` (type: `boolean`):

Only return jobs explicitly flagged as cross-border EURES vacancies (employer welcomes candidates from other EEA countries).

## `publicationPeriod` (type: `string`):

Only return jobs published within this time window.

## `minNumberOfPosts` (type: `integer`):

Only return job vacancies offering at least this many open posts/positions (useful for finding bulk-hiring vacancies).

## `sortBy` (type: `string`):

Order of returned results. `MOST_RECENT` orders by each listing's last-synced/last-modified timestamp in EURES's own index (output field `lastModifiedDate`), which usually but not always matches `publicationDate` -- national employment services periodically re-sync unchanged listings, which refreshes this timestamp without a new posting. Note: when `searchQuery` is set, results are always ordered by relevance (best match) regardless of this setting, since EURES cannot reliably combine keyword search with most-recent ordering.

## `maxItems` (type: `integer`):

Hard cap on emitted records.

## Actor input object example

```json
{
  "mode": "search",
  "searchQuery": "developer",
  "searchIn": "EVERYWHERE",
  "jobIds": [],
  "countries": [],
  "regionCodes": [],
  "sectors": [],
  "positionOfferingCodes": [],
  "positionScheduleCodes": [],
  "educationLevels": [],
  "experienceLevels": [],
  "languageSkills": [],
  "occupationUris": [],
  "skillUris": [],
  "benefitCodes": [],
  "euresFlagOnly": false,
  "publicationPeriod": "",
  "sortBy": "BEST_MATCH",
  "maxItems": 50
}
```

# Actor output Schema

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

Dataset containing all scraped EURES job vacancies.

# 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 = {
    "mode": "search",
    "searchQuery": "developer",
    "searchIn": "EVERYWHERE",
    "jobIds": [],
    "countries": [],
    "regionCodes": [],
    "sectors": [],
    "positionOfferingCodes": [],
    "positionScheduleCodes": [],
    "educationLevels": [],
    "experienceLevels": [],
    "languageSkills": [],
    "occupationUris": [],
    "skillUris": [],
    "benefitCodes": [],
    "euresFlagOnly": false,
    "publicationPeriod": "",
    "sortBy": "BEST_MATCH",
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/eures-job-vacancy-scraper").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 = {
    "mode": "search",
    "searchQuery": "developer",
    "searchIn": "EVERYWHERE",
    "jobIds": [],
    "countries": [],
    "regionCodes": [],
    "sectors": [],
    "positionOfferingCodes": [],
    "positionScheduleCodes": [],
    "educationLevels": [],
    "experienceLevels": [],
    "languageSkills": [],
    "occupationUris": [],
    "skillUris": [],
    "benefitCodes": [],
    "euresFlagOnly": False,
    "publicationPeriod": "",
    "sortBy": "BEST_MATCH",
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/eures-job-vacancy-scraper").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 '{
  "mode": "search",
  "searchQuery": "developer",
  "searchIn": "EVERYWHERE",
  "jobIds": [],
  "countries": [],
  "regionCodes": [],
  "sectors": [],
  "positionOfferingCodes": [],
  "positionScheduleCodes": [],
  "educationLevels": [],
  "experienceLevels": [],
  "languageSkills": [],
  "occupationUris": [],
  "skillUris": [],
  "benefitCodes": [],
  "euresFlagOnly": false,
  "publicationPeriod": "",
  "sortBy": "BEST_MATCH",
  "maxItems": 50
}' |
apify call crawlerbros/eures-job-vacancy-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlerbros/eures-job-vacancy-scraper"
        }
    }
}

```

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/yDYPn2xfPUALsKWgG/builds/SyfkiVrpoA4UTaN59/openapi.json
