docs
  1. SCAYLE Resource Center
  2. Developer Guides
  3. Products
  4. Composite Products

Composite Products

General

Composite products are generally considered to be the same as normal products. The difference is that composite products are not real products, but, as the name suggests, they are composed of several other products.

Use cases for composite products

  • sell a bikini top and a bikini pant product as a product bundle
  • sell a jersey with a flocking (e.g. name and number on the back)
  • bundle socks as product bundle with special pricing

Availability of composite products

Stocks represent the number of the available items and are defined on variant level. It is not possible to submit any stocks for composite product variants. The stock is calculated automatically as soon as one of the related variants (the components of the composite variant) are getting a stock update. As available quantity we will always take the stock of the item that is least available. If the least available item has the flag sellable_without_stock set to true it'll be ignored. In case all components have set that flag to true, the composite variant will also have it set to true. For the expected delivery date (expected_availability_at) of a composite variant, the respective values of the connected variants and the date farthest in the future will be used. Below you will find examples on how stock is calculated.

Assume your composite variant consists of three related variants A, B & C.

Example A

A has a stock of 15, B has a stock of 25 and C has a stock of 14. All three variants have the flag sellable_without_stock set to false. The stock of the composite variant will be 14 as this is the lowest stock of all components, it will not be sellable_without_stock (i.e. set to false).

Example B

A has a stock of 15, B has a stock of 25 and C has a stock of 14. A & B have the flag sellable_without_stock set to false, C has it set to true. The stock of the composite variant will be 15 as this is the lowest stock of all components, it will not be sellable_without_stock (i.e. set to false).

Example C

A has a stock of 15, B has a stock of 25 and C has a stock of 14. All three variants have the flag sellable_without_stock set to true. The stock of the composite variant will be 0 as the stock doesn't matter in that case, it will be sellable_without_stock (i.e. set to true).

Prices of Composite Products

There are two possibilities how prices are defined for composite product variants. It is only possible to use one of these possibilities, you can't use them both. There's a configuration flag called composite_products_sum_up_prices. In case you enabled that flag, you won't be able to submit any prices for composite product variants. Instead, they will be calculated automatically based on the prices of the related variants. If you have disabled summing up prices automatically, you need to submit prices for each composite product variant explicitly using the already known endpoints for submitting variant prices, read more in the Prices section. Below you will find examples on how prices are calculated.

Assume your composite variant consists of three related variants A, B & C.

Example A

This is a simple example, a price promotion key is not set. The price is simply the sum.

variantprice promotion keyprice group idprice
A-110 €
B-115 €
C-120 €

In this example your composite variant will have a price of 45 €.

Example B

A bit more complex as variant A has two prices with different price group ids. To calculate a price, there need to be a price for every variant in every price group.

variantprice promotion keyprice group idprice
A-210 €
A-15 €
B-115 €
C-120 €

Your composite variant will have a price of 40 € for price group id 1. There will be no price for price group = 2.

Example C

Similar as example B, but this time, all prices in all price groups are given.

variantprice promotion keyprice group idprice
A-210 €
A-15 €
B-215 €
B-115 €
C-220 €
C-120 €

Your composite variant will have the following prices

  • 40 €, price group := 1
  • 45 €, price group := 2

Example D

Now we introduce price promotion key, hereinafter referred to as ppk. In contrast to price groups, we don't need to have a price for every single ppk. If a price of a specific ppk is missing, we will try to find a fallback. The fallback would always be a price without a ppk although there might be a default price with a ppk. If there's no price without a ppk, the default price will be used, even if it has a ppk set.

rowvariantprice promotion keyprice group iddefaultprice
1A91110 €
2B-1015 €
3B71012 €
4C-1020 €
5C91015 €

There are three price promotion keys. For every price promotion key, we'll try to build a price. Based on the given data, the composite variant will have the following prices:

#price promotion keyprice group idpricecombination
1-145 €1 & 2 & 4
29140 €1 & 2 & 5
37142 €1 & 3 & 4
  1. Price promotion key is not set; variant A doesn't have a price without a ppk; it has a default price (row: 1)
  2. Price promotion key := 9; variant B doesn't have a price; it has a price without a ppk (row: 2)
  3. Price promotion key := 7; variant A & C don't have prices; variant A has a default price (row: 1); variant C has a price without a ppk (row: 4)

