- API reference
- Data reference
Enums & vocabularies
Every fixed value the API accepts and returns, with live counts and the filter each one belongs to.
The complete vocabularies behind the enum filters on
/v1/jobs. If a filter just returned a 400,
the hint in the body names the values on this page.
A wrong value is a 400 now, not an empty list
Enum filters — seniority, work_arrangement, experience_level,
employment_type, education, taxonomies, continent — are still
case-sensitive equality, but a typo, a wrong case, or a value
from the wrong vocabulary is rejected with a 400 whose
hint lists the allowed values. A typo'd filter can never masquerade as
an empty result.
curl "…/v1/jobs?seniority=Mid-Senior level" # ✓ 198,375 jobs
curl "…/v1/jobs?seniority=mid-senior level" # ✗ 400 — wrong case
curl "…/v1/jobs?seniority=Senior" # ✗ 400 — not a value
curl "…/v1/jobs?seniority=5-10" # ✗ 400 — that's experience_levelThe exceptions are the open vocabularies — skill, source, language, and
city aren't enums, so an unknown value there still matches nothing rather
than erroring.
Counts below are from a recent snapshot. They're here to show you the shape of
each distribution — don't hardcode them. For a live count of any value, send the
filter with limit=1 and read total:
# The live number of Director-level jobs, without transferring any of them.
curl -G https://api.hyperjobs.io/v1/jobs \
-H "Authorization: Bearer $HYPERJOBS_KEY" \
--data-urlencode "seniority=Director" --data-urlencode "limit=1"
# → { "data": [ … ], "total": 26465, "limit": 1, "offset": 0 }/v1/meta gives live totals for jobs, companies,
the source breakdown, and per-field fill rates (coverage_pct) — but no
per-enum counts for the vocabularies below.
Quick reference#
| Filter | Vocabulary | Matching |
|---|---|---|
seniority | 6 values | Exact, case-sensitive · comma = OR |
experience_level | 4 values | Exact, case-sensitive · comma = OR |
work_arrangement | 3 values | Exact, case-sensitive · comma = OR |
employment_type | 8 values | Token in a list, comma = OR |
education | 5 values | Token in a list, comma = OR (overlap) |
taxonomies | 28 values | Token in a list, comma = OR (overlap) |
continent | 7 values | Exact, case-sensitive · comma = OR |
visa_sponsorship | 2 values | Single value |
source | 64 live slugs, live via /v1/meta | Exact, case-sensitive · comma = OR |
language | Full name or ISO-639-1 | Exact · comma = OR |
skill | Open | Token in a list, comma = AND — the exception |
Comma = OR on every multi-value filter, with one exception: skill, where
every listed value is required. That's the one inversion to keep straight — see
below. country and company_country aren't
enums but take a full name or an ISO-2 code, comma = OR — ?country=US,Germany
works, and an unrecognised ISO-2 code is a 400.
seniority#
Six values. Filter: ?seniority=. Field:
job.seniority.
| Value | Jobs |
|---|---|
Mid-Senior level | 198,375 |
Associate | 99,023 |
Entry level | 78,095 |
Management | 28,749 |
Director | 26,465 |
Internship | 11,603 |
Note the shape: Mid-Senior level is nearly half of everything classified, so
it's a weak filter on its own. Management and Director are distinct values,
not synonyms — a query for leadership roles needs both, which is one
request: ?seniority=Director,Management (comma = OR).
experience_level#
Four bands, in years. Filter: ?experience_level=. Field:
job.experience_level.
| Value | Jobs |
|---|---|
5-10 | 170,565 |
2-5 | 112,024 |
0-2 | 100,549 |
10+ | 42,798 |
The values are literal strings — ?experience_level=5-10, not 5 and not
5–10 with an en-dash. There's no range syntax and no numeric comparison.
`experience_level` and `seniority` are separate fields
They're related but derived independently, and a posting can carry a combination that looks inconsistent. The rough correspondence is:
experience_level | Aligns with |
|---|---|
0-2 | Entry level |
2-5 | Associate |
5-10 | Mid-Senior level |
10+ | Mid-Senior level |
Both bands above 5 years map to the same seniority, so seniority cannot
distinguish a 6-year role from a 15-year one — use experience_level when that
difference matters. And because neither field is computed from the other at
read time, don't filter on both expecting a narrower result: you'll get the
intersection of two independent judgements, which is often much smaller than
either alone.
work_arrangement#
Three values. Filter: ?work_arrangement=. Field:
job.work_arrangement.
| Value | Jobs |
|---|---|
On-site | 333,870 |
Remote | 127,895 |
Hybrid | 73,706 |
On-site carries a hyphen and a lower-case s. Onsite, On-Site, and
onsite all return zero rows.
`work_arrangement=Remote` and `remote=true` are different filters
They read different fields and return different sets — 127,895 postings have
work_arrangement=Remote, while 85,069 have
location.remote true. Neither number is wrong;
they're separate signals derived from separate evidence.
?work_arrangement=Remoteis the classification. It's the more precise of the two, and the only one that can expressHybrid.?remote=filters onlocation.remote. It takestrue/false/1/0—falsemeans on-site/hybrid only — and anything else is a400(a pre-release draft silently ignoredremote=true; that trap never shipped).
Prefer work_arrangement. Use both only if you want the stricter
intersection.
continent#
Seven values. Filter: ?continent=, comma = OR. Field:
job.location.continent.
Africa · Antarctica · Asia · Europe · North America · Oceania ·
South America
Title Case, with the space in the two-word names — North America, not
north-america. A value outside the seven is a 400 naming the list.
employment_type#
Filter: ?employment_type=. Field:
job.employment_type.
| Value | Jobs |
|---|---|
FULL_TIME | 429,457 |
PART_TIME | Query total |
CONTRACTOR | Query total |
INTERN | Query total |
TEMPORARY | Query total |
PER_DIEM | Query total |
VOLUNTEER | Query total |
OTHER | Query total |
FULL_TIME dominates so heavily that the rest are a rounding error by
comparison; for a current count of any one of them, send it with limit=1 and
read total.
SCREAMING_SNAKE_CASE, always. Full-time, full_time, and FULLTIME all
return zero rows.
It's an array, not a value#
A posting can carry several types at once, so the field holds a list —
["FULL_TIME","TEMPORARY"] for a full-time contract role, and 20+ combinations
occur in the data. The filter matches a value inside that list, so
?employment_type=FULL_TIME finds both ["FULL_TIME"] and
["FULL_TIME","TEMPORARY"] — and ?employment_type=FULL_TIME,CONTRACTOR
matches either type (comma = OR).
A real array
A pre-release draft of this API returned this field as a raw JSON string —
"[\"FULL_TIME\"]" — and you had to JSON.parse it. It's a real array, like
skills and taxonomies beside it:
job.employment_type.includes("FULL_TIME"); // ✓ — just an arrayIf you shipped a defensive parse, delete it. The same applies to
education_requirements and benefits. See
the Job object.
education_requirements#
Filter: ?education= — note the parameter is education, not
education_requirements, though the field it filters is
job.education_requirements.
| Value | Jobs |
|---|---|
bachelor degree | 49,367 |
high school | 30,156 |
postgraduate degree | 22,749 |
professional certificate | 5,318 |
associate degree | 4,053 |
All lower-case, all with a space, none pluralised. Bachelor degree,
bachelors degree, and Bachelor's are a 400 naming the five values. Comma =
OR (overlap match): ?education=bachelor degree,postgraduate degree matches
postings listing either.
Also an array field — a real array, not a raw JSON
string — and also sparse: the five values
together cover well under a third of the dataset, because most postings state no
education requirement at all. A null means the posting was silent, not
that no degree is required, so filtering on education excludes the large
majority of jobs that simply didn't say.
taxonomies#
28 job families. Filter: ?taxonomies=. Field:
job.taxonomies. Title Case, with spaced
ampersands in the compound names.
Administrative · Agriculture · Art & Design · Construction ·
Consulting · Creative & Media · Customer Service & Support ·
Data & Analytics · Education · Energy · Engineering ·
Finance & Accounting · Healthcare · Hospitality · Human Resources ·
Legal · Logistics · Management & Leadership · Manufacturing ·
Marketing · Sales · Science & Research · Security & Safety ·
Social Services · Software · Technology · Trades · Transportation
Per-taxonomy counts aren't published anywhere. For the distribution, query each
value with limit=1 and read total — 28 cheap requests, well within a minute's
rate limit.
Present on about 64% of postings — a job with no taxonomy isn't uncategorisable, it just wasn't classified.
The & needs URL-encoding as %26, or the query string ends at the ampersand
and the rest of your filters vanish:
# The shell and the URL both need to leave `&` alone. --data-urlencode does both.
curl -G https://api.hyperjobs.io/v1/jobs \
-H "Authorization: Bearer $HYPERJOBS_KEY" \
--data-urlencode "taxonomies=Finance & Accounting"`Software`, `Technology`, and `Engineering` are three separate values
They overlap in ordinary usage but not here, and none implies another. A
backend role might carry ["Software"], ["Software","Technology"], or
["Engineering"] depending on how the posting reads.
Since commas mean OR,
?taxonomies=Software,Technology,Engineering covers all three in one request
— an overlap match: any listed value qualifies. And a value outside the 28 is
a 400 naming the list, so a typo can't silently empty the result. Skills &
taxonomies works through it.
visa_sponsorship#
Two values. Filter: ?visa_sponsorship=. A single value, not a comma list.
| Value | Meaning |
|---|---|
only | Only jobs that mention visa sponsorship |
exclude | Hide jobs that mention it |
Anything else — including true — is a 400.
language#
Filter: ?language=, comma = OR. Field:
job.job_language.
The detected language of the posting. The field stores the short lower-case
ISO-639-1 code — en is by far the most common — but the filter
takes either the full name or the code: ?language=English and ?language=en
are the same query.
33 codes are supported:
en · fr · de · nl · es · it · pt · pl · sv · zh · ja ·
hu · id · no · cs · el · da · fi · uk · ro · tr · bg ·
sk · ko · ru · hr · ar · vi · sl · sr · th · he · ms
Not a validated enum — a value outside the list matches nothing rather than erroring.
source#
64 public platform slugs return rows, documented in full on the sources
page. There is no second tier: every
platform we list is a value you can filter on. The filter takes the slug —
lever, not the stored lever.co (the stored value is still accepted as input) —
and comma = OR. exclude_source takes the same values to filter platforms out.
The keys of /v1/meta.sources are this list,
live, with counts. It's the only vocabulary the API will enumerate for you —
and worth reading, because source is a csv filter rather than an enum: an
unknown value matches nothing instead of returning a 400.
source_type#
| Value | Means |
|---|---|
ats | A board the employer authors and manages applicants on — the canonical record. Includes join.com. |
jobboard | A third-party board the employer posted to — LinkedIn, Wellfound, … |
aggregator | A re-listing site that copied the posting from somewhere else. |
Comma = OR, and this one is a checked enum — a value off the list is a
400. It filters provenance, not duplication: cross-source mirrors are
already folded, so source_type=ats selects
employer-authored records rather than removing copies. The same value appears
as source_type on every Job object.
skill#
An open vocabulary of canonical tokens, not an enum — far too many values to list, and it grows.
The trap is that displayed skills don't always round-trip: you see
Machine Learning, but the filter wants machinelearning. A few aliases
(c++→cpp, c#→csharp, .net→dotnet, node.js/nodejs→node,
react.js→react, vue.js→vue, golang→go) are translated for you;
multi-word names are not. See
skills & taxonomies.
Commas mean OR — except skill#
Every multi-value filter treats a comma-separated list as OR:
curl "…/v1/jobs?taxonomies=Software,Healthcare" # either family
curl "…/v1/jobs?employment_type=FULL_TIME,INTERN" # either type
curl "…/v1/jobs?seniority=Director,Management" # leadership, one requestThe one exception is skill, where comma = AND — every listed skill must be
present, because "needs Python and AWS" is what a skill list means. For OR
across skills, run one request per value and merge on
id.
Shape parameters#
Two more closed vocabularies live on the shape side of the query string, and they're validated the same way:
description_format—text|html. Omit the parameter and the response has no description key at all (smaller payloads).collapse—true|title,organization. Both name the same grouping; see duplicates & collapsing.
Any other value is a 400.
Validating before you send#
The server does this for you now: a bad enum value is a 400 whose hint
lists the allowed values, so a client-side lookup table is a UX nicety rather
than a correctness requirement. The one list still worth checking dynamically
is source — it's a csv filter, not an enum (unknown values match nothing),
and it's the one vocabulary that changes on its own:
const { sources } = await (
await fetch("https://api.hyperjobs.io/v1/meta", { headers })
).json();
if (!(input in sources)) {
throw new Error(`Unknown source: ${input}. Valid: ${Object.keys(sources).join(", ")}`);
}Related#
The three matching modes, and every filter's semantics.
The Job objectWhere each of these values shows up in a response.
Sources & ATS coverageThe 17 source slugs and the platforms behind them.
What the 400 body looks like, and which filters still fail silently.