Skip to main content
Two endpoints search the same index: Both are page-based and ranked by relevance unless you set sort. They reach at most 100,000 results deep (page × page_size, see Pagination). To export the whole corpus, use the feed: it costs the same per job, and its cursor doesn’t skip or repeat jobs as records change.
Filter values match literally. An unrecognized value returns 200 with total: 0, not an error. Send the canonical keys from Enums, not the display values on a job.

Parameter equivalence

  • Unknown parameters and body fields are ignored, and listed in warnings (code unknown_request_fields) and the X-Jobo-Warnings header.
  • POST arrays are not comma-split. "employment_types": ["full-time,contract"] is one literal value and matches nothing; write ["full-time", "contract"].
  • GET filters only include. To exclude skills, industries, company categories, job categories, job subcategories or occupations, use the exclude side of the POST object.

How text queries match

A - entry with several words excludes jobs that contain any of them. Neither mode searches the full description. Set search_description=false when you pass exact job titles: Android Developer then also finds Android Engineer, but not jobs that only mention Android as a skill or in the summary. With the default sort, jobs that match every word of a query rank first. Partial matches fill the tail only when a query has no full match inside your filters (a short posted_after range, for example), so take results from the top.

Quoted phrases match titles only

Only jobs whose title contains either phrase come back. You can mix quoted and unquoted entries; each is evaluated on its own and the results are combined:
On GET /api/jobs, put the quotes inside the value: ?q="Quantitative Developer".
queries takes at most 10 positive entries. More returns a 400 instead of dropping the extras. Negative - entries don’t count toward the limit.

Recipes

The Python samples use the Python SDK and reuse the client from the first recipe. Parameters the 4.0.0 SDKs don’t send, such as sort and the occupation filters, have curl or JSON samples only.

Keyword plus location

location and locations use the same resolver as GET /api/locations/geocode, so you can check how a string will be read before you search. See Resolve locations.

Remote senior roles in a salary band

Experience levels take the canonical key: senior, not senior level.
Since 4.0.0, the client enums (WorkModel, EmploymentType, ExperienceLevel) send canonical values: EmploymentType.FULL_TIME sends full-time, and ExperienceLevel.INTERN and EmploymentType.FREELANCE exist. On 3.x and earlier, pass string literals instead of the enums (Upgrading to 4.0.0).

Include and exclude skills

This include/exclude object is POST-only. include entries are OR’d, and jobs matching any exclude entry are removed. companies, industries, company_categories, job_categories, job_subcategories and occupation_ids take the same object.

Target or exclude specific employers

companies works on POST only. Each entry is matched by its form:
  • A domain ignores a leading www. but not other subdomains: careers.fedex.com doesn’t match fedex.com. Use the main domain.
  • A name must match the whole company name. Case, accents, punctuation and common trailing legal suffixes are ignored, so FedEx Corporation, fedex and FedEx, Inc. find the same companies.
  • It is not a partial match: Glenmark doesn’t find Glenmark Pharmaceuticals, and Fidelity finds the company called Fidelity, not every Fidelity* employer.
To drop staffing agencies, exclude them:
Entries that match nothing never fail the request. When every include entry misses, you get 200 with zero jobs, not a 400. Check warnings and filters.companies.unmatched.
For example, FedEx is indexed on Workday, so FedEx with sources: ["greenhouse"] returns companies_outside_sources and no jobs, however you spell the name. To find a stored spelling, search the name as free text and read company.name: q=Glenmark surfaces jobs at Glenmark Pharmaceuticals.

Filter by occupation

Every classified job carries an occupation in a three-level taxonomy, such as Healthcare › Nursing › Registered Nurse. Filter on any level: The free GET /api/occupations lists every value. Cache it and refetch now and then: its version changes when occupations or the hierarchy change, but display names can improve without a new version. On POST you can also exclude. Every nursing job except registered nurse roles:
  • Matching is exact but ignores case: healthcare works, Health care matches nothing.
  • Filter occupations by id, never by name. Names can improve between taxonomy releases; ids don’t.
  • A job without an occupation never matches an include list.
  • q and queries don’t search occupation names. To find every job of one occupation, whatever the employer called it, use occupation_ids.
  • With a sandbox key, POST /api/jobs/search ignores these filters (with an unknown_request_fields warning) and returns no occupation facets. Test them with a live key.
Four subcategory names contain a comma: Libraries, Archives & Museums, Film, TV & Stage Production, Sports, Fitness & Recreation and Environmental, Health & Safety. GET /api/jobs splits job_subcategories on commas, so these match nothing there. Filter them on POST /api/jobs/search, where each array entry is one value.

Open-ended salary range

Omit either bound. Jobs without disclosed compensation never match a salary filter.

Posted window vs discovered window

To page through a discovered window without jobs shifting between pages, add "sort": "discovered_asc". See Stable pages over a fixed window.

Everything at once

Iterate every match

iter_jobs pages through the results for you.
A page past the 100,000-result depth silently returns the last reachable page again, so on a larger result set iter_jobs yields repeated jobs. Narrow the filters to keep total under 100,000, or use the feed.

Shrinking the response

description dominates payload size; dropping it is the biggest saving for high-volume search. include_fields keeps only the non-core fields you name. Core fields (id, title, company, locations, compensation, dates, source, flags) always come back.
  • The five omittable fields are description, summary, qualifications, responsibilities and benefits. Unknown names are dropped.
  • On GET, pass a comma-separated string (?include_fields=description,benefits), or ?include_fields= for core fields only.
  • A non-core field you didn’t request is still present but empty: description is "", summary is null, and the list fields are [].

Facets

facets holds server-computed counts, keyed by canonical value.
A facet key goes straight back in as a filter value; the display values on a job don’t (see Enums). countries keys are lowercased country names (united states): pass them back as locations. company_categories keys go into the company_categories include/exclude filter.

Occupation facets

job_categories and job_subcategories keys are lowercased names (healthcare, nursing); occupations keys are occupation ids. Each goes back into the filter of the same name (occupations into occupation_ids). Only these buckets carry two extra fields:
Request all three ("include_facets": ["job_categories", "job_subcategories", "occupations"]) and nest them by parent to build a category › subcategory › occupation filter tree from one response.

Facet limits

  • Unknown facet names are silently dropped. If include_facets holds only unknown names (say, the typo work_models), you get no facets, not the defaults.
  • Each facet returns at most 8 buckets, with approximate counts. Take exact totals from total, not from summed facet counts.
  • With that cap, a subcategory’s parent may be missing from the category buckets. Use GET /api/occupations for the full tree.
  • A facet with no buckets is left out of facets, not returned as an empty array.

Building this with an AI coding assistant?

Copy-paste prompts that hand your assistant the full search contract: add job search to your app, or audit an existing integration for silent failures.