If you sell to tech companies, the best time to reach out is right after they start hiring for the team that would use your product, and most of us find out months later from a LinkedIn post.

A company that opens 30 new sales roles in a week is about to buy sales tools. A company that adds 60 engineers is about to buy dev tools, cloud and hiring services. Job boards show this in public, but only as a snapshot of today. They do not tell you what is new, what closed, or how fast the count is moving.

I built a small API that answers those questions from a daily archive of public job boards. In this post I show how to call it from Python and curl, the real output I got today (2026-11-07), and how to turn it into a daily "new sales jobs at companies I care about" feed.

Why the usual approach hurts

The usual way to get hiring signals looks like this:

  1. Find the job board of every company. Greenhouse, Lever and Ashby each have a public JSON endpoint, but you need the board slug for each company first.
  2. Crawl hundreds of boards every day. One company is easy. 800 companies every morning is a job queue, retries and rate limits.
  3. Store every snapshot. "New" and "closed" only exist if you kept yesterday's data. If you start today, you have no history until next week.
  4. Diff the snapshots. Same job, new ID? A posting that disappears for one day and comes back? You need rules for that.
  5. Aggregate per company. Open jobs then vs now, new jobs, closed jobs, remote share.

The data is free. The archive is the work. Paid intent-data tools do this for you, but they are priced for sales teams with a budget, not for a developer who wants a JSON list.

What I built

Company Hiring Signals API reads a daily archive of public Greenhouse, Lever and Ashby boards for 813 tech and startup companies (started 2026-11-03, one snapshot per day). Each run reads that archive, so it does not crawl anything on your time. Today's runs took 4 to 5 seconds end to end.

It has five modes:

mode what you get
top_hiring (default) companies ranked by new jobs, open jobs, change in open jobs or closed jobs
companies current numbers plus the daily history of the companies you list
new_jobs postings first seen after the baseline, with keyword, location, department and remote filters
closed_jobs postings that disappeared two days in a row, with daysOpen
open_jobs currently open postings, with filters

It only returns company data: no recruiter names, emails or other personal data.

Step 1: get an Apify token

The API runs on Apify. Create a free account, open Settings, API & Integrations and copy your personal API token. Keep it in an environment variable:

export APIFY_TOKEN="your-token-here" 

Step 2: which companies are hiring fastest right now?

One curl call. run-sync-get-dataset-items starts the run, waits for it and returns the rows in the same HTTP response:

curl -s -X POST \ "https://api.apify.com/v2/acts/martin10291~company-hiring-signals-history/run-sync-get-dataset-items?token=$APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"mode":"top_hiring","sortBy":"newJobs","limit":5}' 

This is one real row from today's run:

{ "company": "Samsara", "companySlug": "samsara", "ats": "greenhouse", "day": "2026-11-07", "comparedWith": "2026-11-03", "openJobs": 363, "openJobsThen": 326, "openJobsDelta": 37, "openJobsDeltaPct": 11.3, "newJobs": 69, "closedJobs": 32, "remoteSharePct": 85.4, "archiveFrom": "2026-11-03", "archiveSnapshots": 5 } 

And the top 4 by new jobs between 2026-11-03 and 2026-11-07:

company open now open on 11-03 new jobs closed jobs remote share
Anduril Industries 2,633 2,564 166 97 1%
SpaceX 2,930 2,881 124 75 0.6%
Samsara 363 326 69 32 85.4%
Stripe 783 746 62 25 14.8%

The raw count favours big companies, so openJobsDeltaPct is often the more useful column: Samsara grew its open roles by 11.3% in four days, Anduril by 2.7%. Use "sortBy":"openJobsDelta" and minOpenJobs to rank by growth and hide tiny boards.

Step 3: new sales jobs as a lead list, in Python

Here is the version I would actually use for outreach: new postings with sales titles, newest first. It needs only requests.

