---
title: "Semantic product search"
method: POST
path: "/api/search/"
---

# Semantic product search

`POST /api/search/`

## Request

- Base URL: `https://productindex.ai`
- URL: `https://productindex.ai/api/search/`
- Auth: none required

Runs the search-agent Lambda pipeline (pgvector + BM25 RRF + Cohere rerank) and returns ranked product results. If the caller has a verified-email session and either sets `bundle: true` or the query matches a bundle-search pattern (e.g. "full kitchen set $900"), a multi-category bundle response is returned instead of a flat result list — see the two response schemas below. Anonymous callers always get the regular (non-bundle) response shape.

## Request body

- SearchRequest
  - `query` string, required: Free-text search query.
  - `session_id` string: Anonymous session UUID for search-analytics correlation; not an auth credential.
  - `bundle` boolean: Explicitly opt into the bundle-search path (skips the keyword-trigger gate, still requires a verified-email session). Ignored for anonymous callers.
  - `filters` object
    - `category` string: Category slug. Setting this disables bundle-search consideration entirely.
    - `max_price` number
    - `min_price` number
    - `on_sale` boolean
  - `limit` number

## Response `200`

Search results — regular or bundle shape depending on caller eligibility and query.

- union
  - SearchResponse
    - `query_id` string, required
    - `results` SearchResult[], required
      - `slug` string
      - `title` string
      - `category` string
      - `verdict` string, nullable
      - `price_range` string, nullable
      - `url` string: Relative path, e.g. /p/espresso-machines/breville-bambino-plus
      - `similarity` number
      - `is_sponsored` boolean
  - BundleResponse
    - `type` 'bundle', required
    - `query_id` string, required
    - `overview` string: Claude's overall bundle summary.
    - `categories` BundleCategory[], required
      - `slug` string
      - `label` string
      - `reasoning` string: Claude's explanation for this category's allocation.
      - `budget_allocated` number, nullable
      - `over_budget` boolean: True if no candidate was found within the allocated budget and the category was retried unconstrained.
      - `product` BundleProduct
        - `slug` string
        - `url` string
        - `title` string
        - `price_range` string, nullable
        - `verdict` string, nullable

## Other responses

- `400`: Missing or invalid `query`, or request body was not valid JSON.
- `429`: Bundle-search per-user daily limit exceeded (20/day per authenticated, verified-email user). Only reachable when a bundle query is triggered by a verified-email session — bundle search is gated behind email verification, so an anonymous caller through this endpoint can never enter the bundle path or hit this limit (the per-IP hourly limit documented in docs/search.md applies to other, non-REST callers of the same Lambda, not this route).
- `502`: The search-agent Lambda returned a function error.
- `503`: Search is temporarily unavailable. Retry with backoff.

---

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