# Products API

> Keep your shop’s catalog in Emailit for product grids, recommendations and order items.

Base URL: `https://api.emailit.com/v2`. Authenticate with `Authorization: Bearer <API key>`.

## Create or update a product — POST /products

> Add a product to your catalog, or update it, keyed by the product ID in your shop. Alpha: open to the Emailit team only.

# Create or update a product

Add a product to your catalog, or update it if a product with the same `id` already exists. The `id` is the product ID in your shop, so you can send the same product again whenever it changes. Requires an API key with the `full` or `events` scope. With an `events` key, the response holds only `object`, `id` and `external_id`.

> **Commerce is in alpha:** Commerce is open only to the Emailit team while we test it. Other requests return `403` with `error: "commerce_alpha"`. API keys work only in workspaces owned by the Emailit team. See [Who can use Commerce](/docs/commerce/#who-can-use-commerce).

`id` is always required, and `title` is required for a new product. Fields are written only when you send them, so a product you send again with just a new price keeps its title, categories, image and the rest. To send many products at once, use [Create or update products in bulk](/docs/api-reference/products/batch/).

Product grids, recommendations and order items in emails read from the catalog. See [Products, coupons and the cart](/docs/campaigns/products-and-coupons/).

`POST /products`

## Request Body

- `id` (string | number, required): The product ID in your shop, up to 255 characters. Numbers are stored as strings. Stored as `external_id`. You can also send it as `external_id`.

- `title` (string): Product name, up to 500 characters. You can also send it as `name`. Required for a new product.

- `sku` (string): Stock keeping unit, up to 255 characters.

- `description` (string): Product description, up to 10,000 characters.

- `url` (string): Product page in your shop. An `http` or `https` URL, up to 2,048 characters.

- `image_url` (string): Product image. An `http` or `https` URL, up to 2,048 characters.

- `price` (number | string): Current price, as a number or a numeric string such as `"89.00"`. Can't be negative. Stored with up to 4 decimal places.

- `compare_at_price` (number | string): Price before a discount, in the same format as `price`. When it's higher than `price`, the product counts as on sale.

- `currency` (string): Three-letter ISO 4217 code, such as `USD` or `EUR`. Lowercase is accepted. Emails format the product's prices in this currency, or in USD when it isn't set.

- `categories` (string[]): Up to 50 category names of up to 191 characters each. Duplicates are removed.

- `brand` (string): Brand name, up to 191 characters.

- `inventory_quantity` (integer | null): Units in stock. `null` means you don't track stock. Product grids in emails leave out products at `0`.

- `status` (string): `active` or `archived`. New products are `active`. Archived products are hidden from [List products](/docs/api-reference/products/list/) by default and left out of product grids.

- `metadata` (object): Your own key-value data, up to 16 KB as JSON. Sending `metadata` replaces the stored object.

## Returns

`201 Created` with the [product object](/docs/api-reference/products/get/) when the product is new, or `200 OK` when an existing product was updated. Returns `422` when a field is invalid.
**Request** `POST /products`

**cURL**

```bash
curl -X POST https://api.emailit.com/v2/products \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "8123456789",
    "title": "Trail running shoe",
    "sku": "TRS-BLU-42",
    "url": "https://shop.example.com/products/trail-running-shoe",
    "image_url": "https://shop.example.com/images/trail-running-shoe.jpg",
    "price": 89,
    "compare_at_price": 119,
    "currency": "USD",
    "categories": ["Shoes", "Running"],
    "brand": "Acme Outdoors",
    "inventory_quantity": 34
  }'
```

**201**

```json
{
  "object": "product",
  "id": "prd_3r6HHUGUSl8yjYDscEA6aYA8wrA",
  "external_id": "8123456789",
  "sku": "TRS-BLU-42",
  "title": "Trail running shoe",
  "description": null,
  "url": "https://shop.example.com/products/trail-running-shoe",
  "image_url": "https://shop.example.com/images/trail-running-shoe.jpg",
  "price": 89,
  "compare_at_price": 119,
  "currency": "USD",
  "categories": [
    "Shoes",
    "Running"
  ],
  "brand": "Acme Outdoors",
  "inventory_quantity": 34,
  "status": "active",
  "metadata": {},
  "created_at": "2026-10-08T14:21:07.512Z",
  "updated_at": "2026-10-08T14:21:07.512Z"
}
```

**422**

```json
{
  "message": "Validation failed. price: must not be negative",
  "errors": {
    "price": [
      "must not be negative"
    ]
  }
}
```

---
Source: https://emailit.com/docs/api-reference/products/create/

## Retrieve a product — GET /products/{id}

> Retrieve a product from your catalog by its Emailit ID or by the product ID in your shop. Alpha: open to the Emailit team only.

# Retrieve a product

Retrieve a product by its Emailit ID or by the product ID in your shop. Requires an API key with the `full` scope.

> **Commerce is in alpha:** Commerce is open only to the Emailit team while we test it. Other requests return `403` with `error: "commerce_alpha"`. API keys work only in workspaces owned by the Emailit team. See [Who can use Commerce](/docs/commerce/#who-can-use-commerce).

`GET /products/{id}`

## Path Parameters

- `id` (string, required): The product ID (`prd_...`) or the product ID in your shop (`external_id`). URL-encode shop IDs that contain special characters.

## The product object

- `object` (string): Always `product`.

- `id` (string): Emailit product ID (`prd_...`).

- `external_id` (string): The product ID in your shop: the `id` you sent. Order and cart items refer to products by this ID.

- `sku` (string | null): Stock keeping unit.

- `title` (string): Product name.

- `description` (string | null): Product description.

- `url` (string | null): Product page in your shop.

- `image_url` (string | null): Product image.

- `price` (number | null): Current price.

- `compare_at_price` (number | null): Price before a discount. A product is on sale when `compare_at_price` is higher than `price`.

- `currency` (string | null): Three-letter ISO 4217 code of the prices.

- `categories` (string[]): Category names.

- `brand` (string | null): Brand name.

- `inventory_quantity` (integer | null): Units in stock. `null` means stock isn't tracked. Product grids in emails leave out products at `0`.

- `status` (string): `active` or `archived`. Product grids in emails leave out archived products.

- `metadata` (object): Your own key-value data.

- `created_at` (string): When the product was added, in ISO 8601 UTC.

- `updated_at` (string): When the product last changed, in ISO 8601 UTC.

**Request** `GET /products/{id}`

**cURL**

```bash
curl https://api.emailit.com/v2/products/prd_3r6HHUGUSl8yjYDscEA6aYA8wrA \
  -H "Authorization: Bearer your_api_key"
```

**200**

```json
{
  "object": "product",
  "id": "prd_3r6HHUGUSl8yjYDscEA6aYA8wrA",
  "external_id": "8123456789",
  "sku": "TRS-BLU-42",
  "title": "Trail running shoe",
  "description": "Lightweight shoe with a grippy sole for wet trails.",
  "url": "https://shop.example.com/products/trail-running-shoe",
  "image_url": "https://shop.example.com/images/trail-running-shoe.jpg",
  "price": 89,
  "compare_at_price": 119,
  "currency": "USD",
  "categories": [
    "Shoes",
    "Running"
  ],
  "brand": "Acme Outdoors",
  "inventory_quantity": 34,
  "status": "active",
  "metadata": {
    "color": "blue"
  },
  "created_at": "2026-10-02T08:15:22.418Z",
  "updated_at": "2026-10-08T14:21:07.512Z"
}
```

**404**

```json
{
  "error": "Product not found",
  "message": "Product not found."
}
```

---
Source: https://emailit.com/docs/api-reference/products/get/

## Update a product — POST /products/{id}

> Change some fields of a product in your catalog. Alpha: open to the Emailit team only.

# Update a product

Update a product that's already in your catalog. Only the fields you send change. The product ID in your shop can't change: an `id` in the body is ignored. Requires an API key with the `full` or `events` scope. With an `events` key, the response holds only `object`, `id` and `external_id`.

> **Commerce is in alpha:** Commerce is open only to the Emailit team while we test it. Other requests return `403` with `error: "commerce_alpha"`. API keys work only in workspaces owned by the Emailit team. See [Who can use Commerce](/docs/commerce/#who-can-use-commerce).

To create products and update them with the same call, use [Create or update a product](/docs/api-reference/products/create/).

`POST /products/{id}`

## Path Parameters

- `id` (string, required): The product ID (`prd_...`) or the product ID in your shop.

## Request Body

Any of the fields of [Create or update a product](/docs/api-reference/products/create/) except `id`: `title`, `sku`, `description`, `url`, `image_url`, `price`, `compare_at_price`, `currency`, `categories`, `brand`, `inventory_quantity`, `status` and `metadata`. The same rules apply. `categories` and `metadata` are replaced as a whole.

## Returns

`200 OK` with the updated [product object](/docs/api-reference/products/get/). Returns `404` if no product matches `id` and `422` when a field is invalid.
**Request** `POST /products/{id}`

**cURL**

```bash
curl -X POST https://api.emailit.com/v2/products/8123456789 \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "price": 79, "inventory_quantity": 20 }'
```

**200**

```json
{
  "object": "product",
  "id": "prd_3r6HHUGUSl8yjYDscEA6aYA8wrA",
  "external_id": "8123456789",
  "sku": "TRS-BLU-42",
  "title": "Trail running shoe",
  "description": "Lightweight shoe with a grippy sole for wet trails.",
  "url": "https://shop.example.com/products/trail-running-shoe",
  "image_url": "https://shop.example.com/images/trail-running-shoe.jpg",
  "price": 79,
  "compare_at_price": 119,
  "currency": "USD",
  "categories": [
    "Shoes",
    "Running"
  ],
  "brand": "Acme Outdoors",
  "inventory_quantity": 20,
  "status": "active",
  "metadata": {
    "color": "blue"
  },
  "created_at": "2026-10-02T08:15:22.418Z",
  "updated_at": "2026-10-09T10:02:41.205Z"
}
```

**404**

```json
{
  "error": "Product not found",
  "message": "Product not found."
}
```

**422**

```json
{
  "message": "Validation failed. image_url: must be an http(s) URL",
  "errors": {
    "image_url": [
      "must be an http(s) URL"
    ]
  }
}
```

---
Source: https://emailit.com/docs/api-reference/products/update/

## List products — GET /products

> List the products in your catalog, newest first, with search, category and status filters. Alpha: open to the Emailit team only.

# List products

List the products in your catalog, newest first. Without `status`, only active products are listed. Requires an API key with the `full` scope.

> **Commerce is in alpha:** Commerce is open only to the Emailit team while we test it. Other requests return `403` with `error: "commerce_alpha"`. API keys work only in workspaces owned by the Emailit team. See [Who can use Commerce](/docs/commerce/#who-can-use-commerce).

`GET /products`

## Query Parameters

- `page` (integer): Page number for pagination (minimum: 1, default: 1).

- `limit` (integer): Number of products to return (minimum: 1, maximum: 100, default: 25).

- `search` (string): Case-insensitive match on the title, the product ID in your shop (`external_id`) or the SKU.

- `category` (string): Only products in this category. The name must match exactly, including case.

- `status` (string): `active` (default), `archived` or `all`. Use `all` when you filter on the `status` key below, because the default already limits the list to active products.

- `ids` (string): Comma-separated list of up to 100 product IDs. Each one can be the Emailit ID (`prd_...`) or the product ID in your shop.

- `match` (string): `all` (default) requires every `key.condition` filter to match. `or` matches any of them. See [Filtering](/docs/api-reference/filtering/).

- `sort` (string): Sort key: `created_at` (default), `updated_at`, `title` or `price`.

- `order` (string): Sort direction: `asc` or `desc` (default).

## Filters

Filter and sort keys: see [Filtering](https://emailit.com/docs/api-reference/filtering/).

On this endpoint, `order` only accepts `asc` or `desc`. Pass the sort key in `sort`, for example `sort=price&order=asc`.

## Returns

A page of [product objects](/docs/api-reference/products/get/) in `data`, with `total_records`, `next_page_url` and `previous_page_url`. The page URLs carry only `page` and `limit`, so add your search and filters again when you follow them.
**Request** `GET /products`

**cURL**

```bash
curl "https://api.emailit.com/v2/products?category=Running&sort=price&order=asc&limit=25" \
  -H "Authorization: Bearer your_api_key"
```

**200**

```json
{
  "data": [
    {
      "object": "product",
      "id": "prd_3r6HHUGUSl8yjYDscEA6aYA8wrA",
      "external_id": "8123456789",
      "sku": "TRS-BLU-42",
      "title": "Trail running shoe",
      "description": "Lightweight shoe with a grippy sole for wet trails.",
      "url": "https://shop.example.com/products/trail-running-shoe",
      "image_url": "https://shop.example.com/images/trail-running-shoe.jpg",
      "price": 89,
      "compare_at_price": 119,
      "currency": "USD",
      "categories": [
        "Shoes",
        "Running"
      ],
      "brand": "Acme Outdoors",
      "inventory_quantity": 34,
      "status": "active",
      "metadata": {
        "color": "blue"
      },
      "created_at": "2026-10-02T08:15:22.418Z",
      "updated_at": "2026-10-08T14:21:07.512Z"
    }
  ],
  "total_records": 1,
  "next_page_url": null,
  "previous_page_url": null
}
```

---
Source: https://emailit.com/docs/api-reference/products/list/

## Create or update products in bulk — POST /products/batch

> Send up to 1,000 products in one request. Each one is created or updated by the product ID in your shop. Alpha: open to the Emailit team only.

# Create or update products in bulk

Send up to 1,000 products in one request, for example to import your catalog or to sync it on a schedule. Each product follows the rules of [Create or update a product](/docs/api-reference/products/create/): it's created or updated by its `id`, a new product needs a `title`, and fields are written only when you send them. Requires an API key with the `full` or `events` scope. With an `events` key, the response holds only `object`, `id` and `external_id`.

> **Commerce is in alpha:** Commerce is open only to the Emailit team while we test it. Other requests return `403` with `error: "commerce_alpha"`. API keys work only in workspaces owned by the Emailit team. See [Who can use Commerce](/docs/commerce/#who-can-use-commerce).

Invalid products are skipped and listed in `rejected`; the others are stored. The request still returns `200`.

`POST /products/batch`

## Request Body

- `products` (object[], required): 1 to 1,000 products, each with the fields of [Create or update a product](/docs/api-reference/products/create/).

## Returns

- `created` (integer): Number of new products.

- `updated` (integer): Number of existing products that were updated.

- `rejected` (object[]): Products that weren't stored. Each item has the `index` of the product in your `products` array and `errors`, keyed by field path such as `products.3.price`.

Returns `422` if `products` is empty or has more than 1,000 items.
**Request** `POST /products/batch`

**cURL**

```bash
curl -X POST https://api.emailit.com/v2/products/batch \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "products": [
      { "id": "8123456789", "title": "Trail running shoe", "price": 89, "currency": "USD", "categories": ["Shoes", "Running"] },
      { "id": "8123456790", "title": "Merino running socks", "price": 18, "currency": "USD", "categories": ["Accessories"] },
      { "id": "8123456791", "price": 49, "currency": "USD" }
    ]
  }'
```

**200**

```json
{
  "created": 1,
  "updated": 1,
  "rejected": [
    {
      "index": 2,
      "errors": {
        "products.2.title": [
          "is required"
        ]
      }
    }
  ]
}
```

**422**

```json
{
  "message": "Validation failed. products: Send 1 to 1000 products.",
  "errors": {
    "products": [
      "Send 1 to 1000 products."
    ]
  }
}
```

---
Source: https://emailit.com/docs/api-reference/products/batch/

## List product categories — GET /products/categories

> List the categories of the active products in your catalog, with the number of products in each. Alpha: open to the Emailit team only.

# List product categories

List the categories used by active products, in alphabetical order, with the number of active products in each. Archived products aren't counted. Returns up to 1,000 categories and isn't paginated. Requires an API key with the `full` scope.

> **Commerce is in alpha:** Commerce is open only to the Emailit team while we test it. Other requests return `403` with `error: "commerce_alpha"`. API keys work only in workspaces owned by the Emailit team. See [Who can use Commerce](/docs/commerce/#who-can-use-commerce).

Categories come from the `categories` of each product. Use a category name with the `category` parameter of [List products](/docs/api-reference/products/list/).

`GET /products/categories`

**Request** `GET /products/categories`

**cURL**

```bash
curl https://api.emailit.com/v2/products/categories \
  -H "Authorization: Bearer your_api_key"
```

**200**

```json
{
  "data": [
    {
      "name": "Accessories",
      "products": 18
    },
    {
      "name": "Running",
      "products": 12
    },
    {
      "name": "Shoes",
      "products": 27
    }
  ]
}
```

---
Source: https://emailit.com/docs/api-reference/products/categories/

## Delete a product — DELETE /products/{id}

> Remove a product from your catalog. Alpha: open to the Emailit team only.

# Delete a product

Remove a product from your catalog. Orders that contain it keep their items, which store their own title, price and product ID. To hide a product without deleting it, set its `status` to `archived` instead. Requires an API key with the `full` scope.

> **Commerce is in alpha:** Commerce is open only to the Emailit team while we test it. Other requests return `403` with `error: "commerce_alpha"`. API keys work only in workspaces owned by the Emailit team. See [Who can use Commerce](/docs/commerce/#who-can-use-commerce).

If the [tracking script](/docs/commerce/connect-your-shop/#the-tracking-script) reports a view of a deleted product with its title, the product is added to the catalog again from the page data.

`DELETE /products/{id}`

## Path Parameters

- `id` (string, required): The product ID (`prd_...`) or the product ID in your shop.

**Request** `DELETE /products/{id}`

**cURL**

```bash
curl -X DELETE https://api.emailit.com/v2/products/prd_3r6HHUGUSl8yjYDscEA6aYA8wrA \
  -H "Authorization: Bearer your_api_key"
```

**200**

```json
{
  "object": "product",
  "id": "prd_3r6HHUGUSl8yjYDscEA6aYA8wrA",
  "deleted": true
}
```

**404**

```json
{
  "error": "Product not found",
  "message": "Product not found."
}
```

---
Source: https://emailit.com/docs/api-reference/products/delete/
