EU Pay Transparency Job Ad Auditor: pay and pay-history checks avatar

EU Pay Transparency Job Ad Auditor: pay and pay-history checks

Pricing

from $5.00 / 1,000 posting auditeds

Go to Apify Store
EU Pay Transparency Job Ad Auditor: pay and pay-history checks

EU Pay Transparency Job Ad Auditor: pay and pay-history checks

Audits live job postings against Directive (EU) 2023/970 Article 5: does the advert state a pay level or range, and does it ask for the applicant's pay history. Reads public boards on Greenhouse, Lever, Ashby, Personio, Workable, SmartRecruiters and Recruitee, plus career pages.

Pricing

from $5.00 / 1,000 posting auditeds

Rating

0.0

(0)

Developer

Jessy Mariau

Jessy Mariau

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

3 hours ago

Last modified

Categories

Share

EU Pay Transparency Job Ad Auditor

Directive (EU) 2023/970 came with a transposition deadline of 7 June 2026, and Article 5 put two duties on the hiring side of it. An employer has to tell an applicant the initial pay level or its range before the interview, and an employer must not ask an applicant what they earn now or earned before. Most member states were late transposing it, so the rules landed at different times in different places, and a lot of live job adverts have not caught up.

I built this because checking that by hand is slow work. You open forty adverts and read every one. For each advert you note whether a number appears anywhere in it. Then you note whether the application asks what the candidate currently earns. Then you type all of that into a spreadsheet. Point this Actor at a company's job board instead and you get one row per posting, with the exact sentence every finding was matched on, so you can check the machine rather than trust it.

What it does

You give it job boards, career pages, single posting URLs, or any mix. It reads the postings and works out three things for each one.

Is pay stated? PROVIDED when there is an actual figure or range, VAGUE when the advert only gestures at pay with wording like competitive salary or à négocier or leistungsgerechte Vergütung, and MISSING when there is nothing. Figures are read from a structured field where the applicant tracking system publishes one, from JSON-LD baseSalary on a career page, and from the advert text otherwise. Text detection handles European number formats and currencies: 45.000 – 55.000 €, €45k-55k, 45 000 EUR brutto/Jahr, 25 000 - 32 000 zł miesięcznie, 450 000 kr per år. Vague wording is matched in English, German, French, Spanish, Italian, Dutch and Polish.

Does it ask for pay history? That is the Article 5(2) point, and it is kept strictly apart from asking what salary someone wants. "Gehaltsvorstellung", "prétentions salariales" and "salary expectations" are recorded in their own field and are never counted as a breach, because the directive bans asking about pay history, not pay expectations. A payroll job that lists "Gehaltsabrechnung" as a duty is not asking anyone for a payslip either, so document wording only counts when the advert is asking the applicant to send one.

Is the posting in scope? Country comes from the posting's own location field where it has one, from JSON-LD jobLocation, or from the location text. EU-27 plus Iceland, Liechtenstein and Norway are in scope. A role advertised across the Union with no single country is kept as EU rather than thrown away.

Those three combine into a verdict: OK when pay is stated and no pay history is asked for, ATTENTION when the wording is vague or pay is stated but history is asked for, LIKELY_NON_COMPLIANT when an in-scope posting states no pay at all, OUT_OF_SCOPE for a posting outside the EU and EEA, and UNREADABLE when the posting was fetched but carried no usable text.

Seven applicant tracking systems are read through their own public postings APIs:

  • Greenhouse
  • Lever
  • Ashby
  • Personio
  • Workable
  • SmartRecruiters
  • Recruitee

None of them needs a key or a login, and there is no proxy or browser anywhere in the chain.

What you need

Nothing. Every source is a public endpoint.

There is one optional field. If you paste an OpenAI key, postings where the rules found no pay wording at all get a second look from a model, which catches unusual phrasing and languages the lexicon does not cover. It never overrides a figure the rules already found, and rows it decided are marked pay_source: "llm" so you can tell them apart. Leave it empty and the audit runs in full.

