Skip to main content
POST /products/search lets you search and browse products across all catalogs your organization has been granted access to. To restrict search to one catalog, include catalog in the request body. You can combine a free-text query (q) with structured facet filters, price range bounds, and a pagination cursor. The response returns a page of MerchantProductListItem objects and an opaque nextCursor for advancing through results. If you already have a source product and want similar products, use POST /products/more-like-this.

Request

POST https://api.octogen.ai/v1/products/search
Authorization: Bearer <your-platform-api-key>
Content-Type: application/json

Body parameters

catalog
string
Optional key of the catalog to search. When omitted, search runs across all active catalogs currently granted to your organization. When provided, it must be a catalog key from GET /catalogs.
q
string
Free-text keyword query. Omit or set to null to browse without keyword filtering. Can be combined with facets and price filters.
facets
Facet[]
Array of structured facet filters. Each facet has a name and a values array. Multiple facets are ANDed; multiple values within a facet are ORed.
[
  {"name": "color_family", "values": ["Black", "Gray"]},
  {"name": "gender", "values": ["female"]}
]
See Facet names below for the full list of supported names.
price_min
number
Inclusive minimum price filter. Only products with currentPrice >= price_min are returned.
price_max
number
Inclusive maximum price filter. Only products with currentPrice <= price_max are returned.
cursor
string
Opaque pagination cursor from a previous response’s nextCursor field. Pass this value — unmodified — along with the same filters to retrieve the next page. Omit on the first request.
limit
integer
default:"50"
Number of products to return per page. Accepted range: 1–100. Defaults to 50.

Example

curl -sS https://api.octogen.ai/v1/products/search \
  -H "Authorization: Bearer $OCTOGEN_PLATFORM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "q": "black hoodie",
    "facets": [
      {"name": "color_family", "values": ["Black"]},
      {"name": "gender", "values": ["male", "unisex"]}
    ],
    "price_min": 50,
    "price_max": 300,
    "limit": 25
  }'

Response

items
MerchantProductListItem[]
required
The current page of product results. See the Product model for the full field reference.
nextCursor
string | null
Opaque cursor for the next page of results. Pass as cursor on your next request with the same filters. null when you have reached the last page.

Example response

{
  "items": [
    {
      "uuid": "prod_01HX...",
      "catalogKey": "warrenlotas",
      "title": "Black Hoodie",
      "productUrl": "https://warrenlotas.com/products/black-hoodie",
      "imageUrl": "https://cdn.example.com/black-hoodie.jpg",
      "images": ["https://cdn.example.com/black-hoodie.jpg"],
      "brand": {"name": "Warren Lotas", "slug": "warren-lotas", "url": null},
      "currentPrice": 180,
      "originalPrice": null,
      "rating": null,
      "isActive": true,
      "updatedAt": "2026-05-19T18:04:10Z"
    }
  ],
  "nextCursor": "eyJzZWFyY2hBZnRlciI6Wy4uLl19"
}

Facet names

Use these values for the name field in each facet object. Facet values should be lowercase; phrase values may contain spaces.
Facet nameDescription
brand_nameCanonical brand display name.
brand_slugURL-safe brand identifier.
product_typeProduct type classification (e.g. "hoodie", "sneaker").
genderTarget gender: male, female, or unisex.
age_groupsTarget age group: infant, toddler, kids, or adult.
colorSpecific color label as indexed from the merchant.
color_familyNormalized color family: Black, Blue, Brown, Gray, Green, Orange, Pink, Purple, Red, White, Yellow.
category_path.depth_0Top-level category.
category_path.depth_1Second-level category.
category_path.depth_2Third-level category.
category_path.depth_3Fourth-level category.
category_path.depth_4Fifth-level category.
category_path.depth_5Sixth-level category.
category_path.depth_6Seventh-level category.
You can also filter on dynamic product attribute facets by passing the bare attribute key (e.g. fit) or its fully qualified form (e.g. attribute_facets.fit).

Pagination

For a detailed walkthrough of cursor-based pagination, see Pagination guide.
Advance through pages by repeating your request with the same filters and the cursor value from the previous response:
  1. Send your initial search request with the desired filters and an optional limit.
  2. If nextCursor is non-null in the response, send a new request with cursor set to that value and all other fields unchanged.
  3. Repeat until nextCursor is null.
Cursors are opaque — do not parse or construct them. They encode internal continuation state and may change shape across releases.

SDK equivalents

import asyncio
from octogen_ai_sdk import OctogenClient
from octogen_ai_sdk.models import Facet

async def main() -> None:
    async with OctogenClient() as client:
        cursor = None
        while True:
            page = await client.search_products(
                q="black hoodie",
                facets=[
                    Facet(name="color_family", values=["Black"]),
                    Facet(name="gender", values=["male", "unisex"]),
                ],
                price_min=50,
                price_max=300,
                limit=25,
                cursor=cursor,
            )
            for product in page.items:
                print(product.title, product.current_price)
            if page.next_cursor is None:
                break
            cursor = page.next_cursor

asyncio.run(main())
import { OctogenClient } from "@octogen-ai/sdk";

const client = new OctogenClient();
let cursor: string | undefined;

while (true) {
  const page = await client.searchProducts({
    q: "black hoodie",
    facets: [
      { name: "color_family", values: ["Black"] },
      { name: "gender", values: ["male", "unisex"] },
    ],
    priceMin: 50,
    priceMax: 300,
    limit: 25,
    cursor,
  });

  for (const product of page.items) {
    console.log(product.title, product.currentPrice);
  }

  if (page.nextCursor == null) break;
  cursor = page.nextCursor;
}