Job Opportunities API

Check the data. Then trust it.

api

Keyless access

What /public/* gives you with no account: real rows, one page at a time, bounded so that evaluating is free and extracting is not.

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

1What you get without an account

Every read shape the API has, against the live ledger, with no key: rows, companies, facets, coverage, providers, freshness, plans and the spec.

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

The keyless surface is not a sandbox and it is not sample data. It reads from the same projection as /v1, so a row you see here is a row you would receive on a paid key, with the same fields and the same provenance block. That is the whole point: the product's claim is that its data is auditable, and a claim you have to sign up to check is not one.

What you cannot do without a key is walk the corpus. Keyless queries return one page; there is no cursor. You can change the filters and see different rows as many times as you like, which is what evaluation looks like, and you cannot page through two million rows, which is what extraction looks like.

Exact contractTypes, defaults, ranges, errors, edge cases
Keyless endpointKeyed equivalentDifference
/public/jobs/v1/jobs50-row cap, no cursor, paid-only parameters ignored.
/public/jobs/{slug}/v1/jobs/{id}Slug or uuid on both. The keyless one always includes the description.
/public/companies/v1/companiesCapped page, no deep paging.
/public/companies/{slug}/v1/companies/{slug}Same row.
/public/facets/v1/meta/facetsIdentical data.
/public/providers/v1/meta/providersIdentical data.
/public/freshness/v1/meta/freshnessIdentical data.
/public/coverage— (no keyed twin)The canonical coverage report.
/public/coverage/countriesPer-country coverage.
/public/coverage/employersPer-employer coverage.
/public/statsConvenience summary; see why it looks different.
/public/plansThe price list.
/public/openapi.json/v1/openapi.jsonThe same document.
/public/ai-promptAn integration brief, from the API host, for a model.
— (no keyless twin)/v1/changesThe delta feed is licensed.
/v1/jobs/expiredSame.
/v1/jobs/closedSame.
/v1/exportBulk export is licensed.
A whole evaluation, keyless
$ A=https://api.jobopportunitiesapi.org
$ curl -s "$A/public/coverage" | jq '{live_listings, employers, countries, measured_at, stale}'
{
  "age_seconds": 10059,
  "closed_listings": 5690972,
  "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": 189424,
… 111 more lines
Real response, fetched from /public/coverage when this page was built (11 September 2026, 16:47 UTC). Run the command yourself and you will get today's rows, not these.

2The limits, exactly

About two requests a second sustained, a burst of ten, forty a minute, fifty rows a page, and a ten-minute block if you go through all of that.

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

These numbers were chosen against two measured profiles rather than picked. A person evaluating the product peaks at ten to fifteen requests in a minute — clicking the preset buttons on the API page, then a few queries of their own. An extractor does not stop. Forty a minute is comfortably above the first and pointless for the second.

The page cap matters more than the rate. Before it existed, /public/jobs served 294 rows a second to an unauthenticated caller with working keyset pagination: the entire live ledger in a bit over two hours single-threaded, or about twenty minutes across eight workers. The paid entry plan is €80 a month for sixty thousand records, and the keyless path was handing that over every three and a half minutes.

Exact contractTypes, defaults, ranges, errors, edge cases
LimitValueApplies to
Sustained rate2 requests/secondPer client IP, refilled continuously.
Burst10 requestsThe bucket's capacity.
Window ceiling40 requests/minutePer client IP.
Block on breach10 minutes429 with Retry-After. Not permanent.
Page size50 rowslimit above 50 is clamped, not refused.
PagingnoneA cursor on a keyless request is a 402, not a 401.
What a keyless cursor gets you
$ curl -s 'https://api.jobopportunitiesapi.org/public/jobs?limit=2&cursor=anything'
{
  "error": "key_required_to_page",
  "message": "Keyless queries return one page. Change the filters to see different
               rows, or take a free Explore key at https://jobopportunitiesapi.org/login
               — 1,000 records a month, no card, and it pages.",
  "docs": "https://jobopportunitiesapi.org/api"
}
Captured 2026-08-22. HTTP 402 — a licence boundary, not an authentication failure.

Three exemptions exist and none of them helps a third party: requests that did not arrive through the edge (the website's own server-side rendering, talking to the API over the internal network), our own egress addresses (so the QA harness does not rate-limit itself), and authenticated /v1 callers, who already have a per-key limit and a record meter — two limiters on one request is how you get a support ticket nobody can reproduce.

3When keyless stops being enough

The moment you need a second page, a change feed, or more than about forty requests a minute. The free key removes the first and third of those.

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

The free Explore plan is not a trial — it does not expire, and it needs no card. It replaces the IP-based throttle with a per-key request limit and a monthly record allowance, and it turns on cursor paging. For a lot of small projects that is the end of the story.

  • You need page two. Keyless returns one page; any key pages. This is the most common reason.
  • You need the delta feed. /v1/changes and /v1/jobs/expired require a plan with delta_feed. Polling a listing endpoint and diffing is the alternative, and it costs far more records than the feed does.
  • You need bulk export. /v1/export requires bulk_export.
  • You need throughput. The per-minute ceiling on a paid plan is measured in hundreds rather than tens.
  • You need closed rows. status=closed and /v1/jobs/closed are paid-only.
Exact contractTypes, defaults, ranges, errors, edge cases
PlanPriceRecords / monthRequests / dayRequests / minDelta feedBulk export
Explore
explore
Free1,0005,00030nono
Growth
growth
€80/month60,000100,000120yesno
Signal
signal
€299/month400,000400,000300yesyes
Scale
scale
€899/month2,000,0001,000,000600yesyes

Rendered from /public/plans when this page was built. Only purchasable plans appear there, so this table is the price list — if a plan is not here you cannot buy it. Compare on /pricing.

Note that the ladder is about records, not requests, above the free tier. That is the single most commonly misunderstood thing about the pricing and it is explained properly in the record meter — read it before comparing the numbers above with another vendor's, because a request-priced API and a record-priced one are not comparable line by line.

This page was rendered 11 September 2026, 16:47 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.