Products
The catalogue: products, their media and their fields.
List products
GET /api/v1/stores/{storeId}/products
A page of the catalogue, newest first unless sort says otherwise. Archived products are left out unless status asks for them or ids names them. meta.total counts every match.
Needs products:read.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
status | query | string | Only products in this state. One of draft, active, archived. |
search | query | string | Words in the title, handle or vendor, or a SKU. |
ids | query | string | Comma separated product ids, 100 at most: these and no others, in any state. |
sort | query | string | Newest first when not given. An order it does not know is ignored. One of created_desc, created_asc, updated_desc, updated_asc, title_asc, title_desc. |
limit | query | integer | How many to return, up to 250. Fifty when not given, or every product ids names. From 1 to 250. |
offset | query | integer | How many to skip, for the next page. At least 0. |
filter | query | string | Only the products one figure counts: out_of_stock, never_read, or sold_30d (needs analytics:read). One it does not know is ignored. One of out_of_stock, never_read, sold_30d. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | array of ProductRow | |
meta | object | |
meta.total | integer | How many match, across every page. |
curl "https://api.storeware.ai/api/v1/stores/$STORE_ID/products" \
-H "Authorization: Bearer $TOKEN"Create a product
POST /api/v1/stores/{storeId}/products
Makes a variant for every combination of the options, or one variant when there are none, and gives each the price. It is a draft unless status says otherwise, and its handle comes from the title when none is given. Up to 3 options and 200 variants (too_many_variants).
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | 1 to 300 characters. |
handle | string | No | 1 to 128 characters. Matches ^[a-z0-9][a-z0-9-]*$. |
description | string | No | Up to 50,000 characters. |
status | string | No | One of draft, active, archived. Default draft. |
productType | string | No | Up to 200 characters. |
vendor | string | No | Up to 200 characters. |
tags | array of string | No | Up to 100 items. Default []. |
options | array of object | No | Up to 3 items. Default []. |
options[].name | string | Yes | 1 to 80 characters. |
options[].values | array of string | Yes | Up to 50 items. At least 1 item. |
price | object | No | |
price.amount | integer | Yes | At least 0. |
price.currency | string | Yes | Matches ^[A-Z]{3}$. |
Answers 201
| Field | Type | Description |
|---|---|---|
data | ProductDetail |
curl -X POST "https://api.storeware.ai/api/v1/stores/$STORE_ID/products" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @body.jsonCount the catalogue
GET /api/v1/stores/{storeId}/products/figures
Four figures over the catalogue, leaving out archived products: drafts, out of stock, never read by the shop, and sold in the last thirty days. Each but the first counts what the list’s matching filter returns.
Needs products:read.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | ProductFigures |
curl "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/figures" \
-H "Authorization: Bearer $TOKEN"Get a product
GET /api/v1/stores/{storeId}/products/{productId}
The product with its options, its variants and each variant’s prices.
Needs products:read.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
productId | path | string | The product. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | ProductDetail |
curl "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/$PRODUCT_ID" \
-H "Authorization: Bearer $TOKEN"Update a product
PATCH /api/v1/stores/{storeId}/products/{productId}
Changes only the fields sent, and a null empties a field that may be empty. A new handle leaves a redirect from the old address. Variants, options and images have calls of their own.
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
productId | path | string | The product. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | 1 to 300 characters. |
handle | string | No | 1 to 128 characters. Matches ^[a-z0-9][a-z0-9-]*$. |
description | string or null | No | Up to 50,000 characters. |
status | string | No | One of draft, active, archived. |
productType | string or null | No | Up to 200 characters. |
vendor | string or null | No | Up to 200 characters. |
tags | array of string | No | Up to 100 items. |
seoTitle | string or null | No | Up to 300 characters. |
seoDescription | string or null | No | Up to 1,000 characters. |
isGiftCard | boolean | No | |
pinnedCopy | array of string | No | Up to 1 item. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | ProductDetail |
curl -X PATCH "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/$PRODUCT_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @body.jsonArchive a product
DELETE /api/v1/stores/{storeId}/products/{productId}
Archives the product rather than deleting it, so past orders still point at it. Its status becomes archived and lists leave it out. Archiving it again answers product_not_found.
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
productId | path | string | The product. |
Answers 204: Done, with nothing to send back.
curl -X DELETE "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/$PRODUCT_ID" \
-H "Authorization: Bearer $TOKEN"Get a product’s sales by day
GET /api/v1/stores/{storeId}/products/{productId}/sales
Units of this product ordered on each of the last 30 days, oldest first and today last, counting orders that are still sales.
Needs products:read and analytics:read.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
productId | path | string | The product. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | ProductSales |
curl "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/$PRODUCT_ID/sales" \
-H "Authorization: Bearer $TOKEN"Copy a product
POST /api/v1/stores/{storeId}/products/{productId}/duplicate
Makes a draft copy with the same words, options, variants, prices, pictures, custom data, translations and manual collections. SKUs and barcodes are left empty and stock starts at zero. Reviews, sales history, subscription plans and download files stay with the original. Send an Idempotency-Key so that a retry makes one copy.
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
productId | path | string | The product. |
Answers 201
| Field | Type | Description |
|---|---|---|
data | ProductDetail |
curl -X POST "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/$PRODUCT_ID/duplicate" \
-H "Authorization: Bearer $TOKEN"List a product’s images
GET /api/v1/stores/{storeId}/products/{productId}/media
The product’s gallery in order, each image with its address. The first is the cover.
Needs products:read.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
productId | path | string | The product. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | array of ProductMedia |
curl "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/$PRODUCT_ID/media" \
-H "Authorization: Bearer $TOKEN"Add an image to a product
POST /api/v1/stores/{storeId}/products/{productId}/media
Puts an image already uploaded to the shop at the end of the product’s gallery, and answers with the whole gallery. Adding one already there changes nothing.
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
productId | path | string | The product. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
mediaId | string (uuid) | Yes | An image uploaded to the shop. |
Answers 201
| Field | Type | Description |
|---|---|---|
data | array of ProductMedia |
curl -X POST "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/$PRODUCT_ID/media" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @body.jsonReorder a product’s images
POST /api/v1/stores/{storeId}/products/{productId}/media/order
Takes the whole gallery as a list of image ids, the cover first. Ids not on the product are ignored, and images left out keep their order behind the rest.
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
productId | path | string | The product. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
mediaIds | array of string (uuid) | Yes | Every image, in the order wanted. |
Answers 204: Done, with nothing to send back.
curl -X POST "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/$PRODUCT_ID/media/order" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @body.jsonAdd an option to a product
POST /api/v1/stores/{storeId}/products/{productId}/options
Adds an option such as Colour, with the value every existing variant takes: each variant’s title gains that value and nothing else changes. Add more values with addOptionValue. Refused with option_name_taken for a name the product already has, and option_not_addable when it has 3 options.
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
productId | path | string | The product. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The option, such as Colour. 1 to 80 characters. |
value | string | Yes | What every variant the product already has is in it: Navy. 1 to 200 characters. |
Answers 201
| Field | Type | Description |
|---|---|---|
data | ProductDetail |
curl -X POST "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/$PRODUCT_ID/options" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @body.jsonRename an option
PATCH /api/v1/stores/{storeId}/options/{optionId}
Changes an option’s name. Variant titles are made of values alone, so none of them change. Answers with the whole product.
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
optionId | path | string | The option. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | 1 to 80 characters. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | ProductDetail |
curl -X PATCH "https://api.storeware.ai/api/v1/stores/$STORE_ID/options/$OPTION_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @body.jsonAdd a value to an option
POST /api/v1/stores/{storeId}/options/{optionId}
Adds the value, and a variant for it with each combination of the other options. The new variants copy their prices from the variant that differs only in this option, and start with no stock. Up to 50 values and 200 variants. Answers with the whole product.
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
optionId | path | string | The option. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
value | string | Yes | Such as XL. 1 to 200 characters. |
Answers 201
| Field | Type | Description |
|---|---|---|
data | ProductDetail |
curl -X POST "https://api.storeware.ai/api/v1/stores/$STORE_ID/options/$OPTION_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @body.jsonRename an option value
PATCH /api/v1/stores/{storeId}/option-values/{optionValueId}
Changes a value’s wording and retitles the variants that carry it, except one whose title was set by hand. Answers with the whole product.
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
optionValueId | path | string | The option value. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
value | string | Yes | 1 to 200 characters. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | ProductDetail |
curl -X PATCH "https://api.storeware.ai/api/v1/stores/$STORE_ID/option-values/$OPTION_VALUE_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @body.jsonRemove an option value
DELETE /api/v1/stores/{storeId}/option-values/{optionValueId}
Deletes the value and every variant that carries it, stock included. Refused with option_value_sold when any of those variants is on an order, and last_option_value for an option’s only value. Answers with the whole product.
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
optionValueId | path | string | The option value. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | ProductDetail |
curl -X DELETE "https://api.storeware.ai/api/v1/stores/$STORE_ID/option-values/$OPTION_VALUE_ID" \
-H "Authorization: Bearer $TOKEN"Get several listings
GET /api/v1/stores/{storeId}/products/listings
Up to 24 products by id, in the order asked, each with its price in the shop’s currency and its first pictures. An id that is not one of the shop’s products is left out rather than refused, and with no ids the list is empty.
Needs products:read.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
ids | query | string | Comma separated product ids, 24 at most. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | array of Listing |
curl "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/listings" \
-H "Authorization: Bearer $TOKEN"Create a listing
POST /api/v1/stores/{storeId}/products/listings
Makes a product with one variant, its pictures, a price and a count, then puts it on sale last when status is active, so a shopper never meets it half made. Refused before anything is written with listing_needs_price (on sale with no price) or listing_needs_shelf (a count in a shop with no location of its own).
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | 1 to 300 characters. |
description | string | No | Up to 50,000 characters. |
productType | string | No | Up to 200 characters. |
mediaIds | array of string (uuid) | No | Up to 30 items. Default []. |
price | object | No | |
price.amount | integer | Yes | At least 0. |
price.currency | string | Yes | Matches ^[A-Z]{3}$. |
stock | object | No | |
status | string | No | One of draft, active. Default draft. |
Answers 201
| Field | Type | Description |
|---|---|---|
data | ProductDetail |
curl -X POST "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/listings" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @body.jsonChange many prices at once
POST /api/v1/stores/{storeId}/products/prices
One rule across up to 250 products, in the shop’s own currency: a new price, a percentage up or down, or an amount up or down, with an optional endIn (99 for .99). Without apply nothing is written and every variant’s price before and after comes back. With apply: true they are written together, each kept in the variant’s price history as a bulk change. A "was" price the new price reaches is removed. Refused whole, with nothing written, when any price would come to nothing or less (bulk_price_refused).
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
productIds | array of string (uuid) | Yes | Up to 250 items. At least 1 item. |
rule | object | Yes | |
endIn | integer | No | At least 0. |
apply | boolean | No | Default false. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | object | |
data.currency | string | |
data.changed | integer | How many prices moved, or would. |
data.applied | boolean | |
data.rows | array of object | |
data.rows[].variantId | string | |
data.rows[].productId | string | |
data.rows[].productTitle | string | |
data.rows[].variantTitle | string | |
data.rows[].before | integer | |
data.rows[].after | integer | |
data.rows[].compareAtBefore | integer or null | |
data.rows[].compareAtAfter | integer or null | |
data.rows[].refused | string | One of not_positive. |
curl -X POST "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/prices" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @body.jsonPrice and publish several listings
POST /api/v1/stores/{storeId}/products/listings/settle
Names, prices and counts up to 24 products of one variant each, and puts on sale those marked publish. Each entry is done on its own and one refusal does not stop the rest, so read every outcome rather than taking a 200 as all done. An entry is refused with listing_needs_price, listing_needs_shelf or listing_has_options.
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
entries | array of object | Yes | Up to 24 items. At least 1 item. |
entries[].productId | string (uuid) | Yes | |
entries[].title | string | No | 1 to 300 characters. |
entries[].price | object | No | |
entries[].price.amount | integer | Yes | At least 0. |
entries[].price.currency | string | Yes | Matches ^[A-Z]{3}$. |
entries[].stock | object | No | |
entries[].publish | boolean | No | Default false. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | array of object | |
data[].productId | string (uuid) | |
data[].status | string or null | What it is now, whether or not everything asked was done. Null if never read. One of draft, active, archived. |
data[].ok | boolean | |
data[].error | object | Why not, when ok is false. |
data[].error.code | string | |
data[].error.message | string |
curl -X POST "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/listings/settle" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @body.jsonSearch as an AI shopper
POST /api/v1/stores/{storeId}/search/as-ai-shopper
Answers a question the way the shop answers an AI shopper: only live products, in the order one is given them, with what the shop understood the question to mean. Nothing is recorded as a question the shop was asked. Each shop may try 10 questions a minute and 60 an hour, counted together for everybody on it. Past that it answers 429 rate_limited, with Retry-After saying how many seconds to wait.
Needs products:read.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
query | string | Yes | The question, as an AI shopper might ask it. Up to 300 characters. |
limit | integer | No | How many products. 8 unless said. From 1 to 12. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | object | |
data.results | array of object | |
data.results[].rank | integer | 1 for the first product an AI shopper is shown. |
data.results[].productId | string | |
data.results[].title | string | |
data.results[].handle | string | |
data.results[].price | integer or null | In minor units, read live. |
data.results[].currency | string | |
data.results[].available | integer | How many can be bought now. |
data.results[].purchasable | boolean | |
data.results[].image | object or null | |
data.results[].image.url | string | |
data.results[].image.alt | string or null | |
data.understood | object | |
data.understood.query | string | The need, with the parts read as filters taken out. |
data.understood.maxPrice | integer or null | A ceiling it read, in minor units. |
data.understood.minPrice | integer or null | A floor it read, in minor units. |
data.understood.requiredAttributes | array of object | |
data.understood.requiredAttributes[].key | string | |
data.understood.requiredAttributes[].value | string | |
data.currency | string | |
data.filtered | integer | Found and then left out, usually for price or stock. |
data.degraded | boolean | True when the question could not be read and this is a plain match. |
curl -X POST "https://api.storeware.ai/api/v1/stores/$STORE_ID/search/as-ai-shopper" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @body.jsonList a product’s metafields
GET /api/v1/stores/{storeId}/products/{productId}/metafields
Every metafield value set on a product, each with its definition.
Needs products:read.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
productId | path | string | The product. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | array of object | |
data[].definitionId | string (uuid) | |
data[].namespace | string | |
data[].key | string | |
data[].label | string | The merchant’s name for the field. |
data[].type | string | single_line_text, multi_line_text, integer, decimal, boolean, date, url and the other definition types. |
data[].isList | boolean | |
data[].storefrontVisible | boolean | |
data[].searchFacet | boolean | |
data[].position | integer | |
data[].value | any | The value, or a list of values when isList. |
data[].displayValue | string | The value as one line of text, lists joined. |
data[].source | string | Who set it: the merchant, an import, or an app’s slug. |
curl "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/$PRODUCT_ID/metafields" \
-H "Authorization: Bearer $TOKEN"Set a product’s metafields
PUT /api/v1/stores/{storeId}/products/{productId}/metafields
Sets up to 25 values in one call. A null value clears its field; keys not named are left alone. An app may set only the fields its manifest declares.
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
productId | path | string | The product. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
metafields | array of object | Yes | Up to 25 items. At least 1 item. |
metafields[].namespace | string | No | Default custom. |
metafields[].key | string | Yes | |
metafields[].value | any | No |
Answers 200
| Field | Type | Description |
|---|---|---|
data | array of object | |
data[].definitionId | string (uuid) | |
data[].namespace | string | |
data[].key | string | |
data[].label | string | The merchant’s name for the field. |
data[].type | string | single_line_text, multi_line_text, integer, decimal, boolean, date, url and the other definition types. |
data[].isList | boolean | |
data[].storefrontVisible | boolean | |
data[].searchFacet | boolean | |
data[].position | integer | |
data[].value | any | The value, or a list of values when isList. |
data[].displayValue | string | The value as one line of text, lists joined. |
data[].source | string | Who set it: the merchant, an import, or an app’s slug. |
curl -X PUT "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/$PRODUCT_ID/metafields" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @body.jsonList a product’s translations
GET /api/v1/stores/{storeId}/products/{productId}/translations
Every translated field, by locale.
Needs products:read.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
productId | path | string | The product. |
Answers 200
| Field | Type | Description |
|---|---|---|
data | map of map of string | Locale, then field, then the translated text. |
curl "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/$PRODUCT_ID/translations" \
-H "Authorization: Bearer $TOKEN"Set a product’s translations
PUT /api/v1/stores/{storeId}/products/{productId}/translations
Sets translated fields for one locale. Fields not named are left alone, and a null clears one.
Needs products:write.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
storeId | path | string | The shop. |
productId | path | string | The product. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
locale | string | Yes | Matches ^[a-z]{2,3}(-[A-Z]{2})?$. |
fields | map of string or null | Yes |
Answers 200
| Field | Type | Description |
|---|---|---|
data | map of string | The locale’s fields after the change. |
curl -X PUT "https://api.storeware.ai/api/v1/stores/$STORE_ID/products/$PRODUCT_ID/translations" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @body.json