Skip to main content
There are two search endpoints over the same index:
  • GET /api/jobs — query-string filters, comma-separated multi-values. Best for URL-shareable queries and lightweight integrations.
  • POST /api/jobs/search — typed JSON body with include/exclude and range filters. Best for filter UIs and anything programmatic.
Both are page-based and relevance-ranked. To export the whole corpus, use the feed instead — it is cheaper per job and immune to records shifting between pages.
Filter values are matched literally. An unrecognised value is not an error — it returns 200 with total: 0. Send the canonical keys from Enums, not the display values that come back on a job.

Parameter equivalence

POST array fields are not comma-split. "employment_types": ["full-time,contract"] is one literal value and matches nothing — write ["full-time", "contract"].

How text queries match

By default a query is a broad relevance search: each term is matched against the job title, company name, popular skills, and summary, with typo tolerance. That maximises recall, so data engineer can also surface roles whose title is not “data engineer” because the words appear elsewhere. Set search_description=false to keep the query out of the description body — noticeably faster and much more precise.

Quoted phrases match titles only

Wrap a query in double quotes to switch that entry to a strict match: the exact phrase must appear contiguously in the job title. Typo tolerance and partial-word matching are off.
Only jobs whose title contains either phrase are returned. You can mix quoted and unquoted entries — each is evaluated independently and the results combined:
On GET /api/jobs pass the quotes inside the value: ?q="Quantitative Developer".
A request may carry at most 10 positive terms in queries. Exceeding that returns a real 400 rather than silently dropping the extras, so you always know exactly what was searched. Negative - exclusions do not count toward the limit.

Recipes

Keyword plus location

Remote senior roles in a salary band

Remember: senior, not senior level.
Do not use the SDK’s EmploymentType enum — its members serialize as full_time/part_time with underscores, which the API does not accept and which silently returns zero results. Pass the hyphenated string literals "full-time" / "part-time" instead. WorkModel and ExperienceLevel are safe, except that ExperienceLevel is missing intern.

Include and exclude skills

skills, companies, and industries accept an include/exclude object on the typed endpoint. Entries within include are OR’d; anything in exclude is removed.

Target or exclude specific employers

Only available on the typed endpoint. Excluding staffing agencies is the most common use.

Open-ended salary range

Omit either bound.

Posted window vs discovered window

posted_after / posted_before filter on the employer’s posting date, which some platforms do not expose. discovered_after / discovered_before filter on when Jobo indexed the job — use these when you need a guarantee that nothing is missed.

Everything at once

Iterate every match

iter_jobs pages for you.

Shrinking the response

Every job carries all its fields by default, and description dominates payload size. include_fields keeps only the non-core fields you ask for — core fields (id, title, company, locations, compensation, dates, source, flags) are always returned. The five omittable fields are description, summary, qualifications, responsibilities, and benefits. On GET pass a comma-separated string (?include_fields=description,benefits); send ?include_fields= for core only. Unknown names are dropped, and any non-core field you did not request comes back empty ("" / []) rather than absent. Dropping description is the single biggest win for high-volume search.

Facets

Responses include a facets object of server-computed counts, keyed by canonical value.
Facet keys are the canonical values, so you can feed a key straight back in as a filter value. This is not true of the display values on a job — see Enums.
Three facet behaviours surprise people:
  • Unknown facet names are silently dropped, so a request whose include_facets contains only unknown names (e.g. the plural typo work_models) returns no facets rather than the defaults.
  • At most 8 buckets are returned per facet, and counts are approximate. Take exact totals from total, never from summing facet counts.
  • A facet with no buckets is omitted from the facets object entirely rather than appearing as an empty array.