Adding Products to Marqo
This guide shows you how to add product documents to your Marqo ecommerce search index. Learn about the core product data structure, different value types, and comprehensive examples for building a robust product catalog.
Prerequisites
- A Marqo Cloud account (sign up here)
- Your Marqo API key (find your API key guide)
- An existing ecommerce index (create an index guide)
Add (or Replace) Documents
Documents (products) can be added or replaced by a POST /documents request to the following endpoint:
Endpoint: POST https://ecom.marqo-ep.ai/api/v1/indexes/{index_name}/documents
Request Body:
{
"documents": [
{
"_id": "43207430723",
"parentProductId": "43207430720",
"productTitle": "Shirt",
"variantTitle": "Shirt - Light Blue",
"variantImageUrl": "https://cdn.shopify.com/s/files/1/0705/8276/3687/files/light-blue-tshirt.jpg?v=1754569578",
"price": 100,
"description": "Comfortable cotton shirt perfect for casual wear",
"color": "Light Blue",
"productCollections": ["shirts"]
}
]
}
Add Documents Parameters
| Add documents parameters | Value Type | Description |
|---|---|---|
documents | Array of objects | An array of documents. Each document is represented as a JSON object. |
Document Parameters
Each document has the following fields:
| Parameter | Type | Required | Description |
|---|---|---|---|
_id | string | Rejected if missing | Must be the variant ID. A unique string identifying this specific product variant. This should be unique across all variants in your catalogue. It must also correspond to the document IDs captured by the Marqo pixel. |
productTitle | string | Rejected if missing | The main product's name e.g.: "Shirt", "Wireless Headphones" |
variantTitle | string | Optional | The specific variant's name: e.g. "Shirt - Light Blue", "Wireless Headphones - Black". When omitted it defaults to productTitle. |
variantImageUrl | url | Needed for indexing | The image URL for this specific variant. Must be publicly accessible and represent the actual variant being indexed. |
price | number | Needed for indexing | The price of this specific variant. |
description | string | Optional | A detailed description of the product. This text is used for search matching and can include features, materials, use cases, etc. |
productCollections | array of strings | Optional | An array of collections that this product belongs to. |
parentProductId | string | Optional | Enables variant grouping. If provided, variants with the same parentProductId will be grouped together in search results, showing only the top-scoring variant from each group. If omitted, all variants will be treated as separate products in search results. |
:::note Two levels of "required"
Only _id and productTitle are checked when the request arrives. A document missing either one is
rejected immediately and no job is created.
variantImageUrl and price are not checked at request time. A document without them is accepted
with a 202 and fails later during indexing, so the request looks successful and the problem only
appears on the job. Always check the job outcome rather than treating 202
as confirmation that the documents were indexed.
:::
Understanding Variant Grouping
The parentProductId field controls how product variants appear in search results:
- With
parentProductId: Only the highest-scoring variant from each parent group appears in search results - Without
parentProductId: All variants are treated as individual products and can all appear in search results
Example: If you have 3 shirt variants (red, blue, light blue) with the same parentProductId, a search for "shirt" will return only the most relevant variant (e.g., the blue shirt), not all three variants.
Additional Custom Fields
You can add any other custom fields to your product documents beyond the core parameters listed above. Here are the accepted field types and their properties:
| Field Type | Description |
|---|---|
| Strings | Text fields |
| Floats | Numeric fields that can be used to filter search results |
| Bools | Boolean fields that can be used to filter search results |
| Ints | Integer fields that can be used to filter search results |
| Array | Currently only arrays of strings are supported. Can be used for filtering and lexical search |
Example with Custom Fields
{
"_id": "43208765432",
"parentProductId": "43208765430",
"productTitle": "Premium Wireless Headphones",
"variantTitle": "Premium Wireless Headphones - Black",
"variantImageUrl": "https://cdn.example.com/headphones-black.jpg",
"price": 199.99,
"productCollections": ["electronics", "audio"],
// Custom fields
"brand": "AudioTech", // String
"weight_grams": 250.5, // Float
"is_wireless": true, // Bool
"stock_quantity": 45, // Int
"features": ["bluetooth", "noise-cancel"], // Array of strings
"color": "Black", // String
"description": "High-quality over-ear headphones with active noise cancellation"
}
Adding Products to Your Index
Single Product Example
- cURL
- JavaScript
curl -X POST https://ecom.marqo-ep.ai/api/v1/indexes/{index_name}/documents \
-H "Authorization: Bearer {api_key}" \
-H "Content-Type: application/json" \
-d '{
"documents": [
{
"_id": "43208765432",
"parentProductId": "43208765430",
"productTitle": "Shirt",
"variantTitle": "Shirt - Light Blue",
"variantImageUrl": "https://cdn.shopify.com/s/files/1/0705/8276/3687/files/light-blue-tshirt.jpg?v=1754569578",
"price": 199.99,
"description": "Comfortable cotton shirt perfect for casual wear. Made from 100% organic cotton with a relaxed fit.",
"productCollections": ["shirts", "sale", "casual"],
"color": "Blue"
}
]
}'
fetch("https://ecom.marqo-ep.ai/api/v1/indexes/{index_name}/documents", {
method: "POST",
headers: {
"Authorization": "Bearer {api_key}",
"Content-Type": "application/json",
},
body: JSON.stringify({
documents: [
{
_id: "43208765432",
parentProductId: "43208765430",
productTitle: "Shirt",
variantTitle: "Shirt - Light Blue",
variantImageUrl:
"https://cdn.shopify.com/s/files/1/0705/8276/3687/files/light-blue-tshirt.jpg?v=1754569578",
price: 199.99,
description:
"Comfortable cotton shirt perfect for casual wear. Made from 100% organic cotton with a relaxed fit.",
productCollections: ["shirts", "sale", "casual"],
color: "Blue",
},
],
}),
});
This request returns 202 Accepted with a job ID. The documents are indexed in the background; see Response below.
Multiple Variants Example
This example shows how to add multiple variants of the same product with proper variant grouping:
- cURL
- JavaScript
curl -X POST https://ecom.marqo-ep.ai/api/v1/indexes/{index_name}/documents \
-H "Authorization: Bearer {api_key}" \
-H "Content-Type: application/json" \
-d '{"documents": [
{
"_id": "43207430723",
"parentProductId": "43207430720",
"productTitle": "Shirt",
"variantTitle": "Shirt - Light Blue",
"variantImageUrl": "https://cdn.shopify.com/s/files/1/0705/8276/3687/files/light-blue-tshirt.jpg?v=1754569578",
"price": 100,
"description": "Comfortable cotton shirt perfect for casual wear. Made from 100% organic cotton with a relaxed fit.",
"color": "Light Blue",
"productCollections": ["shirts"]
},
{
"_id": "43207430724",
"parentProductId": "43207430720",
"productTitle": "Shirt",
"variantTitle": "Shirt - Blue",
"variantImageUrl": "https://cdn.shopify.com/s/files/1/0705/8276/3687/files/blue-tshirt.jpg?v=1754569578",
"price": 200,
"description": "Classic blue shirt with modern styling. Breathable fabric ideal for work or leisure.",
"color": "Blue",
"productCollections": ["shirts"]
},
{
"_id": "43207430725",
"parentProductId": "43207430720",
"productTitle": "Shirt",
"variantTitle": "Shirt - Red",
"variantImageUrl": "https://cdn.shopify.com/s/files/1/0705/8276/3687/files/red-tshirt.jpg?v=1754569578",
"price": 300,
"description": "Bold red shirt that makes a statement. Premium quality cotton with excellent durability.",
"color": "Red",
"productCollections": ["shirts"]
}
]}'
fetch("https://ecom.marqo-ep.ai/api/v1/indexes/{index_name}/documents", {
method: "POST",
headers: {
"Authorization": "Bearer {api_key}",
"Content-Type": "application/json",
},
body: JSON.stringify({
documents: [
{
_id: "43207430723",
parentProductId: "43207430720",
productTitle: "Shirt",
variantTitle: "Shirt - Light Blue",
variantImageUrl:
"https://cdn.shopify.com/s/files/1/0705/8276/3687/files/light-blue-tshirt.jpg?v=1754569578",
price: 100,
description:
"Comfortable cotton shirt perfect for casual wear. Made from 100% organic cotton with a relaxed fit.",
color: "Light Blue",
productCollections: ["shirts"],
},
{
_id: "43207430724",
parentProductId: "43207430720",
productTitle: "Shirt",
variantTitle: "Shirt - Blue",
variantImageUrl:
"https://cdn.shopify.com/s/files/1/0705/8276/3687/files/blue-tshirt.jpg?v=1754569578",
price: 200,
description:
"Classic blue shirt with modern styling. Breathable fabric ideal for work or leisure.",
color: "Blue",
productCollections: ["shirts"],
},
{
_id: "43207430725",
parentProductId: "43207430720",
productTitle: "Shirt",
variantTitle: "Shirt - Red",
variantImageUrl:
"https://cdn.shopify.com/s/files/1/0705/8276/3687/files/red-tshirt.jpg?v=1754569578",
price: 300,
description:
"Bold red shirt that makes a statement. Premium quality cotton with excellent durability.",
color: "Red",
productCollections: ["shirts"],
},
],
}),
});
Note: Replace {index_name} with your actual index name and {api_key} with your API key.
Response
Adding documents is asynchronous. The request is validated, queued, and answered immediately with 202 Accepted and the ID of the job that will index the documents:
{
"jobId": "4eb23b8c-8d50-4a39-9bb1-490d8e6df521"
}
Documents become searchable once the job completes. The 202 response does not tell you whether each document was indexed. Per-document outcomes are recorded on the job: fetch it with the jobs endpoint and read jobStatus together with errorItemsDetails, failedItemsDetails, conflictDetails and skippedItemsDetails. Field-by-field details are on the Monitor Jobs page.
Checking the outcome
Retrieve the job using the jobId from the response:
- cURL
- JavaScript
curl https://ecom.marqo-ep.ai/api/v1/indexes/{index_name}/jobs/{job_id} \
-H "Authorization: Bearer {api_key}"
fetch(
"https://ecom.marqo-ep.ai/api/v1/indexes/{index_name}/jobs/{job_id}",
{
headers: {
"Authorization": "Bearer {api_key}",
},
}
);
Poll until jobStatus leaves PENDING and IN_PROGRESS:
jobStatus | Meaning |
|---|---|
COMPLETED | Every document was indexed. |
ERROR | Some documents were rejected for a reason on your side, such as an image that could not be downloaded or a field with the wrong type. The affected IDs are listed under errorItemsDetails, grouped by reason. Fix the documents and resend them. |
FAILED | Some documents failed for a server-side reason. The affected IDs are under failedItemsDetails. Resend them later. |
CONFLICT | Some documents were skipped because a newer version was already indexed. See conflictDetails. |
Errors
Requests that fail validation are rejected immediately and no job is created. Error responses have the shape {"error": ...}.
| Status | When | error |
|---|---|---|
400 | documents is an empty array | "No documents to add" |
404 | The index name is unknown, belongs to another account, or is not ready to serve requests yet | "Marqo settings not found for shop {system_account_id}-{index_name}" |
422 | A document has no productTitle | "Document at index 0 (_id='43207430723') is missing required field: productTitle" (the index and _id identify the offending document) |
422 | A document has no _id, or the request body contains a key other than documents | A list of validation issues. Each issue has a loc path to the offending value and a msg such as "Field required" or "Extra inputs are not permitted". |
Example of a 422 for a missing _id:
{
"error": [
{
"type": "missing",
"loc": ["body", "documents", 0, "_id"],
"msg": "Field required",
"input": { "productTitle": "Shirt" }
}
]
}
Each issue always carries type, loc and msg. Other keys may be present: input holds the value
that was rejected, and ctx appears on issues that have extra context such as an allowed value list.
:::note Validation messages vary between indexes
Some indexes run on a newer ingestion service. On those, a missing _id is reported as
400 with {"error": "document at index 0 is missing _id"} instead of the 422 list, an empty
documents array is reported as 400 "at least one document is required", and a document with no
productTitle is accepted with a 202 and fails during indexing instead of being rejected.
Treat any 4xx as "the write did not happen" rather than matching on the exact message, and check
the job outcome to confirm the documents were indexed.
:::
Bypassing the version conflict check
Marqo skips a document when a newer version of it has already been indexed, and reports this as the
CONFLICT job status. To force the write through regardless, send the x-marqo-bypass-conflict
header. It is accepted on adding, updating and updating by search.
curl -X POST https://ecom.marqo-ep.ai/api/v1/indexes/{index_name}/documents \
-H "Authorization: Bearer {api_key}" \
-H "Content-Type: application/json" \
-H "x-marqo-bypass-conflict: true" \
-d '{"documents": [{"_id": "43207430723", "productTitle": "Shirt"}]}'
Use the header, not a body field. documents is the only key the request body accepts, and on most
indexes any other key is rejected with 422 "Extra inputs are not permitted".
Best Practices
Data Quality
- Consistent Variant IDs: Use meaningful, consistent variant IDs that clearly identify each product variant (e.g.,
shirt-red-medium,headphones-black-001) - Proper Variant Grouping: Use
parentProductIdconsistently for variants of the same product to control search result display - High-Quality Images: Use clear, high-resolution images that accurately represent each specific variant
- Accurate Pricing: Keep prices up-to-date and ensure each variant has the correct price
- Descriptive Titles: Make
variantTitledescriptive enough to distinguish between variants
Variant Management
- With Grouping: Use
parentProductIdwhen you want only the most relevant variant to appear in search results - Without Grouping: Omit
parentProductIdwhen you want all variants to potentially appear as separate results - Consistent Grouping: If using
parentProductId, ensure all variants of the same product use the same parent ID
Image Best Practices
- Ensure
variantImageUrlpoints to an image of the specific variant, not a generic product image - Use publicly accessible URLs that won't expire
- Optimize images for fast loading while maintaining quality