Published · Updated

How to Use the Shoply AI Search API

Shoply AI Search API connecting backend code to storefront product search results

You can query Shoply AI Search from your own backend, without loading Shoply’s storefront widgets or sending the request through your Shopify storefront.

The standard Search API is available on every Shoply plan for Shopify, including Forever Free. Each plan indexes a different number of products; see our pricing page. Searches made through the API do not appear in your Shoply analytics.

If you expect high or steady API traffic, or you need a service-level agreement, custom authentication, guaranteed throughput, or a catalog that is not a Shoply-indexed Shopify store, contact us about an Enterprise plan.

Before You Start

Install Shoply AI on the Shopify store whose catalog you want to search and allow the initial product index to finish. You will need the store’s permanent myshopify.com domain, such as:

text
your-store.myshopify.com

The API can be called from any backend environment. A separate API key is not required for the standard product-search endpoint because it returns the same public catalog information that Shoply can display in the storefront search experience.

Endpoints

The Search API has two endpoints. Most integrations need only the first one.

EndpointWhat it does
POST https://api.shoplyai.ai/product_query_v2Returns ranked products, the result count, the sort options, and the filters (in search-page mode, the filters are sometimes sent separately)
POST https://api.shoplyai.ai/search_display_filtersReturns the filters for a search-page result that came back without them; see Loading Filters Separately

Both endpoints take a JSON body with Content-Type: application/json.

Add store_key to the URL of every request, and send the same value in the body:

text
https://api.shoplyai.ai/product_query_v2?store_key=your-store.myshopify.com

Shoply may route requests by this URL value, so include it even though the body also carries it.

Request Body

FieldRequiredDescription
store_keyYesYour store’s permanent myshopify.com domain
queryYesA JSON-encoded string containing at least a keywords field
startNoZero-based result offset for pagination; defaults to 0
limitNoMaximum number of products to return, from 1 to 200; defaults to 4
question_contextNoA JSON object that selects search-page mode, applies filters, sorts results, or sets the language; see Search-Page Mode, Filters, and Sorting

query is a JSON string inside the request body, not a nested JSON object. For example, the search phrase waterproof hiking boots is sent as:

json
"{\"keywords\":\"waterproof hiking boots\"}"

query can also include description, a longer request in the shopper’s own words, for example {"keywords":"boots","description":"waterproof, for wide feet"}.

Send question_context as a normal JSON object, not as a string.

cURL Example

Replace your-store.myshopify.com with your store’s permanent Shopify domain in both places:

bash
curl --request POST "https://api.shoplyai.ai/product_query_v2?store_key=your-store.myshopify.com" \ --header "Content-Type: application/json" \ --data '{ "store_key": "your-store.myshopify.com", "query": "{\"keywords\":\"waterproof hiking boots\"}", "start": 0, "limit": 8 }'

JavaScript Example

This example runs in Node.js 18 or later as an ES module (for example, a .mjs file). Elsewhere, wrap it in an async function.

js
const storeKey = "your-store.myshopify.com"; // Build the inner query separately because the API expects it as a JSON-encoded string. const searchQuery = JSON.stringify({ keywords: "waterproof hiking boots" }); const url = `https://api.shoplyai.ai/product_query_v2?store_key=${encodeURIComponent(storeKey)}`; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ store_key: storeKey, query: searchQuery, start: 0, limit: 8 }) }); if (!response.ok) { throw new Error(`Shoply search failed with status ${response.status}`); } const results = await response.json(); console.log(results.docs);

Python Example

python
import json import requests store_key = "your-store.myshopify.com" # The query value is a JSON-encoded string, not a nested object. payload = { "store_key": store_key, "query": json.dumps({"keywords": "waterproof hiking boots"}), "start": 0, "limit": 8, } response = requests.post( "https://api.shoplyai.ai/product_query_v2", params={"store_key": store_key}, json=payload, timeout=30, ) response.raise_for_status() results = response.json() print(results["docs"])

Response

A successful request returns a response like this one, shortened to a single product:

