Skip to main content
The Octogen search endpoint lets you find products across your granted catalogs using free-text queries, structured facet filters, and price range constraints — in any combination. Omit catalog to search every catalog your API key can access. Include catalog only when you want to restrict search to one granted catalog.

How search works

When you call POST /v1/products/search, Octogen runs the query against all granted catalogs by default, or one granted catalog when catalog is provided. The response returns a page of matching items and an opaque nextCursor you use to fetch subsequent pages. Each item in items is a lightweight product record — enough to render a card or feed a ranking model — while the full canonical record is available via product lookup. If you already have a source product and want similar items, use More Like This instead of inventing a keyword query. Pass a free-text string as q to search by keyword. Omit q entirely to browse without any keyword filter.
curl -sS https://api.octogen.ai/v1/products/search \
  -H "Authorization: Bearer $OCTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "q": "black hoodie",
    "limit": 10
  }'
import asyncio
from octogen_ai_sdk import OctogenClient

async def main() -> None:
    async with OctogenClient() as client:
        results = await client.search_products(
            q="black hoodie",
            limit=10,
        )
        for product in results.items:
            print(product.title, product.product_url)

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

const client = new OctogenClient();
const results = await client.searchProducts({
  q: "black hoodie",
  limit: 10,
});

for (const product of results.items) {
  console.log(product.title, product.productUrl);
}

Faceted filtering

Facets let you filter by structured product attributes. Each facet is an object with a name and a values array. You can apply multiple facets in a single request — the filters are combined with AND logic, so each additional facet narrows the result set. The FacetName enum defines the supported facet keys:
Facet nameDescription
brand_nameCanonical brand name (e.g. "Warren Lotas")
brand_slugURL-safe brand identifier (e.g. "warren-lotas")
product_typeProduct category type (e.g. "hoodie", "sneaker")
gendermale, female, or unisex
age_groupsinfant, toddler, kids, or adult
colorSpecific color label (e.g. "black", "vintage white")
color_familyBroad color family: Black, Blue, Green, Gray, Pink, Purple, Red, Orange, Brown, Yellow, White
category_path.depth_0depth_6Hierarchical category path at a given depth
Facet values are case-insensitive and phrase values may contain spaces. You can also pass dynamic attribute keys directly (e.g. fit, or its fully-qualified form attribute_facets.fit).
curl -sS https://api.octogen.ai/v1/products/search \
  -H "Authorization: Bearer $OCTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "facets": [
      {"name": "color", "values": ["black"]},
      {"name": "gender", "values": ["male"]},
      {"name": "category_path.depth_0", "values": ["tops"]}
    ],
    "limit": 25
  }'
import asyncio
from octogen_ai_sdk import OctogenClient
from octogen_ai_sdk.models import Facet, FacetName

async def main() -> None:
    async with OctogenClient() as client:
        results = await client.search_products(
            facets=[
                Facet(name=FacetName.COLOR, values=["black"]),
                Facet(name=FacetName.GENDER, values=["male"]),
                Facet(name=FacetName.CATEGORY_PATH_DEPTH_0, values=["tops"]),
            ],
            limit=25,
        )
        for product in results.items:
            price = f"${product.current_price:.2f}" if product.current_price else "N/A"
            print(f"{product.title}{price}")

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

const client = new OctogenClient();
const results = await client.searchProducts({
  facets: [
    { name: FacetName.COLOR, values: ["black"] },
    { name: FacetName.GENDER, values: ["male"] },
    { name: FacetName.CATEGORY_PATH_DEPTH_0, values: ["tops"] },
  ],
  limit: 25,
});

for (const product of results.items) {
  const price = product.currentPrice != null ? `$${product.currentPrice.toFixed(2)}` : "N/A";
  console.log(`${product.title ?? "Untitled"}${price}`);
}

Price range filtering

Use price_min and price_max to restrict results to a price range. Both bounds are inclusive. You can use either bound on its own.
curl -sS https://api.octogen.ai/v1/products/search \
  -H "Authorization: Bearer $OCTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "q": "hoodie",
    "price_min": 100,
    "price_max": 300
  }'
import asyncio
from octogen_ai_sdk import OctogenClient

async def main() -> None:
    async with OctogenClient() as client:
        results = await client.search_products(
            q="hoodie",
            price_min=100,
            price_max=300,
        )
        for product in results.items:
            print(product.title, product.current_price)

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

const client = new OctogenClient();
const results = await client.searchProducts({
  q: "hoodie",
  priceMin: 100,
  priceMax: 300,
});

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

Combined example: keyword, facets, and price range

You can combine q, facets, and price filters in a single request. All active filters are applied together.
curl -sS https://api.octogen.ai/v1/products/search \
  -H "Authorization: Bearer $OCTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "q": "graphic tee",
    "facets": [
      {"name": "color_family", "values": ["Black"]},
      {"name": "gender", "values": ["unisex"]}
    ],
    "price_min": 50,
    "price_max": 200,
    "limit": 20
  }'
import asyncio
from octogen_ai_sdk import OctogenClient
from octogen_ai_sdk.models import Facet, FacetName

async def main() -> None:
    async with OctogenClient() as client:
        results = await client.search_products(
            q="graphic tee",
            facets=[
                Facet(name=FacetName.COLOR_FAMILY, values=["Black"]),
                Facet(name=FacetName.GENDER, values=["unisex"]),
            ],
            price_min=50,
            price_max=200,
            limit=20,
        )
        print(f"Found {len(results.items)} products")
        for product in results.items:
            brand = product.brand.name if product.brand else "Unknown"
            print(f"- {product.title} | {brand} | ${product.current_price}")

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

const client = new OctogenClient();
const results = await client.searchProducts({
  q: "graphic tee",
  facets: [
    { name: FacetName.COLOR_FAMILY, values: ["Black"] },
    { name: FacetName.GENDER, values: ["unisex"] },
  ],
  priceMin: 50,
  priceMax: 200,
  limit: 20,
});

console.log(`Found ${results.items.length} products`);
for (const product of results.items) {
  const brand = product.brand?.name ?? "Unknown";
  console.log(`- ${product.title} | ${brand} | $${product.currentPrice}`);
}

Understanding results

Each search response contains two top-level fields:
FieldTypeDescription
itemsarrayList of product records matching the query.
nextCursorstring | nullPagination cursor. Non-null means more pages are available. See Pagination.
Each item in items contains the following fields:
FieldTypeDescription
uuidstringStable unique identifier for the product.
catalogKeystring | nullCatalog that supplied the product.
productUrlstringCanonical URL on the retailer’s site.
titlestring | nullProduct display name.
brandobject | nullBrand with name and optional slug, url, description.
currentPricenumber | nullCurrent selling price.
originalPricenumber | nullPre-discount price, if available.
imageUrlstring | nullPrimary product image URL.
imagesstring[]All product image URLs.
ratingobject | nullAverage rating and review count, if available.
isActivebooleanWhether the product is active in the current index.
updatedAtstring | nullISO 8601 timestamp of the last catalog update.
To get the full product record with fields like description, sizes, colors, variants, identifiers, and enrichment, use product lookup with the productUrl from a search result.