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

# Match news

> Score the corpus against a text or vector query, or omit the query to page the corpus chronologically.



## OpenAPI

````yaml api-reference/openapi.json POST /v1/match
openapi: 3.0.3
info:
  title: Clair data API
  version: 1.0.0
  description: >-
    Semantic match over a maintained news corpus. Describe what you care about
    (as text, or as a vector you embedded yourself) and get the news that means
    it, not the news that contains the keyword. Omit the query entirely to page
    the corpus chronologically.
servers:
  - url: https://clair-news-api.p.rapidapi.com
    description: RapidAPI gateway (use the exact host shown on the Clair RapidAPI page)
security:
  - rapidApiKey: []
paths:
  /v1/match:
    post:
      summary: Match news semantically (or page the corpus)
      description: >-
        The single read endpoint. The query is optional and takes one of three
        shapes:


        - **`text`**: plain language. We embed it with bge-m3 server-side, so
        curl is enough.

        - **`vector`**: a 1024-dim bge-m3 vector you embedded yourself. Same
        open model, same math, reproducible without trusting us.

        - **neither**: no query; the corpus itself, newest-first,
        keyset-paginated. This is the bulk/archive read.


        `order` defaults to `time` in every mode: this is a news API, and the
        latest matching news is almost always the question. Ask for
        `order=relevance` explicitly when you want the best matches regardless
        of when they ran.


        **On completeness.** `order=relevance` is a top-`limit` snapshot by
        definition. `order=time` returns *everything* at or above `min_score`,
        newest-first, and paginates to the end, complete to within ~99% recall
        (measured 99.6–100% against an exhaustive scan of the corpus). If a
        query matches more than one pass can collect you get `too_many_matches`,
        never a quietly incomplete page.


        **`min_score` matters most** with `order=time`: it defines the result
        set rather than filtering a ranked one, and the scale is steep (roughly
        10× the result count per 0.1). Default 0.5; the Introduction has a
        calibration table.
      operationId: match
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MatchRequest'
            examples:
              text:
                summary: 'Text query: the latest matching news (order defaults to time)'
                value:
                  text: >-
                    AI: foundation models, GPUs, inference, chips and
                    datacenters.
                  min_score: 0.5
                  limit: 20
              relevance:
                summary: Best matches regardless of when they ran (opt in explicitly)
                value:
                  text: >-
                    AI: foundation models, GPUs, inference, chips and
                    datacenters.
                  order: relevance
                  min_score: 0.5
                  limit: 20
              poll:
                summary: 'Monitoring poll: everything matching since your last check'
                value:
                  text: AI chips and datacenters
                  order: time
                  since: '2026-07-16T00:00:00Z'
                  min_score: 0.5
                  limit: 100
              latest:
                summary: 'No query: the corpus, newest first'
                value:
                  limit: 100
      responses:
        '200':
          description: A page of articles.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MatchResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          description: >-
            Text mode is unavailable: the embedding service is not configured or
            is unreachable. Retry, or send a precomputed `vector` instead.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                unavailable:
                  value:
                    error: embed_unavailable