json
{ "num_results": 34, "docs": [ { "product_id": "gid://shopify/Product/1234567890", "url": "https://your-store.example/products/example-product", "product_name": "Example Product", "brand": "Example Brand", "current_price": "129.00", "currency": "USD", "main_image": "https://cdn.shopify.com/example-product.jpg", "selected_variant_id": "gid://shopify/ProductVariant/1234567891" } ], "display_filters": [ { "key": "current_price", "name": "Price", "selections": [ { "value": "range_0_100", "name": "$0.00 - $99.99", "count": 12 }, { "value": "range_100_200", "name": "$100.00 - $199.99", "count": 15 } ] } ], "display_filters_pending": false, "num_results_final": true, "sort_by": "relevance", "sort_options": { "price": true, "rating": false } }
  • num_results is the number of matching products Shoply ranked, at most 200. When filters are applied, it counts only the products that match them.
  • docs contains the ranked products for the requested page. Prices, currency, and product links are for the store’s default market.
  • display_filters contains the available filters, such as price, brand, color, or other catalog-specific attributes. Each filter has a key, a display name, and a list of selections. Each selection has a value to send back when applying the filter, a display name, and usually a product count. The list is empty when the merchant has hidden filters or the search found one product or none.
  • num_results_final is false while the AI is still checking the relevance of lower-ranked products, which happens mostly in search-page mode. Until it becomes true, num_results is provisional: it can go up or down when the check finishes. Shoply’s own search page shows such a count with a plus sign, for example “34+”.
  • display_filters_pending is true only in search-page mode, when the products were sent before the filters were ready. In that case display_filters is empty; see Loading Filters Separately.
  • sort_by and sort_options describe sorting; see Sorting.

To get the final count of a provisional result, repeat the same request about two seconds later. You can set limit to 1 for the repeat.

Product objects can contain additional fields depending on the store’s catalog, variants, pricing, and Shoply configuration. Filters can also contain extra fields, such as min and max for a price range. Clients should use the fields they need and safely ignore unfamiliar fields.

Pagination

Use start and limit to request additional results. For example, after requesting eight products with start: 0 and limit: 8, request the next page with:

json
{ "start": 8, "limit": 8 }

Stop when docs is empty. You can also stop when num_results_final is true and start + docs.length has reached num_results. Pages past the 200th product are always empty.

While num_results_final is false, do not stop on the count alone, because it may still change. A later page can also repeat a product from an earlier page or skip one, so drop products whose product_id you have already shown.

Search-Page Mode, Filters, and Sorting

Without question_context, product_query_v2 returns a short list of the best matches, the way Shoply’s chat does. To get the same results as Shoply’s AI Search page, add question_context with ui_type set to search_app:

json
{ "store_key": "your-store.myshopify.com", "query": "{\"keywords\":\"snowboard\"}", "start": 0, "limit": 24, "question_context": { "ui_type": "search_app" } }

Search-page mode returns the longer list that Shoply’s AI Search page shows. It also applies the merchant’s search-page settings, such as hiding sold-out products. Use it for a custom search results page.

question_context accepts these fields:

FieldDescription
ui_typeSet to search_app for search-page results; omit it for the short default list
selected_filtersFilters to apply, as an object that maps a filter key to a list of selection values
sort_byprice_asc, price_desc, or rating; omit it to sort by relevance
language_codeThe shopper’s language, such as fr. Shoply uses it to match keywords and to translate filter names. Omit it to use the store’s default language.

Filters

To apply a filter, copy the filter’s key and the selection’s value from display_filters. For example, this request keeps only products priced under $100:

json
{ "store_key": "your-store.myshopify.com", "query": "{\"keywords\":\"snowboard\"}", "start": 0, "limit": 24, "question_context": { "ui_type": "search_app", "selected_filters": { "current_price": ["range_0_100"] } } }

Sorting

