Technology Aug 29, 2026 · 6 min read

How to Find Companies That Just Started Hiring (Greenhouse, Lever & Ashby APIs)

When a company posts a job, that posting comes with a timestamp, and the timestamp is the interesting part. A new "VP of Sales" role usually means budget just got approved and a decision-maker seat is about to be filled. A first "Data Engineer" opening means someone is standing up a data platform ri...

DE
DEV Community
by FeedHarbor
How to Find Companies That Just Started Hiring (Greenhouse, Lever & Ashby APIs)

When a company posts a job, that posting comes with a timestamp, and the timestamp is the interesting part. A new "VP of Sales" role usually means budget just got approved and a decision-maker seat is about to be filled. A first "Data Engineer" opening means someone is standing up a data platform right now. Three "Account Executive" listings in one week means a team is scaling its go-to-market this quarter.

People call this hiring intent data, and it's one of the more useful buying signals around because it's timed. A role that shows up today is a window that's open today. Six weeks later the tool has been picked and the problem is quietly solved.

You don't need an expensive intent-data subscription to see it. Three of the biggest applicant tracking systems (Greenhouse, Lever, and Ashby) serve their customers' job boards as public, unauthenticated JSON. If you can make an HTTP request, you can build your own "who's hiring" feed. This post covers how to pull the data, and then the part people usually get wrong, which is working out what's actually new.

ATS job boards are public JSON APIs

The careers page on a company's site is usually just a front end for a JSON feed the ATS serves publicly. No key, no OAuth. You just need the company's board slug.

Greenhouse:

https://boards-api.greenhouse.io/v1/boards/{token}/jobs

{token} is the slug, like stripe. Add ?content=true for full descriptions.

Lever:

https://api.lever.co/v0/postings/{company}?mode=json

Ashby:

https://api.ashbyhq.com/posting-api/job-board/{org}?includeCompensation=true

You can usually find the slug by looking at a company's "Careers" link (boards.greenhouse.io/acme, jobs.lever.co/acme, jobs.ashbyhq.com/acme).

Pulling and filtering jobs

Here's a small Node script (built-in fetch, Node 18+) that hits a Greenhouse board and keeps only the roles matching a keyword list.

const BOARD_TOKEN = "stripe";
const KEYWORDS = ["sales", "revops", "account executive"];

async function getJobs(token) {
  const url = `https://boards-api.greenhouse.io/v1/boards/${token}/jobs`;
  const res = await fetch(url);
  if (!res.ok) throw new Error(`Greenhouse ${res.status} for ${token}`);
  const { jobs } = await res.json();
  return jobs; // { id, title, updated_at, location, absolute_url }
}

function matches(title, keywords) {
  const t = title.toLowerCase();
  return keywords.some((k) => t.includes(k.toLowerCase()));
}

(async () => {
  const jobs = await getJobs(BOARD_TOKEN);
  for (const j of jobs.filter((x) => matches(x.title, KEYWORDS))) {
    console.log(`- ${j.title} (${j.location?.name ?? "N/A"})`);
    console.log(`  ${j.absolute_url}`);
  }
})();

Lever and Ashby return slightly different shapes. Lever uses text and hostedUrl, and Ashby nests its postings under jobs. So you write one small normalizer per source that maps each into a common { id, title, location, url }. Once that's done, everything downstream works the same regardless of where the data came from.

The hard part is detecting what's new

This is where most homemade trackers break. These APIs give you a current snapshot, meaning the roles that are open right now. What they don't give you is a reliable "posted at" field you can trust across all three providers. Greenhouse especially has no dependable creation date on the board endpoint, and updated_at changes every time someone edits a description.

So if you want new postings, which is the actual signal, you have to work out the difference yourself. You store what you saw last time and compare it against this run.

seen = load_state(company)      # set of job IDs from previous runs
current = fetch_jobs(company)   # today's snapshot

new_jobs    = [j for j in current if j.id not in seen]
closed_jobs = [id for id in seen if id not in current.ids]

emit(new_jobs)                  # the hiring signal you want
save_state(company, current.ids)

A few things that matter once you run this for real.

Persist the state somewhere durable, even if it's just a set of job IDs per company. Lose it, and the next run treats every open role as new and floods you with false positives.

Key off the provider's job ID, not the title. Titles get edited, and a renamed role shouldn't show up as new.

The first run is a cold start. You have no history yet, so seed the state and expect everything to look new that one time.

Normalize before you diff. If you compare raw responses from three different APIs, your logic turns into a pile of special cases.

Watch for roles that disappear, too. A req that drops off usually means it was filled, which is its own useful signal.

What to do with the signal

Once you have a clean feed of new roles, the uses are fairly obvious. You can watch a list of target accounts and get pinged the moment a relevant role opens. You can rank by seniority, since a "VP" opening in your buyer's department is a stronger, budget-backed trigger than a backfill. Or you can spot scaling motions, like three new AE reqs in a week from a company that's clearly investing in sales.

It holds up at scale too. I recently ran this across ten companies at once (Stripe, OpenAI, GitLab, Anthropic, Databricks, Coinbase, Airbnb, Discord, Ramp, and Notion) and pulled 3,664 normalized job postings across all three ATS platforms in about a second, each tagged as new, updated, or removed against the previous run. The data is right there in public JSON. The real work is the normalizing and the change-detection state.

If you'd rather not maintain the plumbing

All of this is doable in an afternoon. Keeping it running is the annoying part: the change-detection state, the per-provider normalizing, the scheduling, the retries when a board throws a 404. That's the stuff that eats weekends.

If you'd rather skip it, I packaged this same approach as an Apify actor that watches Greenhouse, Lever, and Ashby boards for a list of companies and returns only the new roles run over run: ATS Job Scraper for Greenhouse, Lever & Ashby. You give it your target accounts, turn on monitor mode, and put it on a schedule.

Either way, the data is public. Go find the companies that just started hiring.

DE
Source

This article was originally published by DEV Community and written by FeedHarbor.

Read original article on DEV Community
Back to Discover

Reading List