If you want to know how much demand there is for a skill — how many companies are hiring for it,
what they pay, where the roles are — the usual answer is to page through a jobs API and reduce the
list yourself. For a slice of 14,000 postings that is 140 paged calls plus your own statistics code.
We built the reduction as an endpoint. Same filter object as search, one call, computed server-side.
Everything below is a live response from Hiring Index,
captured on 2026-09-08. No rounded marketing numbers: the exact values the API returned.
The data
The index is read directly from the catalogue APIs of ten applicant tracking systems — Workday,
SmartRecruiters, Greenhouse, Workable, Lever, Ashby, Recruitee, Teamtailor, Breezy, Personio.
No job boards, no aggregators, no scraping of LinkedIn or Indeed. 1,597,584 postings today,
17,334 employers, re-read once a day.
That choice has a cost and we publish it rather than hide it: not every ATS publishes every field.
A salary is disclosed on a small minority of rows, and the coverage table per source is in the
listing docs. Where a field is missing it is null, never a guess.
One call, the whole slice
import requests
H = {"X-RapidAPI-Key": "YOUR_KEY", "X-RapidAPI-Host": "hiringindex.p.rapidapi.com"}
BASE = "https://hiringindex.p.rapidapi.com"
d = requests.post(f"{BASE}/jobs/insights", headers=H,
json={"keywords": ["Terraform"]}).json()
h = d["headline"]
print(h["row_count"], "postings from", h["company_count"], "employers")
print(h["new_this_week"], "new in the last seven days")
14173 postings from 2071 employers
773 new in the last seven days
The same object carries the pay distribution, grouped by currency and pay period rather than
averaged into one meaningless number:
usd = max((g for g in d["salary"] if g["currency"] == "USD"), key=lambda g: g["count"])
print(usd["count"], "postings disclose a range")
print("p25", usd["min"]["p25"], "median", usd["min"]["p50"], "p75", usd["min"]["p75"])
688 postings disclose a range
p25 145000 median 170000 p75 200000
Those percentiles are computed on the lower bound of the advertised range, over the rows that
actually publish one — 688 of 14,173. The count travels with the number, so you can always tell how
much evidence is behind it.
Where the roles are, from the same response:
San Francisco 342 · London 242 · Bengaluru 106 · Singapore 105 · New York 101
And how long they have been open: median 62 days live, 51.4 % of them older than 60 days. For a
skill in demand that is a useful sanity check — a listing sitting open for months is a weaker signal
than a fresh one.
Title search and description search are different questions
The filter vocabulary has two text fields and the difference matters more than it looks:
| Filter | Question it answers | Kubernetes |
|---|---|---|
job_titles |
is this the role? | 314 postings |
keywords |
is it mentioned in the posting? | 25,196 postings |
Eighty times the volume, and both numbers are correct — they answer different questions. Titles
find the roles named after the technology; keywords find every posting that asks for it, which is
what you want when you are measuring demand for a skill.
Very common words are refused rather than served slowly:
{"error": "keyword_too_common",
"message": "'excel' appears in about 38% of postings (~606,767 matches); keyword aggregates are computed for terms under 130,000 matches.",
"meta": {"term": "excel", "estimated_matches": 606767, "limit": 130000}}
A fast, explicit refusal beats a request that ties up the database and times out. Narrowing by city
or country does not help, because the term scan comes first — the message says so instead of making
you discover it.
Values come as the employer wrote them
One deliberate choice worth flagging, because it is the opposite of what most APIs do. Vendor fields
are returned raw. The remote flag on the Terraform slice looks like this:
false 2519 · true 2042 · hybrid 139 · Hybrid 138 · remote 90 · onsite 71 · Partially Remote 51 · Flex 38
Fifteen distinct spellings, because fifteen spellings is what employers publish. We could fold them
into three tidy values, and every fold would be a small guess baked into your data. Instead the
split is returned as it is and the folding — if you want it — happens in your code, where you can
see it. Same for seniority: Mid-Senior level and Mid-Senior Level arrive as two rows.
One employer, precisely
Company names are ambiguous — contractors put a big name in their own job titles. Filtering by the
ATS board handle is exact:
requests.post(f"{BASE}/jobs/search", headers=H,
json={"handles": ["walmart:wd504:WalmartExternal"], "limit": 1}).json()["total_count"]
# 22010 — Walmart's own Workday board
# company_name "Walmart" matches 396, mostly other companies naming it
Try it
Free tier, no card: rapidapi.com/starnikovoleg/api/hiringindex.
The same index with browsable market pages is at hiringindex.org — 4,350
pages of roles, cities and technologies, each one generated from the calls shown above.
If a field looks wrong, the error envelope carries meta.request_id; quote it and we can find the
exact call.
Top comments (0)