# Building a JobQuery

How the filters in a JobQuery combine, how places match remote jobs, and where ids come from.

A `JobQuery` is the one object that describes a job search. The same object drives the API, the MCP server, and the links and saved searches on trawle.com. The [schema](https://developers.trawle.com/api/search-jobs) lists every field; this page explains how they combine.

## OR inside a filter, AND across filters

```json
{
  "roles": ["frontend-engineer", "fullstack-engineer"],
  "places": [2618425, 2673730],
  "seniority": ["senior", "lead"]
}
```

This reads: (frontend **or** full-stack) **and** (Copenhagen **or** Stockholm) **and** (senior **or** lead). An empty query matches every open job.

## Ids come from lookups

Filters take ids and slugs, not names, so a query means the same thing every time.

| Filter      | Takes         | Look up with                                    |
| ----------- | ------------- | ----------------------------------------------- |
| `places`    | GeoNames ids  | [`GET /v1/places?q=`](https://developers.trawle.com/api/resolve-location)    |
| `roles`     | role slugs    | [`GET /v1/roles?q=`](https://developers.trawle.com/api/find-roles)           |
| `families`  | family slugs  | [`GET /v1/families`](https://developers.trawle.com/api/list-families)        |
| `companies` | company slugs | [`GET /v1/companies?q=`](https://developers.trawle.com/api/search-companies) |

Role lookups match common job titles as well as role names: `q=react` finds Frontend Engineer. Place lookups return a `context` ("Washington, United States") to tell homonyms apart.

Unknown slugs and ids match nothing, never everything, and they are left out of the response's `labels`. If a value you sent is missing from `labels`, look it up again.

## Places: where the person wants to work from

`places` describes where the job seeker is, not only where the office is. A place matches:

- onsite and hybrid jobs located in that place, and
- remote jobs open to people living there, such as a remote job open to "Europe" or "Anywhere".

So `places: [2618425]` (Copenhagen) includes a hybrid job in Copenhagen and a remote job open to anyone in the EU, but not a remote job limited to the United States. Each remote job's `remoteScope` says who may apply, in words.

To only get one kind, combine with `workplace`:

```json
{ "places": [2618425], "workplace": ["remote"] }
```

Places can be cities, metro areas, states or regions (`admin1`), countries, or multi-country regions. Multi-country regions such as Europe or EMEA have **negative ids**; `-1` is worldwide. Find them through the same lookup (`GET /v1/places?q=europe`).

## Other filters

- `q`: free text over title, department and company name (2 to 100 characters). Use it for things that have no filter, like a technology.
- `workplace`, `seniority`, `employment`: fixed values; see the schema.
- `salary`: an annual range, for example `{ "min": 60000, "max": null, "currency": "EUR" }`. Jobs match when their own range overlaps it after converting currencies. Jobs that don't show pay never match.
- `pay: "listed"`: only jobs that show pay.
- `posted`: first seen within `1d`, `3d`, `7d` or `30d`. For scheduled runs prefer `postedSince` ([guide](https://developers.trawle.com/guides/daily-new-jobs)).
- `reposts`: `hide` keeps only originals, `only` shows only reposts.
- `sort`: `newest` (default) or `pay`, highest annual pay first, which also limits results to jobs that show pay.

```json
{
  "families": ["software-engineering"],
  "places": [-1],
  "workplace": ["remote"],
  "salary": { "min": 100000, "max": null, "currency": "USD" },
  "reposts": "hide",
  "sort": "pay"
}
```