Admin API

Manage Composite Products

You can manage composite products via the Admin API.

Create a Composite Product

Creating composite products in SCAYLE is fairly simple. We will now guide you through the process step by step.

This method can be used to create a composite product that later gets shown in the storefront for being purchased by customers.

All prices returned within the response will only contain active prices. If you want to get more detailed information, e.g., future prices, please use the Get Prices Method.

let response = await adminApi.apis.Products.createComposite({}, {requestBody: newProduct});
let createdProduct = response.body;

If you upload many images in one request, it might take a long time to execute. In this case, it's recommended to upload images via a dedicated endpoint.

If there exists a custom data configuration for the product entity and the custom data isn't provided within the create request, the defaultValue specified in the custom data configuration for each property would be considered.

Options

Product creation can be used with optional parameters - called options:

ParameterDetails
ignoreMasterIfExist

Boolean

If set to true and a master product with the given master.referenceKey already exists, then the provided master data will be ignored. If set to false or not provided and a master product with the given master.referenceKey already exists, then an error is being returned.

updateIfExists

Boolean

When set to true, a lookup is done for the product with the given reference key and if it exists then the product will be updated, otherwise the product will get created.

ignoreAttributeLocks

Boolean

Force an update or deletion of attributes even if they might be locked.

ignoreCategoryLocks

Boolean

Force an update of categories even if they might be locked.

with

String

The parameter allows you to include the following nested resources in the response:

  • attributes
  • images
  • variants
  • variants.attributes
  • variants.prices
  • variants.stocks
  • variants.customData
  • variants.relatedVariants
  • productSortings
  • customData

It is also possible to exclude all nested resources from the response by providing an empty string as the value.

Master

Providing the master as part of the product hierarchy is mandatory.

  • If an existing master.referenceKey is provided, the product will inherit all attributes and categories of the existing master.
  • Master attributes and categories can only be defined when creating a new master.
  • Master categories must contain a root category.

Status

When creating a new product you can specify if you want to create the product as a draft, meaning it will not be available in the storefront, or live to publish it. You can change the status later using the Update Composite Product Method.

Setting the status to live will trigger the status evaluation. The product might end up in the problem status and not go live.

Parameters

ParameterDetails
id

Integer READ-ONLY

The ID of the product created by SCAYLE.

problems

String[] READ-ONLY

If product is in problem state, the reasons are listed here.

referenceKey

String

A key that uniquely identifies the product (e.g., a shirt in a specific color) within the tenant's ecosystem.

name

String[]

The localized product name. At least the base language that is configured in SCAYLE is mandatory.

master

Master

The master the product is attached to.

state

String

The state of the product is determined by the state evaluation process. The only possible values to request are live, draft and blocked. The problem state can only be the result of the state evaluation process. If product is in problem state, the reasons are listed in read-only 'problems' field. The new and inApproval states can be set in the SCAYLE Panel. If a product belongs to multiple merchants, the state is returned based on the hierarchical order live, inApproval, problem, blocked, draft.

attributes

Attribute[]

A list of attributes attached to the product.

variants

ProductVariant[]

A list of product variants attached to the product.

images

ProductImage[]

A list of product images attached to the product.

productSortings

ProductSorting[]

A list of product sortings.

customDataCustomData
isComposite

Boolean READ-ONLY

Indicates whether the product is composite.

merchantReferenceKeys

String[]

A list of merchant reference keys the product belongs to.

Examples

Create a Basic Composite Product

let newProduct = {
    referenceKey: "myReferenceKey",
    master: {
        referenceKey: "myMasterReferenceKey",
        categories: {
            paths: [
                [
                    "Fashion",
                    "Women",
                    "Shirts"
                ]
            ]
        }
    },
    name: {
        de_DE: "Mein Produkt",
        en_GB: "My Product"
    },
    state: "draft"
};

let response = await adminApi.apis.Products.createComposite({}, {requestBody: newProduct});
let createdProduct = response.body;

Create a Complete Composite Product

