Search your product catalog with a picture instead of a text query. Image search runs through the standard search endpoint. The only differences are that the request is sent as multipart/form-data with the image attached, and it must target a search engine that has been configured for image search.
This guide covers both parts:
- Setup: create an image search engine and a deployment that uses it.
- Searching: send image requests to the search endpoint.
Prerequisites
- Image search must be enabled for your account. The service needs an image model built from your catalog before it can answer requests. If you are not sure whether this has been done, contact your account team.
- API credentials for the search endpoint: an API key and secret.
- Access to the engine and deployment management UI in the XGen Platform.
| Environment | Base URL |
|---|---|
| Production | https://prompt.xgen.dev |
| Staging | https://prompt-stage.xgen.dev |
Part 1: Setup
An engine holds the search settings. A deployment points at an engine and is what your storefront references at request time. You need one engine configured for image search, and a deployment that uses it. If you already manage engines and deployments through the platform UI, you can create them there instead. The fields are the same.
Step 1. Create an image search engine
.png)
You can adjust the image search relevancy threshold here.
.png)
Step 2. Create a deployment that uses the engine
.png)
Part 2: Searching
Endpoint
POST https://prompt.xgen.dev/customers/{customer_id}/users/{user_id}
This is the same endpoint used for text search.
| Path parameter | Description |
|---|---|
customer_id |
Your customer ID. |
user_id |
An identifier you choose for the end user or session. Use the same value for all requests from the same shopper. |
| Query parameter | Default | Description |
|---|---|---|
page_size |
60 | Products per page, from 1 to 120. Values outside that range fall back to the default. |
Authentication
Every request must be authenticated in one of two ways.
Option 1: API key and secret. Send both headers.
| Header | Value |
|---|---|
x-api-key |
Your API key. |
x-api-secret |
Your API secret. |
The response includes a short-lived token in user_data.access_token that you can reuse with option 2. Tokens last 12 hours and are bound to the customer_id and user_id used to create them.
Option 2: Access token.
| Header | Value |
|---|---|
access_token |
A token from a previous response's user_data.access_token. |
Keep the API secret on your server. Do not ship it in browser or mobile code. A common pattern is for your backend to call the API once with the key and secret, then hand the returned access token to the client.
Request body
Send the body as multipart/form-data with two parts.
| Part | Type | Description |
|---|---|---|
json |
text | A JSON string with the search options. See the fields below. |
image |
file | The image. JPEG or PNG. Maximum size 5 MB. |
The image part must carry a Content-Type of image/jpeg, image/jpg, or image/png. Most HTTP clients set this from the file extension. If yours does not, set it explicitly or the request is rejected.
Fields inside the json part:
| Field | Required | Description |
|---|---|---|
deployment_id |
Yes | The deployment from setup step 2. Alternatively send behavior_id with the engine ID. If both are present, deployment_id wins. |
collection |
Yes | The product collection to search, for example "us". The locale is derived from it. |
page |
No | Zero-based page number. Defaults to 0. |
perform_faceting |
No | Set to true to receive facets, as with text search. Requires a filter_id on the deployment. |
A query field is not needed. The image is the query.
Example json part:
{
"deployment_id": "8b23b0d6-5ed1-4c92-a317-ed0b00ea4aac",
"collection": "us",
"page": 0
}
Response
A successful request returns 200 OK with the standard search response.
| Field | Type | Description |
|---|---|---|
products_list |
array | Full product records from your catalog, most similar to the image first. Only products at or above the engine's threshold are included. |
total_result |
number | Total number of matching products across all pages. |
page |
number | The page returned. |
behavior_id |
string | The engine that handled the request. |
is_from_cache |
boolean | Always false. Image results are never cached. |
user_data |
object | Contains customer_id, and when you authenticated with a key and secret, access_token and expiration_date. |
facet |
object | Present when perform_faceting is true and the deployment has a filter. |
Products do not carry a per-item similarity score. The order of products_list reflects similarity. Deployment filters, merchandising rules, and pinned products apply the same way they do for text search.
Example, truncated:
{
"page": 0,
"total_result": 143,
"behavior_id": "d6bd6099-ef18-447d-8cf3-1651c96d8747",
"is_from_cache": false,
"products_list": [
{
"prod_code": "YP0T56ZQU_0NO",
"brand": "Valentino Garavani",
"name": "Rockstud leather pump"
},
{
"prod_code": "XW2S0A66VBS_0NO",
"brand": "Valentino Garavani",
"name": "Roman Stud slingback"
}
],
"refinements": [],
"user_data": {
"customer_id": "rnvk0z2ymu3r4gmt2gjhr7jhg3g8nep7",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiration_date": "2026-09-04T09:15:00"
}
}
Product fields other than prod_code depend on your catalog configuration.
Errors
| Status | Body | Cause |
|---|---|---|
400 |
{"error": "missing json part"} |
The multipart body has no json part. |
400 |
{"error": "invalid json"} |
The json part is not valid JSON. |
401 |
{"message": "Unauthorized"} |
Missing, invalid, or expired credentials, or the token does not match the customer_id and user_id in the URL. |
500 |
{"message": "image search failed, http: no such file"} |
The image part is missing. |
500 |
{"message": "image search failed, file size exceeded."} |
The image is larger than 5 MB. |
500 |
{"message": "image search failed, file format not supported."} |
The image is not JPEG or PNG, or the part has no image content type. |
500 |
{"message": "image search failed, ..."} |
Any other failure, including image search not being enabled for the account. Retry once. If it persists, contact support with the response body. |
If the engine referenced by the deployment does not have search_type set to "image", the request is treated as a text search. It returns 200 with results for an empty query rather than an error. Check the engine configuration if image results look wrong.
Examples
Replace the placeholder values with your own.
cURL
curl -X POST "https://prompt.xgen.dev/customers/CUSTOMER_ID/users/USER_ID" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-api-secret: YOUR_API_SECRET" \
-F 'json={"deployment_id":"DEPLOYMENT_ID","collection":"us","page":0}' \
-F "image=@./shoe.jpg;type=image/jpeg"
JavaScript (browser or Node 18+)
This example uses an access token that your backend obtained with the API key and secret.
async function imageSearch(file, accessToken, page = 0) {
const formData = new FormData();
formData.append(
"json",
JSON.stringify({
deployment_id: "DEPLOYMENT_ID",
collection: "us",
page,
})
);
formData.append("image", file); // a File or Blob with an image/jpeg or image/png type
const response = await fetch(
"https://prompt.xgen.dev/customers/CUSTOMER_ID/users/USER_ID",
{
method: "POST",
headers: { access_token: accessToken },
body: formData, // do not set Content-Type; the browser adds the multipart boundary
}
);
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || data.message || `HTTP ${response.status}`);
}
return data;
}
Python
import json
import requests
url = "https://prompt.xgen.dev/customers/CUSTOMER_ID/users/USER_ID"
headers = {
"x-api-key": "YOUR_API_KEY",
"x-api-secret": "YOUR_API_SECRET",
}
payload = {
"deployment_id": "DEPLOYMENT_ID",
"collection": "us",
"page": 0,
}
with open("shoe.jpg", "rb") as f:
files = {"image": ("shoe.jpg", f, "image/jpeg")}
data = {"json": json.dumps(payload)}
response = requests.post(url, headers=headers, files=files, data=data)
response.raise_for_status()
body = response.json()
print(body["total_result"], "matches")
for product in body["products_list"]:
print(product["prod_code"])
# Reuse this token for later requests instead of sending the key and secret again.
access_token = body["user_data"]["access_token"]
Tips
- Tune the threshold on the engine, not per request. Start around 0.2 and adjust while looking at real results. A higher value returns fewer, closer matches. A value of 0 returns the whole catalog.
- Resize before uploading. Photos from a phone camera are often larger than 5 MB. Downscale to around 800 pixels on the long edge and re-encode as JPEG before sending. Similarity quality does not need a high-resolution image.
- Crop to the product. Results are best when the item of interest fills most of the frame with minimal background clutter.
- Paginate with
pageandpage_size. Because results are not cached, every page request re-runs the image match. Keep the image available on the client so you can resend it. - Cache the access token. Reuse it for the same
user_iduntilexpiration_date, then re-authenticate with the key and secret.