components:
  schemas:
    MatchRequest:
      type: object
      description: >-
        Send at most one of `vector` or `text`. Sending neither is valid and
        means "no query": the corpus, newest-first.
      properties:
        model:
          type: string
          default: bge-m3
          description: >-
            Embedding-space id for `vector` mode. Only "bge-m3" at launch.
            Ignored for `text` mode (we always embed into bge-m3).
        vector:
          type: array
          items:
            type: number
          minItems: 1024
          maxItems: 1024
          description: >-
            The entity's 1024-dim, L2-normalized bge-m3 vector. Omit when using
            `text` or when you want no query.
        text:
          type: string
          minLength: 1
          maxLength: 4000
          description: >-
            A plain-language entity/query (e.g. a name plus a sentence). We
            embed it with bge-m3 server-side, so no local model is needed. Omit
            when using `vector` or when you want no query.
        order:
          type: string
          enum:
            - relevance
            - time
          description: >-
            Newest-first (`time`, default) or best-first (`relevance`).
            `relevance` needs a query and has no cursor.
          default: time
        min_score:
          type: number
          minimum: -1
          maximum: 1
          default: 0.5
          description: >-
            Cosine floor; articles below this are dropped. Ignored when there is
            no query.
        since:
          type: string
          format: date-time
          description: Inclusive ISO-8601 lower bound on event_time.
        until:
          type: string
          format: date-time
          description: Inclusive ISO-8601 upper bound on event_time.
        limit:
          type: integer
          default: 50
          minimum: 1
          maximum: 100
          description: >-
            Page size. The same cap applies to every mode, so an article costs
            the same however you asked for it.
        cursor:
          type: string
          description: >-
            Opaque keyset cursor from a previous page's `next_cursor`. Only
            valid with `order=time`.
        include_edits:
          type: boolean
          default: false
          description: >-
            Publishers edit articles in place (same URL, new headline/body).
            Every revision is stored, but by default a result is one row per
            article: the FIRST-published version, with its original timestamp.
            Set true to return the full edit history (e.g. to study how coverage
            of an event evolved). Default false, so results are deduplicated.
        sources:
          type: array
          items:
            type: string
          minItems: 1
          maxItems: 50
          description: >-
            Restrict results to these sources, given by the exact `name` strings
            GET /v1/sources returns. Mutually exclusive with `exclude_sources`.
            An unknown name is rejected with `invalid_request` (never a silent
            empty page). Note: a very selective include over a broad query can
            under-fill a page (matches become sparse in relevance order), so
            widen the source set or the query if so.
        exclude_sources:
          type: array
          items:
            type: string
          minItems: 1
          maxItems: 50
          description: >-
            Drop these sources from results, given by the exact `name` strings
            GET /v1/sources returns. Mutually exclusive with `sources`. An
            unknown name is rejected with `invalid_request`.
    MatchResponse:
      type: object
      properties:
        model:
          type: string
          example: bge-m3
        order:
          type: string
          example: relevance
        count:
          type: integer
          example: 20
        items:
          type: array
          items:
            $ref: '#/components/schemas/MatchItem'
        next_cursor:
          type: string
          nullable: true
          description: Present only with order=time; null when there are no more pages.
    Error:
      type: object
      properties:
        error:
          type: string
          description: Stable machine-readable code. Branch on this, not on `message`.
          example: too_many_matches
        message:
          type: string
          description: Human-readable detail. May change; do not parse.
        matched_at_least:
          type: integer
          description: >-
            Only on `too_many_matches`: how many matching articles were found
            before giving up. A floor, not a total.
    MatchItem:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
          example: Trader hunt for AI winners leads to a chipmaker in Japan
        body:
          type: string
          description: Short source snippet from the feed (~100-300 chars).
          example: Investors chasing AI exposure…
        source_name:
          type: string
          example: Bloomberg - Markets
        source_url:
          type: string
          format: uri
        event_time:
          type: string
          format: date-time
          description: >-
            When the article was published, as strict ISO-8601 UTC. Safe to pass
            straight back as `since`; that's the monitoring poll loop.
          example: '2026-07-17T12:34:02Z'
        score:
          type: number
          nullable: true
          description: Max chunk cosine to the query. null when there was no query.
          example: 0.71
        best_chunk:
          type: string
          nullable: true
          description: >-
            The sentence chunk that produced the score, the explanation for the
            match. null when there was no query.
  responses:
    BadRequest:
      description: Malformed cursor.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unprocessable:
      description: The request is well-formed but cannot be served as asked.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            invalid_request:
              summary: A field failed validation
              value:
                error: invalid_request
                message: 'limit: Too big: expected number to be <=100'
            order_requires_query:
              summary: Nothing to rank without a query
              value:
                error: order_requires_query
                message: '`order=relevance` needs `vector` or `text`'
            too_many_matches:
              summary: Threshold set too large to collect; raise min_score
              value:
                error: too_many_matches
                message: >-
                  `order=time` returns every article at or above `min_score`,
                  and this query matches too many to collect. Raise `min_score`,
                  or narrow `since`/`until`.
                matched_at_least: 1873
    RateLimited:
      description: Plan rate limit exceeded.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    rapidApiKey:
      type: apiKey
      in: header
      name: X-RapidAPI-Key
      description: >-
        Your RapidAPI key. Also send X-RapidAPI-Host with the value shown on the
        Clair RapidAPI page.

````