# Quickstart

Make your first search, build a JobQuery from lookups, and read the results.

The API lives at `https://trawle.com/v1`. It speaks JSON, needs no key for now, and allows calls from any origin, so you can try it from a terminal or a browser.

## 1. Search

`POST /v1/jobs/search` takes a `JobQuery` and returns a page of open jobs, newest first. Free text is the quickest start:

```bash
curl -X POST "https://trawle.com/v1/jobs/search" \
  -H "Content-Type: application/json" \
  -d '{ "query": { "q": "react" }, "limit": 5 }'
```

```ts
const response = await fetch("https://trawle.com/v1/jobs/search", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ query: { q: "react" }, limit: 5 }),
});
const result = await response.json();
```

```python
import requests

response = requests.post(
    "https://trawle.com/v1/jobs/search",
    json={"query": {"q": "react"}, "limit": 5},
)
result = response.json()
```

The request body, for reference:

```json
{ "query": { "q": "react" }, "limit": 5 }
```

## 2. Build a query from lookups

Free text matches titles, departments and company names. For precise results, filter by ids instead. Each filter has a lookup endpoint that turns words into ids:

| Filter      | Lookup                                          | Use                        |
| ----------- | ----------------------------------------------- | -------------------------- |
| `places`    | [`GET /v1/places?q=`](https://developers.trawle.com/api/resolve-location)    | `items[].id` (GeoNames id) |
| `roles`     | [`GET /v1/roles?q=`](https://developers.trawle.com/api/find-roles)           | `items[].slug`             |
| `companies` | [`GET /v1/companies?q=`](https://developers.trawle.com/api/search-companies) | `items[].slug`             |
| `families`  | [`GET /v1/families`](https://developers.trawle.com/api/list-families)        | `items[].slug`             |

Look up Copenhagen:

```bash
curl "https://trawle.com/v1/places?q=copenhagen"
```

```json
{
  "items": [
    {
      "id": 2618425,
      "name": "Copenhagen",
      "country": "DK",
      "kind": "city",
      "context": "Capital Region, Denmark"
    }
  ]
}
```

Then the role, with `GET /v1/roles?q=frontend`, which returns `frontend-engineer`. Put both into the query, and narrow it to hybrid and remote jobs:

```json
{
  "query": {
    "roles": ["frontend-engineer"],
    "places": [2618425],
    "workplace": ["hybrid", "remote"]
  },
  "limit": 25
}
```

Values inside one filter are OR, filters are AND: frontend jobs, in or open to people in Copenhagen, that are hybrid or remote. [Building a JobQuery](https://developers.trawle.com/guides/building-a-job-query) explains every filter.

## 3. Read the results

```json
{
  "jobs": [
    {
      "id": "4419023",
      "title": "Senior Frontend Engineer",
      "company": { "slug": "lumen-labs", "name": "Lumen Labs", "domain": "lumenlabs.example" },
      "role": { "slug": "frontend-engineer", "name": "Frontend Engineer" },
      "family": { "slug": "software-engineering", "name": "Software Engineering" },
      "locations": [{ "id": 2618425, "name": "Copenhagen", "country": "DK" }],
      "workplace": "hybrid",
      "remoteScope": null,
      "seniority": "senior",
      "employmentType": "full_time",
      "salary": { "min": 720000, "max": 900000, "currency": "DKK", "period": "year" },
      "firstSeenAt": "2026-10-30T06:12:00.000Z",
      "lastSeenAt": "2026-10-31T06:12:00.000Z",
      "postedAt": "2026-10-29T00:00:00.000Z",
      "openSince": "2026-09-14T06:10:00.000Z",
      "repostCount": 1,
      "closedAt": null,
      "applyUrl": "https://job-boards.greenhouse.io/lumenlabs/jobs/7712001",
      "url": "https://trawle.com/jobs/4419023"
    }
  ],
  "total": 38,
  "totalCapped": false,
  "nextCursor": "MjAyNi0xMC0zMFQwNjoxMjowMC4wMDBafDQ0MTkwMjM",
  "labels": { "roles:frontend-engineer": "Frontend Engineer", "places:2618425": "Copenhagen" },
  "dataDate": "2026-10-31T06:12:00.000Z",
  "nextSince": "2026-10-31T09:30:00.000Z"
}
```

A few fields worth knowing:

- `applyUrl` is the employer's own posting. Send people there to read the full description and apply.
- `url` is the job on Trawle.
- `openSince` and `repostCount` tell you how long the job has really been open. This one was first posted in September and reposted once; see [Freshness and reposts](https://developers.trawle.com/guides/freshness-and-reposts).
- `labels` names the ids in your query. An id that is missing from `labels` matched nothing.
- `nextCursor` fetches the next page ([Pagination](https://developers.trawle.com/guides/pagination)); `nextSince` gets only new jobs on your next run ([Daily new-jobs check](https://developers.trawle.com/guides/daily-new-jobs)).

## 4. Show attribution

When you display Trawle results, show "Data by Trawle" with a link to `https://trawle.com` (or to each job's `url`), and link each job to its `applyUrl`. See [Attribution and data policy](https://developers.trawle.com/guides/attribution).

## Typed clients

The [OpenAPI spec](https://developers.trawle.com/openapi.json) is complete enough to generate a client. For TypeScript, `openapi-typescript` plus `openapi-fetch` gives typed requests and responses:

```bash
npx openapi-typescript https://developers.trawle.com/openapi.json -o trawle.d.ts
npm install openapi-fetch
```

```ts
import createClient from "openapi-fetch";

import type { paths } from "./trawle";

const trawle = createClient<paths>({ baseUrl: "https://trawle.com" });
const { data, error } = await trawle.POST("/v1/jobs/search", {
  body: { query: { roles: ["frontend-engineer"], places: [2618425] } },
});
```

Every page in the [API reference](https://developers.trawle.com/api) has samples for cURL, TypeScript and Python.
