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

# Search Jobs

> Search the public job marketplace. No authentication required.

Full-text search over the public OpenTrain job marketplace. This endpoint is **tokenless** — no `Authorization` header needed — and CORS-enabled (`Access-Control-Allow-Origin: *`), so it works from browsers, scripts, and agents without any onboarding. Responses are cached at the edge (`Cache-Control: public, s-maxage=300, stale-while-revalidate=3600`).

For filter values to offer in a UI, fetch [`GET /jobs/facets`](/docs/developers/api-reference/jobs/facets) first. To track marketplace changes incrementally, poll [`GET /jobs/changes`](/docs/developers/api-reference/jobs/changes).

**Requirements:** none — no token, no scope, no feature flag. Rate limited to 120 requests/minute per IP.

## Request

<ParamField query="q" type="string">
  Free-text search across job titles and descriptions.
</ParamField>

<ParamField query="category" type="string">
  Filter by job category (use values from [facets](/docs/developers/api-reference/jobs/facets)).
</ParamField>

<ParamField query="language" type="string">
  Filter by required language.
</ParamField>

<ParamField query="country" type="string">
  Filter by allowed country.
</ParamField>

<ParamField query="payType" type="string">
  `PAY_PER_HOUR`, `FIXED_PRICE`, or `PAY_PER_LABEL`. Any other value returns `400`.
</ParamField>

<ParamField query="limit" type="number" default="50">
  Page size, max 100.
</ParamField>

<ParamField query="cursor" type="string">
  Opaque pagination cursor from a previous response's `nextCursor`.
</ParamField>

`OPTIONS` on this path returns `204` with the CORS headers (preflight support).

## Response

<ResponseField name="jobs" type="object[]">
  Matching public jobs, each a full job object.

  <Expandable title="job object">
    <ResponseField name="id" type="string">
      Job ID — usable with [`GET /jobs/{id}`](/docs/developers/api-reference/jobs/get).
    </ResponseField>

    <ResponseField name="slug" type="string">
      URL slug.
    </ResponseField>

    <ResponseField name="title" type="string">
      Job title.
    </ResponseField>

    <ResponseField name="companyName" type="string">
      Hiring company's display name.
    </ResponseField>

    <ResponseField name="descriptionText" type="string">
      Plain-text description.
    </ResponseField>

    <ResponseField name="descriptionHtml" type="string">
      HTML description.
    </ResponseField>

    <ResponseField name="seoTitle" type="string | null">
      Standardized title when available.
    </ResponseField>

    <ResponseField name="summary" type="string | null">
      Short standardized summary when available.
    </ResponseField>

    <ResponseField name="status" type="string | null">
      Marketplace status (open jobs only appear here).
    </ResponseField>

    <ResponseField name="datePosted" type="string">
      ISO timestamp of the original posting date.
    </ResponseField>

    <ResponseField name="validThrough" type="string">
      ISO timestamp — rolling listing expiry.
    </ResponseField>

    <ResponseField name="updatedAt" type="string">
      ISO timestamp of the last update.
    </ResponseField>

    <ResponseField name="employmentTypes" type="string[]">
      schema.org `employmentType` values (e.g. `CONTRACTOR`).
    </ResponseField>

    <ResponseField name="countries" type="string[]">
      ISO country codes; empty array means worldwide.
    </ResponseField>

    <ResponseField name="languages" type="string[]">
      Required languages.
    </ResponseField>

    <ResponseField name="category" type="string | null">
      Job category.
    </ResponseField>

    <ResponseField name="dataType" type="string | null">
      Data type being labeled (e.g. `Text`, `Audio`).
    </ResponseField>

    <ResponseField name="labelTypes" type="string[]">
      Label/annotation types.
    </ResponseField>

    <ResponseField name="labelingSoftware" type="string | null">
      Required labeling tool, when specified.
    </ResponseField>

    <ResponseField name="experienceLevel" type="string | null">
      Human-readable level (e.g. `Expert`), `null` when unspecified.
    </ResponseField>

    <ResponseField name="skills" type="string[]">
      Employer-tagged skills.
    </ResponseField>

    <ResponseField name="pay" type="object">
      Pay details. `paymentType` is `PAY_PER_HOUR`, `FIXED_PRICE`, `PAY_PER_LABEL`, or `null`; `currency` is always `USD`. Rate fields (`hourlyRate`, `hourlyMin`, `hourlyMax`, `fixedPrice`, `perLabelRate`) are numbers or `null` depending on the payment type.
    </ResponseField>

    <ResponseField name="url" type="string">
      Canonical public listing URL.
    </ResponseField>

    <ResponseField name="applyUrl" type="string">
      Tracked apply redirect. Always use this URL to send applicants — never construct apply links yourself.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="nextCursor" type="string | null">
  Pass back as `cursor` for the next page; `null` when there are no more results.
</ResponseField>

<ResponseField name="generatedAt" type="string">
  ISO timestamp when the response was generated (responses are edge-cached up to 5 minutes).
</ResponseField>

## Errors

| Status | `code`         | Meaning                                   |
| ------ | -------------- | ----------------------------------------- |
| `400`  | `BAD_REQUEST`  | Invalid `payType` or malformed parameters |
| `429`  | `RATE_LIMITED` | Over 120 requests/minute from one IP      |

<RequestExample>
  ```bash curl theme={null}
  curl -sS "https://app.opentrain.ai/api/public/v1/jobs?q=audio%20annotation&payType=PAY_PER_HOUR&limit=10"
  ```

  ```bash CLI theme={null}
  opentrain jobs search --q "audio annotation" --pay-type PAY_PER_HOUR --limit 10 --json
  ```

  ```json MCP: opentrain_search_jobs theme={null}
  {
    "q": "audio annotation",
    "payType": "PAY_PER_HOUR",
    "limit": 10
  }
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "jobs": [
      {
        "id": "<JOB_ID>",
        "slug": "spanish-audio-annotation",
        "title": "Spanish Audio Annotation",
        "companyName": "Acme AI",
        "descriptionText": "Transcribe and annotate Spanish call-center audio...",
        "descriptionHtml": "<p>Transcribe and annotate Spanish call-center audio...</p>",
        "seoTitle": null,
        "summary": null,
        "status": "OPEN",
        "datePosted": "2026-06-01T12:00:00.000Z",
        "validThrough": "2026-07-27T12:00:00.000Z",
        "updatedAt": "2026-06-10T09:30:00.000Z",
        "employmentTypes": ["CONTRACTOR"],
        "countries": [],
        "languages": ["Spanish"],
        "category": "Audio Annotation",
        "dataType": "Audio",
        "labelTypes": ["Transcription"],
        "labelingSoftware": null,
        "experienceLevel": "Intermediate",
        "skills": ["Transcription"],
        "pay": {
          "paymentType": "PAY_PER_HOUR",
          "currency": "USD",
          "hourlyRate": 12,
          "hourlyMin": null,
          "hourlyMax": null,
          "fixedPrice": null,
          "perLabelRate": null
        },
        "url": "https://app.opentrain.ai/jobs/spanish-audio-annotation-<JOB_ID>",
        "applyUrl": "https://app.opentrain.ai/api/out?job=<JOB_ID>&src=www_seo"
      }
    ],
    "nextCursor": null,
    "generatedAt": "2026-06-12T08:00:00.000Z"
  }
  ```
</ResponseExample>
