# EURES EU Job Listings Scraper (`automation-lab/eures-eu-job-listings-scraper`) Actor

Search public EURES jobs by keyword, country and occupation. Export stable vacancy IDs, descriptions, employers, location codes, schedules and dates for bounded cross-border labor-market snapshots.

- **URL**: https://apify.com/automation-lab/eures-eu-job-listings-scraper.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.36 / 1,000 item extracteds

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

## EURES EU Job Listings Scraper

Collect public **EURES jobs** for cross-border recruiting and labor-market research. Search by keyword, European country and ESCO occupation, then export a bounded snapshot with stable vacancy IDs, employer names, descriptions, source dates and classification codes.

This Actor uses the official EURES portal's anonymous search surface. No login, private account data or visa inference is included. It is an independent tool, not an EU service or endorsement.

### Who is it for?

- Researchers comparing public vacancies across European countries.
- Recruiting analysts building repeatable country-and-role samples.
- Data teams refreshing vacancy tables and joining snapshots by vacancy ID.

### Why use this Actor?

Receive consistent JSON records rather than parsing portal pages yourself. Stable IDs support downstream deduplication. Explicit item and page limits bound each snapshot. Source occupation, schedule and offering codes are preserved without guessing missing contract terms.

### Getting started

1. Enter a keyword, such as `engineer`.
2. Optionally choose country codes, such as `DE` or `FR`.
3. Set the maximum number of vacancies and search pages.
4. Run the Actor and inspect the Vacancies dataset tab.
5. Download JSON, CSV or Excel using Apify's dataset export tools.

```json
{
  "keyword": "engineer",
  "countries": ["DE"],
  "maxItems": 10,
  "maxPages": 10
}
```

### Inputs

| Parameter | Meaning |
| --- | --- |
| `keyword` | Optional text passed to EURES EVERYWHERE search. Up to 200 characters. |
| `countries` | Up to 30 European two-letter codes. Empty selects all available countries. |
| `occupationUris` | Up to 30 full ESCO occupation URIs, not occupation labels. |
| `maxItems` | Global unique-vacancy cap. Default 100, minimum 1, maximum 1,000. |
| `maxPages` | Global sequential search-page cap. Default 10, minimum 1, maximum 100. |

Keyword text is trimmed. EURES controls case handling, stemming and multiword interpretation; this is not a guaranteed exact-phrase search. Country values are uppercased, with `GR` mapped to EURES code `EL`. Values within country and occupation lists use ANY semantics; the distinct criteria combine to narrow the search.

Unsupported fields and malformed filters fail explicitly. There is no arbitrary URL input or proxy mode.

### Extracted data

| Field | Meaning |
| --- | --- |
| `vacancyId` | Stable EURES identifier, suitable for joining snapshots. |
| `url` | Official public EURES vacancy page. |
| `title` | Public vacancy title. |
| `description` | Description provided by search; source markup/entities may remain. |
| `employerName` | Public employer name, or null. |
| `countryCodes` | Countries from the source location map. |
| `locationCodes` | Available regional codes, excluding null entries. |
| `occupationCodes` | Source ESCO occupation identifiers. |
| `scheduleCodes` | Source work-schedule signals, such as `fulltime`. |
| `offeringCode` | Source position-offering code, such as `directhire`. |
| `createdAt` | Source creation timestamp, not necessarily original publication date. |
| `modifiedAt` | Source modification timestamp. |
| `numberOfPosts` | Source number of available positions, when supplied. |
| `languages` | Languages available for the vacancy text. |
| `scrapedAt` | Time this record was collected. |

Missing scalar values are null and missing code lists are empty. No missing field is inferred from nationality, title or employer.

### Output example

The field shape below reflects observed current source output; identifying text is illustrative.

```json
{
  "vacancyId": "RXhhbXBsZQ",
  "url": "https://europa.eu/eures/portal/jv-se/jv-details/RXhhbXBsZQ?lang=en",
  "title": "Software Engineer",
  "description": "Develop and maintain business applications.",
  "employerName": "Example Employer",
  "countryCodes": ["DE"],
  "locationCodes": [],
  "occupationCodes": ["http://data.europa.eu/esco/occupation/f2b15a0e-e65a-438a-affb-29b9d50b77d1"],
  "scheduleCodes": ["fulltime"],
  "offeringCode": "directhire",
  "createdAt": "2026-10-01T12:00:00.000Z",
  "modifiedAt": "2026-10-10T12:00:00.000Z",
  "numberOfPosts": 1,
  "languages": ["de"],
  "scrapedAt": "2026-10-20T12:00:00.000Z"
}
```

