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

# Search for a product and where to buy it nearby

> Two-stage product search: BM25 match against the product catalog (fuzzy, synonym-aware), then a geo-filtered lookup of physical store availability. Local results always precede online; online offers appear only when local results are empty or `include_online` is set. Pass `retailer_id` or `store_id` to scope the same pipeline to one retailer or one store — a store-scoped query skips distance sorting and just answers whether that store carries the product.

**Auth:** unauthenticated (anonymous IP-throttled at 60 requests/minute).



## OpenAPI

````yaml /openapi/openapi-search.json get /search/api/v1/search/products
openapi: 3.1.0
info:
  title: Search API
  version: 1.0.0
  description: >
    Catalog search with session-based constraint refinement.


    ## Authentication


    All endpoints require an API key passed via the `X-API-Key` HTTP header,
    unless otherwise noted.


    ```

    X-API-Key: csb_<body>_<checksum>

    ```


    Generate API keys in **Settings > API Keys** in your dashboard, or via the
    Account API.

    Session-based (cookie) authentication is also accepted for browser-based
    access.


    ## Rate Limits


    | Tier        | Requests / minute | Requests / day |

    |-------------|-------------------|----------------|

    | Default     | 300               | 10,000         |

    | Custom      | Contact us        | Contact us     |


    Those windows are per key. Your plan also caps each organization per hour,
    across every key,

    login and session acting for it: 100 requests on Free (or while a
    subscription is lapsed),

    10,000 on Business. While a subscription is lapsed, any method other than
    `GET`, `HEAD` and

    `OPTIONS` returns `403`.


    Rate-limit headers are included on every response so callers can
    self-throttle without

    hitting our 429s ("informed governor"):


    - `RateLimit-Policy` — every active window, e.g. `300;w=60, 10000;w=86400`

    - `RateLimit-Limit` — quota for the **most-restrictive** currently-active
    window

    - `RateLimit-Remaining` — requests left in that window

    - `RateLimit-Reset` — seconds until that window resets (relative; clock-skew
    safe)


    Legacy `X-RateLimit-*` aliases are also emitted for back-compat.
    `X-RateLimit-Reset`

    keeps the absolute Unix-timestamp shape to avoid breaking existing
    consumers.


    When rate-limited, you receive `429 Too Many Requests` with a
    `retry_after_seconds` field

    in the error envelope and a `Retry-After` header.


    ## Pagination


    List endpoints return paginated results in this envelope:


    ```json

    {
      "data": [...],
      "pagination": {
        "page": 1,
        "page_size": 25,
        "total_count": 342,
        "total_pages": 14,
        "has_next": true,
        "has_previous": false
      }
    }

    ```


    Use `?page=2&page_size=50` query parameters. Maximum page size is 100.


    ## Error Responses


    All errors conform to [RFC 9457 Problem
    Details](https://www.rfc-editor.org/rfc/rfc9457)

    with `Content-Type: application/problem+json`:


    ```json

    {
      "type": "https://closient.com/docs/errors/not_found",
      "title": "Not Found",
      "status": 404,
      "detail": "The requested resource was not found.",
      "error_code": "not_found",
      "retryable": false,
      "timestamp": "2026-03-31T12:00:00+00:00"
    }

    ```


    Common error codes: `unauthorized` (401), `forbidden` (403), `not_found`
    (404),

    `validation_error` (422), `rate_limited` (429), `internal_error` (500).
  termsOfService: https://www.closient.com/terms/
servers:
  - url: https://www.closient.com
security: []
tags:
  - name: Search
    description: Catalog search with session-based constraint refinement.
externalDocs:
  description: Closient Documentation
  url: https://docs.closient.com
paths:
  /search/api/v1/search/products:
    get:
      tags:
        - Search
      summary: Search for a product and where to buy it nearby
      description: >-
        Two-stage product search: BM25 match against the product catalog (fuzzy,
        synonym-aware), then a geo-filtered lookup of physical store
        availability. Local results always precede online; online offers appear
        only when local results are empty or `include_online` is set. Pass
        `retailer_id` or `store_id` to scope the same pipeline to one retailer
        or one store — a store-scoped query skips distance sorting and just
        answers whether that store carries the product.


        **Auth:** unauthenticated (anonymous IP-throttled at 60
        requests/minute).
      operationId: search_products
      parameters:
        - in: query
          name: q
          schema:
            description: Search query text.
            maxLength: 255
            minLength: 1
            title: Q
            type: string
            pattern: ^[^\x00]{1,255}$
          required: true
          description: Search query text.
        - in: query
          name: latitude
          schema:
            description: Latitude in decimal degrees (WGS84). Pair with `longitude`.
            title: Latitude
            maximum: 90
            minimum: -90
            type: number
          required: false
          description: Latitude in decimal degrees (WGS84). Pair with `longitude`.
        - in: query
          name: longitude
          schema:
            description: Longitude in decimal degrees (WGS84). Pair with `latitude`.
            title: Longitude
            maximum: 180
            minimum: -180
            type: number
          required: false
          description: Longitude in decimal degrees (WGS84). Pair with `latitude`.
        - in: query
          name: postal_code
          schema:
            description: >-
              Postal code to resolve to a location when latitude/longitude
              aren't available. Ignored when latitude/longitude are both given.
              Resolved against Closient's own store location data (no
              third-party geocoder); returns 422 if it cannot be resolved.
            title: Postal Code
            type: string
            pattern: ^[^\x00]{0,}$
          required: false
          description: >-
            Postal code to resolve to a location when latitude/longitude aren't
            available. Ignored when latitude/longitude are both given. Resolved
            against Closient's own store location data (no third-party
            geocoder); returns 422 if it cannot be resolved.
        - in: query
          name: radius_km
          schema:
            default: 25
            description: Search radius in kilometers from the resolved location.
            exclusiveMinimum: 0
            maximum: 100
            title: Radius Km
            type: number
          required: false
          description: Search radius in kilometers from the resolved location.
        - in: query
          name: retailer_id
          schema:
            description: >-
              Scope to one retailer's stores (a retailer/store landing page's
              own search box). Distinct from `retailer`, the multi-select facet
              filter — both apply together if both are set.
            title: Retailer Id
            type: string
            pattern: ^[^\x00]{0,}$
          required: false
          description: >-
            Scope to one retailer's stores (a retailer/store landing page's own
            search box). Distinct from `retailer`, the multi-select facet filter
            — both apply together if both are set.
        - in: query
          name: store_id
          schema:
            description: >-
              Scope to one physical store. Skips distance sorting — answers
              whether this store has it.
            title: Store Id
            type: string
            pattern: ^[^\x00]{0,}$
          required: false
          description: >-
            Scope to one physical store. Skips distance sorting — answers
            whether this store has it.
        - in: query
          name: include_online
          schema:
            default: false
            description: Include online offers even when local results are non-empty.
            title: Include Online
            type: boolean
          required: false
          description: Include online offers even when local results are non-empty.
        - in: query
          name: page
          schema:
            default: 1
            description: Page number of local results.
            maximum: 10000000
            minimum: 1
            title: Page
            type: integer
          required: false
          description: Page number of local results.
        - in: query
          name: page_size
          schema:
            default: 20
            description: Local results per page.
            maximum: 100
            minimum: 1
            title: Page Size
            type: integer
          required: false
          description: Local results per page.
        - in: query
          name: retailer
          schema:
            description: >-
              Facet: filter offers to one or more retailer ids. Repeat the
              parameter for multiple.
            title: Retailer
            items:
              type: string
              pattern: ^[^\x00]{0,}$
            type: array
          required: false
          description: >-
            Facet: filter offers to one or more retailer ids. Repeat the
            parameter for multiple.
        - in: query
          name: brand
          schema:
            description: >-
              Filter BM25 candidates to one or more brand names
              (case-insensitive). Repeat for multiple.
            title: Brand
            items:
              type: string
              pattern: ^[^\x00]{0,}$
            type: array
          required: false
          description: >-
            Filter BM25 candidates to one or more brand names
            (case-insensitive). Repeat for multiple.
        - in: query
          name: category
          schema:
            description: >-
              Filter BM25 candidates to one or more category paths
              (case-insensitive). Repeat for multiple.
            title: Category
            items:
              type: string
              pattern: ^[^\x00]{0,}$
            type: array
          required: false
          description: >-
            Filter BM25 candidates to one or more category paths
            (case-insensitive). Repeat for multiple.
        - in: query
          name: availability_confidence
          schema:
            description: >-
              Filter offers to one or more confidence levels. Repeat the
              parameter for multiple.
            items:
              description: >-
                How confident the search index is that an offer is in stock.


                Computed at index time from an offer's stock signals (source
                provenance,

                freshness, quantity) rather than stored on the offer itself.
                Echoed on

                each local and online offer row returned by the product search
                endpoint.
              enum:
                - confirmed
                - likely
                - uncertain
                - unknown
                - out_of_stock
              title: AvailabilityConfidenceEnum
              type: string
            title: Availability Confidence
            type: array
          required: false
          description: >-
            Filter offers to one or more confidence levels. Repeat the parameter
            for multiple.
        - in: query
          name: price_min
          schema:
            description: Minimum offer price.
            title: Price Min
            minimum: 0
            type: number
          required: false
          description: Minimum offer price.
        - in: query
          name: price_max
          schema:
            description: Maximum offer price.
            title: Price Max
            minimum: 0
            type: number
          required: false
          description: Maximum offer price.
        - in: query
          name: accuracy_m
          schema:
            description: >-
              Reported accuracy in meters of `latitude`/`longitude`, when the
              caller has one (e.g. the browser Geolocation API's own accuracy
              reading). Never affects search results — captured only for the
              demand-signal event, where it gates whether at_store_candidate is
              computed.
            title: Accuracy M
            minimum: 0
            type: number
          required: false
          description: >-
            Reported accuracy in meters of `latitude`/`longitude`, when the
            caller has one (e.g. the browser Geolocation API's own accuracy
            reading). Never affects search results — captured only for the
            demand-signal event, where it gates whether at_store_candidate is
            computed.
        - in: query
          name: source_surface
          schema:
            allOf:
              - description: >-
                  Which UI surface issued a ``GET /search/products`` request.


                  Used only for the asynchronous demand-signal capture (it never
                  affects the

                  response), so a caller who omits it defaults to ``api``. The
                  website's own

                  results page does not use this parameter at all.
                enum:
                  - navbar
                  - scan_page
                  - watching_notice
                  - esl
                  - mcp
                  - api
                title: SourceSurfaceEnum
                type: string
            default: api
            description: >-
              Which UI surface issued this request. Never affects search results
              — captured only for the demand-signal event.
          required: false
          description: >-
            Which UI surface issued this request. Never affects search results —
            captured only for the demand-signal event.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductSearchOut'
        '400':
          description: Bad Request
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
        '404':
          description: Not Found
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
        '405':
          description: Method Not Allowed
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
        '422':
          description: Unprocessable Content
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
        '429':
          description: Too Many Requests
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
components:
  schemas:
    ProductSearchOut:
      description: Response envelope for ``GET /search/products``.
      examples:
        - disambiguation: []
          facets:
            availability_confidence:
              - count: 3
                value: confirmed
            brand:
              - count: 3
                value: Acme Beauty
            category:
              - count: 3
                value: Beauty > Hair Care > Shampoo
            distance_bands:
              - count: 2
                max_km: 5
                min_km: 0
            price_range:
              max_price: 14.49
              min_price: 6.99
            retailer:
              - count: 4
                value: retailerXyZ789
          is_automatic_winner: true
          local:
            - availability_confidence: confirmed
              currency: USD
              distance_km: 2.4
              gln: '0614141000012'
              price: 9.99
              retailer_id: retailerXyZ789
              retailer_name: Acme Beauty
              store_id: storeAbC123
              store_name: Acme Beauty — Downtown
          local_total_count: 1
          online: []
          page: 1
          page_size: 20
          query: acme shampoo
          winner:
            brand: Acme Beauty
            category_path: Beauty > Hair Care > Shampoo
            gtin: '00012345678905'
            is_default_variant: true
            product_name: Acme Shampoo
            score: 12.4381
            variant_group: fam_9f2c1a
            variant_name: 12oz
      properties:
        query:
          description: The search query that was executed.
          title: Query
          type: string
        winner:
          anyOf:
            - $ref: '#/components/schemas/ProductSummaryOut'
            - type: 'null'
          description: >-
            The best-matching product. Null only when the query matched nothing
            at all.
        is_automatic_winner:
          description: >-
            True when the top BM25 score beat the next distinct product by at
            least the tunable ratio (1.5x) and the winner can be presented
            without disambiguation.
          title: Is Automatic Winner
          type: boolean
        disambiguation:
          description: >-
            Other distinct near-scoring products, for disambiguation chips.
            Always empty when is_automatic_winner is true.
          items:
            $ref: '#/components/schemas/ProductSummaryOut'
          title: Disambiguation
          type: array
        local:
          description: >-
            The winner's physical stores, sorted by distance (or unsorted if no
            location was given).
          items:
            $ref: '#/components/schemas/LocalOfferOut'
          title: Local
          type: array
        local_total_count:
          description: Total matching physical offers, before pagination.
          minimum: 0
          title: Local Total Count
          type: integer
        online:
          description: >-
            The winner's online offers. Populated only when local is empty or
            include_online is set.
          items:
            $ref: '#/components/schemas/OnlineOfferOut'
          title: Online
          type: array
        online_shown_because:
          anyOf:
            - $ref: '#/components/schemas/OnlineShownReasonEnum'
            - type: 'null'
          description: Why the online section is populated. Null when online is empty.
        facets:
          allOf:
            - $ref: '#/components/schemas/FacetsOut'
          description: Facet aggregations over the candidate pool.
        page:
          description: Page number of local results returned.
          minimum: 1
          title: Page
          type: integer
        page_size:
          description: Page size applied to local results.
          minimum: 1
          title: Page Size
          type: integer
        search_request_id:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Correlation token for this search — pass it to POST
            /search/api/v1/search/click when the caller reports a post-search
            interaction (a result was clicked, the radius was expanded, etc.),
            so the click can be attributed back to this search for
            CTR-by-position measurement. Null only if demand-signal capture
            itself failed to dispatch (never surfaces as a request error); a
            caller that gets null has nothing meaningful to click-report
            against.
          title: Search Request Id
      required:
        - query
        - is_automatic_winner
        - local_total_count
        - page
        - page_size
      title: ProductSearchOut
      type: object
    ErrorOut:
      description: |-
        RFC 9457 Problem Details response.

        All API errors are returned in this format with Content-Type:
        application/problem+json.
      examples:
        - detail: The requested resource was not found.
          error_code: not_found
          retryable: false
          status: 404
          timestamp: '2026-03-31T12:00:00+00:00'
          title: Not Found
          type: https://closient.com/docs/errors/not_found
        - detail: Validation error.
          details:
            - loc:
                - body
                - name
              msg: Field required
              type: missing
          error_code: validation_error
          retryable: false
          status: 422
          timestamp: '2026-03-31T12:00:00+00:00'
          title: Validation Error
          type: https://closient.com/docs/errors/validation_error
        - detail: Rate limit exceeded. Please try again later.
          error_code: rate_limited
          retry_after: 31
          retryable: true
          status: 429
          timestamp: '2026-03-31T12:00:00+00:00'
          title: Rate Limited
          type: https://closient.com/docs/errors/rate_limited
      properties:
        type:
          description: URI reference identifying the error type.
          title: Type
          type: string
        title:
          description: Short human-readable summary of the error.
          title: Title
          type: string
        status:
          description: HTTP status code.
          title: Status
          type: integer
        detail:
          description: Human-readable explanation of this specific occurrence.
          title: Detail
          type: string
        error_code:
          description: Machine-readable error code (e.g. not_found, unauthorized).
          title: Error Code
          type: string
        retryable:
          default: false
          description: Whether retrying the same request can succeed.
          title: Retryable
          type: boolean
        timestamp:
          description: ISO 8601 timestamp of when the error occurred.
          title: Timestamp
          type: string
        retry_after:
          anyOf:
            - type: integer
            - type: 'null'
          description: Seconds to wait before retrying (when applicable).
          title: Retry After
        owner_action_required:
          anyOf:
            - type: boolean
            - type: 'null'
          description: Whether the error requires account owner intervention.
          title: Owner Action Required
        details:
          description: Additional context (validation errors, etc.).
          title: Details
      required:
        - type
        - title
        - status
        - detail
        - error_code
        - timestamp
      title: ErrorOut
      type: object
    ProductSummaryOut:
      description: >-
        The product a candidate GTIN represents, as matched against the BM25
        index.
      examples:
        - brand: Acme Beauty
          category_path: Beauty > Hair Care > Shampoo
          gtin: '00012345678905'
          is_default_variant: true
          product_name: Acme Shampoo
          score: 12.4381
          variant_group: fam_9f2c1a
          variant_name: 12oz
      properties:
        gtin:
          description: GTIN-14 identifier of the matched product.
          title: Gtin
          type: string
        product_name:
          description: Product name as indexed.
          title: Product Name
          type: string
        brand:
          default: ''
          description: Brand name, empty string when the product has no brand on file.
          title: Brand
          type: string
        variant_name:
          default: ''
          description: >-
            Variant label (e.g. a shade or pack size), empty for an ungrouped
            product.
          title: Variant Name
          type: string
        category_path:
          default: ''
          description: Breadcrumb category path, empty when uncategorized.
          title: Category Path
          type: string
        variant_group:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Product family id this variant belongs to, null when the product is
            ungrouped.
          title: Variant Group
        is_default_variant:
          description: >-
            True when this is the representative variant of its family (always
            true when ungrouped).
          title: Is Default Variant
          type: boolean
        score:
          description: BM25 relevance score. Comparable only within this response.
          minimum: 0
          title: Score
          type: number
      required:
        - gtin
        - product_name
        - is_default_variant
        - score
      title: ProductSummaryOut
      type: object
    LocalOfferOut:
      description: >-
        One physical store carrying the winning product's variant family,
        collapsed to one row.


        Top-level fields describe the *default variant*'s own offer at this
        store

        (or the closest/first offer there, when the default variant itself isn't

        carried at this particular store) — this is what the collapsed row shows

        before the user expands it. ``variants`` carries every variant GTIN this

        store offers within the same family, including the default, for the

        expand affordance. An ungrouped product (no ``variant_group``) still

        populates ``variants`` with its single offer.
      examples:
        - availability_confidence: confirmed
          currency: USD
          distance_km: 2.4
          gln: '0614141000012'
          gtin: '00012345678905'
          price: 9.99
          retailer_id: retailerXyZ789
          retailer_name: Acme Beauty
          store_id: storeAbC123
          store_name: Acme Beauty — Downtown
          variant_group: fam_9f2c1a
          variants:
            - availability_confidence: confirmed
              currency: USD
              gtin: '00012345678905'
              is_default_variant: true
              price: 9.99
      properties:
        store_id:
          description: Physical store identifier.
          title: Store Id
          type: string
        store_name:
          default: ''
          description: Store display name.
          title: Store Name
          type: string
        gln:
          anyOf:
            - type: string
            - type: 'null'
          description: GS1 Global Location Number of the store, when known.
          title: Gln
        retailer_id:
          description: Retailer identifier operating this store.
          title: Retailer Id
          type: string
        retailer_name:
          default: ''
          description: Retailer display name.
          title: Retailer Name
          type: string
        distance_km:
          anyOf:
            - type: number
            - type: 'null'
          description: >-
            Distance from the request's resolved location, in kilometers. Null
            when no location was provided or the query was store-scoped
            (distance sort is skipped in both cases).
          title: Distance Km
        price:
          anyOf:
            - type: number
            - type: 'null'
          description: Default variant's offer price, null when unknown.
          title: Price
        currency:
          anyOf:
            - type: string
            - type: 'null'
          description: ISO 4217 currency code, null when price is null.
          title: Currency
        availability_confidence:
          $ref: '#/components/schemas/AvailabilityConfidenceEnum'
          description: >-
            Confidence the store actually has the default variant, computed from
            the offer's freshness.
        gtin:
          description: >-
            GTIN-14 of the default (collapsed-row) variant carried at this
            store.
          title: Gtin
          type: string
        variant_group:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Product family id this row collapses, null when the winner is an
            ungrouped product.
          title: Variant Group
        variants:
          description: >-
            Every variant GTIN this store carries within the family, including
            the default variant.
          items:
            $ref: '#/components/schemas/LocalOfferVariantOut'
          title: Variants
          type: array
      required:
        - store_id
        - retailer_id
        - availability_confidence
        - gtin
      title: LocalOfferOut
      type: object
    OnlineOfferOut:
      description: One online offer for the winning product.
      examples:
        - availability_confidence: likely
          country: US
          currency: USD
          price: 11.49
          retailer_id: retailerXyZ789
          retailer_name: Acme Beauty
          url: https://acmebeauty.example/products/acme-shampoo-12oz
      properties:
        retailer_id:
          description: Retailer identifier operating this storefront.
          title: Retailer Id
          type: string
        retailer_name:
          default: ''
          description: Retailer display name.
          title: Retailer Name
          type: string
        country:
          default: ''
          description: ISO 3166-1 alpha-2 country this offer ships to.
          title: Country
          type: string
        price:
          anyOf:
            - type: number
            - type: 'null'
          description: Offer price, null when unknown.
          title: Price
        currency:
          anyOf:
            - type: string
            - type: 'null'
          description: ISO 4217 currency code, null when price is null.
          title: Currency
        availability_confidence:
          $ref: '#/components/schemas/AvailabilityConfidenceEnum'
          description: >-
            Confidence the retailer actually has stock, computed from the
            offer's freshness and source.
        url:
          anyOf:
            - type: string
            - type: 'null'
          description: Product URL on the retailer's site.
          title: Url
      required:
        - retailer_id
        - availability_confidence
      title: OnlineOfferOut
      type: object
    OnlineShownReasonEnum:
      description: >-
        Why the response includes an online offers section.


        ``null`` when the online section is empty. The reason online results are
        shown is always explicit

        rather than left for the caller to infer from ``include_online``.
      enum:
        - no_local_results
        - requested
      title: OnlineShownReasonEnum
      type: string
    FacetsOut:
      description: >-
        Aggregations over the top-25 BM25 candidate pool (not just the winner).


        Lets the UI render refinement chips ("switch to this other brand's
        match")

        without a second round trip. ``retailer``/``distance_bands``/

        ``price_range``/``availability_confidence`` describe physical offers

        across every candidate GTIN, within the request's own scope filters;

        ``brand``/``category`` describe the candidate products themselves.
      examples:
        - availability_confidence:
            - count: 3
              value: confirmed
            - count: 2
              value: likely
          brand:
            - count: 3
              value: Acme Beauty
          category:
            - count: 3
              value: Beauty > Hair Care > Shampoo
          distance_bands:
            - count: 2
              max_km: 5
              min_km: 0
            - count: 3
              max_km: 10
              min_km: 5
          price_buckets:
            - count: 2
              max_price: 10
            - count: 3
              max_price: 25
              min_price: 10
          price_range:
            max_price: 14.49
            min_price: 6.99
          retailer:
            - count: 4
              value: retailerXyZ789
      properties:
        retailer:
          description: Retailers carrying a candidate product.
          items:
            $ref: '#/components/schemas/NameCountOut'
          title: Retailer
          type: array
        brand:
          description: Brands present in the candidate pool.
          items:
            $ref: '#/components/schemas/NameCountOut'
          title: Brand
          type: array
        category:
          description: Category paths present in the candidate pool.
          items:
            $ref: '#/components/schemas/NameCountOut'
          title: Category
          type: array
        distance_bands:
          description: >-
            Physical offer counts bucketed at 5/10/25/50/100 km, empty when no
            location was resolved.
          items:
            $ref: '#/components/schemas/DistanceBandOut'
          title: Distance Bands
          type: array
        price_range:
          allOf:
            - $ref: '#/components/schemas/PriceRangeOut'
          description: Observed price range, currency-naive.
        price_buckets:
          description: >-
            Physical offer counts bucketed at fixed price breakpoints (under
            $10, $10-25, $25-50, $50-100, $100+).
          items:
            $ref: '#/components/schemas/PriceBucketOut'
          title: Price Buckets
          type: array
        availability_confidence:
          description: Physical offer counts by availability_confidence.
          items:
            $ref: '#/components/schemas/NameCountOut'
          title: Availability Confidence
          type: array
      title: FacetsOut
      type: object
    AvailabilityConfidenceEnum:
      description: |-
        How confident the search index is that an offer is in stock.

        Computed at index time from an offer's stock signals (source provenance,
        freshness, quantity) rather than stored on the offer itself. Echoed on
        each local and online offer row returned by the product search endpoint.
      enum:
        - confirmed
        - likely
        - uncertain
        - unknown
        - out_of_stock
      title: AvailabilityConfidenceEnum
      type: string
    LocalOfferVariantOut:
      description: One variant GTIN's own offer within a collapsed store row.
      examples:
        - availability_confidence: confirmed
          currency: USD
          gtin: '00012345678905'
          is_default_variant: true
          price: 9.99
      properties:
        gtin:
          description: GTIN-14 of this variant.
          title: Gtin
          type: string
        is_default_variant:
          description: True when this is the family's representative variant.
          title: Is Default Variant
          type: boolean
        price:
          anyOf:
            - type: number
            - type: 'null'
          description: Offer price, null when unknown.
          title: Price
        currency:
          anyOf:
            - type: string
            - type: 'null'
          description: ISO 4217 currency code, null when price is null.
          title: Currency
        availability_confidence:
          $ref: '#/components/schemas/AvailabilityConfidenceEnum'
          description: >-
            Confidence the store actually has stock, computed from the offer's
            freshness and source.
      required:
        - gtin
        - is_default_variant
        - availability_confidence
      title: LocalOfferVariantOut
      type: object
    NameCountOut:
      examples:
        - count: 4
          value: retailerXyZ789
      properties:
        value:
          description: Facet bucket value (a retailer id, brand name, category path, etc.).
          title: Value
          type: string
        count:
          description: Number of candidate-pool availability documents in this bucket.
          minimum: 0
          title: Count
          type: integer
      required:
        - value
        - count
      title: NameCountOut
      type: object
    DistanceBandOut:
      examples:
        - count: 3
          max_km: 10
          min_km: 5
      properties:
        min_km:
          description: Lower bound of this band, inclusive.
          minimum: 0
          title: Min Km
          type: number
        max_km:
          description: Upper bound of this band, exclusive.
          exclusiveMinimum: 0
          title: Max Km
          type: number
        count:
          description: Number of candidate-pool physical offers in this band.
          minimum: 0
          title: Count
          type: integer
      required:
        - min_km
        - max_km
        - count
      title: DistanceBandOut
      type: object
    PriceRangeOut:
      examples:
        - max_price: 14.49
          min_price: 6.99
      properties:
        min_price:
          anyOf:
            - type: number
            - type: 'null'
          description: Lowest observed price in the candidate pool, null if none priced.
          title: Min Price
        max_price:
          anyOf:
            - type: number
            - type: 'null'
          description: Highest observed price in the candidate pool, null if none priced.
          title: Max Price
      title: PriceRangeOut
      type: object
    PriceBucketOut:
      description: One fixed price-range bucket for the single-select price facet.
      examples:
        - count: 2
          max_price: 10
      properties:
        min_price:
          anyOf:
            - type: number
            - type: 'null'
          description: Lower bound, inclusive. Null for the lowest (open-ended) bucket.
          title: Min Price
        max_price:
          anyOf:
            - type: number
            - type: 'null'
          description: Upper bound, exclusive. Null for the highest (open-ended) bucket.
          title: Max Price
        count:
          description: Number of candidate-pool physical offers in this bucket.
          minimum: 0
          title: Count
          type: integer
      required:
        - count
      title: PriceBucketOut
      type: object

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.