EU Salary Benchmark — Real Disclosed Salaries by Role & Country
Pricing
Pay per event
EU Salary Benchmark — Real Disclosed Salaries by Role & Country
Runs our own StepStone (DE/AT/NL), Karriere.at, HelloWork, Reed, Pracuj, InfoJobs, Jobat and IrishJobs scrapers, keeps only postings with a disclosed salary, converts to annual EUR, and reports p25/median/p75 per country plus the raw datapoints.
Pricing
Pay per event
Rating
0.0
(0)
Developer
Studio Amba
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
EU Salary Benchmark
Give it a role and a list of European countries. This actor runs our own StepStone (Germany, Austria, Netherlands), Karriere.at, HelloWork, Reed, Pracuj, InfoJobs, Jobat and IrishJobs scrapers, keeps only the postings that disclose a salary, converts every disclosed figure to annual EUR, and reports p25/median/p75 per country — plus one pooled EU row when you query more than one country.
It does not scrape a website itself. It fans out to our own job-board scrapers, normalizes each board's salary field (structured min/max or raw text, different currency, different pay period), drops the postings that don't publish a number at all, and computes real percentiles from what's left.
Why use this actor?
Most salary "benchmark" data online is self-reported survey data (Glassdoor, Levels.fyi) or aggregated by a vendor with no visible methodology. This actor works from job postings that literally state a number — you can click through to every datapoint behind a percentile and see the exact listing it came from.
- Recruiters and hiring managers benchmarking an offer against what's actually posted in the market right now, not a survey average from six months ago.
- Job seekers checking whether a range they were offered is above or below p25/median/p75 for the same role in the same country.
- Compensation and People teams doing a quick market check across multiple countries in one call instead of manually reading job boards.
- Labor-market researchers studying salary disclosure itself — this actor surfaces the disclosure rate as a first-class output, not a footnote.
Disclosure rate is the story, not a footnote
Salary transparency varies enormously by country and is not evenly distributed. Austria is the standout: a 2023 law requires job ads to state a salary range, so both StepStone Austria and Karriere.at show disclosure rates well above 50% in most runs. Germany has no such requirement and disclosure on StepStone Germany is typically in the single digits. This actor doesn't paper over that — every report row includes rawPostingsScanned and disclosureRatePct, and a country with 0 disclosed postings still gets a row with sampleSize: 0 rather than being silently dropped. That absence is itself the finding.
How to scrape European salary data
- Enter a Role — a job title (e.g.
software engineer,accountant,data analyst). It's passed as-is to every selected board's own search. - Optionally set Countries to restrict which boards run. Leave empty to query all 9. Austria runs two boards (StepStone + Karriere.at) since it has by far the best disclosure rate.
- Set Max Per Board higher for a bigger, more reliable sample — remember only a fraction of postings disclose a salary at all, so the useful sample is smaller than this number.
- Run the actor. All selected boards run in parallel, so adding countries doesn't multiply run time.
- Open the dataset. Each row is one role/country percentile report. Sort on
disclosureRatePctto see where salary transparency is actually highest. - Check
RUN_SUMMARYin the key-value store for run-level stats: boards queried, boards failed, total disclosed datapoints.
Input
| Field | Type | Description |
|---|---|---|
role | string | Job title/role to benchmark. Defaults to "software engineer". |
countries | array | Filter which country boards to query: DE, AT, NL, FR, GB, PL, ES, BE, IE. Empty means all 9. |
maxPerBoard | integer | Maximum postings to fetch per board before filtering to salary-disclosed ones (5-200, default 30). |
includeDatapoints | boolean | Attach the individual normalized salary observations behind each report as a nested array (default true). |
timeoutPerBoardSecs | integer | Max seconds to wait for each underlying board scraper before treating it as failed (30-600, default 120). |
proxyConfiguration | object | Proxy settings passed to the underlying board scrapers. Leave default — each board otherwise falls back to its own known-good country-pinned residential proxy. |
Example input
{"role": "software engineer","countries": ["AT", "DE"],"maxPerBoard": 30}
Output
Each row is one percentile report for a role in one country (or the pooled EU row).
| Field | Type | Description |
|---|---|---|
role | string | The role this report was built for |
country | string | ISO2 country code, or EU for the pooled report |
region | string | Full country name, or "European Union (pooled)" for the EU row |
currency | string | Always EUR — everything is converted/annualized |
sampleSize | integer | Postings with a disclosed salary this report is built from. Can be 0 — that's real data too |
rawPostingsScanned | integer | Total postings fetched before filtering for disclosed salary |
disclosureRatePct | number | sampleSize / rawPostingsScanned as a percentage |
p25Eur / medianEur / p75Eur | number | Percentiles in EUR/year. Null when sampleSize is 0 |
minEur / maxEur | number | Lowest/highest annualized datapoint in the sample |
boardsSampled | array | Job boards that contributed to this report |
datapoints | array | The individual normalized observations behind the percentiles (when includeDatapoints is on) |
scrapedAt | string | ISO timestamp of collection |
Example output
{"role": "software engineer","country": "AT","region": "Austria","currency": "EUR","sampleSize": 10,"rawPostingsScanned": 16,"disclosureRatePct": 62.5,"p25Eur": 42000,"medianEur": 49200,"p75Eur": 54543,"minEur": 32729,"maxEur": 60000,"boardsSampled": ["StepStone Austria", "Karriere.at"],"scrapedAt": "2026-07-20T08:38:55.077Z"}
Board roster
Only boards whose own dataset exposes a salary field are included — Jobs.ch, JobUp.ch, Jobindex and EURES were dropped from this roster because they don't scrape salary at all and can never contribute a datapoint.
| Country | Board | Currency | Typical disclosure |
|---|---|---|---|
| Austria (AT) | StepStone Austria + Karriere.at | EUR | High — salary disclosure is legally mandated |
| Germany (DE) | StepStone Germany | EUR | Low — no disclosure requirement |
| Netherlands (NL) | StepStone Netherlands | EUR | Moderate |
| France (FR) | HelloWork | EUR | Moderate |
| United Kingdom (GB) | Reed | GBP -> EUR | High — structured salary field on almost every listing |
| Poland (PL) | Pracuj.pl | PLN -> EUR | Moderate |
| Spain (ES) | InfoJobs | EUR | Moderate |
| Belgium (BE) | Jobat | EUR | Low |
| Ireland (IE) | IrishJobs | EUR | Moderate — some listings explicitly say "Not Disclosed" |
Salary normalization assumptions
Every board formats salary differently — some give structured min/max numbers with a currency and pay period, most just show raw text. This actor:
- Prefers structured fields (Reed's
salaryFrom/salaryTo/salaryCurrency/salaryPeriod, HelloWork's and Pracuj's equivalents, StepStone's schema.orgbaseSalary) when the board exposes them. - Falls back to parsing the raw text otherwise, detecting currency (
€/EUR,£/GBP,PLN/zł) and pay period from multilingual markers (jährlich/per annum/annuel= year;monatlich/per month/mois= month;stündlich/per hour/heure= hour). - Assumes a documented default pay period per market when neither a structured field nor a text marker names one: DE/AT/NL/BE = monthly gross (DACH/Benelux convention), FR/ES/GB/IE = annual gross (their listings, when they state a period at all, are almost always annual), PL = monthly gross.
- Annualizes using 12 months/year, 2080 hours/year (40h x 52 weeks), 220 working days/year, 52 weeks/year.
- Converts to EUR using live ECB rates (via Frankfurter, refreshed every run) for GBP and PLN, with an approximate fallback rate if the live fetch fails so a network hiccup never kills the run.
- Drops implausible datapoints — anything annualizing below €5,000/year or above €400,000/year is treated as a parsing error and excluded, not counted.
- When a posting gives a min and a max, the midpoint is used as the single representative value.
One important caveat for Austria: many Austrian postings state "ab X monatlich" (from X per month) — that's the legal minimum the employer is required to disclose under the KV (collective agreement), not necessarily what they'll actually pay. Real offers are often higher. Treat the Austrian percentiles as a documented floor, not a ceiling.
Boards behind this actor
Each board also runs standalone if you need full-catalogue coverage or vacancy details beyond salary:
See also EU Vacancy Feed for a broader 13-board vacancy aggregator (all postings, deduplicated, salary included when disclosed but not the focus).
FAQ
Does this actor scrape the job boards itself? No. It calls our own StepStone, Karriere.at, HelloWork, Reed, Pracuj, InfoJobs, Jobat and IrishJobs scrapers and filters/normalizes their output.
Why is sampleSize 0 for some countries?
Most European job boards don't publish salary — it's negotiated after contact. A sampleSize: 0 row is a real, correct result for a low-disclosure market (Germany, Belgium) run with a small maxPerBoard. Raise maxPerBoard for a bigger raw sample, but the ceiling is the market's real disclosure rate, not this actor.
Why did a board return 0 raw postings?
Job board anti-bot protection is intermittent — a board can 403 one run and work fine the next (Jobat in particular). A single failing board never blocks the run; check boardsFailed in RUN_SUMMARY.
How is the "EU" pooled row different from a country row? It's the percentiles computed across every disclosed datapoint from every queried country in one run, not an average of the country medians. It only appears when 2+ countries were queried.
Can I get the raw postings instead of just the percentiles?
Yes — turn on includeDatapoints. Each report row then includes a datapoints array with the board, title, company, location, raw salary text and the URL for every posting that fed the percentiles.
Limitations
- Salary figures are converted at the FX rate active when the actor ran, not the rate at posting time.
- Pay-period detection falls back to a documented market convention when a board gives neither a structured period field nor a text marker — see "Salary normalization assumptions" above.
- Sample sizes are small by nature (this is filtered, disclosed-salary data, not the full job market) — treat single-digit samples as directional, not statistically robust.
- Data is scraped from public job board listings and may change without notice.