如何透過 Greenhouse、Lever、Ashby 和 Workday 職位板查看招聘情況及每週變化
How to see which companies are hiring right now (and what changed this week) from Greenhouse, Lever, Ashby and Workday boards
文章示範如何讀取 Greenhouse、Lever、Ashby 和 Workday 的公開職位板資料,並以每週快照比較職位新增與關閉。作者提供 24 行 Python 腳本監察 Greenhouse,並展示 Company Hiring Tracker 於 2026 年 10 月 18 日追蹤八家公司的結果,包括開放職位數、近 7 日刊登職位及上次檢查後的變化。
A company's open roles hint at where its money is going next. If it adds five enterprise Account Executives in Germany, it probably plans to sell harder there. If it closes its design roles and opens compliance roles, its priorities may have shifted. You don't need a data vendor to see this. Most tech companies list their jobs through a hosted job board such as Greenhouse, Lever, Ashby or Workday, and those boards serve the jobs as public JSON so that the company's own careers page can show them.
This post covers three things:
- How to read those boards yourself with a 24-line script.
- What a weekly "who is hiring, and what changed" check looked like on eight well-known companies on 18 October 2026, with the real output.
- How to schedule the check weekly and send the results to Slack or a Google Sheet.
Disclosure: I maintain the Company Hiring Tracker actor on Apify, which produced the examples below. The do-it-yourself part works without it.
The job boards serve public JSON
Each of these endpoints answered on 18 October 2026, and the job counts matched the actor's run that day:
# Greenhouse (Vercel: 93 open jobs)
curl -s https://boards-api.greenhouse.io/v1/boards/vercel/jobs
# Lever (Palantir: 310)
curl -s "https://api.lever.co/v0/postings/palantir?mode=json"
# Ashby (Linear: 29)
curl -s https://api.ashbyhq.com/posting-api/job-board/linear
# Workday (Zendesk: 139). Workday uses POST and pages of 20.
curl -s -X POST -H 'Content-Type: application/json' \
https://zendesk.wd1.myworkdayjobs.com/wday/cxs/zendesk/zendesk/jobs \
-d '{"limit":20,"offset":0,"appliedFacets":{},"searchText":""}'
The board name in the URL is usually the company's slug, which you can read off the careers page link (job-boards.greenhouse.io/vercel, jobs.lever.co/palantir, jobs.ashbyhq.com/linear).
The interesting signal is not the number of open jobs but what changed. Here is a minimal weekly diff for one Greenhouse board, using only the Python standard library (3.11 or later):
import json, sys, urllib.request
from datetime import datetime, timedelta, timezone
slug = sys.argv[1] # e.g. vercel -> https://job-boards.greenhouse.io/vercel
url = f"https://boards-api.greenhouse.io/v1/boards/{slug}/jobs"
jobs = json.load(urllib.request.urlopen(url))["jobs"]
week_ago = datetime.now(timezone.utc) - timedelta(days=7)
recent = [j for j in jobs if datetime.fromisoformat(j["first_published"]) >= week_ago]
print(f"{slug}: {len(jobs)} open jobs, {len(recent)} first published in the last 7 days")
# Compare with the snapshot saved by the previous run, then save this one.
snap = f"{slug}.json"
try:
before = json.load(open(snap))
except FileNotFoundError:
before = None
now = {str(j["id"]): j["title"] for j in jobs}
if before is not None:
for jid in now.keys() - before.keys():
print("NEW ", now[jid])
for jid in before.keys() - now.keys():
print("CLOSED", before[jid])
json.dump(now, open(snap, "w"))
python3 board_diff.py vercel printed vercel: 93 open jobs, 2 first published in the last 7 days on 18 October. Run it from cron once a week and you have a basic hiring monitor for one company.
It gets harder once you track a list of accounts instead of one company:
-
Finding the board. A CRM gives you
figma.com, notgreenhouse:figma. You have to read the careers page, find which vendor it embeds, and check that the board really belongs to that company. A careers page can link to someone else's board. -
Each vendor is different. The date fields differ (
first_published,createdAt,publishedAt). Workday is paged and only says "Posted 3 Days Ago", up to "30+ Days Ago". - Counting by team. "How many sales roles?" needs every title mapped to a function and a seniority.
- Keeping the history. You need a snapshot per company, and the diff has to skip boards that were only partly read, or you get phantom "closed" jobs.
That is what the actor does, so the rest of this post uses it.
A real example: eight companies on 18 October 2026
Input (run TcvvetXuELC1Ph1xz, 12 seconds):
{
"companies": [
"stripe.com", "notion.so", "linear.app", "figma.com", "databricks.com",
"https://jobs.lever.co/palantir",
"https://zendesk.wd1.myworkdayjobs.com/zendesk",
"vercel.com"
],
"keywords": ["account executive", "security", "data engineer"]
}
Domains were resolved to their boards automatically. Notion, Linear and Vercel were found through links on their careers pages, and Stripe, Figma and Databricks by trying the company name on the job boards and checking the result. Palantir and Zendesk were given as board URLs. Workday is only used when you give the career-site URL, or when the company's website links to it.
These companies had last been checked on 14 October, so each record also shows what changed in the four days since:
| Company | Board | Open jobs | Posted in last 7 days | Posted in last 30 days | New since last run | Closed since last run | Net change |
|---|---|---|---|---|---|---|---|
| Databricks | Greenhouse | 898 | 47 | 168 | 30 | 26 | +4 |
| Stripe | Greenhouse | 732 | 44 | 240 | 0 | 0 | 0 |
| Palantir | Lever | 310 | 5 | 16 | 2 | 6 | −4 |
| Figma | Greenhouse | 139 | 6 | 32 | 2 | 8 | −6 |
| Zendesk | Workday | 139 | 23 | 78 | 8 | 9 | −1 |
| Notion | Ashby | 128 | 6 | 31 | 1 | 3 | −2 |
| Vercel | Greenhouse | 93 | 2 | 16 | 3 | 0 | +3 |
| Linear | Ashby | 29 | 1 | 3 | 1 | 2 | −1 |
Stripe shows no changes because its previous check was earlier the same morning, not on 14 October. That is what a record looks like when nothing has moved.
Here is Vercel's record, shortened (the per-location, per-department and latest-jobs lists are cut):
{
"id": "greenhouse:vercel",
"companyName": "Vercel",
"ats": "greenhouse",
"boardUrl": "https://job-boards.greenhouse.io/vercel",
"matchedBy": "careers-page",
"isHiring": true,
"openJobs": 93,
"countComplete": true,
"jobsPostedLast7Days": 2,
"jobsPostedLast30Days": 16,
"jobsPostedLast90Days": 48,
"medianJobAgeDays": 89,
"jobsByFunction": {"Sales": 32, "Engineering": 24, "Security": 7, "Design": 5, "Product": 5, "Finance": 4, "Marketing": 4, "People & HR": 4, "Customer Success & Support": 2, "Legal & Compliance": 2, "Other": 2, "Data & AI": 1, "Operations": 1},
"jobsBySeniority": {"mid": 56, "manager": 14, "senior": 10, "lead": 6, "director": 5, "intern": 2},
"leadershipJobs": 19,
"jobsByCountry": {"US": 70, "GB": 11, "DE": 7, "AU": 3, "IN": 2},
"jobsByWorkplace": {"remote": 10, "hybrid": 83, "onsite": 0, "unspecified": 0},
"keywordMatches": {"account executive": 11, "security": 4, "data engineer": 0},
"isFirstRun": false,
"previousRunAt": "2026-10-14T16:10:40.459Z",
"openJobsAtPreviousRun": 90,
"openJobsChange": 3,
"newJobsSinceLastRun": 3,
"closedJobsSinceLastRun": 0,
"functionChanges": {"Sales": 2, "Security": 1},
"newJobs": [
{"title": "GTM Engineer", "department": "Field Readiness", "function": "Sales", "seniority": "mid", "location": "Hybrid - San Francisco, New York City, Austin", "postedAt": "2026-10-15T23:13:47.000Z", "url": "https://job-boards.greenhouse.io/vercel/jobs/6225825004"},
{"title": "Security Software Engineer, IAM", "department": "Security", "function": "Security", "seniority": "mid", "location": "Remote - United States", "postedAt": "2026-06-25T00:19:22.000Z", "url": "https://job-boards.greenhouse.io/vercel/jobs/6093255004"},
{"title": "Solutions Architect", "department": "Solution Architects", "function": "Sales", "seniority": "lead", "location": "Remote - United Kingdom, Germany", "postedAt": "2026-02-09T21:37:43.000Z", "url": "https://job-boards.greenhouse.io/vercel/jobs/5796302004"}
],
"closedJobs": []
}
What the fields mean
Hiring velocity counts the jobs that are open now, grouped by when the board says each one was posted:
-
jobsPostedLast7Days,jobsPostedLast30Days,jobsPostedLast90Days: open jobs whose posting date falls in that window. Databricks had 47 of its 898 open jobs posted in the last 7 days. Palantir had 5 of 310. -
medianJobAgeDays: the age of the typical open job. A high median means many long-running postings. Palantir's was 479 days and Linear's 143. A low median means the openings are mostly recent. -
jobsWithPostingDate: how many jobs had a date at all. This matters for Workday, which only shows dates up to "30+ Days Ago". Zendesk had dates on 81 of its 139 jobs, so its 90-day count covers only those 81.
Changes since the previous run compare this run's job ids with the last run's:
-
newJobsSinceLastRunandnewJobs: jobs that are open now and were not open last time. Some carry an olderpostedAt. Vercel's "Security Software Engineer, IAM" is dated June, yet it was not in the 14 October snapshot. If you only want freshly posted roles, also filter onpostedAt. -
closedJobsSinceLastRunandclosedJobs: jobs that were open last time and are gone now. A closed job may have been filled, paused or re-posted under a new id, which counts as one closed and one new. A job is only reported closed when both runs read the whole board (countComplete: true). -
openJobsChange: the net change. It can hide a lot of movement. Databricks was +4 net, but that was 30 new jobs and 26 closed. -
functionChanges: the net change per function. Databricks was Engineering +4, Data & AI +4 and Sales −7. Figma was Operations −4.
function and seniority come from the job title (and the department when the title is ambiguous). They are rules of thumb, not a ground truth: "Account Executive" is Sales/mid, and "Staff Engineer" is lead. Use them for trends rather than for exact headcount.
Three practical uses
1. Outbound sales triggers
A company that is hiring for a function is likely to spend on it: on tools for the new people, and on the problems that new team was hired to solve. Two ways to use this:
-
Companies mode with
keywordsgives a count per keyword for every account. In the run above, "account executive" matched 106 open titles at Stripe, 81 at Databricks, 26 at Zendesk and 0 at Notion. "security" matched 28 at Databricks and 24 at Palantir. -
Jobs mode with filters lists the matching roles themselves. Run
fgfuI8MIvIvOvYb5Aasked for titles containing "account executive" posted in the last 7 days at five companies, and returned 29 jobs in 9 seconds: 12 at Stripe, 12 at Databricks, 5 at Zendesk, and none at Figma or Vercel. Four of them:
| Company | Title | Location | Posted | Pay (where published) |
|---|---|---|---|---|
| Stripe | Account Executive, Funded Startups DACH | Berlin | 2026-10-16 | not published |
| Databricks | Emerging Enterprise Core Account Executive, DACH | Munich, Germany | 2026-10-15 | not published |
| Databricks | Enterprise Account Executive, Federal Health | Remote - Washington D.C. | 2026-10-14 | $272,000 – $374,000 USD |
| Zendesk | Senior Enterprise Account Executive | Remote, Colorado | about 2026-10-16 (Workday says "2 days ago") | not published |
If you sell sales tooling into DACH, those first two rows suggest which accounts to look at first this week. The data contains roles, not people: no recruiter or hiring-manager names or contact details. Use it to choose accounts and timing, and reach people through your normal, lawful channels.
2. Competitor tracking
Put your competitors in one list with their own tracking key ("monitorKey": "competitors"), run it weekly, and read functionChanges, newJobs and closedJobs. In the four days covered above:
- Figma closed 8 roles and opened 2. The new ones were "Director, Governance Risk & Compliance" and "Compliance Engineer". The closed ones included "Federal Compliance Manager" and "Senior Analyst, Revenue Transformation".
- Vercel added two Sales-function roles (one of them a "GTM Engineer") and one Security role, and closed nothing.
- Notion had 17 of its 128 open roles in Japan and 5 in Korea (
jobsByCountry).
One week of changes is an anecdote. The value comes from the same list read every week for a quarter.
3. Investment and market research
For a portfolio or a market map, compare the velocity and mix fields across companies:
- Pace: the 30-day count against the total open jobs. Stripe: 240 of 732 posted in the last 30 days. Palantir: 16 of 310.
- Mix: Sales was the largest function at Stripe (239 of 732) and Figma (50 of 139). Engineering was largest at Palantir (173 of 310) and Databricks (329 of 898, with Sales close behind at 316).
-
Seniority:
leadershipJobs(manager, director and executive titles) was 136 at Stripe and 176 at Databricks.
Open roles are a signal, not a headcount. Some companies keep evergreen postings open for years, and some hire through channels that never show up on a board. Read the trend across several weeks before drawing conclusions.
Run it weekly and send the results to Slack or a Google Sheet
These steps are all in the Apify Console, with no code.
1. Save the input as a task. Open the actor, paste your companies (domains from a CRM export work as they are), set a tracking key such as crm-tier-1 so this list keeps its own history, and save the input as a task. Make two tasks if you want both views:
- Weekly account signals: mode "companies". One record per company, with the new and closed jobs inside it.
-
New roles feed: mode "jobs", with
onlyNew: true, yourkeywords, andpostedWithinDays: 7. Each run delivers, and charges for, only jobs that earlieronlyNewruns with the same tracking key did not deliver. WithoutpostedWithinDays, the first run delivers every open job that matches.
2. Schedule it. Go to Schedules → Create new, set a cron expression such as 0 8 * * 1 (08:00 every Monday) and a timezone, add your task, and click Enable. New schedules are created disabled.
3. Send to Slack. In the task's Integrations tab, choose Slack, connect your workspace, pick a channel and the event "run succeeded". The message field accepts a template, for example:
*Weekly hiring check done*
Results: https://console.apify.com/actors/runs/{{resource.id}}
The Slack message announces the run and links to it. If you want the job titles themselves in the channel, add a webhook integration on the same event and point it at a Make, Zapier or n8n flow that reads the run's dataset and posts each row.
4. Send to a Google Sheet. In the same Integrations tab, either:
- if your Integrations tab offers it as an actor-to-actor integration, add the Google Sheets Import & Export actor (
lukaskrivka/google-sheets), connect your Google account, choose the spreadsheet, and setdatasetIdto{{resource.defaultDatasetId}}andmodetoappend, so every weekly run adds its new roles below last week's (it is a separate actor, billed for its own platform usage); or - use Upload results to GDrive, which saves each successful run's results as a file in an
Apify Uploadsfolder in your Google Drive.
The "new roles feed" task works best with the Sheet. Because onlyNew delivers each job once, the sheet becomes a running log of every new matching role, one row each, with title, company, location, posting date, link and, where the board publishes it, salary.
What it costs
The actor charges per event. The prices below are from the live pricing record on 18 October 2026, for the Free plan and the Bronze tier. Silver and Gold tiers pay less.
| Event | Free plan | Bronze |
|---|---|---|
| Run start (per GB of memory; the default 512 MB counts as one) | $0.0036 | $0.003 |
| Company record (companies mode) | $0.0048 | $0.004 |
| Job record (jobs mode) | $0.0006 | $0.0005 |
The two runs in this post:
- Eight companies, companies mode: 8 × $0.0048 + $0.0036 = $0.042 on the Free plan ($0.035 on Bronze).
- 29 matching jobs, jobs mode: 29 × $0.0006 + $0.0036 = $0.021 on the Free plan ($0.0175 on Bronze).
A worked example: you track 200 accounts that resolve to a board, once a week, in companies mode. Each run costs 200 × $0.0048 + $0.0036 = $0.96 on the Free plan, or $3.85 for four weekly runs ($0.80 and $3.21 on Bronze). Companies whose board can't be found cost nothing, and they are listed in the run summary with the reason. A maximum charge set on the run is respected: the actor stops before it would exceed it.
Limits worth knowing before you rely on it
- Supported boards: Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Personio, Teamtailor and Workday. Not supported: iCIMS, Taleo, SuccessFactors, Eightfold, Phenom, BambooHR, Jobvite, custom career sites, and EU-hosted Greenhouse boards. In a test on 101 well-known tech domains, 72 resolved to a board and 29 did not (21 linked no supported board). For a company that doesn't resolve, paste its board URL.
- Workday is only used when you give the career-site URL, or when the company's website links to it. Its jobs have no department, and they have no date when older than 30 days.
- Salary only appears where the board publishes it.
- One board per company. A company that lists jobs from several boards on one careers page will show fewer jobs than its page does.
If you only need one or two companies, the script above is enough. If you want a list of accounts checked every Monday, with the diff, the team mix and a Slack or Sheet feed, try Company Hiring Tracker on Apify on your own list. The default input runs on three companies.
來源:DEV Community · dev.to