Job Opportunities API

Check the data. Then trust it.

endpoints

Key and metadata

Four endpoints that describe the API rather than return rows: your key, the filter vocabularies, the freshness report and the provider list.

Last verified 2026-08-22 · Assumes: Authentication. · Markdown copy

1GET /v1/me

Your key, its plan, its limits and today's usage. Costs no records, so it is safe to poll and safe to call on start-up.

In depthWhy it exists, what it is not, what people get wrong

The first call to make with a new key, and the call to make when something is confusing. It answers “is this key alive”, “which plan is it on” and “how much have I used today” in one round trip, and it does not touch the record meter.

It reports request usage, not record usage: the record counters live in the X-RateLimit-Records-* response headers on any row-returning call. That split is worth remembering: this endpoint tells you about the throttle, the headers tell you about the licence.

Exact contractTypes, defaults, ranges, errors, edge cases
CodeWhat it means
200Key status.
400A parameter the endpoint does not take. unknown_parameter: the name is not a parameter of this endpoint; the message names it and, when a valid parameter is within a small edit distance, suggests it ("unknown parameter: 'countyr'; did you mean 'country'?"). unsupported_parameter: a real parameter of another endpoint that has no meaning here (for example limit on /v1/export). Refused before the meter: nothing is charged.
401Missing, unknown, revoked or expired key. All four are reported identically.
429Over your plan's per-minute or per-day limit. Retry-After says how long (seconds to the next minute boundary, or to UTC midnight on a daily breach). The body says exactly when the limit resets (resets_at, resets_at_text) and, when a higher plan exists, links a same-key upgrade (upgrade_url).
The response, from a real call
{
  "created": "2026-08-22",
  "key_prefix": "joadocs",
  "plan": "signal",
  "status": "active",
  "limits": { "per_day": 400000, "per_minute": 300 },
  "renews_or_expires": null,
  "usage_today": 1
}
key_prefix
A short, non-secret label for the key. Safe to log and to show a user; it is how you tell two of your own keys apart.
status
active is the usable state. A revoked or expired key does not reach this endpoint. It gets a 401.
renews_or_expires
The next billing date on a subscription, or null on a plan that does not renew.
usage_today
Requests made today, against limits.per_day. Resets at UTC midnight.
Check a key
$ curl -s -H "Authorization: Bearer $JOA_KEY" https://api.jobopportunitiesapi.org/v1/me | jq
{
  "created": "2026-08-22",
  "key_prefix": "joadocs",
  "plan": "signal",
  "status": "active",
  "limits": { "per_day": 400000, "per_minute": 300 },
  "renews_or_expires": null,
  "usage_today": 1
}
Captured 2026-08-22 from a throwaway Signal key created for this build.

2GET /v1/meta/facets

Every filter value with its live count. This is the list to enumerate at run time instead of hard-coding a vocabulary.

In depthWhy it exists, what it is not, what people get wrong

One response carries every controlled vocabulary the listing endpoints accept: categories, countries, cities, remote statuses, employment types, seniorities, source types and providers, each with the number of live rows behind it. It is the single most useful call for building a filter UI, and the correct defence against a value that has left the vocabulary.

