# Intermediair Scraper: Every Vacancy Gets a Real Salary Estimate (`getascraper/intermediair-scraper`) Actor

Scrapes Intermediair.nl job vacancies (title, company, location, contract, description) and adds a real salary to every one: employer-disclosed, or a sourced market estimate when pay isn't listed. Standalone salary-intelligence dataset. 5-state monitor mode for tracking searches. No login needed.

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

## Pricing

from $0.99 / 1,000 vacancies

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?

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

## 💼 Intermediair Scraper: Vacancies With a Real Salary on Every Row

<table width="100%">
<tr>
<td style="padding:24px 28px;background:#EAF2FB;border:1px solid #B9D4EE;border-top:4px solid #1E5FA8;border-radius:12px">
<span style="font-size:23px;font-weight:800;color:#1C1917;line-height:1.3">Every vacancy gets a real salary, not just the ones that disclose one.</span><br>
<span style="font-size:15px;color:#57534E;line-height:1.6">Scrape Intermediair.nl job vacancies with title, company, location, contract, and full description, then close the biggest gap in this data: a sourced, labeled salary figure on every single row, either the employer's own number or a market-rate estimate pulled from Intermediair's own salary-benchmark pages.</span>
</td>
</tr>
</table>

<table width="100%">
<tr>
<td style="padding:14px 12px;width:25%;background:#FFFFFF;border:1px solid #B9D4EE;border-radius:10px 0 0 10px;vertical-align:top">
<span style="font-size:15px;font-weight:800;color:#154A85">💰 A salary on every row</span><br>
<span style="font-size:12px;color:#57534E">Employer-disclosed when available, a company/city/national market estimate when it isn't. Always clearly labeled which.</span>
</td>
<td style="padding:14px 12px;width:25%;background:#FFFFFF;border:1px solid #B9D4EE;border-left:none;vertical-align:top">
<span style="font-size:15px;font-weight:800;color:#154A85">📊 Vs. market comparison</span><br>
<span style="font-size:12px;color:#57534E">When a job discloses its own pay, see how it stacks up against the real market rate for that role.</span>
</td>
<td style="padding:14px 12px;width:25%;background:#FFFFFF;border:1px solid #B9D4EE;border-left:none;vertical-align:top">
<span style="font-size:15px;font-weight:800;color:#154A85">🏢 Salary Intelligence dataset</span><br>
<span style="font-size:12px;color:#57534E">Standalone role-by-company and role-by-city pay benchmarks, usable on their own.</span>
</td>
<td style="padding:14px 12px;width:25%;background:#FFFFFF;border:1px solid #B9D4EE;border-left:none;border-radius:0 10px 10px 0;vertical-align:top">
<span style="font-size:15px;font-weight:800;color:#154A85">🔄 5-state monitoring</span><br>
<span style="font-size:12px;color:#57534E">Track a saved search over time: new, updated, reappeared, unchanged, or expired.</span>
</td>
</tr>
</table>

***

### 🔍 What does Intermediair Scraper do?

