> ## 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.

# Search jobs and internships

> Cursor-paginated search over active indexed postings, with structured filters, provenance, and explicit freshness metadata.



## OpenAPI

````yaml jobs-api/openapi.json GET /v1/jobs
openapi: 3.0.3
info:
  title: Clair Job Search API
  version: 1.0.0
  description: >-
    Search active jobs and internships from a continuously crawled index. Clair
    normalizes postings across applicant tracking systems into one schema, then
    derives searchable fields such as seniority, skills, country, and
    compensation provenance at ingest. Public requests query Clair's database
    only; they never fetch a caller-supplied URL or contact an applicant
    tracking system.
servers:
  - url: https://clair-job-search-api.p.rapidapi.com
    description: >-
      RapidAPI gateway. Use the exact host shown on the Clair Job Search API
      listing.
security:
  - rapidApiKey: []
paths:
  /v1/jobs:
    get:
      summary: Search indexed jobs and internships
      description: >-
        Returns active postings from Clair's normalized jobs index. Filters are
        evaluated against data already stored and analyzed at ingest, so request
        latency does not depend on an employer site or ATS being available.


        Results are deduplicated with provider-aware source identity. Workday
        sibling sites use tenant and external requisition ID; reviewed Recruitee
        aliases and exact-link SmartRecruiters copies use stricter
        provider-specific keys. Clair does not apply fuzzy title matching that
        could merge distinct openings. Pages use an opaque cursor bound to the
        original filters and sort order. By default, results are sorted by the
        publisher's posting time, falling back to Clair's first-seen time when a
        publisher supplied no date.


        Every returned posting has at least one validated HTTPS posting or
        application URL. An indexed record without either link is omitted until
        a later crawl repairs it; it is not falsely marked closed.


        Set `seniority=intern` to search internships, co-ops, apprenticeships,
        working-student roles, and placements recognized from posting titles.
      operationId: searchJobs
      parameters:
        - name: q
          in: query
          description: >-
            Case-insensitive substring query over the posting title. This
            filters matches; ordering remains controlled by `sort`.
          schema:
            type: string
            minLength: 1
            maxLength: 200
          example: data scientist
        - name: company
          in: query
          description: >-
            Case-insensitive substring match against the normalized company
            name.
          schema:
            type: string
            minLength: 1
            maxLength: 200
          example: stripe
        - name: location
          in: query
          description: >-
            Case-insensitive substring match against any location listed by the
            posting.
          schema:
            type: string
            minLength: 1
            maxLength: 200
          example: Berlin
        - name: country
          in: query
          description: >-
            ISO 3166-1 alpha-2 country code derived from the posting's location
            strings. Case-insensitive. A remote posting whose source names no
            country has a null country and does not match this filter.
          schema:
            type: string
            minLength: 2
            maxLength: 2
            pattern: ^[A-Za-z]{2}$
          example: DE
        - name: workplace_type
          in: query
          description: Normalized workplace arrangement.
          schema:
            type: string
            enum:
              - remote
              - hybrid
              - onsite
          example: remote
        - name: seniority
          in: query
          description: >-
            Seniority derived from the title. Use `intern` for internships,
            co-ops, apprenticeships, working-student roles, and placements.
          schema:
            type: string
            enum:
              - intern
              - entry
              - mid
              - senior
              - staff
              - principal
              - executive
          example: intern
        - name: track
          in: query
          description: Career track derived from the title.
          schema:
            type: string
            enum:
              - ic
              - management
              - executive
        - name: department
          in: query
          description: Case-insensitive substring match against department or team.
          schema:
            type: string
            minLength: 1
            maxLength: 200
          example: engineering
        - name: skills_all
          in: query
          description: >-
            Comma-separated canonical skills, at most 25 entries and 1,000
            characters. Every named skill must be present. Names are
            case-insensitive; an unsupported name returns `unknown_skill`.
          schema:
            type: string
            minLength: 1
            maxLength: 1000
          example: python,pytorch
        - name: skills_any
          in: query
          description: >-
            Comma-separated canonical skills, at most 25 entries and 1,000
            characters. At least one named skill must be present. Names are
            case-insensitive; an unsupported name returns `unknown_skill`.
          schema:
            type: string
            minLength: 1
            maxLength: 1000
          example: typescript,rust
        - name: salary_min
          in: query
          description: >-
            Minimum requested compensation. A posting matches when its stated
            maximum is at least this value. `currency` is required.
            `salary_interval` defaults to `year` when a bound is present.
          schema:
            type: number
            exclusiveMinimum: true
            minimum: 0
          example: 150000
        - name: salary_max
          in: query
          description: >-
            Maximum requested compensation. A posting matches when its stated
            minimum is at most this value. `currency` is required. If both
            bounds are sent, `salary_min` must not exceed `salary_max`.
          schema:
            type: number
            exclusiveMinimum: true
            minimum: 0
        - name: currency
          in: query
          description: >-
            Three-character compensation currency, normalized to uppercase.
            Required with `salary_min` or `salary_max`; sent alone, it filters
            postings by currency.
          schema:
            type: string
            minLength: 3
            maxLength: 3
            pattern: ^[A-Za-z]{3}$
          example: USD
        - name: salary_interval
          in: query
          description: >-
            Compensation interval. Defaults to `year` when a salary bound is
            present.
          schema:
            type: string
            enum:
              - year
              - hour
              - month
              - week
              - day
          example: year
        - name: ats
          in: query
          description: >-
            Advanced provenance filter: only postings from this applicant
            tracking system.
          schema:
            $ref: '#/components/schemas/AtsProvider'
        - name: board
          in: query
          description: >-
            Advanced provenance filter: exact board identifier within an ATS.
            Pair it with `ats` when a board name could be ambiguous.
          schema:
            type: string
            minLength: 1
            maxLength: 300
        - name: posted_after
          in: query
          description: >-
            Return postings whose publisher-supplied posting time is at or after
            this ISO 8601 instant. Postings without a publisher date do not
            match.
          schema:
            type: string
            format: date-time
          example: '2026-07-01T00:00:00Z'
        - name: first_seen_after
          in: query
          description: >-
            Return postings Clair first discovered at or after this ISO 8601
            instant. Prefer this over `posted_after` for alerts and incremental
            discovery.
          schema:
            type: string
            format: date-time
          example: '2026-07-28T00:00:00Z'
        - name: sort
          in: query
          description: >-
            Newest-first ordering. `posted_at` uses the publisher time and falls
            back to `first_seen_at` when missing.
          schema:
            type: string
            enum:
              - posted_at
              - first_seen_at
            default: posted_at
        - name: include_description
          in: query
          description: >-
            When true, return a plain-text `description_excerpt` capped at 2,000
            characters. This does not return an archival or full description.
          schema:
            $ref: '#/components/schemas/BooleanQuery'
          example: 'true'
        - name: include_total
          in: query
          description: >-
            When true, run an additional exact distinct-count query and return
            `meta.total`. Off by default to keep broad searches bounded.
          schema:
            $ref: '#/components/schemas/BooleanQuery'
          example: 'true'
        - name: limit
          in: query
          description: Results per page.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: cursor
          in: query
          description: >-
            Opaque `meta.next_cursor` from the previous page. A cursor is bound
            to the original filters and sort; changing either returns
            `bad_cursor`.
          schema:
            type: string
            minLength: 1
            maxLength: 1024
      responses:
        '200':
          description: A cursor-paginated page of active, deduplicated postings.
          headers:
            X-Request-ID:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchResponse'
        '400':
          description: >-
            `bad_request` for invalid parameters, `bad_cursor` for a malformed
            or mismatched cursor, or `unknown_skill` for a skill outside the
            supported vocabulary.
          headers:
            X-Request-ID:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid RapidAPI authorization.
          headers:
            X-Request-ID:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: >-
            RapidAPI plan rate limit exceeded. Rate limiting occurs at the
            gateway before the request reaches Clair.
        '500':
          description: Unexpected internal error.
          headers:
            X-Request-ID:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: >-
            `dependency_unavailable`, `product_unavailable`, `overloaded`, or
            `index_changed`. Retry with backoff and honor `Retry-After`.
          headers:
            X-Request-ID:
              $ref: '#/components/headers/RequestId'
            Retry-After:
              description: Suggested delay in seconds before retrying.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '504':
          description: '`timeout` — the search exceeded its processing deadline.'
          headers:
            X-Request-ID:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    AtsProvider:
      type: string
      enum:
        - greenhouse
        - lever
        - ashby
        - smartrecruiters
        - workday
        - recruitee
    BooleanQuery:
      type: string
      enum:
        - 'true'
        - '1'
        - 'yes'
        - 'false'
        - '0'
        - 'no'
      default: 'false'
    SearchResponse:
      type: object
      required:
        - jobs
        - meta
      properties:
        jobs:
          type: array
          items:
            $ref: '#/components/schemas/Job'
        meta:
          $ref: '#/components/schemas/SearchMeta'
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: Stable machine-readable code. Branch on this value.
          example: bad_cursor
        detail:
          description: >-
            Human-readable detail. Validation errors return an array; typed
            failures return a string. Do not parse this field.
          oneOf:
            - type: string
            - type: array
              items:
                type: string
    Job:
      type: object
      anyOf:
        - required:
            - posting_url
          properties:
            posting_url:
              type: string
              format: uri
              pattern: ^https://
        - required:
            - apply_url
          properties:
            apply_url:
              type: string
              format: uri
              pattern: ^https://
      required:
        - id
        - title
        - company
        - department
        - team
        - location
        - locations
        - country
        - workplace_type
        - employment_type
        - posting_url
        - apply_url
        - posted_at
        - updated_at
        - compensation
        - seniority
        - track
        - title_family
        - skills
        - description_available
        - description_excerpt
        - first_seen_at
        - last_verified_at
        - source
      properties:
        id:
          type: string
          format: uuid
          description: Stable Clair identifier for the indexed posting.
          example: fd6b984d-f7f4-4ca8-b68e-34b88a4ec299
        title:
          type: string
          example: Machine Learning Intern
        company:
          type: string
          nullable: true
          description: >-
            Normalized company name when published by the posting or present in
            Clair's curated board registry. Null when neither source provides a
            trustworthy name; board identifiers are never substituted as company
            names.
          example: Example Labs
        department:
          type: string
          nullable: true
          example: Engineering
        team:
          type: string
          nullable: true
          example: Applied AI
        location:
          type: string
          nullable: true
          description: Primary location as published by the source.
          example: Berlin, Germany
        locations:
          type: array
          description: All source locations, primary first.
          items:
            type: string
        country:
          type: string
          nullable: true
          description: >-
            ISO 3166-1 alpha-2 country derived from source locations. Null when
            the posting does not identify a country.
          example: DE
        workplace_type:
          type: string
          nullable: true
          enum:
            - remote
            - hybrid
            - onsite
            - null
        employment_type:
          type: string
          nullable: true
          description: >-
            Employment type as normalized by the source adapter. Vocabulary can
            vary when the source publishes a free-form value.
          example: Intern
        posting_url:
          type: string
          format: uri
          pattern: ^https://
          nullable: true
          description: >-
            Validated HTTPS public posting URL. Nullable when the source exposes
            only an application URL; at least one of `posting_url` or
            `apply_url` is non-null.
        apply_url:
          type: string
          format: uri
          pattern: ^https://
          nullable: true
          description: >-
            Validated HTTPS application URL. Nullable when the source exposes
            only a posting URL; at least one of `posting_url` or `apply_url` is
            non-null.
        posted_at:
          type: string
          format: date-time
          nullable: true
          description: Publisher-supplied posting time.
        updated_at:
          type: string
          format: date-time
          nullable: true
          description: Publisher-supplied update time.
        compensation:
          allOf:
            - $ref: '#/components/schemas/Compensation'
          nullable: true
        seniority:
          type: string
          nullable: true
          enum:
            - intern
            - entry
            - mid
            - senior
            - staff
            - principal
            - executive
            - null
          description: Seniority derived from the title.
        track:
          type: string
          nullable: true
          enum:
            - ic
            - management
            - executive
            - null
          description: Career track derived from the title.
        title_family:
          type: string
          nullable: true
          description: Normalized title family for cross-company comparison.
          example: machine-learning engineer
        skills:
          type: array
          description: >-
            Canonical technology names detected in the stored posting
            description.
          items:
            type: string
          example:
            - Python
            - PyTorch
        description_excerpt:
          type: string
          nullable: true
          maxLength: 2000
          description: >-
            Plain-text excerpt returned only when `include_description=true`;
            otherwise null.
        description_available:
          type: boolean
          description: >-
            True when Clair has stored normalized description text for this
            posting. This remains meaningful when `include_description=false`
            and `description_excerpt` is null.
        first_seen_at:
          type: string
          format: date-time
          description: When Clair first discovered this posting.
        last_verified_at:
          type: string
          format: date-time
          description: Most recent successful crawl that still contained this posting.
        source:
          $ref: '#/components/schemas/Source'
    SearchMeta:
      type: object
      required:
        - request_id
        - generated_at
        - count
        - limit
        - sort
        - has_more
        - next_cursor
        - total
        - total_is_exact
        - index
      properties:
        request_id:
          type: string
          format: uuid
          description: Also returned in the `X-Request-ID` response header.
        generated_at:
          type: string
          format: date-time
          description: When Clair generated this response.
        count:
          type: integer
          description: Jobs in this page.
          example: 25
        limit:
          type: integer
          example: 25
        sort:
          type: string
          enum:
            - posted_at
            - first_seen_at
        has_more:
          type: boolean
        next_cursor:
          type: string
          nullable: true
          description: >-
            Pass unchanged as `cursor` for the next page. Null when the result
            set is exhausted.
        total:
          type: integer
          nullable: true
          description: >-
            Exact distinct requisition count when `include_total=true`;
            otherwise null.
        total_is_exact:
          type: boolean
          description: True only when the optional exact count was requested and completed.
        index:
          $ref: '#/components/schemas/IndexFreshness'
    Compensation:
      type: object
      required:
        - min
        - max
        - currency
        - interval
        - summary
        - source
      properties:
        min:
          type: number
          nullable: true
          example: 25
        max:
          type: number
          nullable: true
          example: 35
        currency:
          type: string
          nullable: true
          example: EUR
        interval:
          type: string
          nullable: true
          enum:
            - year
            - hour
            - month
            - week
            - day
            - null
          example: hour
        summary:
          type: string
          nullable: true
          description: Publisher wording retained when available.
        source:
          type: string
          nullable: true
          enum:
            - ats
            - description
            - null
          description: >-
            `ats` means a structured source field; `description` means Clair
            extracted a stated figure from employer-written posting text.
    Source:
      type: object
      required:
        - provider
        - board
        - external_id
      properties:
        provider:
          $ref: '#/components/schemas/AtsProvider'
        board:
          type: string
          description: Board identifier within the provider.
          example: examplelabs
        external_id:
          type: string
          description: Posting identifier assigned by the source.
          example: '4512338005'
    IndexFreshness:
      type: object
      required:
        - status
        - as_of
        - active_boards
        - stale_boards
      properties:
        status:
          type: string
          enum:
            - healthy
            - degraded
            - stale
            - unavailable
          description: >-
            `healthy` means no enabled board is stale; `degraded` means at most
            5% are stale; `stale` means more than 5% are stale; `unavailable`
            means freshness could not be established.
        as_of:
          type: string
          format: date-time
          nullable: true
          description: >-
            Most recent successful board crawl recorded in the index. This is
            not a claim that every board was crawled at this instant.
        active_boards:
          type: integer
          description: Enabled boards included in the freshness calculation.
        stale_boards:
          type: integer
          description: >-
            Enabled boards that have missed two of their own configured crawl
            intervals, have never completed, or most recently failed.
  headers:
    RequestId:
      description: Request identifier to include in support reports.
      schema:
        type: string
        format: uuid
  securitySchemes:
    rapidApiKey:
      type: apiKey
      in: header
      name: X-RapidAPI-Key
      description: >-
        Your RapidAPI subscription key. Also send the `X-RapidAPI-Host` shown on
        the listing.

````