# 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.

**What this covers:** The coverage report; Per-field completeness; Per-country coverage; Freshness — measured_at, stale, and the refresh cycle; /public/stats and why it looks like it disagrees.

**Assumed knowledge:** The [data model](./ledger-data-model.md).

**Canonical HTML:** https://jobopportunitiesapi.org/docs/ledger/coverage  
**Machine-readable index:** https://jobopportunitiesapi.org/docs/ai/index.md  
**Last verified:** 2026-08-22  
**Superseded by:** the live API at https://api.jobopportunitiesapi.org and its spec at https://jobopportunitiesapi.org/openapi.json — where this file and the API disagree, the API is right.

---

<a id="coverage-report"></a>

## 1. The coverage report

One keyless endpoint returns the whole picture: the three populations, per-field completeness, the source breakdown, and the sources we refuse.

### 1.1 In depth

`/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.

### 1.2 Exact contract

| field | rows | definition |
| --- | --- | --- |
| live_listings | 3,485,296 | Rows a caller can obtain from /v1/jobs: not delisted, not opted out, and not withheld by the quality gate. This is the number you can reproduce by paging the API. |
| withheld_listings | 248,898 | 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. |
| closed_listings | 5,414,472 | Roles that came off their source, retained with their closure date and reason. |
| ledger_rows | 9,148,666 | live_listings + withheld_listings + closed_listings. It reconciles exactly. |
| employers | 190,453 | Distinct employers with at least one retrievable row. |
| countries | 249 | Distinct ISO country codes on live rows. |
| posted_last_7d | 233,485 | Live rows posted in the last seven days. |
| stale | false | true means the snapshot is older than its refresh window. |
| age_seconds | 2,208 | Seconds since measured_at. |

Reconciliation: `3,485,296 + 248,898 + 5,414,472 = 9,148,666`, and `ledger_rows` is `9,148,666` — they match exactly.

_Measured 8 September 2026, 22:52 UTC. Generated from `GET https://api.jobopportunitiesapi.org/public/coverage` (no key required). These figures move — the endpoint supersedes this file._

### 1.3 Worked examples

The whole report

```console
$ curl -s https://api.jobopportunitiesapi.org/public/coverage | jq 'del(.fields, .definitions, .excluded_sources)'
{
  "age_seconds": 2208,
  "closed_listings": 5414472,
  "countries": 249,
  "definitions": {
    "closed_listings": "Roles that came off their source, retained with their closure date and reason.",
    "ledger_rows": "live_listings + withheld_listings + closed_listings. It reconciles exactly.",
    "live_listings": "Rows a caller can obtain from /v1/jobs: not delisted, not opted out, and not withheld by the quality gate. 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.",
    "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."
  },
  "employers": 190453,
  "excluded_sources": [
    "adzuna",
    "apify_all_jobs",
    "apify_fantastic_jobs",
    "apify_job_listings",
    "apify_xing",
    "arbeitnow",
    "arbeitsagentur",
    "careerjet",
    "eures",
    "france_travail",
    "himalayas",
    "jobicy",
    "jooble",
    "landing_jobs",
    "remoteok",
    "remotive",
    "the_muse",
… 93 more lines
```

_Real response, fetched from `/public/coverage` when this file was built (8 September 2026, 23:29 UTC)._

**See also**