let newProduct = {
    referenceKey: "myReferenceKey",
    name: {
        "de_DE": "Mein Produkt",
        "en_GB": "My Product"
    },
    state: "live",
    master: {
        referenceKey: "myMasterReferenceKey",
        categories: {
            paths: [
                [
                    "Fashion",
                    "Women",
                    "Shirts"
                ]
            ]
        }
    },
    attributes: [
        {
            name: "color",
            type: "localizedStringList",
            value: [
                {
                    de_DE: "rot",
                    en_GB: "red"
                },
                {
                    de_DE: "blau",
                    en_GB: "blue"
                }
            ]
        }
    ],
    variants: [
        {
            referenceKey: "myVariantKey",
            attributes: [
                {
                    name: "size",
                    type: "simple",
                    value: "M"
                }
            ],
            prices: [
                {
                    price: 2499,
                    tax: 19,
                    currencyCode: "EUR",
                    countryCode: "DE",
                }
            ],
            relatedVariants: [
                {
                    variantReferenceKey: "anExistingRealVariantKey",
                    isMainVariant: true
                },
                {
                    variantReferenceKey: "anotherExistingRealVariantKey",
                    isMainVariant: false
                }
            ]
        }
    ],
    images: [
        {
            source: {
                url: "https://example.com/image.jpg"
            }
        }
    ],
    customData: {
        "additionalDetails": "details"
    }
};

let response = await adminApi.apis.Products.createComposite({}, {requestBody: newProduct});
let createdProduct = response.body;

Create Composite Product with Options

Create Composite Product with using options ignoring locks:

let newProduct = {
    referenceKey: "myReferenceKey",
    name: {
        "de_DE": "Mein Produkt",
        "en_GB": "My Product"
    },
    state: "live",
    master: {
        referenceKey: "myMasterReferenceKey",
        categories: {
            paths: [
                [
                    "Fashion",
                    "Women",
                    "Shirts"
                ]
            ]
        }
    },
    attributes: [
        {
            name: "color",
            type: "localizedStringList",
            value: [
                {
                    de_DE: "rot",
                    en_GB: "red"
                },
                {
                    de_DE: "blau",
                    en_GB: "blue"
                }
            ]
        }
    ],
    variants: [
        {
            referenceKey: "myVariantKey",
            attributes: [
                {
                    name: "size",
                    type: "simple",
                    value: "M"
                }
            ],
            prices: [
                {
                    price: 2499,
                    tax: 19,
                    currencyCode: "EUR",
                    countryCode: "DE",
                }
            ],
            relatedVariants: [
                {
                    variantReferenceKey: "anExistingRealVariantKey",
                    isMainVariant: true
                },
                {
                    variantReferenceKey: "anotherExistingRealVariantKey",
                    isMainVariant: false
                }
            ]
        }
    ],
    images: [
        {
            source: {
                url: "https://example.com/image.jpg"
            }
        }
    ],
    customData: {
        "additionalDetails": "details"
    }
};

let response =
    await adminApi.apis.Products.createComposite(
        { ignoreMasterIfExist: true, updateIfExists: true, ignoreAttributeLocks: true, ignoreCategoryLocks: true },
        { requestBody: newProduct }
    );
let createdProduct = response.body;

Update a Composite Product

Learn how to update composite products with all of its dependencies by providing respective properties. You can also update locked attributes.

When updating composite products, it is essential to consider nested entities and locked attributes and categories. Otherwise, the respective information might not be updated. Furthermore, you need to be aware that attribute changes can cause a product status update resulting in taking a product offline.

This method can be used to update or replace an existing composite product.

This method does not support partial updates.

let response = await adminApi.apis.Products.updateComposite({productIdentifier: productIdentifier}, {requestBody: product});
let updatedProduct = response.body;

If you upload many images in one request, it might take a long time to execute. In this case, it's recommended to upload images via a dedicated endpoint

If there exists a custom data configuration for the product entity and the custom data isn't provided within the create request, the defaultValue specified in the custom data configuration for each property would be considered.

Options

Composite Product update can be used with optional parameters - called options:

ParameterDetails
ignoreMasterIfExist

Boolean

If set to true and a master product with the given master.referenceKey already exists, then the provided master data will be ignored. If set to false or not provided, then we update the existing master with the given data.

ignoreAttributeLocks

Boolean

Force an update or deletion of attributes even if they might be locked.

ignoreCategoryLocks

Boolean

