Products
Keep your shop’s catalog in Emailit for product grids, recommendations and order items.
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.
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.
Product grids, recommendations and order items in emails read from the catalog. See Products, coupons and the cart.
/productsRequest Body
idstring | numberRequiredThe 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.
titlestringProduct name, up to 500 characters. You can also send it as name. Required for a new product.
skustringStock keeping unit, up to 255 characters.
descriptionstringProduct description, up to 10,000 characters.
urlstringProduct page in your shop. An http or https URL, up to 2,048 characters.
image_urlstringProduct image. An http or https URL, up to 2,048 characters.
pricenumber | stringCurrent 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_pricenumber | stringPrice before a discount, in the same format as price. When it’s higher than price, the product counts as on sale.
currencystringThree-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.
categoriesstring[]Up to 50 category names of up to 191 characters each. Duplicates are removed.
brandstringBrand name, up to 191 characters.
inventory_quantityinteger | nullUnits in stock. null means you don’t track stock. Product grids in emails leave out products at 0.
statusstringactive or archived. New products are active. Archived products are hidden from List products by default and left out of product grids.
metadataobjectYour own key-value data, up to 16 KB as JSON. Sending metadata replaces the stored object.
Returns
201 Created with the product object when the product is new, or 200 OK when an existing product was updated. Returns 422 when a field is invalid.
{
"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"
}{
"message": "Validation failed. price: must not be negative",
"errors": {
"price": [
"must not be negative"
]
}
}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.
/products/{id}Path Parameters
idstringRequiredThe product ID (prd_...) or the product ID in your shop (external_id). URL-encode shop IDs that contain special characters.
The product object
objectstringAlways product.
idstringEmailit product ID (prd_...).
external_idstringThe product ID in your shop: the id you sent. Order and cart items refer to products by this ID.
skustring | nullStock keeping unit.
titlestringProduct name.
descriptionstring | nullProduct description.
urlstring | nullProduct page in your shop.
image_urlstring | nullProduct image.
pricenumber | nullCurrent price.
compare_at_pricenumber | nullPrice before a discount. A product is on sale when compare_at_price is higher than price.
currencystring | nullThree-letter ISO 4217 code of the prices.
categoriesstring[]Category names.
brandstring | nullBrand name.
inventory_quantityinteger | nullUnits in stock. null means stock isn’t tracked. Product grids in emails leave out products at 0.
statusstringactive or archived. Product grids in emails leave out archived products.
metadataobjectYour own key-value data.
created_atstringWhen the product was added, in ISO 8601 UTC.
updated_atstringWhen the product last changed, in ISO 8601 UTC.
{
"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"
}{
"error": "Product not found",
"message": "Product not found."
}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.
To create products and update them with the same call, use Create or update a product.
/products/{id}Path Parameters
idstringRequiredThe product ID (prd_...) or the product ID in your shop.
Request Body
Any of the fields of Create or update a product 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. Returns 404 if no product matches id and 422 when a field is invalid.
{
"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"
}{
"error": "Product not found",
"message": "Product not found."
}{
"message": "Validation failed. image_url: must be an http(s) URL",
"errors": {
"image_url": [
"must be an http(s) URL"
]
}
}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.
/productsQuery Parameters
pageintegerPage number for pagination (minimum: 1, default: 1).
limitintegerNumber of products to return (minimum: 1, maximum: 100, default: 25).
searchstringCase-insensitive match on the title, the product ID in your shop (external_id) or the SKU.
categorystringOnly products in this category. The name must match exactly, including case.
statusstringactive (default), archived or all. Use all when you filter on the status key below, because the default already limits the list to active products.
idsstringComma-separated list of up to 100 product IDs. Each one can be the Emailit ID (prd_...) or the product ID in your shop.
matchstringall (default) requires every key.condition filter to match. or matches any of them. See Filtering.
sortstringSort key: created_at (default), updated_at, title or price.
orderstringSort direction: asc or desc (default).
Filters
List filters are one layer of key.condition=value query parameters. See Filtering for match, order, direction and the condition list per type.
Filter keys
| Key | Type | Conditions | Notes |
|---|---|---|---|
title | string | exact, not_exact, contains, not_contains, starts_with, ends_with, empty, not_empty | |
external_id | string | exact, not_exact, contains, not_contains, starts_with, ends_with, empty, not_empty | |
sku | string | exact, not_exact, contains, not_contains, starts_with, ends_with, empty, not_empty | |
brand | string | exact, not_exact, contains, not_contains, starts_with, ends_with, empty, not_empty | |
currency | string | exact, not_exact, contains, not_contains, starts_with, ends_with, empty, not_empty | |
price | number | exact, not_exact, gt, gte, lt, lte, empty, not_empty | |
inventory_quantity | number | exact, not_exact, gt, gte, lt, lte, empty, not_empty | |
status | enum | exact, not_exact | Values: active, archived |
created_at | date | exact, before, after, empty, not_empty | |
updated_at | date | exact, before, after, empty, not_empty |
Sort keys
This endpoint sorts with sort set to one of these keys and order set to asc or desc (order=<key> returns 400 here): created_at, updated_at, title, price
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 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.
{
"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
}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: 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.
Invalid products are skipped and listed in rejected; the others are stored. The request still returns 200.
/products/batchRequest Body
productsobject[]Required1 to 1,000 products, each with the fields of Create or update a product.
Returns
createdintegerNumber of new products.
updatedintegerNumber of existing products that were updated.
rejectedobject[]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.
{
"created": 1,
"updated": 1,
"rejected": [
{
"index": 2,
"errors": {
"products.2.title": [
"is required"
]
}
}
]
}{
"message": "Validation failed. products: Send 1 to 1000 products.",
"errors": {
"products": [
"Send 1 to 1000 products."
]
}
}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.
Categories come from the categories of each product. Use a category name with the category parameter of List products.
/products/categories{
"data": [
{
"name": "Accessories",
"products": 18
},
{
"name": "Running",
"products": 12
},
{
"name": "Shoes",
"products": 27
}
]
}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.
If 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.
/products/{id}Path Parameters
idstringRequiredThe product ID (prd_...) or the product ID in your shop.
{
"object": "product",
"id": "prd_3r6HHUGUSl8yjYDscEA6aYA8wrA",
"deleted": true
}{
"error": "Product not found",
"message": "Product not found."
}