If you sell B2B software, recruit, or invest, who is hiring what is one of the best public signals there is. A company opening five data-engineering roles in a month is buying data tooling. A company posting its first "Head of Sales" is about to build a go-to-market team.
The good news: most companies publish their jobs through an applicant tracking system (ATS) that exposes a public, unauthenticated JSON feed for their careers page. No HTML parsing, no headless browser, no proxies.
This post shows:
- the public endpoints for 8 ATS platforms,
- how to figure out which ATS a company uses from just its name or domain,
- how to turn raw postings into company-level signals (velocity, function mix, tech stack).
1. The public job-board endpoints
| ATS | Endpoint |
|---|---|
| Greenhouse | https://boards-api.greenhouse.io/v1/boards/{slug}/jobs?content=true |
| Lever | https://api.lever.co/v0/postings/{slug}?mode=json |
| Ashby | https://api.ashbyhq.com/posting-api/job-board/{slug}?includeCompensation=true |
| Workable | https://apply.workable.com/api/v1/widget/accounts/{slug}?details=true |
| SmartRecruiters | https://api.smartrecruiters.com/v1/companies/{CompanyId}/postings |
| Recruitee | https://{slug}.recruitee.com/api/offers/ |
| Personio |
https://{slug}.jobs.personio.de/xml (or .com) |
| Teamtailor | https://{slug}.teamtailor.com/jobs.rss |
A few things I learned the hard way:
-
Unknown slugs: seven of them return
404. SmartRecruiters returns200withtotalFound: 0, so check the count, not the status. -
SmartRecruiters' list endpoint has no description; you need one extra call per posting (
/postings/{id}) if you want the text. -
Recruitee and Teamtailor career sites often live on the company's own domain (
careers.company.com). The same feeds work there:https://careers.company.com/api/offers/andhttps://careers.company.com/jobs.rss. -
Greenhouse double-escapes HTML in
content(<p>), so decode entities before stripping tags. -
Lever's EU instance is
api.eu.lever.co.
2. Which ATS does this company use?
Given only a name ("Hugging Face") or a domain ("linear.app"):
- Derive slug candidates:
huggingface,hugging-face; for a domain, the first label (linear). Strip suffixes like Inc, GmbH, Ltd. - Probe every ATS with every candidate, in parallel with a small concurrency limit.
- If several boards match (old boards are surprisingly common), prefer the one whose job URLs point at the company's own domain, then the one with the most jobs.
const PROBES = {
greenhouse: (s) => `https://boards-api.greenhouse.io/v1/boards/${s}/jobs`,
lever: (s) => `https://api.lever.co/v0/postings/${s}?mode=json`,
ashby: (s) => `https://api.ashbyhq.com/posting-api/job-board/${s}`,
workable: (s) => `https://apply.workable.com/api/v1/widget/accounts/${s}`,
recruitee: (s) => `https://${s}.recruitee.com/api/offers/`,
};
function slugCandidates(name) {
const base = name.replace(/^www\./, '').split('.')[0];
const words = base.replace(/\b(inc|ltd|gmbh|llc)\b\.?/gi, ' ').toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
return [...new Set([words.join(''), words.join('-')])];
}
async function detect(name) {
const hits = [];
for (const slug of slugCandidates(name)) {
await Promise.all(Object.entries(PROBES).map(async ([ats, url]) => {
const res = await fetch(url(slug));
if (!res.ok) return;
const data = await res.json();
const jobs = data.jobs ?? data.offers ?? (Array.isArray(data) ? data : []);
hits.push({ ats, slug, count: jobs.length });
}));
}
return hits.sort((a, b) => b.count - a.count)[0] ?? null;
}
console.log(await detect('Hugging Face')); // { ats: 'workable', slug: 'huggingface', count: 6 }
For a careers-page URL, fetch the HTML first and look for embeds such as boards.greenhouse.io/embed/job_board?for=…, jobs.lever.co/… or jobs.ashbyhq.com/…. It is usually right there.
3. From postings to signals
Raw postings get interesting once you aggregate them per company:
- Velocity: jobs posted in the last 7 / 30 / 90 days. Compare the last 30 days with the average of the previous 60 to label the trend accelerating, steady or slowing.
- Function mix: classify each title (engineering, data, sales, marketing, customer…) with keyword rules. A spike in sales and marketing roles means go-to-market expansion.
- Leadership hiring: Head of, Director, VP titles often come before budget decisions.
- Tech stack: match a dictionary of technologies against descriptions. Watch for common-word traps: "go", "rust", "spark", "slack" and "excel" need case-sensitive or contextual patterns, or you will "detect" Go in every job that says go above and beyond.
- Salaries: Ashby and Recruitee expose structured compensation; elsewhere, parse ranges from the text, but require an explicit currency, or "5-7 years" becomes a salary.
- Change tracking: store the job IDs per company and diff on the next run to get new and closed roles.
Here is what a company row looks like once you put it together (real output, Notion, October 2026):
{
"companyName": "Notion",
"ats": "ashby",
"openJobs": 129,
"jobsPostedLast7Days": 7,
"jobsPostedLast30Days": 31,
"engineeringShare": 38,
"salesAndMarketingJobs": 48,
"leadershipJobs": 8,
"topFunctions": "engineering (46), sales (41), people (11), finance (7), marketing (7), customer (4)",
"techStack": "Python, Salesforce, TypeScript, SQL, Node.js, LLMs, React, ...",
"salaryMin": 85000,
"salaryMax": 400000,
"signals": "7 new roles posted in the last 7 days • Hiring 46 engineering roles • Hiring 41 sales roles • Leadership hiring: General Manager, DACH; Head of Early Career Recruiting; Head of Demand Engine • Go-to-market expansion: 48 sales/marketing roles (37% of openings) • Hiring across 9 countries"
}
Don't want to maintain it?
I packaged all of the above (8 platforms, name/domain/careers-page auto-detection, enrichment, change tracking) as an Apify actor: Hiring Signals. Paste company names and get job rows plus one signal row per company, as CSV, JSON, or straight into Sheets or Clay. There are also single-platform versions for Workable (with keyword search across all Workable employers), SmartRecruiters, Recruitee and Personio.
All of these read only publicly published job postings, with no logins and no personal contact data.
Written with AI assistance; the endpoints and outputs were verified against live data before publishing.