- [Per-field completeness](./ledger-coverage.md#coverage-fields-section)
- [Freshness — measured_at, stale, and the refresh cycle](./ledger-coverage.md#freshness)
- [Checking the equation yourself](./ledger-data-model.md#reconciliation)

<a id="coverage-fields-section"></a>

## 2. Per-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.

### 2.1 In depth

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.

### 2.2 Exact contract

| field | live rows carrying it | share | what the gap is |
| --- | --- | --- | --- |
| apply_url | 3,485,296 | 100% | Every row links to the employer's own application page. |
| country | 2,569,306 | 73.7% | ISO country. Absent where the source states only a free-text location. |
| category | 3,300,650 | 94.7% | Inferred by us and published only above 0.6 confidence. |
| remote | 3,479,639 | 99.8% | Stated by the employer, or inferred — field_sources tells you which. |
| city | 2,506,660 | 71.9% | Absent where the source gives a region or a remote-only role. |
| description | 2,807,210 | 80.5% | Full advert text. The largest gap in the ledger, and the honest number. |
| seniority | 1,565,591 | 44.9% | Inferred from the title. Most titles do not state one. |
| salary_eur | 223,600 | 6.4% | Structured salary only. We never publish an estimate as a fact. |

_Measured 8 September 2026, 22:52 UTC. Generated from `GET https://api.jobopportunitiesapi.org/public/coverage` (no key required). These figures move — the endpoint supersedes this file._

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](#countries-list). To retrieve only rows that carry a field as `published`, use [`require_fields`](./ledger-provenance.md#require-fields).

**See also**

- [Per-country coverage](./ledger-coverage.md#countries-list)
- [require_fields — the honest subset](./ledger-provenance.md#require-fields)
- [field_sources — per-field provenance](./ledger-provenance.md#field-sources)

<a id="countries-list"></a>

## 3. Per-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.

### 3.1 In depth

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 `live` with a low `employers` means a few large hirers dominate.

> **This is what the ledger holds, not a list of the world** — The row count moves as employers post and roles close, and it includes dependencies and territories that have their own ISO code — so it is larger than the number of UN member states. For the parameter itself, and the full list of codes you can send, see [Country codes](./api-parameters.md#country-codes).

### 3.2 Exact contract

| country | live | described | described_pct | with_salary | employers |
| --- | --- | --- | --- | --- | --- |
| US | 1,603,326 | 1,235,575 | 77.1% | 182,061 | 104,235 |
| DE | 174,278 | 163,395 | 93.8% | 8,142 | 29,761 |
| GB | 115,057 | 100,407 | 87.3% | 6,141 | 12,067 |
| IN | 73,885 | 60,064 | 81.3% | 832 | 5,151 |
| FR | 64,446 | 60,578 | 94% | 2,882 | 4,137 |
| CA | 61,389 | 56,293 | 91.7% | 4,627 | 6,235 |
| NL | 52,633 | 48,534 | 92.2% | 5,005 | 7,290 |
| SE | 32,776 | 32,000 | 97.6% | 60 | 5,687 |
| AU | 23,556 | 21,243 | 90.2% | 292 | 3,334 |
| ES | 22,098 | 18,899 | 85.5% | 462 | 2,982 |
| BE | 17,149 | 15,116 | 88.1% | 155 | 2,396 |
| CH | 14,693 | 13,585 | 92.5% | 108 | 2,298 |
| IT | 14,343 | 13,098 | 91.3% | 317 | 1,857 |
| MX | 14,105 | 11,750 | 83.3% | 198 | 1,775 |
| HU | 13,772 | 3,002 | 21.8% | 21 | 478 |
| PL | 13,397 | 11,442 | 85.4% | 285 | 1,943 |
| BR | 12,990 | 10,143 | 78.1% | 289 | 1,560 |
| SG | 12,962 | 10,513 | 81.1% | 113 | 1,640 |
| PH | 11,866 | 10,303 | 86.8% | 287 | 1,306 |
| AT | 10,614 | 9,984 | 94.1% | 3,202 | 2,139 |
| AE | 10,386 | 9,311 | 89.6% | 829 | 1,180 |
| JP | 10,286 | 8,924 | 86.8% | 109 | 1,897 |
| CN | 10,066 | 7,775 | 77.2% | 69 | 1,204 |
| MY | 10,065 | 8,710 | 86.5% | 529 | 1,359 |
| HR | 9,952 | 9,702 | 97.5% | 21 | 1,214 |
| IE | 8,940 | 7,855 | 87.9% | 326 | 1,465 |
| NO | 8,593 | 7,922 | 92.2% | 154 | 1,317 |
| ZA | 7,238 | 6,697 | 92.5% | 674 | 995 |
| PT | 7,102 | 6,370 | 89.7% | 120 | 918 |
| GR | 5,863 | 5,520 | 94.1% | 60 | 675 |
| DK | 5,733 | 5,315 | 92.7% | 48 | 839 |
| ID | 5,220 | 4,565 | 87.5% | 13 | 907 |
| FI | 5,125 | 4,413 | 86.1% | 71 | 902 |
| TH | 4,902 | 4,101 | 83.7% | 66 | 647 |
| CO | 4,762 | 4,100 | 86.1% | 86 | 909 |
| SA | 4,663 | 4,188 | 89.8% | 28 | 616 |
| RO | 4,630 | 4,117 | 88.9% | 39 | 770 |
| NZ | 4,097 | 3,790 | 92.5% | 34 | 578 |
| HK | 3,859 | 3,090 | 80.1% | 185 | 760 |
| EG | 3,830 | 3,355 | 87.6% | 10 | 480 |
| TW | 3,713 | 2,924 | 78.8% | 29 | 524 |
| VN | 3,593 | 3,002 | 83.6% | 21 | 558 |
| CZ | 3,375 | 2,765 | 81.9% | 40 | 622 |
| GE | 3,102 | 2,225 | 71.7% | 31 | 630 |
| AR | 3,095 | 2,656 | 85.8% | 91 | 684 |
| LT | 3,079 | 2,835 | 92.1% | 91 | 261 |
| KR | 2,931 | 2,452 | 83.7% | 17 | 616 |
| UA | 2,868 | 2,644 | 92.2% | 34 | 372 |
| BG | 2,833 | 2,547 | 89.9% | 80 | 418 |
| PE | 2,514 | 1,912 | 76.1% | 10 | 325 |
| IL | 2,382 | 2,057 | 86.4% | 23 | 400 |
| CL | 2,115 | 1,770 | 83.7% | 9 | 338 |
| TR | 1,857 | 1,543 | 83.1% | 8 | 431 |
| UN | 1,819 | 1,799 | 98.9% | 539 | 374 |
| MA | 1,809 | 1,632 | 90.2% | 4 | 244 |
| LU | 1,666 | 1,464 | 87.9% | 25 | 359 |
| NG | 1,591 | 1,552 | 97.5% | 7 | 280 |
| PK | 1,516 | 1,458 | 96.2% | 46 | 259 |
| QA | 1,399 | 1,176 | 84.1% | 14 | 167 |
| RS | 1,205 | 1,122 | 93.1% | 13 | 346 |
| CR | 1,196 | 905 | 75.7% | 8 | 238 |
| MT | 1,185 | 1,115 | 94.1% | 38 | 233 |
| CY | 1,164 | 1,116 | 95.9% | 30 | 247 |
| SK | 1,147 | 947 | 82.6% | 27 | 216 |
| EE | 939 | 897 | 95.5% | 17 | 202 |
| LK | 923 | 859 | 93.1% | 4 | 103 |
| GT | 844 | 740 | 87.7% | 16 | 108 |
| LV | 775 | 716 | 92.4% | 35 | 171 |
| KE | 715 | 658 | 92% | 6 | 212 |
| EC | 688 | 614 | 89.2% | 5 | 91 |
| JO | 663 | 516 | 77.8% | 0 | 130 |
| TN | 636 | 543 | 85.4% | 5 | 117 |
| PA | 578 | 473 | 81.8% | 3 | 143 |
| KZ | 547 | 500 | 91.4% | 10 | 129 |
| LB | 458 | 361 | 78.8% | 5 | 144 |
| DO | 454 | 398 | 87.7% | 1 | 106 |
| SI | 417 | 341 | 81.8% | 6 | 114 |
| KW | 413 | 379 | 91.8% | 0 | 87 |
| AZ | 406 | 390 | 96.1% | 179 | 235 |
| PR | 392 | 268 | 68.4% | 7 | 74 |
| MV | 366 | 346 | 94.5% | 0 | 17 |
| UY | 360 | 301 | 83.6% | 0 | 107 |
| BD | 357 | 323 | 90.5% | 5 | 129 |
| LI | 357 | 355 | 99.4% | 0 | 36 |
| BH | 336 | 275 | 81.8% | 3 | 88 |
| MU | 326 | 318 | 97.5% | 0 | 38 |
| RU | 310 | 301 | 97.1% | 3 | 95 |
| OM | 301 | 225 | 74.8% | 1 | 81 |
| SV | 283 | 265 | 93.6% | 79 | 66 |
| AM | 282 | 259 | 91.8% | 1 | 79 |
| TZ | 275 | 272 | 98.9% | 3 | 41 |
| AL | 261 | 243 | 93.1% | 37 | 105 |
| HN | 233 | 214 | 91.8% | 19 | 69 |
| GH | 228 | 213 | 93.4% | 8 | 93 |
| SR | 228 | 181 | 79.4% | 1 | 23 |
| VE | 218 | 205 | 94% | 4 | 65 |
| SW | 211 | 211 | 100% | 0 | 101 |
| NI | 197 | 192 | 97.5% | 1 | 48 |
| MD | 191 | 170 | 89% | 0 | 53 |
| IQ | 189 | 179 | 94.7% | 0 | 41 |
| AD | 169 | 163 | 96.4% | 22 | 149 |
| MK | 151 | 136 | 90.1% | 2 | 67 |
| FJ | 144 | 112 | 77.8% | 0 | 22 |
| DZ | 141 | 114 | 80.9% | 0 | 40 |
| NP | 137 | 113 | 82.5% | 1 | 33 |
| ET | 137 | 112 | 81.8% | 0 | 47 |
| SO | 129 | 129 | 100% | 1 | 22 |
| BM | 125 | 109 | 87.2% | 0 | 12 |
| JM | 125 | 111 | 88.8% | 5 | 47 |
| AO | 122 | 86 | 70.5% | 0 | 19 |
| BA | 122 | 117 | 95.9% | 0 | 55 |
| XK | 120 | 119 | 99.2% | 0 | 26 |
| BO | 112 | 105 | 93.8% | 0 | 35 |
| KH | 110 | 87 | 79.1% | 4 | 44 |
| MZ | 107 | 102 | 95.3% | 0 | 24 |
| GI | 105 | 104 | 99% | 0 | 26 |
| UG | 104 | 104 | 100% | 0 | 34 |
| CI | 101 | 101 | 100% | 0 | 35 |
| CD | 100 | 99 | 99% | 1 | 29 |
| MM | 100 | 71 | 71% | 1 | 31 |
| BY | 99 | 99 | 100% | 0 | 21 |
| TT | 90 | 87 | 96.7% | 0 | 19 |
| IM | 89 | 89 | 100% | 2 | 18 |
| BS | 89 | 80 | 89.9% | 1 | 23 |
| ME | 86 | 66 | 76.7% | 0 | 18 |
| JE | 84 | 83 | 98.8% | 1 | 19 |
| AW | 83 | 69 | 83.1% | 0 | 6 |
| SN | 83 | 78 | 94% | 0 | 29 |
| SP | 83 | 83 | 100% | 0 | 14 |
| UZ | 82 | 78 | 95.1% | 0 | 34 |
| MO | 78 | 55 | 70.5% | 0 | 16 |
| GG | 78 | 78 | 100% | 0 | 15 |
| CM | 77 | 77 | 100% | 0 | 25 |
| LA | 72 | 52 | 72.2% | 2 | 35 |
| KY | 71 | 60 | 84.5% | 1 | 17 |
| IS | 71 | 60 | 84.5% | 0 | 33 |
| MC | 71 | 59 | 83.1% | 1 | 30 |
| PG | 69 | 59 | 85.5% | 0 | 19 |
| MQ | 67 | 66 | 98.5% | 1 | 20 |
| PF | 66 | 60 | 90.9% | 1 | 9 |
| ZM | 65 | 61 | 93.8% | 0 | 25 |
| IR | 63 | 56 | 88.9% | 1 | 15 |
| GY | 62 | 57 | 91.9% | 0 | 17 |
| NA | 61 | 56 | 91.8% | 0 | 19 |
| PY | 61 | 54 | 88.5% | 0 | 32 |
| RW | 56 | 46 | 82.1% | 0 | 22 |
| HO | 51 | 51 | 100% | 0 | 2 |
| RE | 51 | 49 | 96.1% | 1 | 25 |
| MG | 47 | 45 | 95.7% | 0 | 14 |
| BB | 47 | 41 | 87.2% | 0 | 13 |
| SL | 47 | 44 | 93.6% | 0 | 12 |
| SZ | 46 | 46 | 100% | 0 | 5 |
| SC | 46 | 46 | 100% | 0 | 7 |
| JA | 44 | 44 | 100% | 0 | 16 |
| BW | 44 | 44 | 100% | 0 | 16 |
| GA | 43 | 43 | 100% | 0 | 12 |
| NE | 37 | 34 | 91.9% | 4 | 24 |
| LY | 36 | 35 | 97.2% | 0 | 12 |
| TD | 35 | 34 | 97.1% | 0 | 12 |
| WS | 33 | 29 | 87.9% | 0 | 6 |
| PO | 33 | 33 | 100% | 0 | 7 |
| SD | 32 | 27 | 84.4% | 1 | 12 |
| AF | 30 | 24 | 80% | 0 | 14 |
| LR | 28 | 28 | 100% | 0 | 7 |
| GU | 27 | 26 | 96.3% | 1 | 17 |
| SY | 26 | 25 | 96.2% | 0 | 17 |
| PS | 26 | 26 | 100% | 0 | 11 |
| GP | 26 | 26 | 100% | 0 | 13 |
| ZW | 25 | 25 | 100% | 0 | 12 |
| KG | 25 | 23 | 92% | 0 | 13 |
| CF | 25 | 24 | 96% | 0 | 7 |
| SM | 25 | 16 | 64% | 0 | 6 |
| HT | 25 | 24 | 96% | 1 | 12 |

_173 ISO country codes with live rows. This is not a list of the world's countries; it is what the ledger currently carries._

_Measured 8 September 2026, 22:52 UTC. Generated from `GET https://api.jobopportunitiesapi.org/public/coverage/countries` (no key required). These figures move — the endpoint supersedes this file._

### 3.3 Worked examples

Your market, in one keyless request

```bash
curl -s https://api.jobopportunitiesapi.org/public/coverage/countries \
  | jq '.countries[] | select(.country=="DE")'
```

**See also**

- [Country codes](./api-parameters.md#country-codes)
- [Per-field completeness](./ledger-coverage.md#coverage-fields-section)
- [require_fields — the honest subset](./ledger-provenance.md#require-fields)

<a id="freshness"></a>

## 4. Freshness — 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.

### 4.1 In depth

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.

`stale: true` means the snapshot is older than its refresh window — usually because a refresh is running long or has failed. It 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 we confirmed that vacancy still existed at its source. `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”.

### 4.2 Exact contract

| field | value | meaning |
| --- | --- | --- |
| measured_at | 8 September 2026, 22:52 UTC | When the snapshot was taken. |
| generated_at | 8 September 2026, 23:29 UTC | When this response was assembled. |
| live_listings | 3,485,296 | Rows /v1/jobs will serve. |
| ledger_rows | 9,148,666 | live + withheld + closed. |
| closures.last_24h | 116,368 | Roles that left their source in the last day. |
| closures.last_7d | 1,406,384 | …and in the last week. |
| closures.total | 5,414,472 | Retained: kept indefinitely. |

_Measured 8 September 2026, 22:52 UTC. Generated from `GET https://api.jobopportunitiesapi.org/public/freshness` (no key required). These figures move — the endpoint supersedes this file._

| 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` | `measured_at` is older than the refresh window. |
| `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 | When we last confirmed this vacancy exists at its source. |
| `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. |

> **posted_at is not on every row; first_seen_at is** — If you need every row to have a date — for a daily pull, a backfill, or a chart — use `first_seen_at`. `posted_at` is the source's claim and many sources do not make it. Note that `/v1/jobs` orders by `posted_at DESC NULLS LAST, id DESC`, so undated rows sort last.

**See also**

- [GET /v1/meta/freshness](./endpoints-meta.md#endpoint-meta-freshness)
- [How paging works](./api-pagination.md#cursor-basics)
- [A daily pull for one country](./recipes-sync.md#recipe-daily-country-pull)

<a id="stats-vs-coverage"></a>

## 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.

### 5.1 In depth

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.

> **If you are quoting a number anywhere, quote coverage** — `/public/coverage` reconciles exactly and carries `measured_at` and `stale`. `/public/stats` is a convenience summary. When they appear to disagree, it is the definitions differing, and `/public/stats` publishes those definitions in the same response.

### 5.2 Exact contract

The two endpoints side by side

```console
$ curl -s https://api.jobopportunitiesapi.org/public/stats | jq 'del(.definitions)'
{
  "companies": 240180,
  "definitions": {
    "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.",
    "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."
  },
  "employer_direct_companies": 190453,
  "employer_direct_live": 3428565,
  "last_refreshed": "2026-09-08T22:58:03Z",
… 4 more lines
```

_Compare `employer_direct_live` here with `live_listings` on `/public/coverage` — they are the same population. The response also carries a `definitions` object spelling all of this out, which is worth reading once. Real response, fetched from `/public/stats` when this file was built (8 September 2026, 23:29 UTC)._

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.

**See also**

- [The coverage report](./ledger-coverage.md#coverage-report)
- [The three populations](./ledger-data-model.md#ledger-model)
- [The coverage family](./endpoints-public.md#public-coverage-endpoints)

---

## Where to go next

This file is part of **The ledger**. Others in the same group:

- [What the ledger is](./ledger-what-joa-is.md) — Employer-direct openings, kept as a record rather than a feed. What that phrase actually commits us to, and who it suits.
- [The data model](./ledger-data-model.md) — live + withheld + closed = ledger_rows, and it reconciles exactly. Almost every misunderstanding about this product traces back to this one equation.
- [Provenance](./ledger-provenance.md) — Every field on every row says whether the source published it, whether we inferred it, or whether it is absent. This is the most distinctive thing in the product.
- [Sourcing and refusals](./ledger-sourcing.md) — The three source classes, the providers inside each, the redistribution flag that gates them, and the sources we fetch for ourselves and never republish.
- [Employer opt-out and takedowns](./ledger-optout.md) — How a site owner removes themselves, why the removal is verified rather than taken on trust, and how it propagates to every endpoint.

Always useful:

- [index.md](./index.md) — the map of every file here
- [BUILD-A-SITE.md](./BUILD-A-SITE.md) — the paste-whole brief for building against this API
- [quickstart.md](./quickstart.md) — zero to a first authenticated response
- [api-errors.md](./api-errors.md) — every status code and whether to retry it
