Job Opportunities API

Check the data. Then trust it.

website

Legal, cookies and crawlers

The legal pages and what each covers, exactly what this site stores in your browser, the AI disclosure, and the crawler policy — including where it differs by bot.

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

2What this site stores in your browser

A signed session cookie when you are signed in, a record of your consent choice, and analytics only if you allow them.

In depthWhy it exists, what it is not, what people get wrong
StoredPurposeSet when
joa_sessionSigned sign-in session, 30 days, HTTP-only. Not a database row — the server verifies it cryptographically.You follow a sign-in link.
Consent recordRemembers your cookie choice so the notice does not reappear.You answer the notice.
AnalyticsProduct analytics, only with consent.You opt in.
Documentation feedbackWhich documentation pages this browser has voted on, so the tally is not a click counter. No identifier, first-party, never sent to the API.You vote on a docs page.

The consent manager is reachable at any time from Cookie preferences in the footer of every page. The essential category is always on and says so — it covers signing in and remembering the notice itself, which cannot be declined without breaking both.

Exact contractTypes, defaults, ranges, errors, edge cases

Consent decisions are recorded, so there is an auditable record of what was chosen and when, rather than only a cookie in your browser. What the analytics provider does and does not receive is stated explicitly on /privacy rather than left to the provider's own documentation.

3The AI disclosure

Two fields are always machine-generated, three are sometimes, and every row says which. No candidate or customer data is ever sent to a model.

In depthWhy it exists, what it is not, what people get wrong
FieldWhere it comes fromProvenance
titleThe posting, unchanged.always published
companyThe posting. The display name is tidied, never invented.always published
locationThe posting, or derived by us from it.field_sources.location says which: published when the employer stated it, inferred when we resolved it against a gazetteer
apply_urlThe employer's own link. Never rewritten, never proxied.always published
employment_typeThe posting, when the employer states it.published or absent
posted_atThe posting or the source feed.published or absent
descriptionThe employer's own text, unchanged.published or absent
categoryAlways ours. A model reads the title and assigns a family.always inferred
seniorityAlways ours. A model reads the title and assigns a level.always inferred
remotePublished when the employer says so; inferred when we conclude it from the text.either — check the row
salaryPublished when the posting carries structured pay; inferred when parsed out of prose.either — check the row
source_typePublished when the source declares it; inferred otherwise.either — check the row

Nothing else in the ledger is model-generated. There is no generated description text, no generated salary figure, no generated employer and no generated apply link.

Exact contractTypes, defaults, ranges, errors, edge cases

What is sent to a model is the text of a public job posting and a company name — the same words the employer published on their own careers page. No customer data: your account, your API key, your queries and your usage are never used as model input, never used to train anything, and never shared with a model vendor. No candidate data, because none exists.

The disclosure also states where the classification is weakest, which is worth reading if you are building on category or seniority: on a clear job title they are dependable, on a vague or invented one they are a guess, and the marker says inferred either way because the row cannot tell you which case you have. The full page, including which models are used for which pass, is at /ai.

4The crawler policy

Search engines get the whole ledger. Answer engines get the pages that describe the product but not the two million rows that are the product.

In depthWhy it exists, what it is not, what people get wrong
WhoAllowedRefused
Search engines (Googlebot, Bingbot, Applebot, DuckDuckBot, Yandex, Baidu, …)Everything, including /job/* and /company/*./api/, /dashboard, /admin, and the faceted query space /*?.
Answer engines (GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, PerplexityBot, CCBot, …)The home page, /docs, /api, /about, /coverage, /facts, /compare, /pricing, /provenance, and the job and company indexes.The per-row corpus: /job/* and /company/*. Plus the same private paths.
Link unfurlers (Twitterbot, facebookexternalhit, LinkedInBot, Slackbot, Discordbot)Everything except the private paths — an unfurler fetches the one URL a human just pasted, which is a citation, not a harvest./api/, /dashboard, /admin, /*?.
Everything elseNothing.The ledger is free to read in a browser and licensed for bulk use through the API; scraping is the one route that is neither.

This documentation is allowed for everyone, answer engines included. It describes the product; it is not the corpus.

Exact contractTypes, defaults, ranges, errors, edge cases

The reasoning behind the asymmetry, since it is a deliberate choice rather than a reflex: a job page ranking in a search engine sends a human here, and that human can see a price. An answer engine that ingests the same page answers the question inside its own product and the visit never happens. Handing two and a half million rows to a model vendor for free, while selling the same rows through the API, is selling something you are also giving away — to the buyer least likely to come back. What answer engines keep is deliberately generous: enough to describe the dataset accurately, cite it, and tell somebody where to get it.

/*? — the faceted query space — is refused to every crawler. The home page exposes country, city, category, remote and employment as query parameters, so the number of distinct URLs is combinatorial. One crawler enumerating them generated 1,562 requests in three minutes, 97% of them a query string never seen before, so every one missed the page cache and forced a fresh render and a fresh query; that single pattern held the server at 96% CPU for as long as it continued. Nothing of value is lost — a filtered view is duplicate content whose canonical is the bare page, and every individual job and company already has its own indexable URL in the sitemap.

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