Exact contractTypes, defaults, ranges, errors, edge cases
CodeWhat it means
200Facet counts.
400A parameter the endpoint does not take. unknown_parameter: the name is not a parameter of this endpoint; the message names it and, when a valid parameter is within a small edit distance, suggests it ("unknown parameter: 'countyr'; did you mean 'country'?"). unsupported_parameter: a real parameter of another endpoint that has no meaning here (for example limit on /v1/export). Refused before the meter: nothing is charged.
401Missing, unknown, revoked or expired key. All four are reported identically.
429Over your plan's per-minute or per-day limit. Retry-After says how long (seconds to the next minute boundary, or to UTC midnight on a daily breach). The body says exactly when the limit resets (resets_at, resets_at_text) and, when a higher plan exists, links a same-key upgrade (upgrade_url).
Facet groupFiltersNote
familycategory, exclude_categoryNamed family in the facet response and category in the filter.
countrycountry, exclude_countryISO-3166 alpha-2.
citycityResolved city names; thinner coverage than country.
remoteremoteremote, hybrid, on_site, not_stated.
employmentemployment_typeIncludes not_stated.
engagementengagementArrays counted per element; unknown = not established by the posting.
contract_categorycontract_categoryArrays counted per element; includes unknown.
agencyagencyIncludes unknown.
working_scheduleworking_scheduleIncludes unknown. Distinct from the frozen employment group.
seniorityseniorityIncludes not_stated.
source_typesource_type, exclude_source_typeaggregator and agency are reserved and match nothing.
providerprovider, exclude_providerAlso available in richer form at /public/providers.
Value to sendLabelLive rows
HealthcareHealthcare349,929
EngineeringEngineering274,086
Operations & AdminOperations & Admin261,392
SalesSales213,370
Skilled TechnicianSkilled Technician171,169
uncategorisedUncategorised159,934
RetailRetail151,910
HospitalityHospitality133,041
FinanceFinance111,334
Logistics & TransportLogistics & Transport79,320
Customer SupportCustomer Support66,253
EducationEducation64,600
ManufacturingManufacturing57,892
MarketingMarketing56,928
HR & RecruitingHR & Recruiting42,478
Consulting & StrategyConsulting & Strategy38,418
Construction & TradesConstruction & Trades37,056
Data & AnalyticsData & Analytics36,905
Legal & ComplianceLegal & Compliance28,378
DesignDesign21,041
ProductProduct19,659
Science & ResearchScience & Research19,336
SecuritySecurity19,331
ProcurementProcurement12,534
Safety & EnvironmentSafety & Environment9,849

25 values, rendered from /public/facets (keyless): the same data as /v1/meta/facets. Enumerate it at run time rather than hard-coding this list.

The vocabularies, keylessly
curl -s https://api.jobopportunitiesapi.org/public/facets \
  | jq '{groups: (.data | keys), categories: (.data.family[:5])}'

3GET /v1/meta/freshness

How recently the ledger was verified, how much of it carries each field, and how many roles closed in the last day and week.

In depthWhy it exists, what it is not, what people get wrong

Use this to decide whether the data behind an answer is current enough for what you are doing with it, and to show a “last updated” line honestly. It also carries the indicative currency rates used to compute salary_min_annual_eur.

For the broader picture (the three populations, the reconciliation, the excluded sources), use /public/coverage instead. This endpoint is about time; that one is about composition.

Exact contractTypes, defaults, ranges, errors, edge cases
FigureValueMeaning
measured_at27 October 2026, 10:52 UTCWhen the snapshot behind this report was taken.
generated_at27 October 2026, 11:13 UTCWhen this response was assembled.
live_listings2,914,565Rows /v1/jobs will serve.
ledger_rows12,334,935Live + withheld + closed.
closures.last_24h55,708Roles that left their source in the last day.
closures.last_7d440,884…and in the last week.
closures.total8,653,347Retained: kept indefinitely.

Measured 27 October 2026, 10:52 UTC · rendered from /public/freshness, which needs no key. These figures move; the endpoint is always right and this page is only as fresh as its last build.

CodeWhat it means
200Freshness and coverage report.
400A parameter the endpoint does not take. unknown_parameter: the name is not a parameter of this endpoint; the message names it and, when a valid parameter is within a small edit distance, suggests it ("unknown parameter: 'countyr'; did you mean 'country'?"). unsupported_parameter: a real parameter of another endpoint that has no meaning here (for example limit on /v1/export). Refused before the meter: nothing is charged.
401Missing, unknown, revoked or expired key. All four are reported identically.
429Over your plan's per-minute or per-day limit. Retry-After says how long (seconds to the next minute boundary, or to UTC midnight on a daily breach). The body says exactly when the limit resets (resets_at, resets_at_text) and, when a higher plan exists, links a same-key upgrade (upgrade_url).

4GET /v1/meta/providers

Every source the ledger is built from, with its live row count and its description coverage.

In depthWhy it exists, what it is not, what people get wrong

The provider list is both a vocabulary and a data-quality report. Description coverage varies from around 60% to nearly 100% depending on which system published the row, so if your product needs advert bodies, this table tells you which providers to prefer.

