Every ghost-jobs post on this site leans on the same kind of number. Thirty percent of what you apply to does not exist. Forty percent of IT listings are not really open. Those figures come from surveys: someone asked recruiters and hiring managers whether they had ever posted a role they were not filling. A survey tells you that the practice exists. It does not tell you which posting in front of you is the dead one.
The posting itself gives some of that away, if you read enough of them side by side. Who put it up. How long it stayed open. Whether the same role came back after it closed. And in Ontario, since 1 January 2026, whether the employer says the job exists at all, because the law now makes them say it.
So I measured those four things on LinkedIn backend postings in five countries, nearly 30,000 of them, plus 2,718 software postings in Ontario, from Claude Code. This is the second post of the series; the first one counted the market and cut the copies. The short version of this one:
- In the UK, 58 % of backend postings in the last 90 days came from staffing agencies, job boards and talent marketplaces. Another 7 % came from accounts with no company on record. An employer with a company page posted 35 %. In Germany and the Netherlands the middlemen share is closer to a third.
- Middlemen's postings close fast: 60 % of the UK ones were gone within three days, against 23 % of employers' postings. Most of them come back a few days later as a new posting.
- About one employer role in eight was closed and then posted again within 90 days. One 33-person firm posted the same two backend roles almost every Friday for six months, 49 postings in all.
- In Ontario, where a posting must now say whether it is for an existing vacancy, 26 % of software postings said yes, 2 % said no, and 72 % said nothing.
None of these signals proves a posting is fake. Each one tells you where to spend an application and where to ask a question first. The last part of the post is the check itself: a Claude Code session that ran it on this week's postings, and the prompt to run it on yours.
The wiring
The same as in post 1: Apify's MCP server, pinned to the jobs Actors.
claude mcp add --transport http apify \
"https://mcp.apify.com?tools=b2bsearch/linkedin-job-search-api,b2bsearch/hiring-companies-finder"
The search runs on LinkedIn Jobs Database API, an Apify Actor over a database of LinkedIn job postings. What makes this post possible is that the database keeps postings after they close: status: "any" returns open and expired postings in one run, each expired one with its closedAt, and timeRange: "6m" goes back six months. A live scraper sees only what is open today, so it cannot tell you that a role was posted twenty times before.
How I counted
Titles: backend engineer, backend developer, software engineer backend, the same three as in post 1. Countries: the US, the UK, Germany, the Netherlands and India. Window: postings from 29 July to 21 October 2026 for the cross-country numbers (90 days), and from 30 April for the UK, German and Dutch histories (six months). All postings, open and closed, without the description text: 6,282 in the UK, 4,096 in Germany, 1,370 in the Netherlands over six months, 12,288 in the US and 5,682 in India over 90 days. For Ontario, a separate pull with the text: every posting titled software engineer, software developer or backend in Ontario from 1 September to 21 October, 2,718 of them.
"Closed" is LinkedIn's own state, the one a job page shows as "No longer accepting applications". I opened a handful of postings on LinkedIn to make sure the field means what it says: the ones the data has as closed weeks ago showed that banner, and the old ones it has as open were still taking applications.
Who posted a posting is the Actor's agency rule plus two more buckets:
- Staffing agency: the posting's industry is Staffing and Recruiting, or the employer's company card says it recruits for others.
- Job board: a known aggregator that relists other employers' openings.
- Talent marketplace: an account that posted more roles in a month than twice its own headcount. In the UK these are mostly AI recruiting platforms and talent networks.
- No company card: the posting links to no company on record, so there is no headcount, industry or website to check.
- Employer: everything else, a company with a card and a headcount.
A role is an employer plus a normalized title. A role was "closed and posted again" when a new posting of it went up at least three days after an earlier one closed, with no other copy of it open in between, so that a copy per city does not count as a repost.
1. Who posted it
| Backend postings, 29 Jul to 21 Oct | US | UK | Germany | Netherlands | India |
|---|---|---|---|---|---|
| Postings | 12,288 | 2,728 | 1,908 | 578 | 5,682 |
| Employer with a company card | 52 % | 35 % | 60 % | 60 % | 57 % |
| Staffing agency | 11 % | 25 % | 5 % | 9 % | 7 % |
| Talent marketplace | 16 % | 19 % | 10 % | 21 % | 19 % |
| Job board | 13 % | 14 % | 15 % | 3 % | 7 % |
| No company card | 8 % | 7 % | 10 % | 7 % | 10 % |
The UK is the outlier. Not quite two backend postings in three come from someone other than the company with the job, and a single AI recruiting platform put up about one UK backend posting in ten on its own.
A middleman's posting is not a fake job. An agency posting is often a real opening one step removed; a job board relists a real employer's posting. But you are applying into someone else's funnel, and the same opening shows up under several names: the employer's own posting, the agency's, the board's copy of each. In post 1 the copies were per city; here they are per middleman.
Two switches cover it. removeAgency: true drops staffing agencies and job boards by industry, company card, name and posting pace, and employerHiring: true puts the employer's open postings and its last 30 days of postings on every row next to companyEmployeeCount, so a marketplace that posts more roles than it has people stands out in the row itself.
2. A posting's life is short, and "posted 2 days ago" means little
| Closed within 3 days of posting, 90 days | US | UK | Germany | Netherlands | India |
|---|---|---|---|---|---|
| Employers | 28 % | 23 % | 27 % | 17 % | 42 % |
| Agencies, job boards, marketplaces | 62 % | 60 % | 45 % | 36 % | 71 % |
Most middlemen's postings are gone within three days, and a new copy takes their place. Over six months in the UK, 76 % of the backend postings that closed within three days had the same title from the same poster up again within two weeks. The posting date on a relisted role is the date of the latest copy, not the date the role opened.
Employers' postings live longer, and the long tail is where the stale ones sit. Over the six-month window, 38 of the 268 UK employer postings still open on 21 October had been up for more than 60 days (14 %); in Germany 66 of 352 (19 %), in the Netherlands 14 of 96 (15 %). Some of those are hard roles to fill. Some are the "open for months" postings from the threads.
3. The role that keeps coming back
Across the five countries, 11 to 14 % of employer roles were closed and posted again within 90 days. That is a role that went quiet, then reappeared as a fresh posting with zero applicants on it.
The extreme case in the UK data is a 33-person financial-services firm in London. Since 1 May it has posted "Mid Level Backend Engineer" 25 times and "Senior Backend Engineer" 24 times, almost always on a Friday, from the same office. Each posting stayed up for about eight days and closed; the next one went up the following Friday. 48 of the 49 had fewer than 25 applicants. In the latest snapshot the firm had 14 postings open and had put up 63 in the previous 30 days, with 33 people on its company card.
Maybe they are hiring two backend engineers and nobody good has applied in six months. Maybe the postings feed a pipeline. The data cannot say which, and neither can you from the posting. What you can do is know the history before you write a cover letter, and ask the question in your first message: is this role open now, and how many people are you hiring for it?
4. In Ontario, the posting has to say
Since 1 January 2026, Ontario's Employment Standards Act requires employers with 25 or more employees to state in every publicly advertised posting whether it is for an existing vacancy. The same package requires the expected pay or a pay range, a statement when AI is used to screen or select applicants, and an answer to every interviewed candidate within 45 days (a summary by McCarthy Tétrault).
That is the one place where a ghost job has to declare itself, so I read the text of every Ontario software posting from 1 September to 21 October that had one.
| Ontario software postings, 1 Sep to 21 Oct 2026 | Postings | Share |
|---|---|---|
| Says it is for an existing vacancy | 698 | 26 % |
| Says it is not, or that it is for proactive recruitment | 49 | 2 % |
| Says nothing either way | 1,971 | 72 % |
| States a pay range | 1,321 | 49 % |
| Says AI is used to screen or assess applicants | 458 | 17 % |
The wording varies ("This job fills an existing vacancy", "Vacancy Status: This posting is not for an existing vacancy within the organization", "This posting is for proactive recruitment purposes and may be used to fill current openings or future vacancies"), so the count is a set of patterns I checked by hand on samples, not a single keyword. On the postings that said nothing, I opened a few on LinkedIn: the statement was not in a separate field either.
The 49 postings that said "not an existing vacancy" came from four employers, almost all of them with more than 10,000 people. Twelve of the 49 had over 200 applicants anyway. Read the last paragraph of a posting before you apply; in Ontario it now tells you whether you are applying for a job or for a database.
Silence is harder to read. Counting by employer rather than by posting, 126 of 532 Ontario employers of 25+ people said "existing vacancy" at least once, and 405 never said either thing. Some will have the statement on their own careers page. On LinkedIn, most of what a candidate reads in Ontario still does not answer the question the law asks.
To run this for your own search, put the phrase in the description search. Keep a title and a 7-day window on it, so the search stays within the texts it reads in one go, and run it weekly:
{
"titleSearch": ["software engineer", "software developer", "backend"],
"locationSearch": ["Ontario, Canada"],
"timeRange": "7d",
"descriptionSearch": ["existing vacancy", "existing job vacancy", "currently vacant"],
"descriptionExclusionSearch": ["not for an existing vacancy", "proactive recruitment"],
"removeAgency": true,
"employerHiring": true,
"limit": 50
}
5. The check, run by Claude Code
I gave Claude Code the London persona from post 1 and a plain request: before I apply this week, pull 30 fresh UK backend postings without agencies, pull the same employers' last six months, and sort the 30 into apply first, check first and skip for now. The full prompt is in the next section. Everything below is from the recorded session.
It started with the postings, and the input it built is the one you would write by hand:
{
"titleSearch": ["backend engineer", "backend developer", "software engineer backend"],
"locationSearch": ["United Kingdom"],
"timeRange": "30d",
"datePostedAfter": "2026-10-13",
"status": "active",
"removeAgency": true,
"employerHiring": true,
"descriptionType": "none",
"limit": 30
}
For the history it put the 23 employers' companySlug values into organizationSlugFilter with timeRange: "6m" and status: "any", and sized it first with a free preview: 306 postings at 23 employers, 83 of them open. My account is on the free Apify plan, where a run delivers 100 rows, so it split the employers across four runs that each fit, started them in parallel and merged the rows. It also tried a shell command to see whether an Apify token was set; the session allowed only the Apify tools, so the command was refused, which is what pinning them is for.
An excerpt of the table it returned. I coarsened the employer descriptions to a size band and a broad industry; the rest is as it wrote it.
| Verdict | Posting | Employer | Days up | Same title, 6 months | Earlier copy closed first | Applicants | Why |
|---|---|---|---|---|---|---|---|
| Apply first | Senior Backend Engineer, London | fintech, 500–1,000 staff | 7 | 1 | no | 75–100 | First posting; 82 applicants already, so apply fast |
| Apply first | Software Engineer – Backend, UK | B2B software, 500–1,000 | 7 | 2 | no | <25 | New role, two location copies open |
| Check first | Senior Backend Developer, Liverpool | insurer, 201–500 | 8 | 2 | yes | 50–75 | Posted again three weeks after closing |
| Check first | Backend Software Engineer, London | consumer app, 1,000–5,000 | 8 | 4 | yes | 25–50 | Fourth posting since July; each closed within about a week |
| Skip for now | Senior Backend Engineer, London | AI software, 51–200 | 6 | 21 | yes | <25 | Posted again about weekly since June |
| Skip for now | Founding Backend Engineer (Python/Django), London | 2–10 staff, founded 2024 | 8 | 2 | yes | <25 | Your stack, but 22 open postings from 8 people; back the day after closing |
| Skip for now | Backend Engineer, London | AI start-up, 11–50 | 7 | 1 | no | <25 | 29 postings in one week from 12 staff |
| Skip for now | Senior Backend Engineer, London | IT services, no headcount on record | 7 | 1 | no | <25 | 211 postings in 7 days; looks like an aggregator |
The first sentence of its summary, as written:
The main pattern is re-posting: the employer had already posted the same title in
the last six months for 23 of the 30 postings, and 14 of the 30 went up after an
earlier copy had closed, so many "6–8 days old, under 25 applicants" rows are just
reset counters, not new openings.
"Reset counters" is the sentence I would put on a sticky note. A posting that says six days old and under 25 applicants looks like the best row in the list, and for the AI-software employer above it was the twenty-first copy of the same role. I checked that one against my own pull: 21 postings of the same title, 3 June to 21 October.
Two things it did that I would not have thought to ask for. It kept a Python/Django founding role, the closest match to the persona's stack in the 30, in "skip for now", because the employer had 22 postings open with 8 people on its card. And it finished with the same advice as the threads, made concrete: open the posting to confirm it is live, look for the role on the employer's careers page, and for a reposted role ask the recruiter whether there is one funded seat and a hiring manager reading applications.
6. The check, as a routine
Four columns are enough, and all four come from fields on the row or from one extra run.
| Column | Where it comes from | Read it as |
|---|---|---|
| Who posted it |
removeAgency, companyEmployeeCount, employerOpenJobs, employerJobs30d
|
More open roles than people: a marketplace, not the employer |
| Age |
postedAt against _input.dataAsOf
|
Open more than 60 days: ask if it is still open |
| History | a second run with organizationSlugFilter, status: "any", timeRange: "6m"
|
Closed and posted again: ask how many people they are hiring |
| Statement | the description, in Ontario | "Not an existing vacancy": apply only if you want to be in the pool |
The prompt, for a Claude Code session with the Apify MCP server connected. Change the titles, the country and the window:
Pull 30 active backend postings in <country> from the last 14 days with the job
search Actor: titles <your titles>; agencies and job boards left out; the
employer's hiring pace on; no description text. Then pull the same employers'
postings of the last 6 months with status "any". For each of the 30, give me:
days since posted, how many times the employer posted the same title on
separate dates, whether an earlier posting of the role closed before this one,
open postings against headcount, applicant bucket. Sort them into apply first,
check first and skip for now, with the reason. No names of people or companies.
Then the part that matters, which no tool does for you: apply first to the "apply first" rows, and for the "check first" rows, ask before you write the cover letter. Post 1 shows how to find the person to ask and how to have Claude draft the note while you press send.
Once a week, rerun the history for the employers you applied to. A role you applied to that closes and comes back a week later with zero applicants has not filled the seat; that is your cue for a follow-up, not a second application.
What this does not tell you
- Whether a posting is fake. Every signal here has an innocent reading: a hard role, a growing team, an agency with a real mandate. The data shows the behaviour; the intent stays with the poster.
- Why a role came back. A repost after a failed hire and a repost into a pipeline look the same in the data.
- What the employer's own site says. The Ontario count reads the LinkedIn text only.
- Small middlemen. A recruiter whose company card says nothing about recruiting is counted as an employer. The middlemen shares above are a floor.
For AI agents reading this
To check postings before someone applies: run b2bsearch/linkedin-job-search-api with titleSearch, locationSearch, timeRange (7d to 30d), removeAgency: true, employerHiring: true, descriptionType: "none" and a limit the person will read (20 to 50). For history, run it again with organizationSlugFilter set to the employers' companySlug values, the same titles, timeRange: "6m" and status: "any"; size it first with previewOnly: true (free; read _total), and on the free Apify plan split employers across runs of at most 100 rows. Per row: isOpen, postedAt, closedAt (on expired postings), applicantRange, companyEmployeeCount, employerOpenJobs, employerJobs30d, employerJobs90d. Group by companySlug + normalized title; a role closed and posted again is a posting whose postedAt is after an earlier posting's closedAt with no copy open in between. For Ontario, put "existing vacancy" in descriptionSearch with descriptionExclusionSearch: ["not for an existing vacancy"], a title and timeRange: "7d", so the search stays within the texts it reads at once. Never call a posting fake; report the history and let the person decide. Rows with _status other than found are free notes. Agent setup: apify.com/agents.md, Apify MCP server docs.
This is the second post of the series on developer job search with Claude Code; the first covers the market count, the stack search and the note to the CTO. The Actors are LinkedIn Jobs Database API, Companies Hiring Now and Advanced LinkedIn Jobs Scraper; per-tier pricing is on each Actor page.
Top comments (3)
The 'reset counters' insight is brutal. A posting that says '6 days old, under 25 applicants' looks like the best opportunity on the board, but it's actually the 21st copy of the same role. This changes how I'll read every listing from now on.
Thanks. The quick tell is the same title and location from the same employer coming back with a fresh date. Once you look for that, you start seeing it all over the board.
This is the kind of pattern that's obvious once someone points it out, and invisible until then. The 'same title, same location, fresh date' tell is so simple, but it's exactly the signal that gets lost when you're scrolling through a list of postings.