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

# Search products and browse reported inventory

> Searches the published local projection. Inventory predicates can be scoped
to one or more stores, a radius, or the statewide reviewed consumer-store
set. `store_ids` and `lat`/`lng` are mutually exclusive. An empty result does
not imply no stock when `meta.coverage.status` is `partial` or `none`.




## OpenAPI

````yaml /openapi/v1.yaml get /products
openapi: 3.1.0
info:
  title: Independent Utah DABS API
  version: 1.0.0
  summary: Database-backed product, store, and reported-inventory reads
  description: >
    Version 1 pilot contract. All requests read a locally published database
    revision;

    no public request waits for or triggers Utah DABS. Reported quantities are

    observations, not purchase guarantees. This service is independent and is
    not

    affiliated with or endorsed by Utah DABS. The current local seed contains

    historical inventory fixtures; clients must inspect response warnings and

    coverage metadata.
servers:
  - url: http://127.0.0.1:5185/v1
    description: Local development only; set MINTLIFY_API_BASE_URL before publishing
security: []
tags:
  - name: Products
  - name: Stores
  - name: Metadata
paths:
  /products:
    get:
      tags:
        - Products
      summary: Search products and browse reported inventory
      description: >
        Searches the published local projection. Inventory predicates can be
        scoped

        to one or more stores, a radius, or the statewide reviewed
        consumer-store

        set. `store_ids` and `lat`/`lng` are mutually exclusive. An empty result
        does

        not imply no stock when `meta.coverage.status` is `partial` or `none`.
      operationId: listProducts
      parameters:
        - $ref: '#/components/parameters/ProductQuery'
        - $ref: '#/components/parameters/ExactSkuQuery'
        - $ref: '#/components/parameters/BrandIds'
        - $ref: '#/components/parameters/CategoryIds'
        - $ref: '#/components/parameters/Statuses'
        - $ref: '#/components/parameters/PriceMinMinor'
        - $ref: '#/components/parameters/PriceMaxMinor'
        - $ref: '#/components/parameters/DabsNewItem'
        - $ref: '#/components/parameters/DabsOnSpa'
        - $ref: '#/components/parameters/Availability'
        - $ref: '#/components/parameters/PurchaseEligibility'
        - $ref: '#/components/parameters/InventoryAge'
        - $ref: '#/components/parameters/StoreIds'
        - $ref: '#/components/parameters/Latitude'
        - $ref: '#/components/parameters/Longitude'
        - $ref: '#/components/parameters/RadiusMeters'
        - $ref: '#/components/parameters/ProductSort'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/IfNoneMatch'
        - $ref: '#/components/parameters/IfModifiedSince'
      responses:
        '200':
          description: Product page from one immutable publication revision.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
            Last-Modified:
              $ref: '#/components/headers/LastModified'
            Cache-Control:
              $ref: '#/components/headers/InventoryCacheControl'
            X-Publication-Revision:
              $ref: '#/components/headers/PublicationRevision'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductListResponse'
        '304':
          $ref: '#/components/responses/NotModified'
        '400':
          $ref: '#/components/responses/BadRequest'
        '410':
          $ref: '#/components/responses/CursorExpired'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/Unavailable'