Exact contractTypes, defaults, ranges, errors, edge cases
providerLabelLive rowsWith a descriptionsource_classsource_type
workdayWorkday814,982801,106 (98.2%)employer_atsats
company_siteCompany career site807,441722,160 (89.4%)directcareer_site
oracleOracle HCM178,152170,539 (95.7%)employer_atsats
greenhouseGreenhouse92,38192,197 (99.8%)employer_atsats
smartrecruitersSmartRecruiters65,09664,928 (99.7%)employer_atsats
icimsiCIMS55,20055,200 (100%)employer_atsats
teamtailorTeamtailor46,77446,760 (99.9%)employer_atsats
paylocityPaylocity37,77037,587 (99.5%)employer_atsats
workableWorkable34,09834,089 (99.9%)employer_atsats
ashbyAshby32,95632,946 (99.9%)employer_atsats
softgardensoftgarden30,67621,350 (69.5%)employer_atsats
recruiteeRecruitee27,21127,202 (99.9%)employer_atsats
breezyBreezy HR26,34025,801 (97.9%)employer_atsats
adpADP Workforce Now25,32725,238 (99.6%)employer_atsats
leverLever25,30324,913 (98.4%)employer_atsats
personioPersonio23,95723,787 (99.2%)employer_atsats
dayforceDayforce19,88519,816 (99.6%)employer_atsats
ukgUKG Pro18,77618,775 (99.9%)employer_atsats
bamboohrBambooHR15,37914,972 (97.3%)employer_atsats
pinpointPinpoint10,31210,299 (99.8%)employer_atsats
joinjoin.com9,2259,202 (99.7%)employer_atsats
ripplingRippling9,1569,048 (98.8%)employer_atsats
isolvedisolved Hire8,0458,045 (100%)employer_atsats
teaching_vacanciesTeaching Vacancies (DfE)6,9916,991 (100%)governmentpublic_agency
jobtech_swedenJobTech Sweden6,6706,670 (100%)governmentpublic_agency
jobviteJobvite2,6352,632 (99.8%)employer_atsats
paycomPaycom2,4602,457 (99.8%)employer_atsats
eployeploy1,5461,546 (100%)employer_atsats
pageupPageUp691691 (100%)employer_atsats
af_employer_directEmployer career site (Sweden)350346 (98.8%)directcareer_site
dvincid.vinci11720 (17%)employer_atsats
freshteamFreshteam9292 (100%)employer_atsats
exeterExeter employers (own sites)8989 (100%)directcareer_site
kclKing's College London (own site)6060 (100%)directcareer_site

34 providers with at least one served row (the providers count). The provider and exclude_provider filters accept these and the licensed sources listed under not_serving in the same response; a name in neither is a 422, never a silently empty page. Rendered from /public/providers. Read it at request time rather than pinning this list: on 2026-08-15 three names left it and a documented example became a 422 the next morning.

CodeWhat it means
200The provider list.
400A parameter the endpoint does not take. unknown_parameter: the name is not a parameter of this endpoint; the message names it and, when a valid parameter is within a small edit distance, suggests it ("unknown parameter: 'countyr'; did you mean 'country'?"). unsupported_parameter: a real parameter of another endpoint that has no meaning here (for example limit on /v1/export). Refused before the meter: nothing is charged.
401Missing, unknown, revoked or expired key. All four are reported identically.
429Over your plan's per-minute or per-day limit. Retry-After says how long (seconds to the next minute boundary, or to UTC midnight on a daily breach). The body says exactly when the limit resets (resets_at, resets_at_text) and, when a higher plan exists, links a same-key upgrade (upgrade_url).

The keyless mirror is /public/providers, with the same shape. Both list only redistributable providers: the sources we fetch and do not republish are named separately, under excluded_sources on /public/coverage. See Sourcing and refusals.

This page was rendered 27 October 2026, 11:28 UTC. Every figure on it comes from the endpoint named beside it, and every published request is re-sent against the live API before this site is allowed to build. If something here is wrong, the thumbs-down above reaches a person.