Storeware Docs
REST

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

ParameterInTypeDescription
storeIdpathstringThe shop.
statusquerystringOnly products in this state. One of draft, active, archived.
searchquerystringWords in the title, handle or vendor, or a SKU.
idsquerystringComma separated product ids, 100 at most: these and no others, in any state.
sortquerystringNewest 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.
limitqueryintegerHow many to return, up to 250. Fifty when not given, or every product ids names. From 1 to 250.
offsetqueryintegerHow many to skip, for the next page. At least 0.
filterquerystringOnly 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

FieldTypeDescription
dataarray of ProductRow
metaobject
meta.totalintegerHow 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

ParameterInTypeDescription
storeIdpathstringThe shop.

Body

FieldTypeRequiredDescription
titlestringYes1 to 300 characters.
handlestringNo1 to 128 characters. Matches ^[a-z0-9][a-z0-9-]*$.
descriptionstringNoUp to 50,000 characters.
statusstringNoOne of draft, active, archived. Default draft.
productTypestringNoUp to 200 characters.
vendorstringNoUp to 200 characters.
tagsarray of stringNoUp to 100 items. Default [].
optionsarray of objectNoUp to 3 items. Default [].
options[].namestringYes1 to 80 characters.
options[].valuesarray of stringYesUp to 50 items. At least 1 item.
priceobjectNo
price.amountintegerYesAt least 0.
price.currencystringYesMatches ^[A-Z]{3}$.

Answers 201

FieldTypeDescription
dataProductDetail
curl -X POST "https://api.storeware.ai/api/v1/stores/$STORE_ID/products" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d @body.json

Count 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

ParameterInTypeDescription
storeIdpathstringThe shop.

Answers 200

FieldTypeDescription
dataProductFigures
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

ParameterInTypeDescription
storeIdpathstringThe shop.
productIdpathstringThe product.

Answers 200

FieldTypeDescription
dataProductDetail
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

ParameterInTypeDescription
storeIdpathstringThe shop.
productIdpathstringThe product.

Body

FieldTypeRequiredDescription
titlestringNo1 to 300 characters.
handlestringNo1 to 128 characters. Matches ^[a-z0-9][a-z0-9-]*$.
descriptionstring or nullNoUp to 50,000 characters.
statusstringNoOne of draft, active, archived.
productTypestring or nullNoUp to 200 characters.
vendorstring or nullNoUp to 200 characters.
tagsarray of stringNoUp to 100 items.
seoTitlestring or nullNoUp to 300 characters.
seoDescriptionstring or nullNoUp to 1,000 characters.
isGiftCardbooleanNo
pinnedCopyarray of stringNoUp to 1 item.

Answers 200

FieldTypeDescription
dataProductDetail
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.json

Archive 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

ParameterInTypeDescription
storeIdpathstringThe shop.
productIdpathstringThe 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

ParameterInTypeDescription
storeIdpathstringThe shop.
productIdpathstringThe product.

Answers 200

FieldTypeDescription
dataProductSales
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

ParameterInTypeDescription
storeIdpathstringThe shop.
productIdpathstringThe product.

Answers 201

FieldTypeDescription
dataProductDetail
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

ParameterInTypeDescription
storeIdpathstringThe shop.
productIdpathstringThe product.

Answers 200

FieldTypeDescription
dataarray 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

ParameterInTypeDescription
storeIdpathstringThe shop.
productIdpathstringThe product.

Body

FieldTypeRequiredDescription
mediaIdstring (uuid)YesAn image uploaded to the shop.

Answers 201

FieldTypeDescription
dataarray 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.json

Reorder 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

ParameterInTypeDescription
storeIdpathstringThe shop.
productIdpathstringThe product.

Body

FieldTypeRequiredDescription
mediaIdsarray of string (uuid)YesEvery 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.json

Add 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

ParameterInTypeDescription
storeIdpathstringThe shop.
productIdpathstringThe product.

Body

FieldTypeRequiredDescription
namestringYesThe option, such as Colour. 1 to 80 characters.
valuestringYesWhat every variant the product already has is in it: Navy. 1 to 200 characters.

Answers 201

FieldTypeDescription
dataProductDetail
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.json

Rename 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

ParameterInTypeDescription
storeIdpathstringThe shop.
optionIdpathstringThe option.

Body

FieldTypeRequiredDescription
namestringYes1 to 80 characters.

Answers 200

FieldTypeDescription
dataProductDetail
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.json

Add 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

ParameterInTypeDescription
storeIdpathstringThe shop.
optionIdpathstringThe option.

Body

FieldTypeRequiredDescription
valuestringYesSuch as XL. 1 to 200 characters.

Answers 201

FieldTypeDescription
dataProductDetail
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.json

Rename 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

ParameterInTypeDescription
storeIdpathstringThe shop.
optionValueIdpathstringThe option value.

Body

FieldTypeRequiredDescription
valuestringYes1 to 200 characters.

Answers 200

FieldTypeDescription
dataProductDetail
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.json

Remove 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

ParameterInTypeDescription
storeIdpathstringThe shop.
optionValueIdpathstringThe option value.

Answers 200

FieldTypeDescription
dataProductDetail
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

ParameterInTypeDescription
storeIdpathstringThe shop.
idsquerystringComma separated product ids, 24 at most.

Answers 200

FieldTypeDescription
dataarray 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

ParameterInTypeDescription
storeIdpathstringThe shop.

Body