import os import requests URL = ("https://api.apify.com/v2/acts/martin10291~company-hiring-signals-history" "/run-sync-get-dataset-items") payload = { "mode": "new_jobs", "keywords": ["account executive", "sales"], "limit": 50, } r = requests.post(URL, params={"token": os.environ["APIFY_TOKEN"]}, json=payload, timeout=300) r.raise_for_status() for job in r.json(): if "jobId" not in job: # skip the run summary row, if any continue print(f'{job["company"]:<22} {job["title"][:55]:<55} ' f'{job.get("location") or "-":<18} first seen {job["firstSeen"]}') 

Five of the rows I got today (I ran it with limit: 5):

EliseAI Sales Development Representative | Housing | Sales New York City first seen 2026-11-07 Weave Salesforce Development Manager & Systems Architect Weave - Headquarters (Lehi, UT) first seen 2026-11-07 Aledade Network Development Sales Lead Durham, NC first seen 2026-11-07 Braze Account Executive, Enterprise Singapore first seen 2026-11-07 Canarytechnologies Associate Account Executive – Italy (Italian Speaking) Barcelona first seen 2026-11-07 

Notice the second row. Keyword matching is a plain substring test today, so sales also matches Salesforce. If you want only sales roles, filter by department too ("department": ["Sales"]), or drop titles you do not want in your own code. Each row also has the job url, department, remote and a short snippet of the description, so you can tell your outreach tool why you are writing.

Step 4: run it daily and get only what is new

Add onlyNew and a monitorId:

payload.update({"onlyNew": True, "monitorId": "sales-roles"}) 

The first run is your baseline. After that, each run outputs only postings it has not reported to that monitorId before, and you only pay for those rows. Use a different monitorId per list (for example one for sales roles, one for your 50 target accounts with "companies": ["stripe", "samsara", ...]).

To schedule it you have two options:

  • Apify Schedules: open the actor, save your input as a task, then add a schedule such as 0 7 * * *. Connect it to a webhook, Slack, email, Google Sheets or Zapier/Make so the new rows land where you work. There is a ready example task on the actor page ("Which companies are hiring most").
  • Your own cron: keep the Python script above and run it every morning:
0 7 * * * cd /home/me/hiring && APIFY_TOKEN=xxx python3 new_sales_jobs.py >> feed.log 2>&1 

Step 5: closed roles and days open

closed_jobs mode returns postings that were missing from the board two days in a row, with daysOpen. Recruiters use it to see which roles were filled or pulled, and how fast. Because the archive started on 2026-11-03, daysOpen is still capped at a few days. It becomes more useful every day the archive runs, and that history is the part a new scraper cannot backfill.

Honest limits

  • Coverage is 813 companies, mostly US tech and startups on Greenhouse, Lever and Ashby. It is not the whole job market. For a company that is not in the archive, my ATS Jobs Aggregator reads any Greenhouse, Lever, Ashby or Workday board live.
  • The history is young. Day one of the archive was 2026-11-03, so today lookbackDays effectively means "since 11-03".
  • Departments are missing for Greenhouse boards today. Greenhouse rows come back with department: null and topDepartments shows (none). Lever and Ashby rows have departments. Adding them is next on my list.
  • New is relative to the baseline. A company that reposts the same role with a new ID shows it as new plus closed. Small boards with heavy reposting can look busier than they are, so look at openJobsDelta as well as newJobs.
  • Company names come from the board slug for some boards ("Canarytechnologies"), so match on companySlug in your own data.

Price

Pay per result: $2.00 per 1,000 rows, no start fee. The 5-row runs above cost $0.01 each. In only-new mode you pay only for postings you have not seen. Apify's free plan includes a small monthly credit, which is enough to try it on your own watch list.

Wrap-up

I built this because hiring is one of the few buying signals that is both public and early, and I wanted it as a JSON list instead of a paid dashboard. If a company you care about is missing from the 813, open an issue on the actor page with its job board URL and I will add it to the daily archive.

Next in this series: chaining the hiring list with domain and tech data to score accounts.