ledger
Coverage and honesty
How to read /public/coverage, why the weak numbers are published as prominently as the strong ones, and what measured_at and stale actually mean.
1The coverage report
One keyless endpoint returns the whole picture: the three populations, per-field completeness, the source breakdown, and the sources we refuse.
In depthWhy it exists, what it is not, what people get wrong
/public/coverage is the canonical report and everything else on this site defers to it. It is deliberately keyless and deliberately complete: the same response carries the figures that flatter the product and the figures that do not, so there is no version of it that could be quoted selectively without the reader noticing.
It is measured on a schedule rather than counted per request. Counting it per request took twenty-four seconds and returned a 503 instead: a report that times out is not a more honest report. So every response carries measured_at, age_seconds and a stale boolean, and you should read those before you read anything else in it.
Exact contractTypes, defaults, ranges, errors, edge cases
| Figure | Rows | What it counts |
|---|---|---|
live_listings | 2,914,565 | Rows a caller can obtain from /v1/jobs: exactly the API's default filter (database view joa.v_served_v1). This is the number you can reproduce by paging the API. |
withheld_listings | 767,023 | Rows present in the ledger and deliberately not served. quality_removed breaches the employer-direct guarantee or comes from a discovery-only source; quality_gated is reversible doubt; optout_hidden is a verified employer opt-out; duplicate_hold is a re-crawl of an advert already served under another id; poster_hold is an employer whose adverts are held by poster type; other_api_hold covers portal holds, partner withdrawals and 'Copy of' re-posts. The reasons add up to withheld_listings exactly. |
closed_listings | 8,653,347 | Roles that came off their source, retained with a closure date and reason. JOA records as the closure date the day its daily verification confirmed that an advert had gone. Before late September 2026 that verification ran in large catch-up batches, so closure dates recorded before then show when JOA confirmed a closure, not when the employer closed the role; daily verification applies from late September 2026. |
ledger_rows | 12,334,935 | live_listings + withheld_listings + closed_listings. It reconciles exactly. |
employers | 139,921 | Distinct employers with at least one row you can retrieve. |
countries | 236 | Distinct ISO country codes present on live rows. |
posted_last_7d | 235,268 | Live rows whose posted_at falls in the last seven days. |
Reconciliation, computed from the same response: 2,914,565 + 767,023 + 8,653,347 = 12,334,935 and ledger_rows is 12,334,935: they match exactly, which is the guarantee.
The report also carries stale: false and age_seconds: 2,142. stale: true means the snapshot behind these figures is older than its refresh window; treat the numbers as indicative and re-read the endpoint before quoting them.
Measured 27 October 2026, 10:52 UTC · rendered from /public/coverage, which needs no key. These figures move; the endpoint is always right and this page is only as fresh as its last build.
$ curl -s https://api.jobopportunitiesapi.org/public/coverage | jq 'del(.fields, .definitions, .excluded_sources)' { "age_seconds": 2142, "as_of": "2026-10-27", "closed_listings": 8653347, "closure_date_note": "JOA records as the closure date the day its daily verification confirmed that an advert had gone. Before late September 2026 that verification ran in large catch-up batches, so closure dates recorded before then show when JOA confirmed a closure, not when the employer closed the role; daily verification applies from late September 2026.", "countries": 236, "definitions": { "as_of_times": "/public/facets and /public/providers carry the latest snapshot (every 3 hours, generated_at). /public/coverage, /public/coverage/countries, /public/stats, /public/site-stats and the website carry the snapshot frozen once a day (as_of, measured_at = that snapshot's generated_at). Two surfaces agree exactly when their timestamps are equal; otherwise each states its own.", "closed_listings": "Roles that came off their source, retained with a closure date and reason. JOA records as the closure date the day its daily verification confirmed that an advert had gone. Before late September 2026 that verification ran in large catch-up batches, so closure dates recorded before then show when JOA confirmed a closure, not when the employer closed the role; daily verification applies from late September 2026.", "countries": "Distinct country codes with at least one served row. Only ISO 3166-1 alpha-2 codes (plus XK, Kosovo) are countries; any other value is published as country unknown and filtered by no country= value.", "employers": "Distinct employers with at least one served row.", "ledger_rows": "live_listings + withheld_listings + closed_listings. It reconciles exactly.", "live_listings": "Rows a caller can obtain from /v1/jobs: exactly the API's default filter (database view joa.v_served_v1). This is the number you can reproduce by paging the API.", "measured_at": "When these figures were counted. The report is measured on a schedule, not per request, because counting it per request took 24 seconds and returned 503 instead.", "population": "Every count here is over the rows GET /v1/jobs serves by default (database view joa.v_served_v1), measured in ONE pass of the database at generated_at (db/393). A facet chip's count is what the matching filter returned at that moment.", "providers": "Providers with at least one served row. A redistributable provider with none is listed under providers_not_serving (/public/coverage) or not_serving (/public/providers), is still accepted by ?provider=, and is not counted.", "withheld_listings": "Rows present in the ledger and deliberately not served. quality_removed breaches the employer-direct guarantee or comes from a discovery-only source; quality_gated is reversible doubt; optout_hidden is a verified employer opt-out; duplicate_hold is a re-crawl of an advert already served under another id; poster_hold is an employer whose adverts are held by poster type; other_api_hold covers portal holds, partner withdrawals and 'Copy of' re-posts. The reasons add up to withheld_listings exactly." }, "employers": 139921, "excluded_sources": [ "adzuna", "apify_all_jobs", "apify_fantastic_jobs", "apify_job_listings", "apify_xing", "arbeitnow", "arbeitsagentur", "careerjet", "eures", "france_travail", … 160 more lines
2Per-field completeness
How many live rows carry each field, as a count and a share, with a sentence saying what the gap is rather than leaving you to guess.
In depthWhy it exists, what it is not, what people get wrong
The point of publishing this is that a percentage on its own is not information. “City: 71%” could mean the pipeline is broken or it could mean that a lot of sources state only a free-text location: those are different problems with different consequences for you. So every row carries a note saying which it is.
Read the weak rows first. salary_eur and seniority are the two fields people most often plan around and are the two with the thinnest coverage; if your product needs either of them on most rows, that is worth knowing on day one rather than in week three.
Exact contractTypes, defaults, ranges, errors, edge cases
| Field | Live rows carrying it | Share | What the gap is |
|---|---|---|---|
apply_url | 2,914,565 | Every row links to the employer's own application page. | |
country | 2,732,573 | ISO country. Stated by the source, or inferred from the place; field_sources.country tells you which. Absent where the location names no unique country. | |
category | 2,731,003 | Inferred by us and published only above 0.6 confidence. | |
remote | 2,911,276 | Stated by the employer, or inferred: field_sources tells you which. | |
city | 2,349,663 | Absent where the source gives a region or a remote-only role. | |
description | 2,781,003 | Full advert text. The largest gap in the ledger, and the honest number. | |
seniority | 1,328,251 | Inferred from the title. Most titles do not state one. | |
salary_eur | 514,244 | Structured salary only. We never publish an estimate as a fact. | |
engagement_stated | 0 | The posting states the relationship (employee / vendor / other). Most do not. | |
engagement_established | 0 | Stated by the source, plus rows our audited rules established. Never guessed from silence. | |
contract_category_stated | 0 | The posting states the contract kind (permanent, fixed-term, ...). Most do not, especially US postings. | |
contract_category_established | 0 | Stated by the source, plus rows our audited rules established. Never guessed from silence. | |
agency_stated | 0 | The posting or feed states the direct / agency / umbrella arrangement. | |
agency_established | 0 | Stated, plus rows classified from the source's own agency status. | |
working_schedule_stated | 0 | The posting states full-time / part-time / flexible. | |
working_schedule_established | 0 | Stated by the source, plus rows our audited rules established. |
Measured 27 October 2026, 10:52 UTC · rendered from /public/coverage, which needs no key. These figures move; the endpoint is always right and this page is only as fresh as its last build.
These counts are ledger-wide over live rows. They are not conditioned on your filters, and the intersection with your filters is at most the smallest of them. For the per-market view (which is usually the one that decides anything) use the per-country table. To retrieve only rows that carry a field as published, use `require_fields`.
3Per-country coverage
How much data sits behind each country code: live rows, how many carry a description, how many carry a published salary, and how many employers.
In depthWhy it exists, what it is not, what people get wrong
This is the answer to “is there enough of this in my market”, and it is the table to read before planning around any ledger-wide percentage. The variation between markets is enormous: description coverage runs from the high sixties to the high nineties, and published salary from a fraction of a per cent to a double-digit share, so a single global figure will mislead you in one direction or the other.
live- Rows you can retrieve for that country right now.
described/described_pct- How many of them carry an advert body, as a count and a share.
with_salary- How many carry a salary the employer published, not one we read out of the text.
employers- Distinct companies behind those rows. A high
livewith a lowemployersmeans a few large hirers dominate.
Exact contractTypes, defaults, ranges, errors, edge cases
Every country in the ledger: 165 rows, sorted by live listings
| ISO | Live | With a description | Described | With a published salary | Employers |
|---|---|---|---|---|---|
US | 1,391,884 | 1,332,215 | 95.7% | 488,138 | 77,320 |
DE | 180,303 | 164,482 | 91.2% | 25,559 | 24,415 |
GB | 97,406 | 95,203 | 97.7% | 28,722 | 13,279 |
IN | 88,057 | 77,374 | 87.9% | 577 | 3,622 |
CA | 67,551 | 65,985 | 97.7% | 26,420 | 5,386 |
FR | 59,228 | 57,749 | 97.5% | 11,769 | 3,810 |
NL | 40,967 | 39,096 | 95.4% | 18,886 | 6,320 |
SE | 22,438 | 22,258 | 99.2% | 599 | 4,826 |
AU | 21,760 | 21,422 | 98.4% | 2,047 | 2,172 |
MX | 19,327 | 17,788 | 92% | 419 | 1,653 |
ES | 18,583 | 17,679 | 95.1% | 2,118 | 2,451 |
SG | 15,483 | 15,192 | 98.1% | 269 | 1,669 |
CN | 15,277 | 13,077 | 85.6% | 102 | 1,216 |
IT | 14,277 | 13,988 | 98% | 3,573 | 1,614 |
BE | 12,453 | 11,224 | 90.1% | 857 | 2,067 |
MY | 12,246 | 11,688 | 95.4% | 89 | 967 |
PL | 12,153 | 11,672 | 96% | 981 | 1,652 |
BR | 12,120 | 11,328 | 93.5% | 261 | 1,358 |
JP | 11,722 | 11,302 | 96.4% | 1,910 | 1,389 |
PH | 11,418 | 11,058 | 96.8% | 468 | 1,232 |
CH | 11,343 | 10,874 | 95.9% | 608 | 2,015 |
AT | 9,261 | 8,823 | 95.3% | 5,297 | 1,867 |
AE | 8,709 | 8,249 | 94.7% | 187 | 980 |
IE | 7,698 | 7,456 | 96.9% | 1,152 | 1,274 |
PT | 5,847 | 5,103 | 87.3% | 392 | 871 |
TH | 5,687 | 5,431 | 95.5% | 90 | 701 |
NO | 5,573 | 5,329 | 95.6% | 275 | 1,113 |
TW | 5,359 | 5,182 | 96.7% | 45 | 568 |
ZA | 5,023 | 4,876 | 97.1% | 190 | 829 |
ID | 4,860 | 4,712 | 97% | 49 | 601 |
HU | 4,779 | 4,158 | 87% | 79 | 521 |
VN | 4,654 | 4,259 | 91.5% | 68 | 558 |
RO | 4,645 | 4,288 | 92.3% | 195 | 758 |
HK | 4,182 | 4,038 | 96.6% | 39 | 708 |
DK | 3,857 | 3,750 | 97.2% | 160 | 834 |
SA | 3,806 | 3,526 | 92.6% | 65 | 596 |
GR | 3,796 | 3,642 | 95.9% | 241 | 592 |
FI | 3,732 | 3,295 | 88.3% | 455 | 744 |
CO | 3,682 | 3,558 | 96.6% | 208 | 748 |
KR | 3,129 | 3,032 | 96.9% | 30 | 654 |
NZ | 3,116 | 3,048 | 97.8% | 287 | 613 |
CZ | 3,054 | 2,873 | 94.1% | 171 | 611 |
AR | 2,570 | 2,483 | 96.6% | 132 | 638 |
IL | 2,551 | 2,494 | 97.8% | 30 | 494 |
EG | 2,494 | 2,363 | 94.7% | 40 | 415 |
PE | 2,028 | 1,973 | 97.3% | 140 | 289 |
CL | 1,987 | 1,906 | 95.9% | 102 | 324 |
UA | 1,979 | 1,855 | 93.7% | 53 | 326 |
TR | 1,952 | 1,787 | 91.5% | 28 | 458 |
BG | 1,894 | 1,761 | 93% | 115 | 380 |
LT | 1,781 | 1,727 | 97% | 911 | 249 |
LU | 1,411 | 1,373 | 97.3% | 99 | 355 |
MA | 1,410 | 1,344 | 95.3% | 15 | 237 |
SK | 1,339 | 1,289 | 96.3% | 493 | 273 |
CR | 1,148 | 1,087 | 94.7% | 21 | 275 |
QA | 1,048 | 1,037 | 99% | 9 | 143 |
PK | 1,044 | 995 | 95.3% | 71 | 246 |
RS | 988 | 943 | 95.4% | 42 | 297 |
CY | 825 | 800 | 97% | 78 | 199 |
NG | 720 | 704 | 97.8% | 5 | 177 |
PR | 707 | 655 | 92.6% | 50 | 133 |
GE | 686 | 675 | 98.4% | 30 | 196 |
HR | 674 | 658 | 97.6% | 43 | 201 |
MT | 654 | 631 | 96.5% | 64 | 191 |
JO | 647 | 609 | 94.1% | 70 | 103 |
LV | 636 | 619 | 97.3% | 286 | 156 |
LK | 601 | 595 | 99% | 5 | 87 |
PA | 582 | 570 | 97.9% | 23 | 177 |
EC | 547 | 527 | 96.3% | 19 | 97 |
DO | 537 | 530 | 98.7% | 11 | 113 |
GT | 535 | 489 | 91.4% | 15 | 108 |
KE | 523 | 488 | 93.3% | 11 | 185 |
EE | 518 | 507 | 97.9% | 161 | 167 |
TN | 452 | 428 | 94.7% | 21 | 121 |
SI | 426 | 378 | 88.7% | 69 | 113 |
KZ | 367 | 363 | 98.9% | 11 | 114 |
RU | 365 | 251 | 68.8% | 13 | 75 |
MO | 327 | 257 | 78.6% | 4 | 33 |
MV | 311 | 308 | 99% | 0 | 14 |
UY | 310 | 305 | 98.4% | 5 | 88 |
GH | 294 | 278 | 94.6% | 16 | 114 |
KW | 281 | 271 | 96.4% | 5 | 91 |
TZ | 271 | 267 | 98.5% | 2 | 53 |
MU | 254 | 249 | 98% | 5 | 47 |
OM | 247 | 208 | 84.2% | 8 | 62 |
SV | 247 | 240 | 97.2% | 40 | 68 |
BH | 233 | 218 | 93.6% | 2 | 56 |
AM | 230 | 215 | 93.5% | 6 | 80 |
HN | 203 | 194 | 95.6% | 18 | 64 |
BD | 201 | 190 | 94.5% | 8 | 87 |
SR | 201 | 197 | 98% | 12 | 21 |
VE | 201 | 200 | 99.5% | 18 | 57 |
LI | 192 | 189 | 98.4% | 73 | 33 |
CI | 177 | 171 | 96.6% | 1 | 60 |
KY | 176 | 174 | 98.9% | 22 | 49 |
LB | 168 | 166 | 98.8% | 3 | 64 |
CD | 146 | 146 | 100% | 10 | 45 |
NI | 142 | 140 | 98.6% | 3 | 49 |
ME | 141 | 138 | 97.9% | 0 | 13 |
PY | 141 | 140 | 99.3% | 3 | 24 |
DZ | 132 | 128 | 97% | 3 | 43 |
MD | 132 | 125 | 94.7% | 14 | 47 |
MK | 132 | 128 | 97% | 10 | 70 |
BS | 129 | 117 | 90.7% | 2 | 40 |
JM | 128 | 124 | 96.9% | 8 | 56 |
PG | 128 | 128 | 100% | 0 | 32 |
UG | 124 | 123 | 99.2% | 1 | 48 |
NP | 123 | 115 | 93.5% | 1 | 41 |
UZ | 123 | 113 | 91.9% | 6 | 43 |
AZ | 119 | 116 | 97.5% | 4 | 54 |
BA | 118 | 118 | 100% | 3 | 53 |
ZM | 113 | 112 | 99.1% | 0 | 43 |
ET | 112 | 108 | 96.4% | 3 | 46 |
NE | 112 | 112 | 100% | 56 | 29 |
KH | 109 | 107 | 98.2% | 2 | 44 |
JE | 108 | 108 | 100% | 9 | 37 |
AL | 107 | 105 | 98.1% | 7 | 62 |
AW | 107 | 107 | 100% | 0 | 9 |
IQ | 104 | 104 | 100% | 1 | 29 |
XK | 102 | 100 | 98% | 20 | 26 |
BM | 100 | 99 | 99% | 3 | 29 |
BB | 97 | 95 | 97.9% | 4 | 20 |
MZ | 89 | 86 | 96.6% | 0 | 36 |
MM | 88 | 88 | 100% | 1 | 28 |
MG | 84 | 83 | 98.8% | 1 | 24 |
GI | 78 | 78 | 100% | 44 | 19 |
GG | 77 | 77 | 100% | 1 | 30 |
CM | 76 | 73 | 96.1% | 1 | 33 |
SN | 75 | 71 | 94.7% | 1 | 36 |
FJ | 72 | 71 | 98.6% | 1 | 16 |
RW | 72 | 68 | 94.4% | 1 | 34 |
IS | 71 | 71 | 100% | 5 | 30 |
MC | 64 | 64 | 100% | 3 | 24 |
NA | 64 | 64 | 100% | 0 | 27 |
PF | 63 | 63 | 100% | 3 | 11 |
BO | 61 | 61 | 100% | 1 | 30 |
GU | 59 | 56 | 94.9% | 6 | 28 |
GY | 57 | 53 | 93% | 0 | 11 |
LA | 57 | 57 | 100% | 4 | 29 |
SC | 57 | 57 | 100% | 0 | 12 |
AO | 56 | 56 | 100% | 0 | 24 |
SS | 55 | 46 | 83.6% | 0 | 13 |
TT | 55 | 52 | 94.5% | 0 | 22 |
IM | 53 | 50 | 94.3% | 7 | 23 |
SO | 49 | 49 | 100% | 1 | 21 |
SL | 46 | 46 | 100% | 23 | 15 |
BY | 42 | 42 | 100% | 1 | 19 |
BJ | 41 | 40 | 97.6% | 1 | 16 |
IR | 41 | 41 | 100% | 0 | 13 |
SY | 38 | 38 | 100% | 5 | 26 |
SZ | 37 | 37 | 100% | 1 | 7 |
MW | 36 | 36 | 100% | 1 | 20 |
GF | 34 | 34 | 100% | 2 | 14 |
BW | 33 | 33 | 100% | 0 | 17 |
SD | 33 | 27 | 81.8% | 2 | 19 |
BZ | 30 | 25 | 83.3% | 0 | 10 |
MQ | 30 | 30 | 100% | 3 | 17 |
GP | 29 | 29 | 100% | 3 | 14 |
TC | 29 | 29 | 100% | 0 | 3 |
BN | 28 | 28 | 100% | 0 | 13 |
TD | 26 | 24 | 92.3% | 0 | 16 |
HT | 25 | 25 | 100% | 0 | 11 |
IO | 25 | 25 | 100% | 9 | 3 |
LY | 25 | 25 | 100% | 0 | 8 |
RE | 25 | 25 | 100% | 4 | 16 |
165 countries with 25 or more live rows (236 with at least one: the countries figure of /public/coverage), rendered from /public/coverage/countries, which needs no key. Only ISO 3166-1 codes (plus XK) count as countries. It is not a list of the world’s countries and it moves as employers post and roles close. Measured 27 October 2026, 10:52 UTC.
curl -s https://api.jobopportunitiesapi.org/public/coverage/countries \
| jq '.countries[] | select(.country=="DE")'4Freshness: measured_at, stale, and the refresh cycle
The ledger is refreshed on a schedule, not on a webhook. Every report says when it was measured and whether that is older than it should be.
In depthWhy it exists, what it is not, what people get wrong
There are two different clocks and confusing them is easy. last_refreshed is when the ledger itself was last rebuilt from its sources. measured_at is when the coverage snapshot was counted. The second is always at or after the first, and it is the one the figures on a report belong to.
/public/coverage and /public/site-stats serve a daily snapshot, measured and frozen once a day after 04:40 UTC so that every page and endpoint shows the same figures for the same day; refresh and stale_after_hours in the response say so. stale: true means measured_at is older than stale_after_hours (30 for the daily snapshot): a daily snapshot was missed, usually because a refresh failed. If the daily snapshot is unavailable, /public/coverage falls back to its 3-hourly measurement and the threshold is 6 hours. stale does not mean the data is wrong; it means the counts are indicative and you should re-read the endpoint before quoting them anywhere that matters.
Per-row freshness is a separate thing again. Every listing carries last_verified_at: the last time JOA positively confirmed that vacancy at its source: a request that found the posting live (its own page, or its record in the employer's ATS API), or the posting listed in a fresh read of the employer's own job list; if nothing is newer, the time it was first harvested from that source. For a role the employer pushes to JOA it is the partner's last push or heartbeat. Nothing else moves it: not a rebuild of the ledger, not a timer. last_verified_via says which kind of confirmation it was (posting_page, ats_api, board_api, source_list, first_harvest, partner_feed, or verifier for a check made before 23 Oct 2026 whose method was not recorded). An Ashby confirmation reads Ashby's own job board, its system of record, and finds the posting listed there: that is a read of Ashby's system of record rather than of the individual posting URL, and it counts as page-verified for exactly that reason. It is labelled board_api (24 Oct 2026) so a buyer can tell a board-level read from a posting-level check; the counting is unchanged. verified_after filters on it, which is the parameter you want if you are asking “what has been re-confirmed since yesterday” rather than “what is new”.
Exact contractTypes, defaults, ranges, errors, edge cases
| Figure | Value | Meaning |
|---|---|---|
measured_at | 27 October 2026, 10:52 UTC | When the snapshot behind this report was taken. |
generated_at | 27 October 2026, 11:13 UTC | When this response was assembled. |
live_listings | 2,914,565 | Rows /v1/jobs will serve. |
ledger_rows | 12,334,935 | Live + withheld + closed. |
closures.last_24h | 55,708 | Roles that left their source in the last day. |
closures.last_7d | 440,884 | …and in the last week. |
closures.total | 8,653,347 | Retained: 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.
| Field | Where | What it times |
|---|---|---|
measured_at | /public/coverage, /public/freshness | When the counts were taken. |
age_seconds | /public/coverage | Seconds since measured_at. |
stale | /public/coverage, /public/site-stats | measured_at is older than stale_after_hours (30 h for the daily snapshot). |
last_refreshed | /public/coverage, /public/stats | When the ledger was last rebuilt from sources. |
generated_at | /public/freshness | When that response was assembled. |
last_verified_at | every job row | The last positive confirmation at the source: the posting's own page or ATS API record found live, or the posting listed in a fresh read of the employer's job list; otherwise its first harvest. last_verified_via names which. |
first_seen_at | every job row | When the vacancy first entered this ledger. Populated on 100% of rows. |
posted_at | job rows, where stated | The source's own posting date. Often absent. Sort or backfill on first_seen_at instead. |
5/public/stats and why it looks like it disagrees
It does not disagree: it publishes a broader live_listings that includes gated rows, and names the narrower one employer_direct_live. Coverage is canonical.
In depthWhy it exists, what it is not, what people get wrong
This is the single most confusing thing on the keyless surface, so it is worth stating precisely. /public/stats carries two counts where /public/coverage carries one:
stats.employer_direct_live- The rows you can actually retrieve. Identical to
coverage.live_listings. This is the number the website quotes and the one to use. stats.live_listings- A broader population: not delisted and not opted out, but including rows held back by the quality gate. Larger than what you can fetch.
stats.not_employer_direct_live- The difference between the two: the quality-gated rows.
live_listings − not_employer_direct_live = employer_direct_live. stats.companies- Employers with at least one row in the broader population, so it is larger than
coverage.employers, which counts employers with at least one retrievable row.
Exact contractTypes, defaults, ranges, errors, edge cases
$ curl -s https://api.jobopportunitiesapi.org/public/stats | jq 'del(.definitions)' { "as_of": "2026-10-27", "companies": 185348, "definitions": { "as_of": "The UTC day of the daily snapshot these values come from. Every page and statistics endpoint shows the same values for the same as_of (see /public/site-stats).", "authority": "For a reconciling report with per-field completeness, use /public/coverage: live + withheld + closed = ledger_rows, exactly.", "companies": "Employers with at least one live listing. Like live_listings, this INCLUDES employers whose every live listing is withheld by the quality gate, so it is larger than the number of employers you can reach. Use employer_direct_companies for that.", "employer_direct_companies": "Employers a caller can actually obtain from /v1/jobs: at least one live listing that is not opted out and not withheld. This is the number you can reproduce by paging the API, and the one /public/coverage publishes as employers.", "employer_direct_live": "Rows a caller can actually obtain from /v1/jobs: live, not opted out, and not withheld. This is the number you can reproduce by paging the API, and the one the website quotes.", "live_listings": "Rows in the ledger that are not delisted and not opted out. This INCLUDES rows withheld by the quality gate, so it is larger than what you can retrieve. Use employer_direct_live for that.", "measured_at": "When the snapshot's counts were measured from the database.", "not_employer_direct_live": "Rows held back by the quality gate: aggregator or agency destinations, and discovery-only sources. live_listings minus this equals employer_direct_live.", "posted_last_7d": "Listings whose posted_at falls in the last seven days, counted over live_listings (withheld rows included). /public/coverage posted_last_7d counts the served rows only." }, … 9 more lines
There is a third figure in the same neighbourhood: posted_last_7d appears on both endpoints and is computed over the respective population, so it too differs. Use coverage's.
6What each public count counts, and as of when
Every public count is taken over the rows GET /v1/jobs serves, in one pass of the database with one timestamp. Two surfaces show the same number when they show the same timestamp; otherwise each states its own.
In depthWhy it exists, what it is not, what people get wrong
Once every three hours one database pass counts the served rows (the view joa.v_served_v1, exactly the default filter of /v1/jobs) and writes, in a single transaction stamped with one time, the facet chips, the provider table, the country table and the coverage report. So a facet chip's count is what that filter returned at that moment, and the per-provider counts add up to live_listings exactly. Once a day one of those passes is frozen, and that frozen copy is what the website and the headline endpoints show all day.
| Count | Definition | Where | Timestamp |
|---|---|---|---|
live_listings | Rows /v1/jobs serves by default. | /public/coverage, /public/site-stats, the website | daily: as_of, measured_at |
countries | Distinct ISO 3166-1 alpha-2 codes (plus XK, Kosovo) with at least one served row. Any other value is not a country: it is published as country unknown. | /public/coverage, /public/site-stats (countries_with_live_roles), countries_total on /public/coverage/countries | daily: measured_at |
providers | Providers with at least one served row. A licensed provider with none is listed separately (providers_not_serving, not_serving), is still accepted by ?provider=, and is not counted. | /public/coverage (providers, provider_rows), /public/site-stats (providers_with_live_roles) | daily: measured_at |
employers | Distinct employers with at least one served row. | /public/coverage, /public/site-stats | daily: measured_at |
| facet chips | Served rows per value, from the same pass. | /public/facets | every 3 h: generated_at |
| provider rows | Served rows per provider, from the same pass as the facets. | /public/providers | every 3 h: generated_at (equal to /public/facets) |
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.