# Dataset metadata

> Live counts, per-field coverage, the freshness timestamp, and the exact source vocabulary.

Source: https://hyperjobs.io/docs/api-reference/meta

<EndpointHeader method="GET" path="/v1/meta" />

Live dataset statistics: how many jobs, how many companies, how fresh, the
breakdown by source — and `coverage_pct`, the measured fill rate of every
enriched field. One cheap request, no parameters.

<CodeGroup>
```bash title="cURL"
curl -H "Authorization: Bearer $HYPERJOBS_KEY" https://api.hyperjobs.io/v1/meta
```

```js title="Node"
const res = await fetch("https://api.hyperjobs.io/v1/meta", {
  headers: { Authorization: `Bearer ${process.env.HYPERJOBS_KEY}` },
});
const { jobs, companies, newest, sources, coverage_pct } = await res.json();

// The keys of `sources` are exactly the valid values for ?source=
const validSources = Object.keys(sources);
```

```python title="Python"
import os, requests

res = requests.get(
    "https://api.hyperjobs.io/v1/meta",
    headers={"Authorization": f"Bearer {os.environ['HYPERJOBS_KEY']}"},
    timeout=30,
)
res.raise_for_status()
meta = res.json()
```
</CodeGroup>

## Query parameters

None. The endpoint takes no input; anything you send is ignored.

It does require a key, unlike [`/v1/health`](/docs/api-reference/health), and it
counts against your [rate limit and monthly quota](/docs/rate-limits) like any
other authenticated call.

## Response

<Fields>
  <ResponseField name="jobs" type="integer">
    Live postings — non-duplicate and non-expired. This is the population
    [`/v1/jobs`](/docs/api-reference/jobs-list) searches, and it's
    the same number [`/v1/health`](/docs/api-reference/health) reports.
  </ResponseField>
  <ResponseField name="companies" type="integer">
    Companies with an enriched profile. See
    [the Company object](/docs/objects/company).
  </ResponseField>
  <ResponseField name="newest" type="string">
    ISO 8601 timestamp of the most recent posting — the freshness signal. The
    index refreshes hourly.
  </ResponseField>
  <ResponseField name="sources" type="object">
    Map of **public source slug** to posting count, **sorted descending by
    count**. The keys are the exact valid values for `?source=` — `lever`, not
    the stored `lever.co`.
  </ResponseField>
  <ResponseField name="coverage_pct" type="object">
    Live per-field fill rates, as percentages of the searchable pool: `country`,
    `skills`, `work_arrangement`, `seniority`, `taxonomies`, `salary`,
    `education`, `language`. Remeasured roughly every 15 minutes — see
    [below](#published-coverage).
  </ResponseField>
  <ResponseField name="full_text_search" type="boolean">
    Whether Google-style and advanced text search (`title`, `description`,
    `title_advanced`, `description_advanced`) is active on this deployment.
  </ResponseField>
  <ResponseField name="version" type="string">
    API version, e.g. `"1.0.0"`.
  </ResponseField>
</Fields>

```json title="Response"
{
  "jobs": 596768,
  "companies": 158545,
  "newest": "2026-07-16T09:48:15.000Z",
  "sources": {
    "smartrecruiters": 216791,
    "linkedin": 87462,
    "lever": 74374
  },
  "coverage_pct": {
    "country": 96.8,
    "skills": 83.7,
    "work_arrangement": 91.2,
    "seniority": 88.7,
    "taxonomies": 63.9,
    "salary": 36.0,
    "education": 71.5,
    "language": 94.2
  },
  "full_text_search": true,
  "version": "1.0.0"
}
```

That's the entire shape — there are no other keys.

## The `sources` keys are the `?source=` vocabulary

The keys are **public slugs** — `lever`, not the stored value `lever.co`
that an early draft of this API exposed — and they are exactly what `?source=` on
[`/v1/jobs`](/docs/api-reference/jobs-list) accepts *and* what the `source`
field on every Job payload carries: one vocabulary everywhere.
(`?source=lever.co` still works as input for backward compatibility, but
you'll never see it in a response.)

So `/v1/meta` remains the authoritative answer to "what can I pass to
`?source=`" — read the keys rather than guessing:

```js
const { sources } = await (await fetch(url, { headers })).json();
if (!(userInput in sources)) {
  throw new Error(`Unknown source. Valid: ${Object.keys(sources).join(", ")}`);
}
```

That check is still worth writing: `source` is an open vocabulary rather than a
fixed enum, so an unrecognized value returns an empty result instead of a
[`400`](/docs/errors). The [sources reference](/docs/reference/sources) has the
prose version, including which ATS each slug corresponds to.

## Published coverage

`coverage_pct` is the honesty feature: we publish the fill rate of every
enriched field, measured live against the searchable pool, refreshed about
every 15 minutes. When salary coverage is 36.6%, the API says 36.6 — you can
size a `has_salary=true` result set before you query, and you never have to
wonder whether a low fill rate is your filter or our data.

Read it before building on any enriched field, and see [dataset
coverage](/docs/reference/coverage) for what drives each number.

<Note title="`jobs` matches /v1/health">
  In a pre-release draft, [`/v1/health`](/docs/api-reference/health) counted every row —
  duplicates and expired included — and reported a higher number than `/v1/meta`.
  Both endpoints report the same live
  (non-duplicate, non-expired) count. Quote it from either.
</Note>

## Use it instead of hardcoding numbers

The dataset grows hourly. Anything that states a count — a dashboard, a landing
page, a "we track N jobs" line — should read it from here at build or request
time rather than baking in a number that starts rotting immediately.

`newest` is the freshness check: if it drifts more than a couple of hours from
now, ingestion is behind, and that's more diagnostic than any count.

## Errors

| Status | Cause |
| --- | --- |
| `401` | Missing, unknown, or revoked key. |
| `429` | [Rate limit](/docs/rate-limits) exceeded, or monthly quota spent. |
| `500` | Server error. Retry with backoff. |

No `400` — there's nothing to send that could be malformed.