### How much does it cost to extract EURES jobs?

Pricing is pay per event: **$0.001 per run start** plus one event per unique vacancy saved. Empty searches incur only the start event. Duplicate records do not incur an item event.

| Apify spend tier | Price per vacancy |
| --- | ---: |
| FREE | $0.0026128 |
| BRONZE | $0.002272 |
| SILVER | $0.0017722 |
| GOLD | $0.0013632 |
| PLATINUM | $0.0013632 |
| DIAMOND | $0.0013632 |

At BRONZE, 10 vacancies cost about $0.02372, 100 cost $0.2282 and 1,000 cost $2.273, including one start. These are Actor-event estimates, not guaranteed invoices; applicable platform charges and account conditions appear in Apify. Spend tiers reflect qualifying aggregate monthly Store spend, not this Actor's per-run volume. Consult the Pricing tab for the applicable current tier.

### Pagination and limits

Each page requests up to 50 vacancies, or fewer when `maxItems` is smaller. Collection stops at the item limit, page limit or source exhaustion, whichever comes first. Zero does not mean unlimited; it is invalid.

A run is a bounded snapshot, not a complete historical archive. Live vacancies can change while pages are collected, so source pagination cannot guarantee a transactionally consistent census. IDs are deduplicated within a run only. Schedule separate runs and compare IDs and modification dates downstream for recurring research.

### Reliability and failure behavior

Network failures, rate limits and temporary server errors receive up to two retries per page. Permanent HTTP rejection, malformed responses and filter mismatches fail the run rather than silently producing an empty success. A naturally empty valid query succeeds with zero records.

A failed run can contain partial results. Inspect its status before treating exports as a complete requested snapshot. Runtime and billing limits may stop a run before its requested item count.

### Integrations

- Send exported vacancy rows to a spreadsheet for country and occupation analysis.
- Use Apify schedules for recurring snapshots, then compare stable IDs in your database.
- Connect dataset exports to Make, n8n, Zapier or a data warehouse.
- Retain source URLs and collection dates in research reports for attribution.

These are downstream workflow possibilities; the Actor does not send alerts, apply for jobs or maintain a cross-run change database itself. No AI model processes input or vacancy data during execution.

### API usage

Use an Apify token from your environment, never a hardcoded secret.

```bash
curl -X POST \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  'https://api.apify.com/v2/acts/automation-lab~eures-eu-job-listings-scraper/runs' \
  -d '{"keyword":"engineer","countries":["DE"],"maxItems":10}'
```

JavaScript with the Apify client:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/eures-eu-job-listings-scraper').call({
    keyword: 'engineer', countries: ['DE'], maxItems: 10,
});
const page = await client.dataset(run.defaultDatasetId).listItems({ limit: 10 });
console.log(page.items);
```

Python with the Apify client:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/eures-eu-job-listings-scraper').call(
    run_input={'keyword': 'engineer', 'countries': ['DE'], 'maxItems': 10})
page = client.dataset(run['defaultDatasetId']).list_items(limit=10)
print(page.items)
```

### Use with MCP

Select this Actor explicitly:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/eures-eu-job-listings-scraper"
```

For **Claude Desktop**, **Cursor**, and **VS Code** clients supporting HTTP MCP:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/eures-eu-job-listings-scraper"
    }
  }
}
```

Authenticate using your client's supported Apify authentication mechanism. Discover actual tool names and argument schemas with `tools/list`; selection also exposes helper tools for run status, dataset items, key-value records and aborting runs. Discovery alone does not authorize execution.

Example prompt: “Collect up to 10 public engineering vacancies in Germany and summarize their source occupation and schedule codes. Do not infer visa sponsorship.”

For an authorized invocation, start once and retain the returned run ID and storage IDs. `call-actor` can wait 0–45 seconds (default 30); a run response is metadata, not the result rows. If still running, follow that same ID with bounded 2/4/8-second backoff capped at 10 seconds and a 120-second total deadline. Set client request timeout above the server wait plus transport margin. On timeout, recover the known run rather than starting another. At deadline, report pending with its ID. Treat rows from failed runs as partial.

