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

**What this covers:** GET /v1/me; GET /v1/meta/facets; GET /v1/meta/freshness; GET /v1/meta/providers.

**Assumed knowledge:** [Authentication](./api-authentication.md).

**Canonical HTML:** https://jobopportunitiesapi.org/docs/endpoints/meta  
**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="endpoint-me"></a>

## 1. GET /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.

### 1.1 In depth

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.

### 1.2 Exact contract

| code | meaning |
| --- | --- |
| 200 | Key status. |
| 401 | Missing, unknown, revoked or expired key. All four are reported identically. |
| 429 | Over your plan's per-minute or per-day limit. `Retry-After` says how long. |

The response, from a real call

```json
{
  "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.

### 1.3 Worked examples

Check a key

```console
$ 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._

**See also**

- [The response headers](./api-rate-limits.md#limit-headers)
- [Creating, naming, rotating and revoking](./api-authentication.md#auth-keys-lifecycle)
- [Every control on the dashboard](./account-sign-in.md#dashboard-tour)

<a id="endpoint-meta-facets"></a>

## 2. GET /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.

### 2.1 In depth

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.

> **The keyless mirror is identical** — `/public/facets` returns the same data with no key, so there is no reason for a start-up validation step to consume a record. It is a large response — several hundred kilobytes — so cache it rather than fetching it per request.

### 2.2 Exact contract

| code | meaning |
| --- | --- |
| 200 | Facet counts. |
| 401 | Missing, unknown, revoked or expired key. All four are reported identically. |
| 429 | Over your plan's per-minute or per-day limit. `Retry-After` says how long. |

| Facet group | Filters | Note |
| --- | --- | --- |
| `family` | `category`, `exclude_category` | Named `family` in the facet response and `category` in the filter. |
| `country` | `country`, `exclude_country` | ISO-3166 alpha-2. |
| `city` | `city` | Resolved city names; thinner coverage than country. |
| `remote` | `remote` | `remote`, `hybrid`, `on_site`, `not_stated`. |
| `employment` | `employment_type` | Includes `not_stated`. |
| `seniority` | `seniority` | Includes `not_stated`. |
| `source_type` | `source_type`, `exclude_source_type` | `aggregator` and `agency` are reserved and match nothing. |
| `provider` | `provider`, `exclude_provider` | Also available in richer form at `/public/providers`. |

| value | label | live rows |
| --- | --- | --- |
| Engineering | Engineering | 457,051 |
| Healthcare | Healthcare | 436,307 |
| Operations & Admin | Operations & Admin | 385,181 |
| Sales | Sales | 297,385 |
| Skilled Technician | Skilled Technician | 226,485 |
| Retail | Retail | 210,474 |
| Hospitality | Hospitality | 194,548 |
| Finance | Finance | 182,930 |
| uncategorised | Uncategorised | 182,437 |
| Logistics & Transport | Logistics & Transport | 113,013 |
| Marketing | Marketing | 99,866 |
| Customer Support | Customer Support | 97,286 |
| Education | Education | 88,029 |
| Manufacturing | Manufacturing | 86,466 |
| HR & Recruiting | HR & Recruiting | 73,098 |
| Data & Analytics | Data & Analytics | 67,542 |
| Consulting & Strategy | Consulting & Strategy | 64,644 |
| Construction & Trades | Construction & Trades | 53,426 |
| Legal & Compliance | Legal & Compliance | 49,160 |
| Design | Design | 37,814 |
| Product | Product | 37,735 |
| Security | Security | 31,670 |
| Science & Research | Science & Research | 29,615 |
| Procurement | Procurement | 20,433 |
| Safety & Environment | Safety & Environment | 14,838 |

_25 values, generated from `GET https://api.jobopportunitiesapi.org/public/facets` (no key required). Enumerate it at run time rather than hard-coding this list._


### 2.3 Worked examples

The vocabularies, keylessly

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

**See also**

- [Controlled vocabularies — never hard-code these](./api-parameters.md#filter-vocabularies)
- [Classification — category, seniority, employment type, remote](./api-parameters.md#params-classification)
- [The filter panel, including Allow and Exclude](./website-ledger.md#site-facets)

<a id="endpoint-meta-freshness"></a>

## 3. GET /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.

### 3.1 In depth

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.

### 3.2 Exact contract

| field | value | meaning |
| --- | --- | --- |
| measured_at | 7 September 2026, 22:52 UTC | When the snapshot was taken. |
| generated_at | 8 September 2026, 00:59 UTC | When this response was assembled. |
| live_listings | 3,537,432 | Rows /v1/jobs will serve. |
| ledger_rows | 9,091,718 | live + withheld + closed. |
| closures.last_24h | 288,677 | Roles that left their source in the last day. |
| closures.last_7d | 1,329,197 | …and in the last week. |
| closures.total | 5,298,104 | Retained: kept indefinitely. |

_Measured 7 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._

| code | meaning |
| --- | --- |
| 200 | Freshness and coverage report. |
| 401 | Missing, unknown, revoked or expired key. All four are reported identically. |
| 429 | Over your plan's per-minute or per-day limit. `Retry-After` says how long. |

**See also**

- [Freshness — measured_at, stale, and the refresh cycle](./ledger-coverage.md#freshness)
- [The coverage report](./ledger-coverage.md#coverage-report)
- [The coverage family](./endpoints-public.md#public-coverage-endpoints)

<a id="endpoint-meta-providers"></a>

## 4. GET /v1/meta/providers

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

### 4.1 In depth

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.

> **Read it at run time** — On 2026-08-15 three names left this list. The following morning an evaluator copied `?exclude_provider=eures` from the old documentation and received a 422. An unknown provider is always a 422, never a silently empty page — which is the right behaviour, and only helps if you handle it.

### 4.2 Exact contract

| provider | label | live_listings | with_description | with_description_pct | source_class | source_type |
| --- | --- | --- | --- | --- | --- | --- |
| company_site | Company career site | 1,993,428 | 1,689,127 | 84.7% | direct | career_site |
| workday | Workday | 628,953 | 305,085 | 48.5% | employer_ats | ats |
| oracle | Oracle HCM | 250,698 | 216,318 | 86.2% | employer_ats | ats |
| greenhouse | Greenhouse | 169,338 | 169,319 | 99.9% | employer_ats | ats |
| smartrecruiters | SmartRecruiters | 93,169 | 92,015 | 98.7% | employer_ats | ats |
| paylocity | Paylocity | 90,064 | 84,162 | 93.4% | employer_ats | ats |
| teamtailor | Teamtailor | 70,655 | 70,591 | 99.9% | employer_ats | ats |
| workable | Workable | 69,308 | 69,305 | 99.9% | employer_ats | ats |
| ashby | Ashby | 69,184 | 69,184 | 100% | employer_ats | ats |
| lever | Lever | 51,947 | 51,207 | 98.5% | employer_ats | ats |
| personio | Personio | 50,358 | 45,799 | 90.9% | employer_ats | ats |
| recruitee | Recruitee | 36,829 | 36,810 | 99.9% | employer_ats | ats |
| breezy | Breezy HR | 36,326 | 35,655 | 98.1% | employer_ats | ats |
| bamboohr | BambooHR | 29,967 | 29,289 | 97.7% | employer_ats | ats |
| join | join.com | 16,200 | 16,146 | 99.6% | employer_ats | ats |
| rippling | Rippling | 16,145 | 15,799 | 97.8% | employer_ats | ats |
| pinpoint | Pinpoint | 15,392 | 15,392 | 100% | employer_ats | ats |
| jobtech_sweden | JobTech Sweden | 11,544 | 11,544 | 100% | government | public_agency |
| ukg | UKG Pro | 10,541 | 10,541 | 100% | employer_ats | ats |
| af_employer_direct | Employer career site (Sweden) | 1,054 | 1,051 | 99.7% | direct | career_site |
| nav_norway | NAV Norge | 158 | 0 | 0% | government | public_agency |
| extension_submission | Direct submission | 7 | 6 | 85.7% | direct | career_site |

_This list is the vocabulary the `provider` and `exclude_provider` filters accept. A name that is not here is a 422, never a silently empty page. Read it at run time: on 2026-08-15 three names left this list and a documented example became a 422._

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

| code | meaning |
| --- | --- |
| 200 | The provider list. |
| 401 | Missing, unknown, revoked or expired key. All four are reported identically. |
| 429 | Over your plan's per-minute or per-day limit. `Retry-After` says how long. |

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](./ledger-sourcing.md#excluded-sources).

**See also**

- [Every provider, with its live row count](./ledger-sourcing.md#providers-list)
- [The sources we fetch and never republish](./ledger-sourcing.md#excluded-sources)
- [Controlled vocabularies — never hard-code these](./api-parameters.md#filter-vocabularies)

---

## Where to go next

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

- [Listings](./endpoints-jobs.md) — The four endpoints that return job rows: the live list, one listing, the closure list, and the cheap id-only closure feed.
- [Companies](./endpoints-companies.md) — The employer directory, the single-company endpoint, and an honest account of why company slugs are not yet stable.
- [The delta feed](./endpoints-changes.md) — One ordered stream of everything that changed — created, updated, withdrawn and delisted — so you never have to re-read the ledger to find out what moved.
- [Bulk export](./endpoints-export.md) — The whole corpus as a stream of NDJSON, resumable to the exact row, with one failure mode you must handle: the error can arrive as the last line of a 200.
- [Keyless endpoints](./endpoints-public.md) — Everything under /public/*, what each returns, and which of them have no keyed equivalent at all.

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
