Overview
Use product discovery to power search bars, product detail pages, and catalog browsers across any merchant on the internet. Bothproducts.search and products.details are async jobs - they return a refId, and results arrive once the background work completes. See Polling or Webhooks for how to handle the async pattern.
Prerequisites
Search the catalog
1
Kick off a product search
Call
products.search with a text query and optional filters. Returns immediately with a refId.2
Poll for results
Use
products.pollSearch to check status until it’s "complete" or "failed". See Polling for the reusable helper and strategies.3
Read the results
Fetch product details
If you need more granular product information, such as available options and pricing, callproducts.details with the link from search for the full enriched payload.
1
Request product details
2
Poll for results
Use
products.pollDetails to check status. See Polling for the reusable helper.3
Read the enriched product
availability is one of in_stock, limited_stock, out_of_stock,
preorder, backorder, or discontinued. out_of_stock is temporary -
keep the product and re-check it later. discontinued means the merchant no
longer offers the product, or the specific variant your link names - drop it
from your catalog rather than re-polling. A discontinued response carries
no price, sku, or images: Henry never substitutes a surviving
variant’s figures for one the merchant removed. The options tree still
lists what the merchant sells now, with each value’s own images.Each value in the options tree carries its own availability, a link
naming that exact variant, and - where the merchant exposes it - its own
price / originalPrice. Variants of one product do not always share a
price, and the product-level price is the lowest in-stock figure for the
featured variant group, so a catalog sync should prefer per-variant prices
from the option tree and fall back to the product-level price only when a
value carries none. One details call on the base product URL returns the
whole tree - no per-variant calls needed.Affiliate links
When one of Henry’s affiliate networks covers the merchant, the result includes anaffiliateLink — a Henry-branded URL (https://api.henrylabs.ai/v1/go/...) that redirects through the affiliate network to the same product. Commissions from purchases made through it attribute to your app, and end-users see a henrylabs.ai link instead of a raw tracking domain.
The two link fields have distinct jobs:
link— the merchant’s product URL. Use this for every API call (products.details, cart items, and so on).affiliateLink— a Henry-branded URL for end-users to click (in your UI, emails, or chat responses). Never pass it back to the API: it redirects through the affiliate network’s tracking domain rather than resolving to the merchant, so Henry can’t resolve products from it.
Image search
Search by image instead of text. Pass a URL, data URL, or base64 payload to find visually similar products. Results combine web-wide visual matches with exact and similar products from Henry’s own product-image catalog. WithsupportedOnly: true, results come only from Henry’s catalog, so every product returned can be bought through Henry.
Search one merchant by image
Addfilters.imageUrl to a merchant search to find visually similar products in that merchant’s catalog. Results come only from Henry’s product-image catalog, so they’re available for merchants whose catalog Henry has prepared for image search. Other merchants return a 400 error.
Sync mode
All async operations supportmode: "sync", which waits up to 30 seconds for the operation to complete before returning. This can simplify your code for quick lookups by avoiding polling entirely.
If the operation takes longer than 30 seconds, sync mode returns with a non-terminal status and you’ll need to poll normally. For operations that may take longer (e.g. large searches), use
mode: "async" with Polling.Merchant search
Usetype: 'merchant' to search within a specific merchant’s catalog. Pass the merchant name, host, or any URL from that merchant.
Merchant search accepts a case-insensitive name (e.g.
"Nike"), a host (e.g. "nike.com"), or any URL from the merchant. When query is omitted, the merchant’s catalog browse is returned.Search one category
Passfilters.category to page through one category of a merchant’s catalog, or to search within it. Get the merchant’s categories from searchFilters.category on merchants.retrieve.
Category search is available for merchants that list
searchFilters.category (Shopify stores and zara.com). Other merchants, and categories the merchant doesn’t have, return a 400. category can’t be combined with filters.imageUrl. A page may contain fewer than limit results - page until nextCursor is null.Search parameters reference
Top-level parameters
Global search filters (type: 'global')
Global search results are ordered by merchant checkout coverage: products
from
supported merchants rank first, then testing, then unsupported
(the search engine’s order is preserved within each tier). Passing sortBy
disables this ranking - an explicit price sort is kept as-is. To exclude
non-supported merchants entirely, set filters.supportedOnly: true.supportedOnly is a per-request filter on global search results, and is
separate from your app’s Experimental
merchants setting.
Global search is never gated by that setting, so a testing merchant can
appear in results even when your app may not transact with it - the
merchant-scoped search, product details, or cart call is where the 403
surfaces. Setting supportedOnly: true keeps those merchants out of the
results in the first place.Merchant search filters (type: 'merchant')
Sold-out products are dropped by default. Only the explicit cannot-buy
states are dropped -
out_of_stock and discontinued (the merchant no
longer offers the product at all): products whose availability is unknown,
and products that are limited_stock, preorder, or backorder, are
always returned.Where the filter runs depends on the merchant. For merchants served from
Henry’s search index it runs inside the query, so limit counts products you
can actually buy. The Shopify catalog search methods apply it after the page
is cut, so a page may contain fewer than limit results - page until
nextCursor is null, not until a page comes back short. Best-effort:
other live search methods return whatever the merchant lists.filters.options values are case-insensitive and accept either the option
value the merchant displays ("M", "29", "black") or the merchant’s own
option code as returned by Product Details
("size-6000", "color-001") - both match the same products. Matching is
product-level: a product matches when any of its variants carries the value,
which may be a different variant than the one its link resolves to.Error handling
Next steps
Universal Cart
Use the product
link to add items to a cartMerchants
Browse supported merchants beforehand