When a company opens five finance roles in a month, it is probably about to buy finance software. When it starts hiring sales staff in a new city, it is probably about to need office space, payroll in a new state and recruiting help there too. Job postings are one of the few public signals that show where a company is putting its money, and they change week to week.
Sales and business development teams know this. The problem is the effort. Checking fifty careers pages by hand every week does not last long, and a single snapshot tells you little. What you want is the trend: which accounts are adding roles faster than usual, in which departments, and what changed since last week.
This tutorial shows how to build that trend from public job boards using the Job Postings Scraper: Company Hiring Signals Actor on the Apify Store.
Where the data comes from
Many fast-growing companies run recruiting on one of three applicant tracking systems: Greenhouse, Lever or Ashby. Each one hosts a public job board for its customers (pages like boards.greenhouse.io/<company>, jobs.lever.co/<company> and jobs.ashbyhq.com/<company>) and offers public endpoints so companies can list their open roles on their own careers sites.
The Actor reads those public endpoints for the companies you list. It uses no login and reads only jobs the company has published. It returns:
- One record per open job: title, department, team, locations, remote flag, employment type, posted date, apply link and the salary range when the company publishes one.
- One summary per company (optional): open roles, roles by department, roles posted in the last 7, 30 and 90 days, hiring velocity, median role age, and how many roles opened or closed since your last run.
It does not return job descriptions, recruiter names or contact details.
The Actor is independent and is not affiliated with, endorsed by or sponsored by Greenhouse, Lever, Ashby or any company whose jobs it returns.
What "hiring velocity" means here
In the company summary, hiringVelocityPerWeek is the number of roles posted per week, averaged over the last 30 days. A company with 36 roles posted in the last 30 days has a velocity of 8.4.
Velocity is more useful than the open role count on its own. A company with 150 open roles might have had most of them open for months. A company with 30 open roles that posted 20 of them this month is moving. medianRoleAgeDays helps you tell the two apart: a low median age means most roles are recent.
Step 1: Run it once in the Apify Console
Open the Store page (link at the end). With an empty company list the Actor runs a small example set, which is a good way to see the output.
Then fill in your own:
-
Companies: one entry per company. The most reliable format is
greenhouse:<board>,lever:<site>,lever-eu:<site>orashby:<name>. You can also paste a job board URL, a careers page URL, or just a name, and the Actor will try to find the board. -
Department or team contains: for example
Finance,Sales,Engineering. -
Job title contains and Job title does not contain: for example include
Account Executive, excludeIntern. -
Location contains: for example
London,Germany,Remote. - Remote jobs: any, remote only, or exclude remote.
- Posted in the last N days: 0 means any age.
- Add a hiring summary per company: on by default.
- Maximum jobs per company and Maximum job records: caps on job records. The company summary still counts every open role.
To find a company's board, open its careers page, click any job and look at the link. jobs.lever.co/acme means lever:acme.
Step 2: Schedule it daily with onlyNew
- Switch on Only jobs not seen in previous runs (
onlyNew). The Actor remembers which job IDs it has returned for this exact input, in a key-value store in your account (hiring-signals-feed-stateby default). Later runs return only jobs that are new since then. - Leave Add a hiring summary per company on. The summary's
newRolesSinceLastRun,closedRolesSinceLastRunandnetChangeSinceLastRunuse the same history, so each run tells you what changed. - Save the input as a task. In Schedules, create a schedule (for example cron
0 7 * * 1-5in your timezone) and add the task.
The first run returns every matching job, and the "since last run" fields are empty. From the second run on, you get only new jobs and real change numbers. Changing the company list or filters starts a fresh history, so settle your filters before you rely on the trend.
Step 3: Rank accounts by velocity in Python
The official apify-client package runs the Actor and reads the results. Install it with pip install apify-client and copy your API token from the Integrations section of the Console.
This script runs the Actor over a list of target accounts, then ranks them by hiring velocity and flags any account with a jump in open roles since the last run.
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run_input = {
"companies": [
"greenhouse:figma",
"greenhouse:discord",
"lever:spotify",
"ashby:linear",
"ashby:ramp",
],
"onlyNew": True,
"includeCompanySummary": True,
"includeSalary": True,
}
run = client.actor("agents001/company-hiring-signals-feed").call(
run_input=run_input,
max_total_charge_usd=3,
)
if run is None:
raise SystemExit("Run failed to start or finish")
summaries, new_jobs = [], []
for item in client.dataset(run.default_dataset_id).iterate_items():
if item["recordType"] == "companySummary":
summaries.append(item)
else:
new_jobs.append(item)
summaries.sort(key=lambda s: s["hiringVelocityPerWeek"] or 0, reverse=True)
print(f"{'Company':<12}{'Open':>6}{'7d':>5}{'30d':>6}{'Per wk':>8} Top departments")
for s in summaries:
print(
f"{s['company']:<12}{s['openRoles']:>6}{s['postedLast7Days']:>5}"
f"{s['postedLast30Days']:>6}{s['hiringVelocityPerWeek'] or 0:>8} {s['topDepartments']}"
)
net = s.get("netChangeSinceLastRun")
if net is not None and net >= 10:
print(f" -> {s['company']} added a net {net} roles since the last run")
print(f"\n{len(new_jobs)} new job records this run")
On a sample run over those five companies, the ranking came out like this:
Company Open 7d 30d Per wk Top departments
Ramp 168 15 51 11.9 Sales (67), Engineering (38), People & Talent (15), ...
Figma 142 11 36 8.4 Sales (44), Engineering (28), Early Career (11), ...
Spotify 81 5 34 7.9 Engineering (32), Markets and Subscriber Growth (9), ...
Discord 46 2 14 3.3 Product Engineering (12), Developers (7), ...
Linear 27 1 3 0.7 Product (13), GTM (12), Operations (2)
Linear has 27 open roles but a median role age of 144 days and only three roles posted in the last month. Ramp is posting about twelve roles a week, most of them in sales. Those are two very different conversations for a seller.
What the output looks like
A job record, trimmed:
{
"recordType": "job",
"company": "Ramp",
"companyKey": "ashby:ramp",
"ats": "ashby",
"title": "Senior Backend Engineer | Token Spend Management",
"department": "Engineering",
"team": "Backend",
"location": "New York, NY (HQ)",
"locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)"],
"remote": true,
"workplaceType": "hybrid",
"employmentType": "full-time",
"postedAt": "2026-10-20T13:40:20Z",
"ageDays": 0,
"salaryText": "USD 204,000 - 280,000 per year",
"jobUrl": "https://jobs.ashbyhq.com/ramp/b7514711-8a29-4bd2-a97a-967be14e336f",
"isNew": true
}
A company summary, trimmed:
{
"recordType": "companySummary",
"company": "Figma",
"companyKey": "greenhouse:figma",
"openRoles": 142,
"topDepartments": "Sales (44), Engineering (28), Early Career (11), Business Operations (10), Design (10)",
"postedLast7Days": 11,
"postedLast30Days": 36,
"postedLast90Days": 75,
"hiringVelocityPerWeek": 8.4,
"medianRoleAgeDays": 87,
"remoteShare": 0.0,
"rolesWithSalary": 93,
"newRolesSinceLastRun": null,
"closedRolesSinceLastRun": null,
"netChangeSinceLastRun": null
}
The null change fields are expected on a first run.
What it costs
The Actor uses pay-per-event pricing:
- $3 per 1,000 job records ($0.003 each)
- $0.005 per company summary
A daily onlyNew run over 50 target accounts costs $0.25 for the summaries plus $0.003 per new job. If those accounts post 60 new matching roles a day between them, that is $0.43 a day, or about $9 over 21 working days.
Watch the first run. It returns every matching open job, so 50 companies with 100 open roles each would be 5,000 records, or $15. Department, title and location filters cut that down, and Maximum jobs per company caps job records while the summary still counts every open role. You can also set a maximum cost per run. Check the Store page for current pricing.
What to do with the data
-
CRM: write
hiringVelocityPerWeek,postedLast30DaysandtopDepartmentsonto the account record in your CRM through Zapier, Make or a webhook, and sort your territory by them. - Google Sheets: send the Company summaries view to a sheet each morning and chart velocity per account over time.
-
Alerts: post to Slack when an account's
netChangeSinceLastRuncrosses a threshold, or when a new role appears in the department you sell into. -
Territory planning: use
topLocationsand new locations in job records to spot accounts expanding into your region.
Limitations
- Only Greenhouse, Lever and Ashby boards are supported. Companies on Workday, SmartRecruiters, iCIMS and other systems are not covered yet.
- You supply the company list. The Actor does not discover companies for you.
- A posting is an intent to hire, which is different from a hire. Some roles stay open for months and some are evergreen listings.
- Department names are whatever each company uses, so "GTM" at one company may be "Sales" at another.
- Employment type and department are only filled when the company fills them. Salary appears only where the company publishes it, which is more common for US roles.
- Job descriptions are not included.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.