Force an update of categories even if they might be locked.

with

String

The parameter allows you to include the following nested resources in the response:

  • attributes
  • images
  • variants
  • variants.attributes
  • variants.prices
  • variants.stocks
  • variants.customData
  • variants.relatedVariants
  • productSortings
  • customData

It is also possible to exclude all nested resources from the response by providing an empty string as the value.

In general, this method replaces the product with the provided information, i.e., not provided properties will get deleted except nested entities as stated below.

Nested Entities

  • If a nested entity or array of nested entities like variants is not provided, it will NOT get deleted.
  • If a nested entity or array of nested entities like variants is provided, it will be replaced by the provided information.
  • If a nested entity or array of nested entities like variants is provided and set to null or empty, it will get deleted.

Attribute Locks

To avoid accidentally deleting or updating attributes that were manually added or edited via the SCAYLE, the query parameter ignoreAttributeLocks can be used to control the behaviour of how these updates should be handled.

The default behaviour is to respect attribute locks. Thus, even if an attribute is present in the payload, it will not be updated. The same behaviour will be applied, if an attribute is omitted in the payload, so it will not get deleted.

If you want to force updates or deletion of attributes, you need to set ignoreAttributeLocks = true. In this case, all attributes get updated or deleted even if they are locked.

Master

Master attributes and categories are shared between products.

  • Changes applied to the master will affect all related products.
  • The status evaluation is triggered for each product of the same master.

Master Categories and Locks

To avoid accidentally updating master categories that were manually changed via the SCAYLE, the query parameter ignoreCategoryLocks can be used to control the behaviour of how these updates should be handled.

The default behaviour is to respect category locks. This will prevent category updates.

If you want to force updates of categories, you need to set ignoreCategoryLocks = true. In this case, all categories for all products of the same master get updated even if they are locked.

Status

Updating the product will trigger the status evaluation. When the desired state is live, the product might end up in the problem status. Note, that it is not possible to change live status to draft.

Parameters

ParameterDetails
id

Integer READ-ONLY

The ID of the product created by SCAYLE.

problems

String READ-ONLY

If product is in problem state, the reasons are listed here.

referenceKey

String

A key that uniquely identifies the product (e.g., a shirt in a specific color) within the tenant's ecosystem.

name

String

The localized product name. At least the base language that is configured in SCAYLE is mandatory.

master

Master

The master the product is attached to.

state

String

The state of the product is determined by the state evaluation process. The only possible values to request are live, draft and blocked. The problem state can only be the result of the state evaluation process. If product is in problem state, the reasons are listed in read-only 'problems' field. The new and inApproval states can be set in the SCAYLE Panel. If a product belongs to multiple merchants, the state is returned based on the hierarchical order live, inApproval, problem, blocked, draft.

attributes

Attribute

A list of attributes attached to the product.

variants

ProductVariant

A list of product variants attached to the product.

images

ProductImage

A list of product images attached to the product.

productSortings

ProductSorting

A list of product sortings.

customDataCustomData
isComposite

Boolean READ-ONLY

Indicates whether the product is composite.

merchantReferenceKeys

String

A list of merchant reference keys the product belongs to.

Examples

let response = await adminApi.apis.Products.getProduct({productIdentifier: 1});
let product = response.body;

product.name = {
    de_DE: "Mein neuer Name",
    en_GB: "My new name"
};

adminApi.apis.Products.updateComposite({productIdentifier: product.id}, {requestBody: product});

Update Composite Product With Options

Update Composite Product with using options ignoring locks:

let response = await adminApi.apis.Products.getProduct({productIdentifier: 1, with: "attributes"});

let product = response.body;
let key = product.attributes.findIndex(attribute => attribute.name === "color");

product.attributes[key].value.push({
    de_DE: "grün",
    en_GB: "green"
});

adminApi.apis.Products.updateComposite(
    {productIdentifier: product.id, ignoreAttributeLocks: true},
    {requestBody: product}
);

Delete a Composite Product

Delete an existing composite product along with all its dependencies, except related variants.

This action can not be undone!

adminApi.apis.Products.deleteComposite({productIdentifier: productIdentifier});

Delete by ID

Delete a composite product by id:

adminApi.apis.Products.deleteComposite({productIdentifier: 1});