After success, fetch bounded dataset pages with explicit fields, offset and limit. For example, admit pages of 20 source rows, at most 100 rows and 64 KiB of serialized UTF-8 content into model context, whichever comes first. Enforce the byte cap host-side, disclose omitted oversized rows, and retain full exports outside model context. If your client cannot intercept oversized responses, do not claim a hard byte guarantee. Track source offsets and report truncation and continuation offset. These are consumer context limits, not Actor collection limits. Client discovery/caching capabilities vary; no universal token savings are promised.

### Legality and privacy

Use public vacancy data responsibly and comply with applicable source terms, licensing and privacy law. Public availability is not permission to republish personal information without a lawful basis. Descriptions can contain source-published contact information; this Actor does not separately enrich contacts or access private accounts.

Failed operations send sanitized diagnostic input, exceptions and actor/build/run IDs to our private GlitchTip service for repair. Secret fields and URL queries are removed; reports are retained for 30 days. Vacancy rows remain in your run's Apify storage under its configured retention settings; this Actor does not copy them into a separate database or establish a separate retention period. Delete datasets and logs using your Apify storage controls. Contact us through the Actor's Apify Issues tab for support. Runtime dependencies are the official EURES public portal, Apify hosting/storage and our private diagnostic service; no additional account or paid proxy is required.

### Limitations

- EURES coverage depends on participating employment services and source availability.
- Descriptions and classification fields are returned as supplied, not translated or enriched from detail pages.
- A position-offering code is not a guaranteed legal contract classification.
- No salary normalization, application submission, sponsorship inference or historical completeness.
- The vacancy URL points to EURES; a separate external application URL is not promised.

### FAQ and troubleshooting

**Why are there fewer results than requested?** The source may be exhausted, filters may be narrow, or `maxPages` may stop the snapshot first.

**Can I search for jobs for foreigners?** You can research public vacancies by country and occupation. The Actor does not determine eligibility, relocation support or visa sponsorship.

**Why did a run fail?** Check the run log for rejected input, upstream status or changed response shape. Partial datasets are not a successful complete snapshot.

**Are descriptions always English?** No. Available original languages are preserved; request context does not imply translation.

### Related Actors

For complementary regional vacancy research, consider [Canada Job Bank Scraper](https://apify.com/automation-lab/canada-job-bank-scraper) and [Built In Job Listings Scraper](https://apify.com/automation-lab/builtin-job-listings-scraper). They cover different sources, not additional EURES records.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/eures-eu-job-listings-scraper/changelog.md

# Actor input Schema

## `keyword` (type: `string`):

Optional trimmed search text passed to EURES EVERYWHERE search across public vacancy fields. Matching, stemming, case and multiword interpretation are controlled by EURES, not exact phrase or whole-field matching. Empty searches browse the selected countries and occupations.

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

Optional European two-letter country codes, matched with ANY semantics by EURES. Input is trimmed and uppercased; GR maps to EL. Country filters combine with keyword and occupation criteria. Empty means all available countries. Results are checked against the requested countries.

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

Optional full http://data.europa.eu/esco/occupation/<UUID> identifiers, with ANY semantics within this list. Combined with country and keyword criteria. Empty means no occupation restriction. Returned occupation codes must intersect this list; labels or free text are not supported here.

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

Global maximum unique vacancies written and charged per run, after ID deduplication. Default 100, range 1–1000; zero is invalid. May return fewer when maxPages, source exhaustion, billing or runtime limits stop collection. Does not imply full EURES coverage.

## `maxPages` (type: `integer`):

Global cap on sequential EURES search pages, each requesting up to 50 vacancies (or maxItems when smaller). Default 10, range 1–100. Collection stops earlier at maxItems or source exhaustion. Snapshot pagination is not historical or resumable; live changes can shift pages.

## Actor input object example

```json
{
  "keyword": "engineer",
  "countries": [
    "DE"
  ],
  "maxItems": 10,
  "maxPages": 10
}
```

# Actor output Schema

## `dataset` (type: `string`):

Unique public EURES vacancies collected in this run

# 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 = {
    "keyword": "engineer",
    "countries": [
        "DE"
    ],
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/eures-eu-job-listings-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 = {
    "keyword": "engineer",
    "countries": ["DE"],
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/eures-eu-job-listings-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 '{
  "keyword": "engineer",
  "countries": [
    "DE"
  ],
  "maxItems": 10
}' |
apify call automation-lab/eures-eu-job-listings-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/eures-eu-job-listings-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/wWdWYCtNbCcV3gi4v/builds/okV3VUbsgbE1NMsoc/openapi.json