Run it with no sources at all and you get three sample rows in demo mode, nothing charged, so you can see the shape before you point it at anything.

How to run it

Audit a few company boards:

{
"atsBoards": "greenhouse:getyourguide\nlever:qonto\npersonio:orderbird\nrecruitee:channable\nworkable:skroutz",
"maxPostingsPerSource": 50
}

The identifier after the colon is whatever that system puts in the board URL: the Greenhouse board token, the Lever site name, the Ashby job board name, the Personio subdomain, the Workable subdomain, the SmartRecruiters company identifier, the Recruitee company name.

Audit a career page and a single advert:

{
"careerPageUrls": "https://jobs.channable.com/",
"jobUrls": "https://jobs.channable.com/o/apar-specialist-join-our-finance-team",
"maxPostingsPerSource": 25
}

On a career page, JobPosting JSON-LD is read where the page publishes it, and job links on the page are followed where it does not.

The OUTPUT record in the key-value store holds the run summary: how many postings were audited and charged, the count for each verdict, a per-employer breakdown with the postings that ask for pay history listed out, and any source that returned nothing along with why.

Output fields

FieldWhat it holds
employer, title, urlthe company, the role, the live advert
sourcegreenhouse, lever, ashby, personio, workable, smartrecruiters, recruitee, or page
verdictOK, ATTENTION, LIKELY_NON_COMPLIANT, OUT_OF_SCOPE or UNREADABLE
pay_disclosedPROVIDED, VAGUE or MISSING
pay_min, pay_max, currency, periodthe parsed figures, ISO currency, and year, month, week, day or hour
pay_sourceats, json-ld, text or llm: where the pay finding came from
pay_evidencethe exact text the pay finding was matched on
asks_pay_history, history_evidencewhether the advert asks what the applicant earns or earned, and the sentence
asks_pay_expectations, expectations_evidencewhether it asks what they want, reported separately and never a breach
location_country, in_scope_eu_eea, remote, locationISO 3166-1 alpha-2 or EU, whether the directive applies, and the location as advertised
date_posted, department, employment_typeas published by the source
checked_at, errorwhen the row was produced, and why a posting could not be read

Pricing

Pay per event: one posting-audited charge per posting that was fetched and audited. A posting that came back with no usable text costs nothing, a board that 404s costs nothing, and a source that returns an error costs nothing. Demo mode is free.

Limits

This is a text audit of public job adverts. It is not legal advice, and it does not tell you whether an employer is breaking the law.

Three things worth being clear about. National rules vary, because member states transposed the directive at different times and some went further than the minimum, so what a given advert has to say depends on where the job is. The pay-range duty can be met before the interview rather than in the advert itself, so an employer who tells candidates the range on the first call is complying while their advert still reads as MISSING here. And a MISSING verdict means one thing only: the posting itself does not state pay. Treat it as a place to look, not a finding.

Two practical limits on sources. Teamtailor is not supported, because its jobs API needs an API key issued per company; give the career page URL instead. Personio's public XML feed often ships with the description field empty, and its job pages sit behind a bot challenge, so a Personio board can come back UNREADABLE even though the adverts read fine in a browser. When that happens the run says so rather than guessing.

Sources that fail are reported by name with their HTTP status in source_errors, never quietly dropped.

Pair it with a workflow

If you run n8n, my free templates on https://n8n.io/creators/fractional-hq take a list like this Actor's output and turn it into lead scoring, outreach drafts and follow-ups without another tool in the chain.

There is a longer write-up of how the audit works, and what it deliberately does not claim, at https://fractionalhq.uk/use-cases/pay-transparency-audit

Directive text: https://eur-lex.europa.eu/eli/dir/2023/970/oj

Built by Fractional HQ

I run Fractional HQ, a one-man studio for design and automation. I do not write code by hand: I architect, review and hold the standard while AI agents do the typing. That method runs my own estate: seven brands, a Postgres operations system, more than 100 scheduled jobs, published n8n templates, Zapier Silver certified.

Want this wired into your CRM and outreach, or built and handed over working? Get in touch: https://fractionalhq.uk/brief