FieldTypeRequiredDescription
titlestringYes1 to 300 characters.
descriptionstringNoUp to 50,000 characters.
productTypestringNoUp to 200 characters.
mediaIdsarray of string (uuid)NoUp to 30 items. Default [].
priceobjectNo
price.amountintegerYesAt least 0.
price.currencystringYesMatches ^[A-Z]{3}$.
stockobjectNo
statusstringNoOne of draft, active. Default draft.

Answers 201

FieldTypeDescription
dataProductDetail
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.json

Change 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

ParameterInTypeDescription
storeIdpathstringThe shop.

Body

FieldTypeRequiredDescription
productIdsarray of string (uuid)YesUp to 250 items. At least 1 item.
ruleobjectYes
endInintegerNoAt least 0.
applybooleanNoDefault false.

Answers 200

FieldTypeDescription
dataobject
data.currencystring
data.changedintegerHow many prices moved, or would.
data.appliedboolean
data.rowsarray of object
data.rows[].variantIdstring
data.rows[].productIdstring
data.rows[].productTitlestring
data.rows[].variantTitlestring
data.rows[].beforeinteger
data.rows[].afterinteger
data.rows[].compareAtBeforeinteger or null
data.rows[].compareAtAfterinteger or null
data.rows[].refusedstringOne 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.json

Price 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

ParameterInTypeDescription
storeIdpathstringThe shop.

Body

FieldTypeRequiredDescription
entriesarray of objectYesUp to 24 items. At least 1 item.
entries[].productIdstring (uuid)Yes
entries[].titlestringNo1 to 300 characters.
entries[].priceobjectNo
entries[].price.amountintegerYesAt least 0.
entries[].price.currencystringYesMatches ^[A-Z]{3}$.
entries[].stockobjectNo
entries[].publishbooleanNoDefault false.

Answers 200

FieldTypeDescription
dataarray of object
data[].productIdstring (uuid)
data[].statusstring or nullWhat it is now, whether or not everything asked was done. Null if never read. One of draft, active, archived.
data[].okboolean
data[].errorobjectWhy not, when ok is false.
data[].error.codestring
data[].error.messagestring
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.json

Search 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

ParameterInTypeDescription
storeIdpathstringThe shop.

Body

FieldTypeRequiredDescription
querystringYesThe question, as an AI shopper might ask it. Up to 300 characters.
limitintegerNoHow many products. 8 unless said. From 1 to 12.

Answers 200

FieldTypeDescription
dataobject
data.resultsarray of object
data.results[].rankinteger1 for the first product an AI shopper is shown.
data.results[].productIdstring
data.results[].titlestring
data.results[].handlestring
data.results[].priceinteger or nullIn minor units, read live.
data.results[].currencystring
data.results[].availableintegerHow many can be bought now.
data.results[].purchasableboolean
data.results[].imageobject or null
data.results[].image.urlstring
data.results[].image.altstring or null
data.understoodobject
data.understood.querystringThe need, with the parts read as filters taken out.
data.understood.maxPriceinteger or nullA ceiling it read, in minor units.
data.understood.minPriceinteger or nullA floor it read, in minor units.
data.understood.requiredAttributesarray of object
data.understood.requiredAttributes[].keystring
data.understood.requiredAttributes[].valuestring
data.currencystring
data.filteredintegerFound and then left out, usually for price or stock.
data.degradedbooleanTrue 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.json

List 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

ParameterInTypeDescription
storeIdpathstringThe shop.
productIdpathstringThe product.

Answers 200

FieldTypeDescription
dataarray of object
data[].definitionIdstring (uuid)
data[].namespacestring
data[].keystring
data[].labelstringThe merchant’s name for the field.
data[].typestringsingle_line_text, multi_line_text, integer, decimal, boolean, date, url and the other definition types.
data[].isListboolean
data[].storefrontVisibleboolean
data[].searchFacetboolean
data[].positioninteger
data[].valueanyThe value, or a list of values when isList.
data[].displayValuestringThe value as one line of text, lists joined.
data[].sourcestringWho 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

ParameterInTypeDescription
storeIdpathstringThe shop.
productIdpathstringThe product.

Body

FieldTypeRequiredDescription
metafieldsarray of objectYesUp to 25 items. At least 1 item.
metafields[].namespacestringNoDefault custom.
metafields[].keystringYes
metafields[].valueanyNo

Answers 200

FieldTypeDescription
dataarray of object
data[].definitionIdstring (uuid)
data[].namespacestring
data[].keystring
data[].labelstringThe merchant’s name for the field.
data[].typestringsingle_line_text, multi_line_text, integer, decimal, boolean, date, url and the other definition types.
data[].isListboolean
data[].storefrontVisibleboolean
data[].searchFacetboolean
data[].positioninteger
data[].valueanyThe value, or a list of values when isList.
data[].displayValuestringThe value as one line of text, lists joined.
data[].sourcestringWho 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.json

List a product’s translations

GET /api/v1/stores/{storeId}/products/{productId}/translations

Every translated field, by locale.

Needs products:read.

Parameters

ParameterInTypeDescription
storeIdpathstringThe shop.
productIdpathstringThe product.

Answers 200

FieldTypeDescription
datamap of map of stringLocale, 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

ParameterInTypeDescription
storeIdpathstringThe shop.
productIdpathstringThe product.

Body

FieldTypeRequiredDescription
localestringYesMatches ^[a-z]{2,3}(-[A-Z]{2})?$.
fieldsmap of string or nullYes

Answers 200

FieldTypeDescription
datamap of stringThe 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

On this page