Delete by Reference Key

Delete a composite product by reference key:

adminApi.apis.Products.deleteComposite({productIdentifier: "key=my-key"});

Manage Composite Variants

Composite Variants are the actual products to be sold in a shop and manage prices and stocks.

In the product hierarchy of SCAYLE each composite product has one or more Composite Variants. These variants are the entities that customers buy online. Stock is managed on variants. Variants differ from each other by defining attributes such as "size" or "pack-size". Just like products, variants can have simple and advanced attributes.

Variants are entities with the most specific representation of a product and are the actual items sold in shops, e.g., sneaker: specific brand > color: white > size: 42).

Variant Stocks

See Composite Products for a detailed explanation on variant stocks.

Variant Prices

See Composite Products for a detailed explanation on variant prices.

Each composite variant consists of other real variants. It must contain at least two real variants. There has to be one but only one main variant. Simple examples are a bikini top and a bikini pant or a jersey with a flocking (e.g. name and number) on the back.

Product Variant Entity

ParameterDetails
id

Integer READ-ONLY

ID assigned by SCAYLE.

referenceKey

String

A key that uniquely identifies the variant of a product (usually an SKU) within the tenant's ecosystem.

ean

String

An ean that refers to a product variant .

attributes

Attribute

A list of attributes attached to the product variant.

prices

ProductVariantPrice

A list of prices attached to the product variant.

stocks

ProductVariantStock

The product variant stock information.

customDataCustomData
isComposite

Boolean READ-ONLY

Indicates whether the variant is composite.

relatedVariants

RelatedProductVariant

A list of variants that belong to the composite variant.

Custom Data

This entity supports custom data, please check the documentation at Custom Data.

Create a Composite Product Variant

Creating a composite variant in its basic version only requires a reference key and at least two related variants. However, if you want to create a complete product variant, you can add additional attributes likes size, prices or promotion keys.

The method is used to create a composite product variant, which can be purchased by a customer.

All prices returned within the response will only contain active prices. If you want to get more detailed information, e.g., future prices, please use the Get Prices Method.

Method Signature

let response = await client.apis.ProductVariants.createComposite({productIdentifier: productIdentifier}, {requestBody: newProductVariant});
let createdProductVariant = response.body;

If there exists a custom data configuration for the productVariant entity and the custom data isn't provided within the create request, the defaultValue specified in the custom data configuration for each property would be considered.

Options

Composite product variant creation can be used with optional parameters - called options:

ParameterDetails
updateIfExists

Boolean

When set to true, a lookup is done for the variant with the given reference key and if it exists then the variant will be updated, otherwise the variant will get created.

ignoreAttributeLocks

Boolean

Force an update or deletion of attributes even if they might be locked.

with

String

The parameter allows you to include the following nested resources in the response:

  • attributes
  • customData
  • prices
  • relatedVariants
  • stocks

It is also possible to exclude all nested resources from the response by providing an empty string as the value.

See this example for details on how to use options.

Create a Basic Composite Product Variant

let newProductVariant = {
    referenceKey: "myReferenceKey",
    ean: "0000007738357",
    relatedVariants: [
        {
            variantReferenceKey: "anExistingRealVariantKey",
            isMainVariant: true
        },
        {
            variantReferenceKey: "anotherExistingRealVariantKey",
            isMainVariant: false
        }
    ]
};

let response = await client.apis.Variants.createComposite({productIdentifier: 1}, {requestBody: newProductVariant});
let createdProductVariant = response.body;

Create a Complete Composite Product Variant

let newProductVariant = {
    referenceKey: "myReferenceKey",
    ean: "0000007738357",
    attributes: [
        {
            name: "size",
            type: "simple",
            value: "M"
        }
    ],
    prices: [
        {
            price: 5000,
            tax: 19.0,
            currencyCode: "EUR",
            countryCode: "DE",
            oldPrice: 6000,
            recommendedRetailPrice: 5500,
            groupKey: "myGroupKey",
            promotionKey: "myPromotionKey",
            validFrom: "2020-06-18T12:00:00+00:00",
            validTo: null
        }
    ],
    customData: {
        "additionalDetails": "details"
    },
    relatedVariants: [
        {
            variantReferenceKey: "anExistingRealVariantKey",
            isMainVariant: true
        },
        {
            variantReferenceKey: "anotherExistingRealVariantKey",
            isMainVariant: false
        }
    ]
};

