> ## Documentation Index
> Fetch the complete documentation index at: https://clair.im/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Jobs quickstart

> Search normalized jobs and internships, inspect freshness and provenance, and page safely with an opaque cursor.

## 1. Get an API key

The Jobs API is distributed through
[RapidAPI](https://rapidapi.com/clairapis-clairapis-default/api/clair-job-search-api).
Subscribe and copy your **`X-RapidAPI-Key`** from the dashboard.

<Note>
  The examples use `clair-job-search-api.p.rapidapi.com`. Use the exact
  `X-RapidAPI-Host` shown on the listing.
</Note>

## 2. Search the index

This request finds recently published remote data roles in Germany that mention
Python or PyTorch:

```sh cURL theme={null}
curl -G https://clair-job-search-api.p.rapidapi.com/v1/jobs \
  -H "X-RapidAPI-Key: YOUR_RAPIDAPI_KEY" \
  -H "X-RapidAPI-Host: clair-job-search-api.p.rapidapi.com" \
  --data-urlencode "q=data scientist" \
  --data-urlencode "country=DE" \
  --data-urlencode "workplace_type=remote" \
  --data-urlencode "skills_any=python,pytorch" \
  --data-urlencode "posted_after=2026-07-01T00:00:00Z" \
  --data-urlencode "limit=25"
```

Clair answers from its own crawled index. The request does not contact an
employer or applicant tracking system.

```json theme={null}
{
  "jobs": [
    {
      "id": "fd6b984d-f7f4-4ca8-b68e-34b88a4ec299",
      "title": "Data Science Intern",
      "company": "Example Labs",
      "department": "Engineering",
      "team": "Applied AI",
      "location": "Berlin, Germany",
      "locations": ["Berlin, Germany"],
      "country": "DE",
      "workplace_type": "remote",
      "employment_type": "Intern",
      "posting_url": "https://jobs.example.com/4512338005",
      "apply_url": "https://jobs.example.com/4512338005/apply",
      "posted_at": "2026-07-24T09:00:00Z",
      "updated_at": null,
      "compensation": {
        "min": 25,
        "max": 35,
        "currency": "EUR",
        "interval": "hour",
        "summary": "€25–€35 per hour",
        "source": "description"
      },
      "seniority": "intern",
      "track": "ic",
      "title_family": "data scientist",
      "skills": ["Python", "PyTorch"],
      "description_available": true,
      "description_excerpt": null,
      "first_seen_at": "2026-07-24T09:08:12Z",
      "last_verified_at": "2026-07-28T08:03:51Z",
      "source": {
        "provider": "greenhouse",
        "board": "examplelabs",
        "external_id": "4512338005"
      }
    }
  ],
  "meta": {
    "request_id": "730ecf8c-3b70-48d3-ae2a-173c51b9cfda",
    "generated_at": "2026-07-28T12:00:00Z",
    "count": 1,
    "limit": 25,
    "sort": "posted_at",
    "has_more": true,
    "next_cursor": "OPAQUE_CURSOR",
    "total": null,
    "total_is_exact": false,
    "index": {
      "status": "healthy",
      "as_of": "2026-07-28T11:57:00Z",
      "active_boards": 9000,
      "stale_boards": 0
    }
  }
}
```

The sample values are illustrative. Fields remain present with `null` when the
publisher did not state a value or Clair could not derive one safely.

## 3. Search internships

Jobs and internships use the same endpoint and schema:

```sh cURL theme={null}
curl -G https://clair-job-search-api.p.rapidapi.com/v1/jobs \
  -H "X-RapidAPI-Key: YOUR_RAPIDAPI_KEY" \
  -H "X-RapidAPI-Host: clair-job-search-api.p.rapidapi.com" \
  --data-urlencode "seniority=intern" \
  --data-urlencode "workplace_type=remote" \
  --data-urlencode "skills_all=python" \
  --data-urlencode "first_seen_after=2026-07-28T00:00:00Z" \
  --data-urlencode "sort=first_seen_at"
```

`seniority=intern` includes titles recognized as internships, co-ops,
apprenticeships, working-student roles, and placements.

Use `first_seen_after` with `sort=first_seen_at` for a polling or alerting loop.
Use `posted_after` when the publisher's own publication time is what matters.

## 4. Page with the cursor

When `meta.has_more` is true, repeat the same search and pass
`meta.next_cursor` unchanged:

```sh cURL theme={null}
curl -G https://clair-job-search-api.p.rapidapi.com/v1/jobs \
  -H "X-RapidAPI-Key: YOUR_RAPIDAPI_KEY" \
  -H "X-RapidAPI-Host: clair-job-search-api.p.rapidapi.com" \
  --data-urlencode "q=data scientist" \
  --data-urlencode "country=DE" \
  --data-urlencode "workplace_type=remote" \
  --data-urlencode "skills_any=python,pytorch" \
  --data-urlencode "posted_after=2026-07-01T00:00:00Z" \
  --data-urlencode "limit=25" \
  --data-urlencode "cursor=CURSOR_FROM_PREVIOUS_RESPONSE"
```

The cursor is bound to the original filters and sort. Changing them while
reusing the cursor returns `bad_cursor`.

Stop when:

```json theme={null}
{
  "has_more": false,
  "next_cursor": null
}
```

`limit` defaults to 25 and caps at 100.

## 5. Ask only for payloads you need

An exact total requires an additional distinct-count query and is off by
default:

```sh theme={null}
--data-urlencode "include_total=true"
```

With it, `meta.total` is an exact count of deduplicated requisitions and
`meta.total_is_exact` is true. Without it, `total` is null.

Descriptions are also opt-in:

```sh theme={null}
--data-urlencode "include_description=true"
```

This adds a plain-text `description_excerpt` capped at 2,000 characters.
`description_available` is always present, so a null excerpt is distinguishable
from a description that was simply not requested. The excerpt is not a
complete or archival description; use `posting_url` for the publisher's current
copy.

## 6. Read freshness and provenance

For one result:

* `posted_at` and `updated_at` come from the publisher.
* `first_seen_at` is when Clair discovered it.
* `last_verified_at` is the latest successful crawl that still contained it.
* `source` identifies the ATS provider, board, and source posting ID.
* `compensation.source` distinguishes a structured ATS value from a figure
  extracted from employer-written description text.

For the whole response, inspect `meta.index.status`, `active_boards`, and
`stale_boards`. `meta.index.as_of` is the most recent successful board crawl,
not a claim that every source refreshed at that instant.

<Card title="Full Jobs API reference" icon="arrow-right" href="/docs/jobs-api/search">
  Every filter, response field, freshness state, and error code.
</Card>
