# Product catalog APIs overview

**How to work with the APIs for the Product catalog.**

This page describes rules that apply across the Product catalog APIs. For what the Product catalog resources are and how they behave, see the [Product catalog overview](/api/product-catalog-overview.md).

## APIs in this topic

| Resource | Purpose | Reference |
| --- | --- | --- |
| Product Types | Describe common characteristics, most importantly common custom Attributes, of many concrete Products. | [Product Types](/api/projects/productTypes.md) |
| Attribute Groups | Allow fine-grained access management to a subset of Attributes of a Product. | [Attribute Groups](/api/projects/attribute-groups.md) |
| Products | Manage your products and product information in your project. | [Products](/api/projects/products.md) |
| Product Projections | Retrieve product data projected to the context of your Store, specific to the customer locale and country. | [Product Projections](/api/projects/productProjections.md) |
| Product Selections | Manage individual Store assortments through Product Selections. | [Product Selections](/api/projects/product-selections.md) |
| Product Tailoring | Manage market-specific Product information. | [Product Tailoring](/api/projects/product-tailoring.md) |
| Categories | Organize your Products into various classifications. | [Categories](/api/projects/categories.md) |
| Reviews | Evaluate Products and Channels. | [Reviews](/api/projects/reviews.md) |

## Data model

The Product catalog resources are organized around the [Product](/search.md?urn=ctp:api:type:Product). Other resources define the structure of a Product, provide views of its data, or adapt it to a [Store](/search.md?urn=ctp:api:type:Store).

### Product structure

Each Product references exactly one [Product Type](/search.md?urn=ctp:api:type:ProductType), which defines the custom [Attributes](/search.md?urn=ctp:api:type:Attribute) of the Product and its Product Variants. You can't change the Product Type of an existing Product. To customize resources other than Products, refer to [Types](/search.md?urn=ctp:api:type:Type).

An [Attribute Group](/search.md?urn=ctp:api:type:AttributeGroup) references Attributes by their name as it appears in their [Attribute Definition](/search.md?urn=ctp:api:type:AttributeDefinition), regardless of the Product Type. As a result, an Attribute Group covers the Attributes with that name in all Product Types.

### Product representations

