Every B2B prospecting tool sells you the same thing: a list of companies, and maybe an email pattern. The problem was never the list. It is knowing which companies to call this week, and almost nothing in a static database tells you that.
Hiring does. A company that opened 40 roles this month has budget and is changing shape. A company opening its first sales, security or finance role has just changed strategy, and that is the single strongest timing signal you can get for free.
And it is genuinely free, because companies publish their openings through APIs designed to be read. Greenhouse, Ashby, Workable, Recruitee, Personio, Rippling and BambooHR each expose a public JSON or XML endpoint per company: no login, no cookies, no proxy.
curl -s "https://boards-api.greenhouse.io/v1/boards/stripe/jobs" | head -c 300
That is the whole trick, and it is why job aggregators exist at all.
The part everyone gets wrong
The per-company endpoint needs the company's board token. There is no directory of tokens. So "who is hiring for sales in Berlin" is unanswerable from these APIs directly: you would need the token of every company that might be, which is the list you were trying to build.
You have to invert it. Enumerate the boards first, then read all of them on a schedule. I run that index: 11,157 companies across those seven platforms, re-read every night.
Two things a live scraper structurally cannot give you
This is the part I actually want to write about, because it took me a while to see it.
1. You cannot see a job close if you were not watching when it opened.
A scraper that runs today sees today's postings. It cannot tell you that a company closed 30 roles this month, because "closed" is the absence of something it never recorded. Only an index with history has that, and it is the difference between a growth signal and a churn warning:
company opened30 closed30 net
growing co +48 -6 +42
shrinking co +3 -37 -34
The second row is a customer about to churn, or a competitor in trouble, depending on who you are. No amount of scraping harder produces it.
2. "First time hiring for X" needs a baseline.
Right now 3,486 of the 11,157 companies are hiring into a department they were not hiring into before. You can only know that by having watched what they hired for previously.
company opened30 just started hiring for
Bjak +1230 A1 CEO Office, A1 Engineering, A1 HR
Renuity +362 Corporate, Field
Mindrift +274 Creator (Writer)
OpenAI +270 Applied AI, Communications, Consumer Product
Getting the contact details, honestly
A hiring signal without a way to reach the company is half a product. The approach I settled on is deliberately narrow: fetch the company's own website and take only what it publishes as a way to be contacted. A role address like info@ or sales@, a phone number inside a tel: link, its own social profiles.
No guessed first.last@ patterns, no permutation, no verification service, no named individuals. Partly because guessing produces bounces, mostly because a person's work address was published so they could do their job, not so a stranger could buy it in a list.
That honesty has a price and it shows up as a number: 6,634 of 11,157 companies are reachable this way, with every one of them crawled. If a vendor tells you they have 95% coverage of decision-maker emails, they are permuting names against a mail server, and you are buying a bounce rate.
Two implementation notes that cost me time:
-
tel:links only. My first pass regexed phone-shaped strings out of the page and collected SVG path coordinates, longitudes and unix timestamps. Atel:href is the publisher asserting "this is a phone number". -
Role allowlist, not a blocklist. Filtering out obvious personal addresses still let
sarah@through. Allowing only a known set of role mailboxes is the version that holds.
Absent is not zero
The rule that ended up mattering most: a company we have not crawled yet and a company whose site publishes nothing must never look the same. They send you to two different places. So every row carries contactsHarvested, and closed30 is absent rather than 0 until there is enough history to know the difference between "closed nothing" and "we do not know yet".
Same for salary: two of the seven platforms publish a band, so 92% of rows have no salary and say so, rather than carrying a plausible guess.
Try it
The index is on Apify: https://apify.com/glitchbound/b2b-leads-finder
{
"enteringNewDepartments": true,
"minOpenedLast30": 20,
"withEmailOnly": true
}
One row per company, with the hiring signal and the contact details. The jobs themselves are a separate Actor if you want the postings instead: https://apify.com/glitchbound/ats-jobs-search
Happy to answer questions about any of the seven APIs in the comments. If you are pulling job data today, I am curious what you are using and what breaks.
Top comments (0)