The response’s sort_options field tells you which sorts the store can offer for that search, for example {"price": true, "rating": false}. A sort the store cannot offer, or an unrecognized sort_by value, falls back to relevance. The response’s sort_by field names the sort that was applied.

Loading Filters Separately

Filters take longer to build than the product list because they need an extra AI step. For some short searches in search-page mode, Shoply returns the products first and builds the filters in the background.

Shoply decides this for each search, so always check display_filters_pending instead of predicting it. It can happen only when all of these are true:

  • question_context.ui_type is search_app.
  • The search is one or two words, with no description, and is not a product or model code.
  • At least 12 of the top 18 results already contain the searched words.

When it happens, the product_query_v2 response has display_filters_pending: true and an empty display_filters list. Show the products right away, then post these fields to search_display_filters:

FieldRequiredDescription
store_keyYesThe same store_key you sent to product_query_v2
queryYesThe same query string you sent to product_query_v2
question_contextYesThe same question_context object you sent to product_query_v2

Shoply saves each search’s full result for up to a day. This endpoint reads the filters from that saved search, which it finds from these fields, so send the same values you sent to product_query_v2. start and limit are not needed because the filters cover the whole result list, not one page.

The response looks like this:

json
{ "found": true, "display_filters": [ { "key": "current_price", "name": "Price", "selections": [ { "value": "range_0_100", "name": "$0.00 - $99.99", "count": 12 } ] } ] }
  • If the filters are still being built, the endpoint waits up to 5 seconds before it answers.
  • If display_filters is still empty after that wait, show the products without filters. An empty list can also mean the merchant hides filters or the search has too few products. Do not rerun the search just to get filters.
  • found is false when Shoply has no saved search for that store_key, query, and question_context. This endpoint never runs a search itself, so call product_query_v2 first.

You do not need to wait for the filters before applying one. If a shopper picks a filter, send it in selected_filters to product_query_v2 as usual. Shoply waits for its own filter data before applying the selection.

js
const API_BASE = "https://api.shoplyai.ai"; // Both endpoints take store_key in the URL as well as in the body. async function postToShoply(endpoint, body) { const url = `${API_BASE}/${endpoint}?store_key=${encodeURIComponent(body.store_key)}`; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); if (!response.ok) { throw new Error(`Shoply ${endpoint} failed with status ${response.status}`); } return response.json(); } // Returns the products at once, plus a promise for the filters, so the page can show // products before the filters arrive. async function searchWithFilters(storeKey, keywords) { const searchRequest = { store_key: storeKey, query: JSON.stringify({ keywords }), question_context: { ui_type: "search_app" } }; const results = await postToShoply("product_query_v2", { ...searchRequest, start: 0, limit: 24 }); const filters = results.display_filters_pending // Same store_key, query, and question_context, so Shoply can find the saved search. ? postToShoply("search_display_filters", searchRequest) .then(answer => (answer.found ? answer.display_filters : [])) : Promise.resolve(results.display_filters); return { results, filters }; }

Requests without question_context, or with a ui_type other than search_app, always return their filters in the product_query_v2 response. They never set display_filters_pending.

Errors

StatusWhen it happensWhat to do
400The body is not valid JSON, store_key is missing or blank, a field has the wrong type, query is not a JSON-encoded object, question_context is not an object, start is negative, or limit is outside 1 to 200Fix the request. The response’s detail field names the problem.
409Shoply has no search index for this store_key: the key is misspelled, it is a custom domain instead of the myshopify.com domain, the store has not installed Shoply AI, or the first index is still being builtCheck the store_key first. If it is correct and the store installed Shoply AI recently, retry in a few minutes.

Integration Notes

  • Keep the call on your backend if you want to centralize caching, logging, retries, or access control.
  • Your Shopify store must remain connected to Shoply AI so its product index can stay current.
  • In search-page mode, handle display_filters_pending so short searches still show filters.
  • In every mode, treat num_results_final: false as a provisional count.
  • If your custom storefront also needs conversation, add chat to a headless Shopify storefront.

For help confirming your store key or planning a production integration, contact Shoply AI.