EU Salary Benchmark — Real Disclosed Salaries by Role & Country avatar

EU Salary Benchmark — Real Disclosed Salaries by Role & Country

Pricing

Pay per event

Go to Apify Store
EU Salary Benchmark — Real Disclosed Salaries by Role & Country

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

Studio Amba

Maintained by Community

Actor 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

  1. 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.
  2. 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.
  3. 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.
  4. Run the actor. All selected boards run in parallel, so adding countries doesn't multiply run time.
  5. Open the dataset. Each row is one role/country percentile report. Sort on disclosureRatePct to see where salary transparency is actually highest.
  6. Check RUN_SUMMARY in the key-value store for run-level stats: boards queried, boards failed, total disclosed datapoints.

Input

FieldTypeDescription
rolestringJob title/role to benchmark. Defaults to "software engineer".
countriesarrayFilter which country boards to query: DE, AT, NL, FR, GB, PL, ES, BE, IE. Empty means all 9.
maxPerBoardintegerMaximum postings to fetch per board before filtering to salary-disclosed ones (5-200, default 30).
includeDatapointsbooleanAttach the individual normalized salary observations behind each report as a nested array (default true).
timeoutPerBoardSecsintegerMax seconds to wait for each underlying board scraper before treating it as failed (30-600, default 120).
proxyConfigurationobjectProxy 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).

FieldTypeDescription
rolestringThe role this report was built for
countrystringISO2 country code, or EU for the pooled report
regionstringFull country name, or "European Union (pooled)" for the EU row
currencystringAlways EUR — everything is converted/annualized
sampleSizeintegerPostings with a disclosed salary this report is built from. Can be 0 — that's real data too
rawPostingsScannedintegerTotal postings fetched before filtering for disclosed salary
disclosureRatePctnumbersampleSize / rawPostingsScanned as a percentage
p25Eur / medianEur / p75EurnumberPercentiles in EUR/year. Null when sampleSize is 0
minEur / maxEurnumberLowest/highest annualized datapoint in the sample
boardsSampledarrayJob boards that contributed to this report
datapointsarrayThe individual normalized observations behind the percentiles (when includeDatapoints is on)
scrapedAtstringISO 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.

CountryBoardCurrencyTypical disclosure
Austria (AT)StepStone Austria + Karriere.atEURHigh — salary disclosure is legally mandated
Germany (DE)StepStone GermanyEURLow — no disclosure requirement
Netherlands (NL)StepStone NetherlandsEURModerate
France (FR)HelloWorkEURModerate
United Kingdom (GB)ReedGBP -> EURHigh — structured salary field on almost every listing
Poland (PL)Pracuj.plPLN -> EURModerate
Spain (ES)InfoJobsEURModerate
Belgium (BE)JobatEURLow
Ireland (IE)IrishJobsEURModerate — 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:

  1. Prefers structured fields (Reed's salaryFrom/salaryTo/salaryCurrency/salaryPeriod, HelloWork's and Pracuj's equivalents, StepStone's schema.org baseSalary) when the board exposes them.
  2. Falls back to parsing the raw text otherwise, detecting currency (/EUR, £/GBP, PLN/) and pay period from multilingual markers (jährlich/per annum/annuel = year; monatlich/per month/mois = month; stündlich/per hour/heure = hour).
  3. 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.
  4. Annualizes using 12 months/year, 2080 hours/year (40h x 52 weeks), 220 working days/year, 52 weeks/year.
  5. 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.
  6. Drops implausible datapoints — anything annualizing below €5,000/year or above €400,000/year is treated as a parsing error and excluded, not counted.
  7. 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.