---
title: "Search products"
method: POST
path: "/products/search"
tags: ["Products"]
---

# Search products

`POST /products/search`

## Request

- Base URL: `https://api.savanto.ai`
- URL: `https://api.savanto.ai/products/search`
- Auth: HTTP bearer

Hybrid (BM25 + k-NN vector) search across the workspace's product catalog. Pass a natural-language `query` — the backend expands and filters it with an agent when that improves recall. Optional `filters` support `priceMin` / `priceMax`, `stockStatus`, `brand`, `category`, and arbitrary `attributes` equality. Optional `facetFields` returns aggregate counts for the requested fields. Use for "find X" style requests; use the chat endpoint instead if you want a conversational answer that cites the products.

## Request body

- ProductSearchRequest
  - `text` string, required
  - `filters` object
  - `limit` integer
  - `useAgent` boolean
  - `facetFields` union[]: Fields to return aggregate counts for. Valid values: categories, brands, tags, stockStatus, onSale, featured, type, priceRanges, attributes.{key}, metadata.{key}. Facet keys in the response match the requested field names.
    - union
      - 'categories' | 'brands' | 'tags' | 'stockStatus' | 'onSale' | 'featured' | 'type' | 'priceRanges'
      - string
  - `searchContext` 'widget' | 'native' | 'chat' | 'api' | 'internal' | 'unknown'
  - `trackAnalytics` boolean

## Response `200`

Search results

- object
  - `object` 'search_result', required
  - `requestId` string, required
  - `data` object, required
    - `items` Product[], required
      - `id` string, required
      - `name` string, required
      - `content` string
      - `excerpt` string
      - `searchableText` string
      - `price` number, nullable
      - `salePrice` number, nullable
      - `regularPrice` number, nullable
      - `onSale` boolean
      - `priceMin` number, nullable
      - `priceMax` number, nullable
      - `stockQuantity` number, nullable
      - `stockStatus` 'instock' | 'outofstock' | 'onbackorder'
      - `sku` string
      - `type` 'simple' | 'variable' | 'grouped' | 'external'
      - `weight` number, nullable
      - `dimensions` object, nullable
        - `length` number, nullable
        - `width` number, nullable
        - `height` number, nullable
      - `rating` number, nullable
      - `reviewCount` number
      - `categories` string[]
      - `tags` string[]
      - `brands` string[]
      - `image` string, nullable
      - `url` string
      - `status` string
      - `visibility` string
      - `featured` boolean
      - `virtual` boolean
      - `downloadable` boolean
      - `createdAt` string
      - `updatedAt` string
      - `attributes` object, nullable
      - `metadata` object
      - `source` string
      - `indexStatus` 'active' | 'hidden' | 'disabled'
      - `score` number
      - `matchingChunks` object[]
        - `chunkText` string, required
        - `order` number, required
    - `pagination` Pagination, required
      - `total` number, nullable, required
      - `limit` number, required
      - `hasMore` boolean, required
      - `offset` number
      - `cursor` string, nullable
    - `query` object
      - `text` string, required
      - `truncated` boolean
      - `originalLength` integer
      - `filters` object
      - `limit` number
    - `facets` object
    - `meta` object
      - `classification` unknown
      - `enhanced` boolean
      - `originalText` string
      - `agentUsed` boolean
      - `usageMultiplier` number
      - `fallbackUsed` boolean

## Other responses

- `400`: Validation error
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not found
- `409`: Conflict
- `413`: Payload too large
- `429`: Rate limit exceeded

---

[API](https://skmtc.dev/savanto/apis/savanto-api.md) · [All operations](https://skmtc.dev/savanto/apis/savanto-api/llms.txt) · [OpenAPI document](https://skmtc.dev/savanto/apis/savanto-api/revisions/82437327a878?raw)