[Intermediair](https://www.intermediair.nl) is a leading Dutch job board for professional and
management roles. This Actor scrapes its vacancy listings and returns clean, structured data for
every matching job, no account or login required.

Each vacancy comes back with title, company, location, contract type, education and career level,
working hours, posting and expiry dates, and the full job description. Where an employer already
published a direct contact for the role in the posting text itself, that's captured too.

The gap this closes: on Intermediair, only a portion of postings state their own salary. Every
other vacancy this Actor returns still gets a sourced pay figure, pulled from Intermediair's own
salary-benchmark pages and matched at the most specific level available: that exact role at that
exact company first, then that role in that city, then that role nationally. The field that
produced the number is always labeled, so an estimate is never mistaken for what the employer
actually offered.

***

### 👥 Who uses it?

**Recruiters and talent-sourcing teams** - "We need a clean feed of Dutch professional-market
roles we can filter by industry and city, without chasing down pay data manually for every
posting we shortlist."

**HR and compensation teams** - "Before we finalize a role's band, we check what similar
positions pay at comparable companies and in comparable cities. This gives us that comparison in
one pull instead of ten separate searches."

**Job seekers and candidates** - "A posting doesn't say what it pays. This tells me what the role
realistically pays at that company or in that city, so I know if it's worth applying, and what to
ask for."

**Buyers switching from another Intermediair scraper** - "We were only getting a salary figure on
about half our results. Same core fields we're used to, plus every row now carries a market-rate
number when the employer didn't share their own."

***

### 🚀 How to use it

<table width="100%">
<tr>
<td style="padding:16px 14px;width:33%;background:#EAF2FB;border:1px solid #B9D4EE;border-radius:10px 0 0 10px;vertical-align:top">
<span style="font-size:12px;font-weight:800;color:#1E5FA8;letter-spacing:1px">STEP 1</span><br>
<span style="font-size:14px;font-weight:700;color:#1C1917">Set your search</span><br>
<span style="font-size:12px;color:#57534E">Enter job title keywords, or filter by city, industry, province, contract type, or career level.</span>
</td>
<td style="padding:16px 14px;width:33%;background:#EAF2FB;border:1px solid #B9D4EE;border-left:none;vertical-align:top">
<span style="font-size:12px;font-weight:800;color:#1E5FA8;letter-spacing:1px">STEP 2</span><br>
<span style="font-size:14px;font-weight:700;color:#1C1917">Run in the cloud</span><br>
<span style="font-size:12px;color:#57534E">The Actor reads Intermediair's public pages directly. No login, no API key needed.</span>
</td>
<td style="padding:16px 14px;width:33%;background:#EAF2FB;border:1px solid #B9D4EE;border-left:none;border-radius:0 10px 10px 0;vertical-align:top">
<span style="font-size:12px;font-weight:800;color:#1E5FA8;letter-spacing:1px">STEP 3</span><br>
<span style="font-size:14px;font-weight:700;color:#1C1917">Download your data</span><br>
<span style="font-size:12px;color:#57534E">Export vacancies and salary intelligence as JSON, CSV, or Excel, or connect to Sheets and BigQuery.</span>
</td>
</tr>
</table>

Want a standing watch instead of a one-off pull? Turn on **Only return changed listings** and give
it a State Name. Every scheduled run after that reports only what actually moved.

***

### ⚙️ Input

| Field | Type | Required | Description |
|---|---|---|---|
| `keywords` | array of strings | No | Job title keywords to search for, e.g. `Data Engineer`. |
| `vacancyUrls` | array of URLs | No | Specific Intermediair vacancy URLs to scrape directly. |
| `city` | string | No | Only show vacancies in and around this Dutch city. |
| `radiusKm` | integer | No | City catchment radius. Mirrors Intermediair's own default city search behavior. Default: `40`. |
| `contractType` | string | No | Filter by contract type, e.g. `Vast` (permanent) or `Tijdelijk` (temporary). |
| `educationLevel` | string | No | Filter by required education level, e.g. `WO`, `HBO`, `MBO`. |
| `careerLevel` | string | No | Filter by career level, e.g. `Starter` or `Ervaren`. |
| `industry` | string | No | Which industry category to browse, e.g. `ict`, `finance`, `zorg`. Default: `ict`. |
| `province` | string | No | Filter by Dutch province. |
| `workingPlace` | enum | No | Best-effort match for `remote`, `hybrid`, or `on-site` against the posting text. |
| `proxyConfiguration` | proxy | No | Apify proxy settings. Residential proxy is the default: it connects far more reliably than datacenter IPs against this site's bot protection. |
| `includeDetails` | boolean | No | Re-fetch each vacancy's own page for the most complete record. Default: off. |
| `includeSalaryIntelligence` | boolean | No | Add market-rate salary estimates to every vacancy and a standalone benchmark dataset. Default: on. |
| `maxItems` | integer | No | Maximum number of vacancies to return. Default: `20`. |
| `onlyChangedListings` | boolean | No | Return only vacancies that are new, updated, reappeared, or expired since the last run. Default: off. |
| `stateName` | string | No | Identifies which saved search a monitoring run belongs to. Default: `default`. |
| `resetState` | boolean | No | Clears saved monitoring history for State Name before this run. Default: off. |

***

### 📤 Data table

This Actor produces two datasets: **Vacancies** (the default output) and **Salary Intelligence**
(when Add Market-Rate Salary Estimates is on).

#### Vacancies

| Field | Type | Description |
|---|---|---|
| `id` | string | Unique Intermediair vacancy ID. |
| `title` | string | Job title. |
| `url` | string | Link to the vacancy page. |
| `company` | string | Employer name. |
| `careerLevel` | string | Required career level. |
| `contractType` | string | Contract type. |
| `educationLevel` | string | Required education level. |
| `categories` | array | Job categories/function groups. |
| `city` | string | Work location city. |
| `province` | string | Work location province. |
| `hoursFormatted` | string | Working hours range as shown on the posting. |
| `publishedTimeAgo` | string | How long ago the vacancy was published, as shown on the site. |
| `startDate` | string | When the vacancy first went live. |
| `endDate` | string | When the vacancy listing expires. |
| `description` | string | Full job description. |
| `recruiterContact` | object | Recruiter name, email, and/or phone, only when the employer already published it directly on the posting. Omitted otherwise. |
| `salaryMin` | number | Employer-disclosed minimum pay. Omitted when not disclosed. |
| `salaryMax` | number | Employer-disclosed maximum pay. Omitted when not disclosed. |
| `salarySource` | enum | `employer`, `market_estimate`, or `unavailable`. Tells you exactly where the salary figure on this row came from. |
| `salaryEstimateMin` | number | Market-rate estimate, minimum. Present when the employer didn't disclose its own figure. |
| `salaryEstimateMax` | number | Market-rate estimate, maximum. |
| `salaryEstimateSource` | enum | `company`, `city`, or `national`: how specific the estimate is. |
| `salaryVsMarketPercent` | number | How the employer's own disclosed pay compares to the market estimate, when both exist. |
| `changeType` | enum | `NEW`, `UPDATED`, `REAPPEARED`, `UNCHANGED`, or `EXPIRED` when monitoring is on. |

#### Salary Intelligence

| Field | Type | Description |
|---|---|---|
| `jobTitle` | string | The job title this benchmark covers. |
| `nationalMin` | number | National average minimum pay for this role. |
| `nationalAverage` | number | National average pay for this role. |
| `nationalMax` | number | National average maximum pay for this role. |
| `topCompanies` | array | Companies paying the most for this role, each with its own pay range. |
| `topCities` | array | Cities paying the most for this role, each with its own pay range. |
| `yearlyTrend` | array | This role's average pay by year, where available. |
| `sourceUrl` | string | The Intermediair page this benchmark was read from. |

***

### 💰 Pricing

This Actor charges per vacancy returned. Salary Intelligence rows are included at no extra
charge. Empty runs cost nothing. There are no monthly subscriptions or seat fees.

***

### ⭐ Enjoying Intermediair Scraper?

<table width="100%">
<tr>
<td style="padding:20px 24px 14px;background:#EAF2FB;border:1px solid #B9D4EE;border-left:5px solid #1E5FA8;border-radius:10px 10px 0 0">
<span style="font-size:20px;letter-spacing:4px">⭐ ⭐ ⭐ ⭐ ⭐</span><br>
<span style="font-size:17px;font-weight:800;color:#1C1917">If a salary estimate helped you evaluate a role or an offer, we'd love to hear it.</span><br>
<span style="font-size:14px;color:#57534E">A 5-star rating takes 10 seconds and helps other recruiters, HR teams, and job seekers find it. Your feedback also tells us what to build next.</span>
</td>
</tr>
<tr>
<td style="padding:0;background:#1E5FA8;border:1px solid #B9D4EE;border-top:none;border-radius:0 0 10px 10px;text-align:center">
<a href="https://apify.com/getascraper/intermediair-scraper/reviews" style="display:block;padding:13px 16px;color:#FFFFFF;text-decoration:none;font-weight:800;font-size:15px;letter-spacing:0.3px">★&nbsp;&nbsp;Rate this Actor on Apify</a>
</td>
</tr>
</table>

***

### ❓ FAQ

**Does this Actor require an Intermediair account or API key?**
No. It reads the public Intermediair website directly. No registration and no API key are needed
to run it.

**Why do some vacancies show no employer-disclosed salary?**
Intermediair lets employers choose whether to publish a pay range. When they don't, `salaryMin`
and `salaryMax` are simply left out of that row rather than filled with a guess or a zero. The
row still gets a market-rate estimate in a separate set of fields whenever one can be sourced.

**How is the salary estimate different from the employer's own disclosed number?**
They're always kept in separate fields. `salaryMin`/`salaryMax` only ever reflect what the
employer itself published. `salaryEstimateMin`/`salaryEstimateMax` are a market-rate figure
sourced from Intermediair's own salary-benchmark pages, and `salarySource` tells you at a glance
which regime produced the number on that row.

**Is recruiter contact information always included?**
No, and it's never looked up separately. It's only captured when an employer already printed a
name, email, or phone number directly in the visible posting text. Most postings route applicants
through the site's own apply button instead, so this field is often absent, correctly.

***

### 🔗 Other actors

- [CWJobs Scraper: UK tech jobs, salaries & employers](https://apify.com/getascraper/cwjobs-scraper) ↗ - Scrape UK tech job listings with disclosed salary ranges and employer details.
- [Job Bank Canada Scraper & LMIA Job Monitor](https://apify.com/getascraper/jobbank-scraper) ↗ - Track Canadian government job board listings and LMIA-flagged postings.
- [EURAXESS Jobs Scraper: research vacancies that stay current](https://apify.com/getascraper/euraxess-jobs-scraper) ↗ - Scrape European research and academic job vacancies.
- [Ethosia Scraper: Israel Tech Jobs](https://apify.com/getascraper/ethosia-scraper) ↗ - Scrape Israeli tech job listings from a leading national job board.
- [Habr Career Scraper: Вакансии Хабр Карьера](https://apify.com/getascraper/habr-career-scraper) ↗ - Scrape Russian tech vacancies with real salary benchmarks by seniority level.

# Actor input Schema

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

Job titles to search for, e.g. "Data Engineer" or "Product Owner". Each keyword is matched against Intermediair's own job-title browse pages, so a keyword close to a real Dutch job title works best.

## `vacancyUrls` (type: `array`):

Direct links to specific Intermediair vacancy pages to scrape, bypassing search entirely. Useful for re-checking known listings or tracking a saved shortlist over time.

## `city` (type: `string`):

Only show vacancies in and around this Dutch city, e.g. "Amsterdam" or "Rotterdam". Leave blank to search all locations.

## `radiusKm` (type: `integer`):

How far from the chosen city to include vacancies. This mirrors Intermediair's own default catchment area for a city search (40 km) rather than a separately tunable site parameter, so changing it has no effect unless a city is also set.

## `contractType` (type: `string`):

Only keep vacancies with this contract type, e.g. "Vast" (permanent) or "Tijdelijk" (temporary). Matched against each vacancy's own listed contract type after it is scraped, so it can be combined freely with the other filters below.

## `educationLevel` (type: `string`):

Only keep vacancies requiring this education level, e.g. "WO" (university), "HBO" (applied sciences), or "MBO". Matched against each vacancy's own listed education requirement.

## `careerLevel` (type: `string`):

Only keep vacancies at this career level, e.g. "Starter" or "Ervaren" (experienced). Matched against each vacancy's own listed career level.

## `industry` (type: `string`):

Which Intermediair industry category to browse, e.g. "ict", "finance", or "zorg". This is the Actor's main discovery seed when no keywords are given, so it defaults to "ict" to keep a first run fast and populated with real vacancies.

## `province` (type: `string`):

Only show vacancies in this Dutch province, e.g. "Noord-Holland" or "Utrecht". Leave blank to search all provinces.

## `workingPlace` (type: `string`):

Best-effort keyword match for "remote", "hybrid", or "on-site" against the vacancy's own description text. Intermediair does not expose working arrangement as a structured filter anywhere on the site, so this is a text heuristic, not a precise site-side filter, and some genuinely-matching vacancies may be missed if the posting does not spell it out.

## `proxyConfiguration` (type: `object`):

Residential proxy by default: browser-TLS impersonation alone clears the target's Akamai/DPG Media WAF over a direct connection, but a real Apify Cloud run confirmed Apify's own datacenter proxy IPs get silently timed out by the same WAF, while residential IPs work reliably.

## `includeDetails` (type: `boolean`):

Re-fetches each vacancy's own dedicated page for the most authoritative, cross-checked record. Off by default because the search results already carry the full job description server-rendered, so this mainly matters if you want an extra completeness guarantee at the cost of one additional request per vacancy.

## `includeSalaryIntelligence` (type: `boolean`):

Fetches Intermediair's own salary-intelligence pages (national average, per-company pay, per-city pay, and year-over-year trend for each job title) and attaches a sourced market-rate salary estimate to every vacancy that did not disclose its own salary. This is the Actor's core value: on its own, Intermediair only shows a real salary figure on roughly half to two-thirds of postings. Kept on by default because it is cheap (a handful of role-level page fetches per run, not one per vacancy) and is what makes every row usable for pay comparison.

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

The maximum number of vacancies to return in this run. Kept modest by default so a first run finishes quickly; raise it once you know the search returns what you expect.

## `onlyChangedListings` (type: `boolean`):

When enabled, vacancies that are identical to the last run (UNCHANGED) are left out of the output entirely, so you only see what actually moved: NEW, UPDATED, REAPPEARED, or EXPIRED listings. Intended for scheduled monitoring runs rather than one-off searches.

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

Names the saved tracking state for this search, so you can run several independent monitors (e.g. one per keyword or city) without them overwriting each other's history. Reuse the same name across scheduled runs of the same search to keep its change history intact.

## `resetState` (type: `boolean`):

Clears the saved tracking history for this state name before this run, so every vacancy found is reported as NEW again. Use this once if a saved search's history needs a clean restart; leave off for normal scheduled runs.

## Actor input object example

```json
{
  "keywords": [],
  "vacancyUrls": [],
  "city": "",
  "radiusKm": 40,
  "contractType": "",
  "educationLevel": "",
  "careerLevel": "",
  "industry": "ict",
  "province": "",
  "workingPlace": "",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "includeDetails": false,
  "includeSalaryIntelligence": true,
  "maxItems": 20,
  "onlyChangedListings": false,
  "stateName": "default",
  "resetState": false
}
```

# Actor output Schema

## `results` (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": [],
    "vacancyUrls": [],
    "city": "",
    "radiusKm": 40,
    "contractType": "",
    "educationLevel": "",
    "careerLevel": "",
    "industry": "ict",
    "province": "",
    "workingPlace": "",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    },
    "includeDetails": false,
    "includeSalaryIntelligence": true,
    "maxItems": 20,
    "onlyChangedListings": false,
    "stateName": "default",
    "resetState": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("getascraper/intermediair-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 = {
    "keywords": [],
    "vacancyUrls": [],
    "city": "",
    "radiusKm": 40,
    "contractType": "",
    "educationLevel": "",
    "careerLevel": "",
    "industry": "ict",
    "province": "",
    "workingPlace": "",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
    "includeDetails": False,
    "includeSalaryIntelligence": True,
    "maxItems": 20,
    "onlyChangedListings": False,
    "stateName": "default",
    "resetState": False,
}

# Run the Actor and wait for it to finish
run = client.actor("getascraper/intermediair-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 '{
  "keywords": [],
  "vacancyUrls": [],
  "city": "",
  "radiusKm": 40,
  "contractType": "",
  "educationLevel": "",
  "careerLevel": "",
  "industry": "ict",
  "province": "",
  "workingPlace": "",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "includeDetails": false,
  "includeSalaryIntelligence": true,
  "maxItems": 20,
  "onlyChangedListings": false,
  "stateName": "default",
  "resetState": false
}' |
apify call getascraper/intermediair-scraper --silent --output-dataset

```

## MCP server setup

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