Manage Prices
General
Prices in SCAYLE can be concurrently applied to different countries, groups and campaigns as needed.
Prices are defined on a product variant level and contain all information about prices, taxes and validity — upcoming prices can also be defined for automatic future price updates. For a specific variant, there can be multiple prices active at the same time based on the following dimensions:
countryCode: The country in which the price is effective.groupKey: Price groups can be used to apply different prices in different shops within the same country (e.g., B2C vs. B2B).promotionKey: The price promotion key can be used to have multiple valid prices for the same country and price group within the same shop at the same time. The Storefront API can be used to retrieve a specific price by sending a request with a defined price promotion key.
The currency (currencyCode) is not part of these dimensions as it must match the respective country.
Price key
Every new price gets a generated key. This key might change due to certain conditions. For example, when a future price becomes an active price, the key is modified.
Merchant-Specific Prices
You can create prices for a specific merchant by providing its reference key in the merchantReferenceKey field.
Product Variant Price Entity
| Parameter | Details |
|---|---|
| key | String Key assigned by SCAYLE. |
| price | Integer Price of the variant. |
| oldPrice | Integer Old price of the variant. |
| recommendedRetailPrice | Integer Recommended retail price of the variant. |
| buyingPrice | Integer Buying price of the variant. |
| tax | Double A valid tax rate. |
| countryCode | String ISO 3166 alpha 2 country code. |
| currencyCode | String ISO 4217 currency code. |
| groupKey | String Key of the group the price is assigned to. |
| promotionKey | String Key of the promotion the price is assigned to. |
| unitPrice | ProductVariantUnitPrice Describes the price for a specific unit. |
| validFrom | String Controls when the price will be activated. If not present or null, the valid from is specified from now. |
| validTo | String Controls when the price will be deactivated. If not present or null, the price is valid forever. |
| merchantReferenceKey | String A merchant reference key the price belongs to. |
| customData | Object |
Admin API
Create/Update a Price
Prices are defined on variant level and contain all information about prices, taxes and validity — upcoming prices can also be defined for automatic future price updates.
About this Method
This method can be used to create a new Product Variant Price. You can create prices which are valid from now or in the future. If there is an already existing price for the same validation time frame, it will get replaced.
If multiple prices of the same variant are to be created then use update-variant endpoint which supports sending multiple prices at once.
Method Signature
Details
- Creating a new price, which is valid from the time of creation, will invalidate the current active price and become the new active one based on the dimensions explained on the prices overview page.
- It is not possible to create a price for a composite product variant, when the automatic price calculation for composite variants is enabled.
- Future prices are activated by a price activator cron job running every hour. Old prices are then deactivated.
merchantReferenceKeycan be provided to create a merchant-specific price. Note thatmerchantReferenceKeymust refer to one of the merchants provided on product creation.
If you send prices by specifying a merchantReferenceKey for which no product variant exists, you will receive the error message MERCHANT_REFERENCE_KEY_OUT_OF_RANGE.
Create Valid Price from Now
Create Valid Price for Future
Create Valid Expiring Price
Create Price with Unit Price
Create Merchant-Specific Price
Get a Collection of Prices
When you request variant prices, you can extend the query with several identifiers to get multiple active prices.
About this Method
This method can be used to get a collection of existing product variant prices.
The price collection contains currently active and future prices. Expired or invalid prices are not included.
Method Signature
Parameters
| Parameter | Details |
|---|---|
| entities | ProductVariantPrice A collection of product variant prices. |
List of Prices
Delete a product variant price
You can easily delete prices by using the respective identifiers.
About this Method
This method can be used to delete a price.
Method Signature
Details
- It is not possible to remove a price from a composite product variant, when the automatic price calculation for composite variants is enabled.
- It is not allowed to delete a merchant price other than a future price.
- It is not allowed to delete a custom price. When trying to remove a custom price, endpoint would respond with 400 Bad Request,
PRODUCT_VARIANT_CUSTOM_PRICE_CANNOT_BE_DELETEDerror key.
Delete a Price
Price Rounding
A shop country price rounding represents a price rounding assigned to a shop country.
SCAYLE allows users to create and manage any number of price rounding configurations.
The primary purpose of this feature is to allow users to customize price rounding settings.
Price rounding precision
You can set the price rounding precision (the value by which the price is rounded up/down/ to the nearest integer). For simplicity, the precision values used in the following table are allowed:
- 1.0
- 5.0
- 0.05
- 0.95
- 0.99
Example of precision value rounding
| Precision | Rounding Type | Price | Rounded Price |
|---|---|---|---|
| 1.0 | nearest | 1458.90 | 1459 |
| 1.0 | up | 1458.90 | 1459 |
| 1.0 | down | 1458.90 | 1458 |
| 5.0 | nearest | 1458.90 | 1460 |
| 5.0 | up | 1458.90 | 1460 |
| 5.0 | down | 1458.90 | 1455 |
| 0.05 | nearest | 1.02 | 1 |
| 0.05 | down | 1.02 | 1 |
| 0.05 | up | 1.02 | 1.05 |
| 0.99 | nearest | 14.87 | 14.99 |
| 0.99 | down | 14.87 | 13.99 |
| 0.99 | up | 14.87 | 14.99 |
| 0.9 | nearest | 14.87 | 14.9 |
| 0.9 | down | 14.87 | 13.9 |
| 0.9 | up | 14.87 | 14.9 |
The price rounding configurations apply at the shop country and currency level. For example, a configuration set for ACME Switzerland will specifically impact prices in Swiss Francs (CHF) for the corresponding shop and country.
By default, price rounding is disabled. To have it enabled, at least one price rounding configuration should be created.
Only the following prices are affected:
- prices
- old prices
- promotion prices
- campaign prices
Create a Shop Country Price Rounding
Method Signature
Example
Delete a Shop Country Price Rounding
Method Signature
Example
Get a Shop Country Price Rounding Collection
Method Signature
Example
Rounding Basket/Order prices
In case rounding also needs to be applied on the total of an basket/order, a different configuration shall be applied on shop level.
Order value precision
- 1,0
- 5,0
Rounding Type
- nearest
- up
- down
Please note that the float precisions present on price rounding are not available for basket and order level
Rounding basket/order prices when promotions are applied
Whenever promotions or coupons are applied on the Basket, the configured rounding will be taken into consideration when calculating the final item price.
Example:
| Precision | Rounding Type | Discount | Original Price | Final rounded Price |
|---|---|---|---|---|
| 1.0 | nearest | 10% | 1458.90 | 1313 |
| 1.0 | up | 10% | 1458.90 | 1314 |
| 1.0 | down | 10% | 1458.90 | 1312 |
| 5.0 | nearest | 10% | 1458.90 | 1315 |
| 5.0 | up | 10% | 1458.90 | 1315 |
| 5.0 | down | 10% | 1458.90 | 1305 |
Rounding order total
In case a configuration is also set on order level for rounding, the total value of the order will be rounded as per configuration. This will also ensure that, whenever vouchers or other costs are applied during Checkout, the total value or the order will follow the rule set on configuration level.
Storefront API
This section explains how the Storefront API resolves and returns prices at the time of the request.
Please keep in mind that Storefront API does not configure prices.
Price lists, country prices, campaigns, promotions, and sale categories are configured in the SCAYLE Panel or Admin API. For guidance on configuring prices, campaigns, promotions, and rounding, see above.
How Prices Are Returned
The Storefront API returns prices as structured objects. The exact fields depend on where the price appears. The sections below describe each surface.
Product and Variant Prices
Products and variants return the full price object. It appears at variants[].price (List Products, Get a Product), at price (Get a Variant), and as priceRange.min and priceRange.max.
| Field | Meaning |
|---|---|
withTax | Final sellable price including VAT and all applied reductions. This is the price a customer pays. |
withoutTax | Same price excluding VAT. |
currencyCode | Currency returned for the current shop country (for example EUR, USD). |
tax.vat.amount | Absolute VAT amount included in withTax. |
tax.vat.rate | VAT rate for the current shop country. |
recommendedRetailPrice | Reference price without reductions, often shown as a strike-through price. Nullable. |
beforeSaleWithTax | The price before the current sale, including VAT. Present whenever a prior price exists, whether or not a sale is currently advertised. Null when there is none. |
appliedReductions | Reductions applied to reach the final price. Each entry has a category: sale, campaign, or promotion. |
totalAppliedReductions | Combined discount of all applied reductions, as { amount: { relative, absoluteWithTax } }, measured against the reference named in reductionsRelativeTo. Use this instead of summing entries, since per-entry percentages are rounded. null when there is no reduction. |
reductionsRelativeTo | Which reference the discount is measured against: beforeSaleWithTax or lowestPriorPrice.withTax. null whenever totalAppliedReductions is null. |
The lowest prior price is returned separately, as a sibling of the price object (variants[].lowestPriorPrice), requested with variants.lowestPriorPrice.
Basket Prices
Basket prices use a reduced price object. It appears at cost and at each item's price.unit and price.total. It returns appliedReductions and the standard fields, but not beforeSaleWithTax, totalAppliedReductions, or reductionsRelativeTo.
| Field | Meaning |
|---|---|
withTax | Final price including VAT. For unit this is one unit, for total it is unit times quantity. |
withoutTax | Same price excluding VAT. |
currencyCode | Currency for the current shop country. |
tax.vat.amount | Absolute VAT amount included in withTax. |
tax.vat.rate | VAT rate for the current shop country. |
recommendedRetailPrice | Reference price without reductions. Nullable. |
appliedReductions | Reductions applied to reach the final price. Each entry has a category. |
Each basket item returns lowestPriorPrice as a sibling of its price. The item also embeds the variant at variant.price, which carries the full product/variant price object, including beforeSaleWithTax, totalAppliedReductions, and reductionsRelativeTo. Read those from variant.price when a basket line needs them.
Order Prices
Order prices use their own price objects. They return the standard price fields and appliedReductions, but not beforeSaleWithTax, totalAppliedReductions, or reductionsRelativeTo.
Wishlist Prices
Wishlist items do not carry their own price object. Prices come from the embedded product or variant, requested with items.variant or items.product. That embedded price is the full product/variant price object, including beforeSaleWithTax, totalAppliedReductions, and reductionsRelativeTo. Read pricing from items.variant.price (or items.product.variants[].price).
Sale Prices and Lowest Prior Price Enforcement
A reduction is always measured against a reference price, called the reduction anchor. The field reductionsRelativeTo tells you which price that is:
beforeSaleWithTax: the old price - the regular price before the current sale.lowestPriorPrice.withTax: the lowest price of the last 30 days.
You can turn on Lowest Prior Price enforcement to comply with EU price-transparency rules. It is configured separately for sale prices and campaign prices in the SCAYLE Panel under Sale Indicator Rules. Enforcement decides which anchor is used, and whether a reduction is advertised at all. Always read reductionsRelativeTo and strike the price names. Do not assume the previous price is always the reference.
The four cases below all describe the same variant: a current price (withTax) of 5790 and a previous price (beforeSaleWithTax) of 11900. Only the shop country's enforcement setting and the 30-day low change. The excerpts show just the price fields that matter, plus the sibling lowestPriorPrice.
1. Enforcement off (default): measured against the previous price
The reduction is 51% off the previous price 11900. reductionsRelativeTo is beforeSaleWithTax. A lowest prior price may still be returned, but it is not used.
2. Enforcement on, current price below the 30-day low: genuine sale
The 30-day low is 8000. Because the current price 5790 is below it, the sale is genuine and the reduction is measured against the low: 28% off 8000, not off the previous price. reductionsRelativeTo is lowestPriorPrice.withTax. beforeSaleWithTax is still returned.
3. Enforcement on, current price not below the 30-day low: no sale
The 30-day low is 5600. The current price 5790 is not below it, so there is nothing to advertise. appliedReductions is empty, totalAppliedReductions and reductionsRelativeTo are null, and the product is not treated as on sale, even though 5790 is still below the previous price 11900. This is the EU Omnibus case.
4. Enforcement on, but no lowest prior price: fallback to the previous price
Without a 30-day low, for example on a product without enough recent price history, enforcement has no reference to apply. The reduction falls back to the previous price, exactly as when enforcement is off, and reductionsRelativeTo is beforeSaleWithTax.
These fields appear wherever the full price object is returned, so the same four cases apply to product and variant prices (List Products, Get a Product, Get a Variant). On basket lines the reduction is suppressed in the same way, but beforeSaleWithTax, totalAppliedReductions, and reductionsRelativeTo are read from the embedded variant.price; see Basket Prices.
Campaign context
Enforcement is configured independently for the base sale price and the campaign price, so a variant can be a genuine sale on one and suppressed on the other. When your request includes a campaignKey, the same four cases apply to the campaign price, totalAppliedReductions reflects the combined reduction (for example a base sale plus the campaign), and appliedReductions lists each reduction separately.
For example, a variant with a previous price of 10000 that is 20% off as a base sale and then part of a 25% campaign returns both reductions in appliedReductions, while totalAppliedReductions gives the combined 40% off the previous price:
The sale takes 20% off the 10000 previous price (2000), the campaign takes a further 25% off the resulting 8000 (2000), and totalAppliedReductions reports the combined 4000 (40%) measured against beforeSaleWithTax.
General Pricing Behavior
Price Calculation
When Storefront API calculates a price, it evaluates the pricing layers in the following order:
| Pricing Layer | Name | Notes |
|---|---|---|
| 1 | Promotion price (pricePromotionKey) | Only if a promotion price exists |
| 2 | Campaign price (campaignKey) | Only if a campaign price exists |
| 3 | Merchant price | Marketplace setups |
| 4 | Customer group price | E.g., B2B customer |
| 5 | Country price | Derived from shopId |
| 6 | Base price | Requirement for products to be returned in Storefront API. |
- If a pricing layer does not exist, the Storefront API falls back to the next one.
To receive variants without a base price in the Storefront API, you must configure products as sellable for free as described here. Otherwise, variants without a base price will be ignored.
Where Prices Appear in Storefront API
Products → List Products
- Endpoint:
GET /v1/products
Returned Price Fields
| Field | Meaning |
|---|---|
priceRange.min / priceRange.max | Lowest / highest final price among the product's sellable variants. Each is a full price object. |
variants[].price (optional, if variants are included) | Full price object per variant, including beforeSaleWithTax, totalAppliedReductions, and reductionsRelativeTo. |
variants[].lowestPriorPrice (optional) | Lowest prior price, returned as a sibling of the variant price. Requested with variants.lowestPriorPrice. |
Request example:
Context of the request:
shopId=10001applies country-specific price, currency (EUR), and VAT rules.with=priceRange,variants.lowestPriorPricereturns the variants with their prices, the product price range, and the lowest prior price for each variant.- The sale reduction appears in
appliedReductions, and the combined figure intotalAppliedReductions. lowestPriorPriceis returned as a sibling of each variant's price.
Response (excerpt):
Products → Get a Product
- Endpoint:
GET /v1/products/{productId}
| Field | Meaning |
|---|---|
variants[].price | Full price object per variant, including beforeSaleWithTax, totalAppliedReductions, and reductionsRelativeTo. |
variants[].lowestPriorPrice (optional) | Lowest prior price, returned as a sibling of the variant price. |
priceRange (optional) | Lowest and highest final price across the product's sellable variants. |
Request example:
Context of the request:
shopId=10001applies country-specific price, currency (EUR), and VAT rules.campaignKey=BLACKWEEKapplies the campaign reduction on top of the base/country price.- This product is on sale and also part of the campaign, so
appliedReductionslists both entries andtotalAppliedReductionsis the combined figure. lowestPriorPriceis returned as a sibling of each variant's price.
Response (excerpt):
Filters (Price Object)
- Endpoint:
GET /v1/filters(You may also receive filters embedded in other endpoints.)
| Field | Meaning |
|---|---|
price.min | Lowest price in the result set |
price.max | Highest price in the result set |
Difference of the price facet as opposed to per-product ranges:
| Price Source | What it measures |
|---|---|
priceRange in /v1/products | Min/max across all variants of one product |
price facet in filters | Min/max price of the cheapest variant across products |
Request example:
Context of the request:
categoryId=123limits filters to products in this category.shopId=10001applies country-specific price, currency (EUR), and VAT rules.with=valuesreturns filter values, including the price facet withmin/max.campaignKey=BLACKWEEKapplies campaign reductions where available, affecting the returned price boundaries.- Each product contributes one value: the price of its cheapest sellable variant in this request context.
Response (excerpt):
Variants → Get a Variant
- Endpoint:
GET /v1/variants/{variantId}
| Field | Meaning |
|---|---|
price | Full price object for this variant, including beforeSaleWithTax, totalAppliedReductions, and reductionsRelativeTo. |
lowestPriorPrice (optional) | Lowest prior price, returned as a sibling of the price object. Requested with lowestPriorPrice. |
Request example:
Context of the request:
shopId=10001applies country-specific price, currency (EUR), and VAT rules.campaignKey=BLACKWEEKapplies the campaign reduction on top of the base/country price.- This variant is on sale and also part of the campaign, so
appliedReductionslists both entries andtotalAppliedReductionsis the combined figure. lowestPriorPriceis returned as a sibling of the price object when requested withlowestPriorPrice.
Response (excerpt):
Basket → Get a Basket
- Endpoint:
GET /v1/baskets/{basketId}
| Field | Meaning |
|---|---|
items[].price.unit | Final price for one unit. |
items[].price.total | Unit price times quantity. |
items[].price.unit.appliedReductions | Sale, campaign, or promotion reductions on the item. |
items[].lowestPriorPrice | Lowest prior price, returned as a sibling of the item's price. |
cost | Full basket totals. |
Request example:
Context of the request:
shopId=10001applies country-specific price, currency (EUR), and VAT rules.campaignKey=BLACKWEEKapplies a campaign reduction, shown inappliedReductions.- Basket pricing is recalculated on every GET.
items[].price.unitis the final unit price anditems[].price.totalis unit times quantity.costreturns the basket totals.
Response (excerpt):
Basket → Add or Update
- Endpoints:
POST /v1/baskets/{basketId}/itemsPUT /v1/baskets/{basketId}/promotions
Request example:
Context of the request:
campaignKey=BLACKWEEKapplies a campaign reduction, shown inappliedReductions.- The returned basket contains fully recalculated prices for all items and totals.
Response (excerpt):
campaignKey vs. pricePromotionKey
Assume a variant has the following pricing configuration:
- Base price: €219.00
- Promotion price (PPK): €199.00
- Active campaign discount: 10%
| Storefront API Request | Final Price | Explanation |
|---|---|---|
| No keys | €219.00 | Base price applies. |
pricePromotionKey=24 | €199.00 | The promotion price replaces the base price. |
| €179,10 | The promotion price replaces the base price. The campaignKey reduction is applied on top of the promotion price. |
This shows that:
campaignKeyapplies a reduction on top of the resolved price (base, country, merchant, customer group, or PPK) and does appear inappliedReductions(usually withcategory: "campaign"and thecampaignKeyas a label).
Price Range And Filter Calculation
| Calculation | Description | Used For |
|---|---|---|
Per-product priceRange | For each product, the Storefront API resolves prices for all sellable variants and returns the min/max values. | Product Listing Page - “from” prices, Price summaries on Product Detail Page |
| Filters price facet | For each product in the result set, the Storefront API takes the price of its cheapest sellable variant, then calculates global min/max across these values. | Price sliders and global boundaries |
To fully understand how priceRange and the filters price facet behave, it’s important to clarify which variants are included, how prices are resolved, and how aggregation differs between product-level ranges and global filters:
- Sellable variants only: Variants without a valid resolved price (for example, missing base price) are ignored in both
priceRangeand filters. - Context-aware: Campaigns (
campaignKey), price promotions (pricePromotionKey), customer group, merchant, and other filters (e.g., size, color) all influence which variants are considered sellable and what their final prices are. - Different aggregation levels:
priceRangeaggregates within a single product.- The price facet aggregates across products in the current result set.
Both use the fallback logic of general pricing behavior.
Sale And NoSale Category Behavior
You have the option only to include products that are on sale in a category as described here (this does not include products included in campaigns or other promotions). The opposite, where you only include products that are not on sale, is also possible. Whether a category is a sale or a non-sale category can be identified through the reserved sale property in the Storefront API. They will be called sale and noSale categories from here on.
Request example:
Response (sale category):
International Prices
Prices may differ from shop to shop. That may be due to local prices being applied, as well as the different VATs or currencies being applied to different shops.
| Behavior | Result |
|---|---|
| Country price exists | The price corresponding to the country of the shopId will be returned. |
| Country price missing | The base price, independent of the shopId, will be returned. |
| VAT and currency | VAT and currency corresponding to the respective shopId will apply to the prices returned. |
Edge Cases
These edge cases are important for interpreting price ranges, filters, and basket values.
| Situation | Storefront API Behavior | Explanation |
|---|---|---|
| Variant has no valid base price | Variant cannot be sold; ignored in product priceRange and filters | A base price is mandatory. Without it, the Storefront API cannot produce a final price. |
| All variants have base price but some have missing country/campaign/PPK prices | Only variants with valid resolved prices are included in priceRange and basket responses | Falls back through resolution layers. If fallback fails, variant is treated as unsellable for this request. |
| Only some variants have campaign prices | Dependent on variant. | Campaign applies per variant, not per product. |
| Mixed merchant or customer group pricing | priceRange and filters reflect the resolved price per variant under current merchant/group | Results may differ between anonymous and logged-in customers, or between merchants |