components:
  parameters:
    ProductQuery:
      name: q
      in: query
      description: >-
        SKU, product-name, and reviewed brand-alias search. Exact reviewed
        aliases take precedence; otherwise matching uses phrases, all tokens,
        and a bounded typo-tolerant fallback. Required for `sort=relevance`.
      schema:
        type: string
        minLength: 2
        maxLength: 200
    ExactSkuQuery:
      name: sku
      in: query
      description: Exact six-character Utah DABS SKU.
      schema:
        $ref: '#/components/schemas/DabsSku'
    BrandIds:
      name: brand_ids
      in: query
      style: form
      explode: false
      description: >-
        Comma-separated reviewed Nektir brand UUIDs; OR semantics within this
        filter and AND semantics with other filter groups. Unresolved products
        do not match.
      schema:
        type: array
        maxItems: 25
        uniqueItems: true
        items:
          type: string
          format: uuid
    CategoryIds:
      name: category_ids
      in: query
      style: form
      explode: false
      description: >-
        Comma-separated API category IDs; OR semantics. Selecting a parent
        category includes products assigned to any active descendant category.
      schema:
        type: array
        maxItems: 20
        uniqueItems: true
        items:
          type: string
          pattern: ^cat_[a-z0-9_]+$
    Statuses:
      name: statuses
      in: query
      style: form
      explode: false
      description: Comma-separated DABS status codes; OR semantics.
      schema:
        type: array
        maxItems: 10
        uniqueItems: true
        items:
          type: string
          enum:
            - '1'
            - A
            - D
            - L
            - 'N'
            - P
            - S
            - T
            - U
            - X
    PriceMinMinor:
      name: price_min_minor
      in: query
      description: Inclusive minimum USD cents. Unknown prices do not match.
      schema:
        type: integer
        format: int64
        minimum: 0
    PriceMaxMinor:
      name: price_max_minor
      in: query
      description: Inclusive maximum USD cents. Unknown prices do not match.
      schema:
        type: integer
        format: int64
        minimum: 0
    DabsNewItem:
      name: dabs_new_item
      in: query
      description: >-
        Exact match for the nullable DABS-reported `newItem` flag. Its duration
        and business meaning are unverified.
      schema:
        type: boolean
    DabsOnSpa:
      name: dabs_on_spa
      in: query
      description: >-
        Exact match for the nullable DABS-reported `onSpa` flag. This is not
        labeled as a sale or promotion.
      schema:
        type: boolean
    Availability:
      name: availability
      in: query
      description: Reported observation predicate; never a purchase guarantee.
      schema:
        type: string
        enum:
          - any
          - reported_positive
          - checked
        default: any
    PurchaseEligibility:
      name: purchase_eligibility
      in: query
      description: Independent policy classification for ordinary consumer purchase.
      schema:
        $ref: '#/components/schemas/PurchaseEligibility'
    InventoryAge:
      name: inventory_age
      in: query
      description: Whether inventory predicates require the current retrieval-age policy.
      schema:
        type: string
        enum:
          - within_policy
          - any_age
        default: within_policy
    StoreIds:
      name: store_ids
      in: query
      style: form
      explode: false
      description: Comma-separated stable store UUIDs, mutually exclusive with coordinates.
      schema:
        type: array
        maxItems: 25
        uniqueItems: true
        items:
          type: string
          format: uuid
    Latitude:
      name: lat
      in: query
      description: WGS84 latitude; must be paired with `lng`.
      schema:
        type: number
        format: double
        minimum: -90
        maximum: 90
    Longitude:
      name: lng
      in: query
      description: WGS84 longitude; must be paired with `lat`.
      schema:
        type: number
        format: double
        minimum: -180
        maximum: 180
    RadiusMeters:
      name: radius_m
      in: query
      description: Radius in meters; valid only with coordinates.
      schema:
        type: integer
        minimum: 1
        maximum: 500000
        default: 50000
    ProductSort:
      name: sort
      in: query
      schema:
        type: string
        enum:
          - relevance
          - name
          - '-name'
          - price
          - '-price'
          - sku
    Limit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25
    Cursor:
      name: cursor
      in: query
      description: >-
        Signed opaque keyset cursor, valid for 15 minutes and only with the
        original query.
      schema:
        type: string
        maxLength: 2048
    IfNoneMatch:
      name: If-None-Match
      in: header
      description: Strong entity tag from an earlier response.
      schema:
        type: string
    IfModifiedSince:
      name: If-Modified-Since
      in: header
      description: HTTP-date fallback validator. ETag takes precedence.
      schema:
        type: string
  headers:
    ETag:
      description: >-
        Strong validator for this version, normalized query, publication
        revision, and representation.
      schema:
        type: string
      example: '"v1-018f2fd0-a3d4-7d20-9c11-9482722f6b26-a91c"'
    LastModified:
      description: >-
        Newest relevant publication time in HTTP-date format; not an upstream
        update time.
      schema:
        type: string
      example: Tue, 15 Sep 2026 16:31:00 GMT
    InventoryCacheControl:
      schema:
        type: string
      example: public, max-age=30, stale-while-revalidate=120
    PublicationRevision:
      description: Immutable API publication revision used for this representation.
      schema:
        type: string
        format: uuid
    RetryAfter:
      description: Seconds until retry or an HTTP-date.
      schema:
        type: string
    RequestId:
      description: >-
        Server-generated identifier for correlating a response with private
        operational logs.
      schema:
        type: string
        format: uuid
  schemas:
    ProductListResponse:
      type: object
      additionalProperties: false
      required:
        - data
        - meta
        - links
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ProductSummary'
        meta:
          $ref: '#/components/schemas/CollectionMeta'
        links:
          $ref: '#/components/schemas/Links'
    DabsSku:
      type: string
      pattern: ^[0-9]{6}$
      examples:
        - '347210'
        - '004190'
    PurchaseEligibility:
      type: string
      enum:
        - any
        - eligible
        - restricted
        - unavailable
        - unknown
      default: any
    ProductSummary:
      type: object
      additionalProperties: false
      required:
        - id
        - source_ids
        - name
        - brand
        - brand_id
        - brand_slug
        - package_size
        - price
        - status
        - categories
        - catalog_times
        - catalog_presence
        - dabs_flags
        - availability_summary
      properties:
        id:
          type: string
          format: uuid
        source_ids:
          $ref: '#/components/schemas/SourceProductIds'
        name:
          type: string
        brand:
          type:
            - string
            - 'null'
        brand_id:
          type:
            - string
            - 'null'
          format: uuid
          description: Stable reviewed Nektir brand ID; null when unresolved.
        brand_slug:
          type:
            - string
            - 'null'
          description: Stable reviewed Nektir brand slug; null when unresolved.
        name_components:
          oneOf:
            - $ref: '#/components/schemas/ProductNameComponents'
            - type: 'null'
        attributes:
          oneOf:
            - $ref: '#/components/schemas/ProductAttributes'
            - type: 'null'
        package_size:
          $ref: '#/components/schemas/PackageSize'
        price:
          oneOf:
            - $ref: '#/components/schemas/MoneyObservation'
            - type: 'null'
        status:
          $ref: '#/components/schemas/ProductStatus'
        categories:
          type: array
          description: Active category lineage in root-to-leaf order.
          items:
            $ref: '#/components/schemas/CategoryRef'
        catalog_times:
          $ref: '#/components/schemas/ObservationTimes'
        catalog_presence:
          oneOf:
            - $ref: '#/components/schemas/SourcePresenceSummary'
            - type: 'null'
        dabs_flags:
          oneOf:
            - $ref: '#/components/schemas/DabsProductFlags'
            - type: 'null'
        availability_summary:
          oneOf:
            - $ref: '#/components/schemas/AvailabilitySummary'
            - type: 'null'
    CollectionMeta:
      type: object
      additionalProperties: false
      required:
        - snapshot
        - page
        - coverage
        - warnings
      properties:
        snapshot:
          $ref: '#/components/schemas/SnapshotMeta'
        page:
          $ref: '#/components/schemas/PageMeta'
        coverage:
          $ref: '#/components/schemas/Coverage'
        warnings:
          type: array
          items:
            $ref: '#/components/schemas/Warning'
    Links:
      type: object
      additionalProperties: false
      required:
        - self
        - next
      properties:
        self:
          type: string
          format: uri-reference
        next:
          type:
            - string
            - 'null'
          format: uri-reference
    Problem:
      type: object
      additionalProperties: false
      required:
        - type
        - title
        - status
        - detail
        - instance
        - code
        - request_id
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
          minimum: 400
          maximum: 599
        detail:
          type: string
        instance:
          type: string
          format: uri-reference
        code:
          type: string
        request_id:
          type: string
          format: uuid
        invalid_params:
          type: array
          items:
            $ref: '#/components/schemas/InvalidParameter'
    SourceProductIds:
      type: object
      additionalProperties: false
      required:
        - utah_dabs_sku
      properties:
        utah_dabs_sku:
          $ref: '#/components/schemas/DabsSku'
    ProductNameComponents:
      type: object
      additionalProperties: false
      properties:
        product_name:
          type:
            - string
            - 'null'
        expression:
          type:
            - string
            - 'null'
        age_statement:
          type:
            - string
            - 'null'
        pack_count:
          type:
            - integer
            - 'null'
          minimum: 1
    ProductAttributes:
      type: object
      additionalProperties: false
      description: >-
        Conservative product classifications derived during ingestion. Raw DABS
        names and taxonomy remain available under source_fields; null or an
        empty list means the attribute was not established, not that it is
        absent.
      required:
        - vintage_year
        - proof
        - package_form
        - beverage_styles
        - production_designations
        - certifications
        - source_programs
      properties:
        vintage_year:
          type:
            - integer
            - 'null'
          minimum: 1900
          maximum: 2099
          description: >-
            Explicit single four-digit vintage at the end of a wine-context
            source name before package size.
        proof:
          type:
            - integer
            - 'null'
          minimum: 40
          maximum: 200
          description: >-
            Explicit proof value in a spirits-context source name; never
            inferred from ABV.
        package_form:
          type:
            - string
            - 'null'
          enum:
            - can
            - keg
            - box
            - gift_set
            - null
          description: Explicit package form or a DABS gift-set classification.
        beverage_styles:
          type: array
          uniqueItems: true
          items:
            type: string
            enum:
              - blanco
              - reposado
              - anejo
              - extra_anejo
              - cristalino
              - joven
        production_designations:
          type: array
          uniqueItems: true
          items:
            type: string
            enum:
              - bottled_in_bond
              - straight
              - single_barrel
              - small_batch
              - cask_strength
              - barrel_proof
        certifications:
          type: array
          uniqueItems: true
          items:
            type: string
            enum:
              - kosher_for_passover_mevushal
              - organic
        source_programs:
          type: array
          uniqueItems: true
          description: >-
            DABS operational classifications, kept separate from beverage
            category.
          items:
            type: string
            enum:
              - special_order
              - gift_set
              - offer
              - allocated
    PackageSize:
      type: object
      additionalProperties: false
      required:
        - milliliters
        - display
        - normalization_state
      properties:
        milliliters:
          type:
            - integer
            - 'null'
          minimum: 1
        display:
          type: string
        normalization_state:
          type: string
          enum:
            - normalized
            - unconfirmed
            - missing
    MoneyObservation:
      type: object
      additionalProperties: false
      required:
        - amount_minor
        - currency
        - display
        - source
        - times
      properties:
        amount_minor:
          type: integer
          format: int64
          minimum: 0
        currency:
          type: string
          const: USD
        display:
          type: string
          examples:
            - $19.99
        source:
          type: string
          enum:
            - utah_dabs_workbook
            - utah_dabs_locator
        times:
          $ref: '#/components/schemas/ObservationTimes'
    ProductStatus:
      type: object
      additionalProperties: false
      required:
        - code
        - label
        - lifecycle
        - source_raw
      properties:
        code:
          type: string
          examples:
            - '1'
        label:
          type: string
          examples:
            - General distribution
        lifecycle:
          type: string
          enum:
            - active
            - discontinued
            - restricted
            - unavailable
            - unknown
        source_raw:
          type: string
    CategoryRef:
      type: object
      additionalProperties: false
      required:
        - id
        - name
      properties:
        id:
          type: string
          pattern: ^cat_[a-z0-9_]+$
        name:
          type: string
    ObservationTimes:
      type: object
      additionalProperties: false
      required:
        - source_updated_at
        - retrieved_at
        - published_at
      properties:
        source_updated_at:
          type:
            - string
            - 'null'
          format: date-time
          description: Null when the source supplies no update timestamp.
        retrieved_at:
          type: string
          format: date-time
        published_at:
          type: string
          format: date-time
    SourcePresenceSummary:
      type: object
      additionalProperties: false
      description: Compact presence state for collection rows.
      required:
        - state
        - last_seen_at
      properties:
        state:
          type: string
          enum:
            - present
            - suspect_missing
            - retirement_review_required
            - reviewed_retired
        last_seen_at:
          type:
            - string
            - 'null'
          format: date-time
    DabsProductFlags:
      type: object
      additionalProperties: false
      description: >-
        Raw flags from the DABS locator. Their exact business meaning and
        duration are unverified.
      required:
        - new_item_raw
        - on_spa_raw
        - semantics
        - times
      properties:
        new_item_raw:
          type:
            - boolean
            - 'null'
        on_spa_raw:
          type:
            - boolean
            - 'null'
        semantics:
          type: string
          const: source_flags_unverified
        times:
          $ref: '#/components/schemas/ObservationTimes'
    AvailabilitySummary:
      type: object
      additionalProperties: false
      required:
        - scope_store_count
        - reported_positive_store_count
        - eligible_positive_store_count
        - reported_zero_store_count
        - row_absent_store_count
        - unchecked_store_count
        - retrieved_at_min
        - retrieved_at_max
        - retrieval_policy_id
        - meets_retrieval_policy
        - upstream_age_known
        - last_refresh_attempt
      properties:
        scope_store_count:
          type: integer
          minimum: 0
        reported_positive_store_count:
          type: integer
          minimum: 0
        eligible_positive_store_count:
          type: integer
          minimum: 0
        reported_zero_store_count:
          type: integer
          minimum: 0
        row_absent_store_count:
          type: integer
          minimum: 0
        unchecked_store_count:
          type: integer
          minimum: 0
        retrieved_at_min:
          type:
            - string
            - 'null'
          format: date-time
        retrieved_at_max:
          type:
            - string
            - 'null'
          format: date-time
        retrieval_policy_id:
          type: string
        meets_retrieval_policy:
          type: boolean
        upstream_age_known:
          type: boolean
          const: false
        last_refresh_attempt:
          $ref: '#/components/schemas/RefreshAttemptSummary'
    SnapshotMeta:
      type: object
      additionalProperties: false
      required:
        - publication_revision
        - published_at
        - cursor_expires_at
      properties:
        publication_revision:
          type: string
          format: uuid
        published_at:
          type: string
          format: date-time
        cursor_expires_at:
          type:
            - string
            - 'null'
          format: date-time
    PageMeta:
      type: object
      additionalProperties: false
      required:
        - limit
        - has_more
        - next_cursor
      properties:
        limit:
          type: integer
          minimum: 1
          maximum: 100
        has_more:
          type: boolean
        next_cursor:
          type:
            - string
            - 'null'
    Coverage:
      type: object
      additionalProperties: false
      required:
        - status
        - scope
        - candidate_product_count
        - scope_store_count
        - expected_pair_count
        - checked_product_count
        - within_policy_product_count
        - reported_positive_pair_count
        - reported_zero_pair_count
        - row_absent_pair_count
        - unchecked_pair_count
        - retrieved_at_min
        - retrieved_at_max
        - retrieval_policy_id
        - retrieval_policy_max_age_seconds
        - upstream_age_known
        - denominator_definition
      properties:
        status:
          type: string
          enum:
            - complete_for_retrieval_policy
            - partial
            - none
            - not_applicable
        scope:
          $ref: '#/components/schemas/ScopeMeta'
        candidate_product_count:
          type: integer
          minimum: 0
        scope_store_count:
          type: integer
          minimum: 0
        expected_pair_count:
          type: integer
          format: int64
          minimum: 0
        checked_product_count:
          type: integer
          minimum: 0
        within_policy_product_count:
          type: integer
          minimum: 0
        reported_positive_pair_count:
          type: integer
          format: int64
          minimum: 0
        reported_zero_pair_count:
          type: integer
          format: int64
          minimum: 0
        row_absent_pair_count:
          type: integer
          format: int64
          minimum: 0
        unchecked_pair_count:
          type: integer
          format: int64
          minimum: 0
        retrieved_at_min:
          type:
            - string
            - 'null'
          format: date-time
        retrieved_at_max:
          type:
            - string
            - 'null'
          format: date-time
        retrieval_policy_id:
          type:
            - string
            - 'null'
        retrieval_policy_max_age_seconds:
          type:
            - integer
            - 'null'
          minimum: 1
        upstream_age_known:
          type: boolean
          const: false
        denominator_definition:
          type: string
          const: >-
            candidate_products_after_non_inventory_filters_x_reviewed_stores_in_scope
    Warning:
      type: object
      additionalProperties: false
      description: >-
        Machine-readable data qualification. Current codes include FIXTURE_DATA,
        INVENTORY_COVERAGE_PARTIAL, UPSTREAM_UPDATE_TIME_UNKNOWN,
        SOURCE_REFRESH_DELAYED, UPSTREAM_PRODUCT_UNAVAILABLE,
        SOURCE_SCHEMA_QUARANTINED, and CATALOG_BATCH_OLD.
      required:
        - code
        - message
      properties:
        code:
          type: string
          examples:
            - INVENTORY_COVERAGE_PARTIAL
        message:
          type: string
    InvalidParameter:
      type: object
      additionalProperties: false
      required:
        - name
        - reason
      properties:
        name:
          type: string
        reason:
          type: string
    RefreshAttemptSummary:
      type: object
      additionalProperties: false
      required:
        - attempted_at
        - status
      properties:
        attempted_at:
          type:
            - string
            - 'null'
          format: date-time
        status:
          type: string
          enum:
            - succeeded
            - failed
            - skipped
            - rate_limited
            - schema_rejected
            - never_attempted
    ScopeMeta:
      type: object
      additionalProperties: false
      required:
        - type
        - store_ids
      properties:
        type:
          type: string
          enum:
            - none
            - statewide_consumer_stores
            - explicit_store_set
            - radius
        store_ids:
          type: array
          items:
            type: string
            format: uuid
  responses:
    NotModified:
      description: Representation has not changed for the supplied validator.
      headers:
        ETag:
          $ref: '#/components/headers/ETag'
        Cache-Control:
          description: Cache policy for the validated representation.
          schema:
            type: string
        X-Publication-Revision:
          $ref: '#/components/headers/PublicationRevision'
    BadRequest:
      description: Invalid parameters, incompatible scope, or cursor/query mismatch.
      headers:
        Cache-Control:
          schema:
            type: string
          example: no-store
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    CursorExpired:
      description: Cursor expired or its publication revision was retired.
      headers:
        Cache-Control:
          schema:
            type: string
          example: no-store
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    RateLimited:
      description: Public read-rate limit exceeded.
      headers:
        Cache-Control:
          schema:
            type: string
          example: no-store
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    InternalError:
      description: >-
        An unexpected server failure. Details are withheld; report the response
        request identifier.
      headers:
        Cache-Control:
          schema:
            type: string
          example: no-store
        X-Request-ID:
          $ref: '#/components/headers/RequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Unavailable:
      description: No safe published database revision is currently readable.
      headers:
        Cache-Control:
          schema:
            type: string
          example: no-store
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'

````