# Products

> The catalogue: products, their media and their fields.

Source: https://storeware.ai/docs/api-reference/rest/products

## 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](https://storeware.ai/docs/api-reference/rest/objects#productrow) |  |
| `meta` | object |  |
| `meta.total` | integer | How many match, across every page. |

```bash
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](https://storeware.ai/docs/api-reference/rest/objects#productdetail) |  |

```bash
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**

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `storeId` | path | string | The shop. |

**Answers** `200`

| Field | Type | Description |
| --- | --- | --- |
| `data` | [ProductFigures](https://storeware.ai/docs/api-reference/rest/objects#productfigures) |  |

```bash
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](https://storeware.ai/docs/api-reference/rest/objects#productdetail) |  |

```bash
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](https://storeware.ai/docs/api-reference/rest/objects#productdetail) |  |

```bash
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**

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `storeId` | path | string | The shop. |
| `productId` | path | string | The product. |

**Answers** `204`: Done, with nothing to send back.

```bash
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](https://storeware.ai/docs/api-reference/rest/objects#productsales) |  |

```bash
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](https://storeware.ai/docs/api-reference/rest/objects#productdetail) |  |

```bash
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](https://storeware.ai/docs/api-reference/rest/objects#productmedia) |  |

```bash
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](https://storeware.ai/docs/api-reference/rest/objects#productmedia) |  |

```bash
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**

| 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.

```bash
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**

| 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](https://storeware.ai/docs/api-reference/rest/objects#productdetail) |  |

```bash
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**

| 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](https://storeware.ai/docs/api-reference/rest/objects#productdetail) |  |

```bash
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**

| 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](https://storeware.ai/docs/api-reference/rest/objects#productdetail) |  |

```bash
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**

| 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](https://storeware.ai/docs/api-reference/rest/objects#productdetail) |  |

```bash
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**

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `storeId` | path | string | The shop. |
| `optionValueId` | path | string | The option value. |

**Answers** `200`

| Field | Type | Description |
| --- | --- | --- |
| `data` | [ProductDetail](https://storeware.ai/docs/api-reference/rest/objects#productdetail) |  |

```bash
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](https://storeware.ai/docs/api-reference/rest/objects#listing) |  |

```bash
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](https://storeware.ai/docs/api-reference/rest/objects#productdetail) |  |

```bash
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**

| 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`. |

```bash
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**

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

```bash
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**

| 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. |

```bash
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**

| 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. |

```bash
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. |

```bash
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**

| 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. |

```bash
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. |

```bash
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
```
