Upload items to a dataset
/v1/datasets/{datasetId}/items/uploadQueues up to 5,000 products for processing in one request. Nothing is
searchable yet when this call returns: each product becomes a
pending queue entry, and a background worker turns it into a
DatasetItem — downloading and storing its images, extracting style
metadata and computing embeddings. Track progress with the queue
endpoints; the itemId of a queue entry is the itemId of the item it
produces.
disableGenerativeAssets is accepted for compatibility but ignored:
generative thumbnails are disabled platform-wide and every entry is
stored with the value true.
Validation
Validation is all-or-nothing. Every product is checked before any is
queued; if one fails, the whole request is rejected and nothing is
written. Failures come back in details as { index, path, message }
where index is the product's position in items (or -1 for
request-wide SKU problems) and path is the offending field. The
per-field messages are:
Product must be an object.Expected string, got <type>— one ofname,handle,description,publishedAt,createdAt,updatedAt,productType,skuis not a stringPrice must be a non-negative number.SKU must not be empty.—skuis blank or only whitespaceUnparseable date: <value>—publishedAt,createdAtorupdatedAtcannot be parsed byDateExpected an array.—tags,images,optionsorvariantsis not an arrayExpected <type>, got <type>— an element of those arrays has the wrong type (tagsmust hold strings; the others objects)Expected string.— a variant'ssku,name,createdAt,updatedAtorproductIdis not a stringPrice must be non-negative number.— a variant'spriceExpected boolean.— a variant'savailableExpected one of mens, womens, unisex, or null.—genderis present and not one of those
Dates are normalised to ISO 8601 UTC before they are stored. An empty
publishedAt is accepted and means the product is unpublished. Fields
outside the documented shape are dropped.
A few rules are enforced only by the database when the batch is
written, and break the whole request with the shared
500 Internal server error. (nothing is queued): name and handle
must be non-empty; each variant's name and productId must be
non-empty; every images entry needs a src; tags and option
values must not contain empty strings. A variants value that is
neither an array nor omitted, or a null element inside variants,
also returns the shared 500 Internal server error.
Re-uploading a product
SKUs are matched ignoring case and surrounding whitespace, the same way
a data source sync matches them. A SKU must appear only once in the
request. When it already has a queue entry in the dataset (in any
status except archived), the upload updates that entry instead of
creating a second one:
updatedAtdiffers from the stored product → the entry is re-queued. If the image URLs changed it goes through full processing again (pending); otherwise only name, description, price and sizes are refreshed (partial_update).- The entry is
failed→ it is re-queued for full processing. - Otherwise nothing is written; the product is counted as
unchanged.
A SKU whose only entry is archived brings that entry back, with the
same itemId, and is counted as revived. If it was fully processed
and its image URLs, name and product type are unchanged, it returns
through partial_update; otherwise it is processed again (pending).
This makes the endpoint safe to retry, and usable for price and stock
refreshes as long as updatedAt moves with the change.
Path parameters
1The dataset's id, as returned when it was created.
Request body
application/jsonrequiredProducts to queue. Between 1 and 5,000 entries.
Product title. Must be non-empty.
URL slug. The product URL is built as <store URL>/products/<handle>. Must be non-empty.
Product description; HTML is allowed. May be an empty string.
Any string Date can parse (ISO 8601 recommended).
Any string Date can parse.
Any string Date can parse.
Merchant category. May be an empty string.
Product-level SKU. Must be non-empty, unique within the request, and not already present in the dataset's queue.
Product price. Must be a non-negative number.
Free-form tags. Every element must be a non-empty string.
Images to download. Every element must be an object with src.
Product options. Every element must be an object.
Purchasable variants. Every element must be an object; a null element fails the request with the shared 500.
Optional. Who you sell the product to, when you know. It is stored
as the item's metadata.gender in place of the one the labeller
would choose, ahead of any department the title or tags name and
of the dataset's targetGender. Products that are not worn (home
goods, beauty, gift cards) are always unisex. Omit it or send
null to let the labeller decide. Changing it on an existing SKU
relabels the product.
menswomensunisexnullMust be a boolean when present (null is rejected). Currently ignored — every entry is stored with true.
Response
Always true on this response.
Number of products accepted — equal to the length of items, and to added + updated + revived + unchanged.
Products queued as new entries.
Existing queue entries re-queued with the new product data.
Archived entries brought back with the new product data, keeping their itemId.
Products whose queue entry was already up to date; nothing was written for them.
Non-blocking notices about individual products. Reserved; the current validator emits none, so the key is absent.
Position of the product in items.
Field the notice refers to.
The notice.
Errors
26Every error is JSON with a single error string unless noted. Match on the status code; the message is for humans.
Request body must be valid JSON.The request body could not be parsed as JSON.
Request body must be a JSON object.The request body is empty, or parses to something other than a JSON object (for example an array or null).
Organization not found. Please check the organizationId.The organization encoded in the API key no longer exists.
Dataset not found for this organization.No dataset with this datasetId belongs to the key's organization.
Upload limit exceeded (<count> > 5000).items holds more than 5,000 products. Split the upload into batches.
The 'disableGenerativeAssets' field must be either true or false when provided.disableGenerativeAssets is present but not a boolean (null counts as invalid).
`items` must be a non-empty array.items is missing, not an array, or empty.
Duplicate SKUs in payload.The same sku (ignoring case and surrounding whitespace) appears on more than one product in items. Checked after per-product validation passes. details names each repeat by its position in items.
{
"error": "Duplicate SKUs in payload.",
"details": [
{
"index": 3,
"path": "sku",
"message": "A1A2P-BB2J appears earlier in this request."
}
]
}Validation failed.One or more products failed field validation. details lists every failure as { index, path, message }; nothing was queued.
{
"error": "Validation failed.",
"details": [
{
"index": 0,
"path": "price",
"message": "Price must be a non-negative number."
},
{
"index": 2,
"path": "variants[0].available",
"message": "Expected boolean."
}
]
}curl -X POST "https://stylor.ai/api/v1/datasets/YOUR_DATASET_ID/items/upload" \
-H "Authorization: Bearer $STYLOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "Classic Crew Tee",
"handle": "classic-crew-tee",
"description": "<p>A heavyweight organic cotton tee with a relaxed fit.</p>",
"publishedAt": "2024-11-02T09:15:00Z",
"createdAt": "2024-11-02T09:15:00Z",
"updatedAt": "2025-02-10T12:00:00Z",
"productType": "T-Shirts",
"sku": "A1A2P-BB2J",
"price": 49,
"tags": [
"organic",
"basics"
],
"images": [
{
"src": "https://cdn.shopify.com/s/files/1/0001/products/tee-front.jpg"
},
{
"src": "https://cdn.shopify.com/s/files/1/0001/products/tee-back.jpg"
}
],
"options": [
{
"name": "Size",
"values": [
"Medium",
"Large"
]
}
],
"variants": [
{
"sku": "A1A2P-BB2J-M",
"name": "Medium",
"available": true,
"price": 49,
"createdAt": "2024-11-02T09:15:00Z",
"updatedAt": "2025-02-10T12:00:00Z",
"productId": "8412345678901"
},
{
"sku": "A1A2P-BB2J-L",
"name": "Large",
"available": false,
"price": 49,
"createdAt": "2024-11-02T09:15:00Z",
"updatedAt": "2025-02-10T12:00:00Z",
"productId": "8412345678901"
}
]
}
]
}'{
"success": true,
"uploaded": 1,
"added": 1,
"updated": 0,
"unchanged": 0
}