A Product contains both [current](/api/product-catalog-overview.md#current) and [staged](/api/product-catalog-overview.md#staged) data simultaneously. A [ProductProjection](/search.md?urn=ctp:api:type:ProductProjection) restricts the data to just one of these representations. You can check the `hasStagedChanges` and `published` fields on a Product Projection to determine whether a Product has unpublished edits or is currently published. For how publishing changes these fields, see [Publishing state](/api/product-catalog-apis-overview.md#publishing-state).

### Store-specific Product data

A [Product Selection](/search.md?urn=ctp:api:type:ProductSelection) can contain several Products. You can assign several Product Selections to a Store, and assign a Product Selection to several Stores. You can query the Product Projections of those Products per Store. The following diagram shows the relationship between the Product Selection entity and other entities in the commerce APIs.

![Product Selections related to Stores and Products](https://docs.commercetools.com/api/images/stores/product-selections-relationships.png)

A [Product Tailoring](/search.md?urn=ctp:api:type:ProductTailoring) contains Store-specific Product information. It belongs to exactly one Product and one Store.

```mermaid
classDiagram
	ProductTailoring *-- "1" Product
	ProductTailoring *-- "1" Store
	class Product {

	}
	class Store {

	}
```

## Working with the APIs

### Products

The Master Variant always has `id` set to `1`. You can reassign which variant is the Master Variant by using the [Change Master Variant](/api/projects/products.md#change-master-variant) update action.

When updating product data through the HTTP API, you can use the [Set Attribute in All Variants](/api/projects/products.md#set-attribute-in-all-variants) update action.

### Product Projections

You can project Product data with the [Product Projections API](/api/projects/productProjections.md) and the [Products API](/api/projects/products.md). On the [GraphQL API](/api/graphql.md), the `products` query blends the capabilities of both REST APIs. The REST query parameters map to GraphQL arguments and fields with similar names, usually without the parameter prefix. For example, the `priceCurrency` query parameter maps to the `currency` argument of the `price` field. For more information, see [Query Products](/api/graphql.md#query-products).

A [Store](/search.md?urn=ctp:api:type:Store) can define settings such as languages, and Channels for Inventory and pricing. When you project Product data by Store, the Product Projection contains only the localized values, Inventory entries, and Prices that suit the Store. For the query parameter of each projection dimension, see [Projection dimensions](/api/projects/productProjections.md#projection-dimensions).

#### Publishing state

A Product has either one or two projections that you can retrieve, depending on whether the Product is published. Only published Products have a `current` projection. Newly created Products have only a `staged` projection. This doesn't change the data that the Product stores: every Product contains both [current](/api/product-catalog-overview.md#current) and [staged](/api/product-catalog-overview.md#staged) data from the moment it's created.

When the `staged` data differs from the `current` data, the `hasStagedChanges` flag on [ProductCatalogData](/search.md?urn=ctp:api:type:ProductCatalogData) is set to `true`.

When you [publish](/api/projects/products.md#publish) a Product, the platform does the following:

- Copies the `staged` data to `current`.
- Sets the `published` flag on ProductCatalogData to `true`.
- Sets the `hasStagedChanges` flag on ProductCatalogData to `false`.

Typical storefront applications retrieve only the `current` projection of published Products, by [querying Product Projections](/api/projects/productProjections.md#query-productprojections) or by [searching Products](/search.md?urn=ctp:api:endpoint:/{projectKey}/products/search:POST). To restrict access to published Products only, assign the `view_published_products:{projectKey}` [scope](/api/scopes.md#product-projections) instead of `view_products:{projectKey}`.

[Unpublishing](/api/projects/products.md#unpublish) a Product only sets the `published` flag on ProductCatalogData to `false`. It doesn't change the data stored in the `current` or `staged` representations. By default, the Product Projection endpoints no longer return an unpublished Product, and the Product is removed from the [Product Search](/api/projects/product-search.md) index.

#### Locale projection

To remove unneeded translations from Product Projections, you can filter [LocalizedStrings](/api/types.md#localizedstring) by locale. The locales to include are determined in the following order:

1. Locales that you request explicitly.
2. Locales configured in the Store that you project by.

If a field has no translation for the requested locales, the API checks the [Project](/search.md?urn=ctp:api:type:Project) languages in their configured order and uses the first localized value it finds. This ensures that required fields can't remain empty in the Product Projection.

Locale projection also affects the [Line Items](/api/projects/carts.md#lineitem) of Carts that are bound to a Store.

#### Price projection by Store

To remove unneeded [Embedded Prices](/api/pricing-and-discounts-overview.md#embedded-prices) from Product Projections, you can filter them by the distribution Channels and countries configured in the Store that you project by.

If the Store has `distributionChannels` set, the Product Projection includes only the Prices that meet one of the following criteria:

- The Price has a distribution Channel that is included in the Store's `distributionChannels`.
- The Price doesn't have a distribution Channel assigned.

If the Store has `countries` set, the Product Projection includes only the Prices that meet one of the following criteria:

- The Price has a country that is included in the Store's `countries`.
- The Price doesn't have a country assigned.

This filtering applies to the `prices` array of the Product Projection.

> Price projection by Store applies only when the [Product](/search.md?urn=ctp:api:type:Product) `priceMode` is `Embedded`.
>
> [Standalone Prices](/api/projects/standalone-prices.md) are not computed using the Channels or countries of a Store. To retrieve a Standalone Price in Product Projections, apply [projection by price selection](/api/projects/productProjections.md#projection-by-price-selection).

#### Inventory projection by Store

To remove unneeded [Product Variant Availabilities](/api/projects/products.md#productvariantavailability) from Product Projections, you can filter them by the Inventory supply Channels configured in the Store that you project by.

If the Store has `supplyChannels` set, the availability of the projected Product Variants includes only the following Inventory entries:

- Inventory entries with a supply Channel that is included in the Store's `supplyChannels`.
- Inventory entries without a supply Channel.

Inventory projection also affects the [Line Items](/api/projects/carts.md#lineitem) of Carts that are bound to a Store.

#### Product Variants in Store

When a Product is part of an active Product Selection of a Store and has an [associated Variant Selection](/api/projects/product-selections.md#productvariantselection), retrieving the Product Projection in that Store returns only the selected Product Variants.

When a Store has two or more active Product Selections that contain the same Product but have different Variant Selections, the representation of the Product in the Store is resolved as follows:

- All Product Variants that are part of a [Variant Exclusion](/api/projects/product-selections.md#productvariantexclusion) across Product Selections are discarded.
- All Product Variants that are part of a [Variant Selection of type IncludeOnly](/api/projects/product-selections.md#productvariantselection-includeonly) across Product Selections are kept, except those that are part of a [Variant Selection of type IncludeAllExcept](/api/projects/product-selections.md#productvariantselection-includeallexcept).

If the Variant Selection of a Product excludes all of its Product Variants, or if the Product is assigned to a Product Selection with mode `IndividualExclusion` without any [Variant Exclusion](/api/projects/product-selections.md#productvariantexclusion), the Product is considered absent in the Store.

#### Categories in Store

When you retrieve a Product Projection in a Store, the [ProductProjection](/search.md?urn=ctp:api:type:ProductProjection) `categories` array includes only the [Categories](/api/projects/categories.md) that are available in that [Store](/search.md?urn=ctp:api:type:Store). The excluded Categories are also removed from `categoryOrderHints`.

### Product Projections API vs. Product Search API

Two primary APIs are available for Product discovery and retrieving Product information: the Product Projection API and the Product Search API. Both read-only APIs are optimized for Customer-facing storefront needs, but they serve different use cases and have distinct characteristics.

The following table compares use cases and features of each API, and it provides performance tips for optimal usage:

| Feature | [Product Projections API](/api/projects/productProjections.md) | [Product Search API](/api/projects/product-search.md) |
| :--- | :--- | :--- |
| **Purpose** | Retrieves projected representations of Products by their Key or ID or by using Query Predicates. | Provides advanced storefront Product discovery at scale, especially across many Stores, Product Selections, and Standalone Prices. |
| **Use case** | Use in scenarios where tailored and contextualized product data is needed, such as on the Product details page (PDP). | Use for full-text and prefix search, filters, and facetted navigation on Product listing pages (PLPs). |
| **Data consistency** | [Strong consistency](/api/general-concepts.md#strong-consistency). Queries the Product database directly. | [Eventual consistency](/api/general-concepts.md#eventual-consistency). Queries the Product Search index, which has some indexing delay. |
| **Query language** | Simple filtering by using Query Predicates. Only exact match on standard and Attribute values. | Complex filters with expressive search query language, supporting full-text, prefix, and wildcard queries. Calculates facets. |
| **Data returned** | Product Projections tailored to the requested context. | IDs of matching Products and the IDs and SKUs of matching Product Variants. Facets |
| **Publishing state** | Retrieves published Product data by default, but supports retrieval of staged data by query parameter. Can retrieve tailored as well as original Product data. | Indexes the current representation of published Products with their Standalone Prices. Doesn't index tailored Product data. |
| **Product Selections** | Supports retrieval of only those Products that are assigned to a particular Product Selection. | Supports filtering by Stores and Product Selections. |
| **Performance tips** | Use this API only for a small number of lookups by ID or Key or for simple queries. Cache retrieved Product data. Avoid [inefficient query patterns](/api/predicates/query.md#inefficient-patterns). | Fetch the full Product data for the returned Product IDs from an application-side cache or a secondary API call (GraphQL or Get Product Projection by ID). In Projects with the [ProductCatalogModel](/api/projects/project.md#productcatalogmodel) set to `Modular`, also query [Variant Projections](/api/projects/variant-projections.md) for Variant-level data. Apply [search query optimization](/api/performance-tips.md#search-query-optimization) techniques. |

For discovering Products based on attribute values and prices on your Product Listing Page, use the [Product Search](/api/projects/product-search.md) API.
For retrieving product information to be presented on your Product Detail Page, use the [Product Projection](/api/projects/productProjections.md) API or, when using the `Modular` catalog model, the [Variant Projections](/api/projects/variant-projections.md) API.

### Product Selections

With the [Product Search API](/api/projects/product-search.md#keyword-fields) you can query if a Product belongs to a specific Product Selection or Store. Currently, [Product Variant Selections](/api/projects/product-selections.md#productvariantselection) are not supported. The deprecated [Product Projection Search API](/api/projects/product-projection-search.md) does not provide Store-specific search requests that are scoped to certain Product Selections.

The guide [Integrate external search](/guides/search-integration.md) explains how to integrate an external search service to populate a search index per Store.
Alternatively, you can populate one search index per Product Selection.
This approach lets you preview upcoming assortments, such as a summer collection, before you publish them.

> **Warning:** Treat the `sku` values as immutable identifiers. Finalize SKUs before assigning Products or Variant Selections to Product Selections to avoid background recalculation of `ProductSelectionAssignments` when the Product is published.

### Product Tailoring

Attributes with `savedToLineItem` set to `false` on their [AttributeDefinition](/search.md?urn=ctp:api:type:AttributeDefinition) are excluded from [Line Items](/search.md?urn=ctp:api:type:LineItem) when a Product with tailoring is added to a Cart.

> **Warning:** Tailored data is associated with a Product via the Product's `key`. Deleting a Product, or changing the Product `key`, will delete all associated tailored data.

Tailored information is not indexed for the [Storefront Search APIs](/api/storefront-search-apis-overview.md).

> **Warning:** Attribute Constraints are validated on Tailoring creation, update, and publishing, but their consistency is not guaranteed with respect to changes on the corresponding Product side.

#### Tailor product information

For each piece of product information you want to tailor for the Store, add the appropriate Product field with the **tailored** value to the [ProductTailoringData](/api/projects/product-tailoring.md#producttailoringdata) object.
For fields that you don't add to this object, the API returns the corresponding **original** value from the Product's [ProductData](/search.md?urn=ctp:api:type:ProductData) object when [retrieving the ProductProjection in Store](/api/projects/product-tailoring.md#retrieve-product-projection-with-tailored-information).

```mermaid
flowchart LR
	P[ProductData]--> PTA[ProductTailoringData]
	PTA-->|tailored| PPA[ProductProjection in Store]
	P-->|original| PPA
```

The following example shows a Product Tailoring for the `name` and `description` fields of a Product, but not for its slug.
For the `slug` field, the ProductProjection takes the original value from the ProductData.

| Field name | Field value on ProductData | Field value on ProductTailoringData | Field value on ProductProjection in Store |
| --- | --- | --- | --- |
| `name` | `"en: original name"` | `"en: tailored name"` | `"en: tailored name"` |
| `description` | `"en: original desc"` | `"en: tailored desc"` | `"en: tailored desc"` |
| `slug` | `"en: original_slug"` |  | `"en: original_slug"` |

##### Tailor Product Variant information

The API allows you to customize [Images](/api/projects/products.md#image) and [Assets](/api/types.md#asset) per Store by tailoring existing [ProductVariants](/api/projects/products.md#productvariant) or [Variants](/api/projects/variants.md).
To assign Store-specific Product Variant information, add the **tailored** Image or Asset to the corresponding [ProductVariantTailoring](/api/projects/product-tailoring.md#productvarianttailoring) object.
For Images and Assets that you don't add to this object, the API returns the corresponding **original** Images and Assets from the ProductVariant or Variant when [retrieving the ProductProjection in Store](/api/projects/product-tailoring.md#retrieve-product-projection-with-tailored-information).

```mermaid
flowchart LR
	PV[ProductVariant]--> PVT[ProductVariantTailoring]
	PVT-->|tailored| PVS[ProductVariant in Store]
	PV-->|original| PVS
```

The following example shows a ProductVariantTailoring for the UK Store, but no tailoring for the ES Store.
The API retrieves the Images for ProductVariant in UK Store from the Images given in the ProductVariantTailoring UK ignoring the Images in the ProductVariant:

```mermaid
flowchart LR
	PV[ProductVariant]--> PVTUK[ProductVariantTailoring UK]
	PVTUK-->|tailored| PVSUK[ProductVariant in UK Store]
	PV-->|original| PVSES[ProductVariant in ES Store]
```

The images for ProductVariant in ES Store are retrieved from the original Images in the ProductVariant because no ProductVariantTailoring is defined for the ES Store.

##### Stage and publish tailored product information

Similar to the Product's [ProductCatalogData](/search.md?urn=ctp:api:type:ProductCatalogData), where you can prepare your original product information in the `staged` version of [ProductData](/search.md?urn=ctp:api:type:ProductData), you can prepare the tailored product information in a `staged` version of the [ProductTailoringData](/api/projects/product-tailoring.md#producttailoringdata), before you [publish](/api/projects/product-tailoring.md#publish) the [ProductTailoring](/api/projects/product-tailoring.md#producttailoring) to `current`.

The `published` flag on the ProductTailoring indicates the publication status of the tailoring as a whole. When `true`, you'll find only the fields in the `current` ProductTailoringData object in the related current ProductProjection in Store. With the [unpublish](/api/projects/product-tailoring.md#unpublish) action you can set `published` to `false` which leads to all fields falling back to their original values from ProductData regardless of what is stored in the `current` version of the ProductTailoringData.

The published status of the Product Tailoring is independent of the published status of the Product itself. The `published` field of the Product on [ProductCatalogData](/search.md?urn=ctp:api:type:ProductCatalogData) determines whether the current [Product Projection in Store](/search.md?urn=ctp:api:endpoint:/{projectKey}/in-store/key={storeKey}/product-projections/{id}:GET) is retrievable. The following table summarizes how the published statuses affect the availability of Product Projections in Store and their tailored information:

| Product | Product Tailoring | Staged Product Projection in Store | Current Product Projection in Store |
| --- | --- | --- | --- |
| unpublished | unpublished | original Product information | not retrievable |
| unpublished | **published** | **tailored** Product information | not retrievable |
| **published** | unpublished | **tailored** Product information | original Product information |
| **published** | **published** | **tailored** Product information | **tailored** Product information |

For the query parameters to retrieve each of these, see [Retrieve Product Projection with tailored information](/api/projects/product-tailoring.md#retrieve-product-projection-with-tailored-information).

The `hasStagedChanges` field is `true` whenever someone modified tailored information in the `staged` tailoring information, that are not yet reflected in the `current` tailoring information. This can give your team some indication to review the staged changes before publishing them, but your reviewer needs to find out which fields have changed. If you are interested in changes on particular fields of the ProductTailoring, we recommend subscribing to [Product Tailoring Messages](/api/projects/messages/product-catalog-messages.md#product-tailoring-messages).

### Categories

#### Category tree locking

To keep ancestor lists (paths) and Store assignments consistent, write operations on the Category tree lock the Category nodes they affect. If another operation already holds a required lock, the request fails with an [InvalidOperation](/search.md?urn=ctp:api:type:InvalidOperationError) (400) error.

The lock scope depends on the operation:

- **Whole Category tree**: a [Change Parent](/search.md?urn=ctp:api:type:CategoryChangeParentAction) action locks the entire Category tree in the Project for the duration of the request, because it can change the ancestor path of every Category below it. While it is in progress, any other Change Parent action, any [Create Category](/api/projects/categories.md#create-category) request with a `parent`, and any Store update action anywhere in the Project fails.
- **Parent Category**: a [Create Category](/api/projects/categories.md#create-category) request with a `parent` locks that parent Category. The request fails if another Create Category request under the same parent, a Store update action on that parent or one of its existing children, or a Change Parent action is in progress. Creating a Category without a `parent` doesn't take this lock. Requests targeting different parents don't conflict with each other.
- **Category and its parent**: the [Set Stores](/search.md?urn=ctp:api:type:CategorySetStoresAction), [Add Store](/search.md?urn=ctp:api:type:CategoryAddStoreAction), and [Remove Store](/search.md?urn=ctp:api:type:CategoryRemoveStoreAction) actions lock the Category itself and its parent Category. Concurrent Store updates to the same Category, to sibling Categories, or to a Category and one of its direct children can conflict.
- **Category and its descendants**: the [Set Stores with Descendants](/search.md?urn=ctp:api:type:CategorySetStoresWithDescendantsAction), [Add Store with Descendants](/search.md?urn=ctp:api:type:CategoryAddStoreWithDescendantsAction), and [Remove Store with Descendants](/search.md?urn=ctp:api:type:CategoryRemoveStoreWithDescendantsAction) actions don't change any Store when the API receives the request. The API only records the request and responds with `202 Accepted`. A background process then updates the target Category and every descendant Category, which is [eventually consistent](/api/general-concepts.md#eventual-consistency). While the background process runs, it locks the target Category and its subtree, so Store updates, [Change Parent](/search.md?urn=ctp:api:type:CategoryChangeParentAction) actions, and [Create Category](/api/projects/categories.md#create-category) requests with a `parent` for Categories in that subtree fail and should be retried. These actions are only available on the [general endpoint](/api/projects/categories.md#update-category) and are rejected on the [Store-specific endpoint](/api/projects/categories.md#update-category-in-store).

To reduce conflicts, serialize requests that create or update Categories under the same parent. If a request fails because of a lock conflict, retry it after a short delay. For general retry recommendations, see [Retry policies](/api/error-handling.md#retry-policies). For Category imports, see [CategoryImport](/search.md?urn=ctp:import:type:CategoryImport).

### Manage workflow and governance

To enforce custom validation, automated data enrichment, or content filtering before changes are saved, you can use [API Extensions](/api/projects/api-extensions.md) on [Products](/api/projects/products.md). For example, an API Extension can validate that SKUs follow internal conventions, enrich Attributes with data from an external PIM system, or redact unapproved content on create and update requests before persisting data.

Product modeling elements (such as Product Types, Attributes, and Categories) span multiple Projects including development, staging, and production. Set keys on all entities to ensure that you can replicate the data across different environments. After establishing the foundational structure, consider using an infrastructure-as-code tool to manage Product Types, Attributes, Categories, and more. For example, you can use Terraform with the [commercetools Terraform provider](https://registry.terraform.io/providers/labd/commercetools/latest/docs).

## Limits

In the `Classic` [ProductCatalogModel](/search.md?urn=ctp:api:type:ProductCatalogModel), a [Product](/search.md?urn=ctp:api:type:Product) can have up to **100** [Product Variants](/search.md?urn=ctp:api:type:ProductVariant).
In the `Modular` [ProductCatalogModel](/search.md?urn=ctp:api:type:ProductCatalogModel), a Product can have up to **10000** [Variants](/search.md?urn=ctp:api:type:Variant), which are managed as standalone entities through the [Variants API](/api/projects/variants.md).
These limits can be increased per project after we review the performance impact.
Contact the [commercetools support team](https://support.commercetools.com/) and provide the region, project key, and use case.

A maximum number of **1000** [ProductTypes](/api/projects/productTypes.md#producttype) can be created per Project. This is a soft limit that can be increased per Project after a performance impact review. See [Limit increase guidance](/api/limit-increase-guidance.md#resource-count-limits).

A maximum number of **100** Attribute Groups can be created per Project.

The maximum number of SKUs per Product Selection matches the [Product Variants](/api/projects/products.md#productvariant) limit per Product. This is a soft limit that can be increased per Project after a performance impact review. See [Limit increase guidance](/api/limit-increase-guidance.md#product-and-pricing-model-limits).

A maximum number of **100000000** [Product Tailoring](/api/projects/product-tailoring.md) across all Products and Stores can be created per Project. This is a soft limit that can be increased per Project after a performance impact review. See [Limit increase guidance](/api/limit-increase-guidance.md#product-and-pricing-model-limits).

A maximum number of **10000** Categories can be created per Project. This is a soft limit that can be increased per Project after a performance impact review. See [Limit increase guidance](/api/limit-increase-guidance.md#resource-count-limits).

## Related pages

- [Area overview page with navigation](/api.md)
- [Previous page: Overview](/api/product-catalog-overview.md)
- [Next page: Product Types](/api/projects/productTypes.md)
- [Search documentation and API specs](/search.md)