let response = await client.apis.Variants.createComposite({productIdentifier: 1}, {requestBody: newProductVariant});
let createdProductVariant = response.body;

Create a composite product variant with reference key and EAN

let newProductVariant = {
    referenceKey: "myReferenceKey",
    ean: "0000007738357",
    relatedVariants: [
        {
            variantReferenceKey: "anExistingRealVariantKey",
            isMainVariant: true
        },
        {
            variantReferenceKey: "anotherExistingRealVariantKey",
            isMainVariant: false
        }
    ]
};

let response = await client.apis.Variants.createComposite({productIdentifier: 1, updateIfExists: true}, {requestBody: newProductVariant});
let createdProductVariant = response.body;

Update a Composite Product Variant

As updating composite variants will replace attribute values, it is necessary to confirm overriding locked attributes. In case the ean is not provided it will be deleted for the composite product variant. Also, when an empty array of prices is provided, all active prices will be invalidated and all future prices will be deleted. Passing an empty array of related variants would result in a validation failure and a 400 Bad Request response.

This method can be used to update/replace an existing composite product variant.

This method does not support partial updates.

Method Signature

let response = await client.apis.ProductVariants.updateComposite(
    {productIdentifier: productIdentifier, variantIdentifier: variantIdentifier},
    {requestBody: productVariant}
);

let updatedProductVariant = response.body;

If there exists a custom data configuration for the productVariant entity and the custom data isn't provided within the create request, the defaultValue specified in the custom data configuration for each property would be considered.

Options

Composite product variant update can be used with optional parameters - called options:

ParameterDetails
ignoreAttributeLocks

Boolean

Force an update or deletion of attributes, even if they might be locked.

with

String

The parameter allows you to include the following nested resources in the response:

  • attributes
  • customData
  • prices
  • relatedVariants
  • stocks

It is also possible to exclude all nested resources from the response by providing an empty string as the value.

See this example for details on how to use options.

Attribute Locks

To avoid accidentally deleting or updating attributes that were manually added or edited in the SCAYLE Panel, you can use the query parameter ignoreAttributeLocks to control the behaviour of how these updates should be handled.

The default behaviour is to respect attribute locks, meaning even if an attribute is present in the payload, it will not be updated. The same behaviour will be applied, if an attribute is omitted in the payload, so it will not get deleted.

If you want to force updates or deletion of attributes, you need to set ignoreAttributeLocks = true. In this case all attributes get updated/deleted even if they are locked.

Update Composite Product Variant Prices

let response = await client.apis.Variants.getProductVariant({
    productIdentifier: 1,
    variantIdentifier: 1,
    with: "prices"
});

let productVariant = response.body;
let key = productVariant.prices.findIndex(price => price.groupKey === "myGroupKey");

productVariant.prices[key].price = 9999;

client.apis.Variants.updateComposite(
    {productIdentifier: 1, variantIdentifier: productVariant.id},
    {requestBody: productVariant}
);

Update with Options

let response = await client.apis.Variants.getProductVariant({
    productIdentifier: 1,
    variantIdentifier: 1,
    with: "attributes"
});

let productVariant = response.body;
let key = productVariant.attributes.findIndex(attribute => attribute.name === "size");

productVariant.attributes[key].value = "S";

client.apis.Variants.updateComposite(
    {productIdentifier: 1, variantIdentifier: productVariant.id, ignoreAttributeLocks: true},
    {requestBody: productVariant}
);

Delete a Composite Product Variant

This method can be used to delete an existing composite product variant along with all its dependencies, except related variants.

Deleting a composite product variant requires using one of two identifiers:

  • id
  • referenceKey

This action can not be undone!

Method Signature

client.apis.ProductVariants.deleteComposite({productIdentifier: productIdentifier, variantIdentifier: variantIdentifier});

Delete a composite product variant by id

client.apis.ProductVariants.deleteComposite({productIdentifier: 1, variantIdentifier: 1});

Delete a composite product variant by reference key

client.apis.ProductVariants.deleteComposite({productIdentifier: "key=product-key", variantIdentifier: "key=variant-key"});