A Product is the core sellable entity in the platform. Products are typed via product_type, which fundamentally changes their role and behavior. All products share the same Product table and class — child and grouped products are not separate entity types.
Products support soft deletion: is_deleted (bool) and deleted_at (DateTime?) are set when a product is deleted rather than removing the row. Regular users cannot query deleted products at all; admin users can retrieve them by passing is_deleted=true as a query parameter.
product_type |
Value | Description |
|---|---|---|
normal |
0 | Standard standalone product |
grouped |
3 | A parent product that presents selectable child products via variant groups |
child |
4 | A sub-product used as the backing product for a variant option; not sold independently |
fee |
5 | A fee automatically added to an order; not a purchasable item |
mix_and_match_bundle |
1 | A bundle the customer assembles themselves from a pool of products |
predefined_bundle |
2 | A bundle with a fixed set of included products |
The default type. Has its own prices, localizations, images, variants, and categories. Appears in catalog listings unless hidden. Everything described in the rest of this document applies to normal products as the baseline.
product_type = child)Child products are sub-products used as the backing for variant options — most commonly inside grouped products. They are not directly purchasable from the catalog.
VariantProperty.product. When a variant group has is_product_variants = true (the default), each variant option is backed by a product — typically a child product.product_type = fee) are similar — hidden from the catalog and attached to purchasable products via the fees field. The fees field is not available on child or fee type products.product_type = grouped)A grouped or grouping product is a parent product whose purchasable content is defined via variant groups. The customer selects which child products to buy by choosing from variant options.
group_kind = variation. The quantity "sum" of a grouped product is the total of all items selected from variation-kind groups.VariantProperty.product with product_type = child).ingredient treats that group as variation.Text content lives in localizations (IList<IProductLocalization>), one entry per language. Resolution falls back to a default language if the user's language has no entry.
Each localization record contains:
| Field | Notes |
|---|---|
title |
Full product name |
short_title |
Compact name used in cart and receipts |
short_description |
HTML rich text; shown in listings |
long_description |
HTML rich text; shown on product detail page |
highlight_text |
Promotional callout text |
eye_catcher_text |
Attention-grabbing badge/label |
checkout_text |
Additional text shown during checkout |
receipt_text |
Appears on customer order confirmation |
merchant_receipt_text |
Appears on merchant-side receipts |
unit_title |
Custom unit label (e.g. "package", "box") |
keywords |
List<string> for search indexing |
details_url |
External URL to product page in another system |
Title resolution priority in code: short_title→ title→ computed_title→ #<id>.
Description resolution: short_description (when preferShorter = true) → highlight_text fallback; or long_description for full detail.
prices is an IList<EntityPrice> — each product can have multiple price entries.
Key pricing rule: When multiple prices are present, the lowest value is the sale price (what the customer pays) and the next-lowest is the regular/original price (shown as a crossed-out "was" price).
Each EntityPrice has:
| Field | Notes |
|---|---|
value |
Price amount |
currency_id / currency |
Currency for this price |
kind |
each (per unit), total (flat), per_unit (by measurement) |
min_amount |
Minimum quantity this price applies to (default: 1) |
campaign_id |
If set, price is only active during that campaign |
price_group_id |
Price group for segment-based pricing (e.g. wholesale) |
client_account_id |
Account-specific price override |
title |
Internal name for the price entry |
computed_active_prices (not persisted) reflects the currently applicable prices for the requesting user/context after campaign, account, and price group resolution. See Computed Fields for how to request it.
category_relations is an IList<ProductRelCategory> — a product can belong to multiple categories.
Each ProductRelCategory record:
| Field | Notes |
|---|---|
product_id |
The product |
category_id |
The category |
is_primary_category |
Marks the main category (used for breadcrumbs, computed_main_category_id) |
ordinal |
Sort position within this specific category |
campaign_id |
Category membership is campaign-scoped (optional) |
computed_main_category_id and computed_main_category_title are derived from the is_primary_category = true relation.
Products also have a top-level ordinal field for global ordering outside of a category context. When ordering within a category, product_rel_category.ordinal takes precedence over the product-level ordinal.
| Field | Behavior |
|---|---|
is_active |
false= product is completely inactive; not shown or purchasable |
is_hidden |
true= hidden from catalog listings but accessible via direct link |
is_campaign_only |
Only purchasable when an active campaign includes it |
is_purchasing_disabled |
Product is visible but cannot be added to cart |
is_restricted_to_specific_client_accounts |
Only shown to explicitly granted customer accounts |
is_restricted_to_providers |
Only shown to logged-in sellers/admins |
Variants allow a single product to have configurable options (e.g. size, color, or — for grouped products — which child products to include). The system has several interconnected layers.
VariantGroup is a reusable shared entity. A product links to variant groups via variant_group_relations (IList<ProductVariantGroup>).
Key fields on VariantGroup:
| Field | Notes |
|---|---|
group_kind |
Controls how selections count — see VariantKind below |
picker_kind |
UI control used to display this group — see VariantPickerKind below |
is_product_variants |
When true (default), each variant option is backed by a Product. When false, variants are localization-only labels with no backing product; pricing comes from VariantProperty.price directly. |
is_amount_pickable |
Customer can specify a quantity per option |
min_amount / max_amount |
Min/max total quantity across all options in the group |
min_number_of_choices / max_number_of_choices |
Min/max number of distinct options the customer must/can select (independent of individual quantities) |
prices |
Default prices for this group; can be overridden at the product level |
properties |
The variant options (IList<VariantProperty>) |
group_kind)| Value | Description |
|---|---|
ingredient |
Option is a component of the product. Quantities multiply with the parent product amount when processed in orders. |
variation |
Option is a product variation. In grouped products, selections from variation groups are what counts toward the grouped product's quantity sum. |
acquisition |
Add-on purchase. Counted and priced independently — quantities do not multiply with the parent product amount. |
picker_kind)| Value | Description |
|---|---|
toggle_box |
Checkbox (default) |
button |
Button selector |
toggle_box_button |
Toggle button |
dropdown |
Dropdown — implicitly single-choice |
amount_only |
Quantity picker only, no label |
file |
Image/file upload — implicitly single-choice |
text |
Free-text input — implicitly single-choice |
dropdown, file, and text pickers enforce a maximum of one selection.
Each entry in product.variant_group_relations is a ProductVariantGroup — a product-specific configuration that overrides the shared VariantGroup settings for this product only.
| Field | Notes |
|---|---|
variant_group_id / variant_group |
The shared group definition |
is_locked |
Prevents the customer from changing selections (staff can still override) |
free_amount |
Number of units from this group that are included at no charge |
min_amount / max_amount |
Overrides the group's total quantity limits for this product |
free_number_of_choices |
Number of distinct choices that are free (no charge) |
min_number_of_choices / max_number_of_choices |
Overrides the group's choice count limits for this product |
prices |
Product-specific prices for this group |
product_variant_properties |
Product-specific settings per variant option (IList<ProductVariantProperty>) |
Effective limits resolve as: ProductVariantGroup value if set → VariantGroup value → special default (e.g. min=1 when it is a grouped product with a single variant group).
Each option within a group. When VariantGroup.is_product_variants = true, each VariantProperty has a product reference to its backing Product (typically product_type = child).
Title resolution: computed_title→ product.title→ localization name.
| Field | Notes |
|---|---|
product_id / product |
The backing product for this variant option |
price |
Default price surcharge for this variant (used when no product-level override exists) |
is_default |
Pre-selected by default |
amount_per_product |
Required quantity of this variant per unit of parent product ordered |
min_amount / max_amount |
Quantity limits for this option |
Each entry in product_variant_group.product_variant_properties is a ProductVariantProperty — a product-specific override for a single variant option within a ProductVariantGroup.
| Field | Notes |
|---|---|
price |
Overrides the variant's default price when purchased as part of this specific parent product |
is_default |
Pre-selection override for this product |
is_locked |
Customer cannot change this variant on this product |
amount_per_product |
Quantity override |
min_amount / max_amount |
Quantity limit overrides |
When a variant is backed by a product (VariantProperty.product), the variant's price is taken from that product's prices. ProductVariantProperty.price overrides this price specifically for that variant when it is purchased through a particular parent product — allowing the same child product to carry a different price depending on which parent it is bought through.
variant_combinations on the product pre-defines valid or forced combinations of variant choices (e.g. only certain size+colour pairings are available). Used to restrict or preset selections. Categories can also carry their own variant_combinations; these are merged with the product's combinations (computed_variant_combinations on the category) to control which variants are visible when browsing that category.
bundle_levels (IList<ProductBundleLevel>) — quantity-tier pricing (e.g. buy 5 get a lower price). Each level has number_of_units and its own prices. If no bundle level matches the quantity ordered, the product's base prices apply as the fallback.hierarchy_relations (IList<IProductHierarchyRelation>) — the child products contained in mix_and_match_bundle or predefined_bundle product types. Each relation has relation_weight (how many units the child counts as in the bundle) and an optional amount_text descriptor (e.g. "500g").stock_relations (IList<IProductStock>) — one record per inventory location.
| Field | Notes |
|---|---|
is_available |
Manual on/off switch |
is_stock_managed |
Whether quantity is tracked |
amount_in_stock |
Current quantity |
allow_order_out_of_stock |
Allow ordering even when depleted |
campaign_id |
Stock record is campaign-scoped (optional) |
| Field | Notes |
|---|---|
min_amount_per_order |
Minimum quantity customer must order |
max_amount_per_order |
Maximum quantity customer can order |
is_amount_locked |
Customer cannot choose quantity; it is assigned by rules |
order_periods |
Time windows during which the product is purchasable |
delivery_periods |
Time windows for delivery/handover |
min_customer_age |
Age-gated product |
required_customer_type |
Restrict to a customer kind (e.g. business, individual) |
| Field | Notes |
|---|---|
is_digital |
No physical shipment |
shipping_price |
Fixed shipping cost override |
is_shipping_price_per_order |
Fixed cost regardless of quantity |
weight |
kg |
length / width / height |
Meters |
is_packaged_separately |
Must ship alone, not combined |
compatible_shipping_method_relations |
Allowed shipping methods |
| Field | Notes |
|---|---|
number_of_preparation_days |
Days needed before the product is ready |
preparation_start_latest_time_of_day |
Latest time of day to start preparation |
preparation_off_days_of_week |
Days when no preparation happens |
manufacture_duration |
Time to produce one unit (multiplies with quantity ordered) |
Purchasing a product can trigger side-effects:
| Field | Notes |
|---|---|
product_granting_ownable_items |
Adds items to the user's inventory |
product_granting_permissions |
Grants platform permissions |
product_granting_subscriptions |
Creates or extends a subscription plan |
product_granting_rebates |
Grants discount codes |
product_granting_tokens |
Grants redemption tokens from a pool |
is_granting_sub_order_token |
Generates a sub-order token after purchase |
| Field | Notes |
|---|---|
is_redeemable |
Generates a voucher/ticket on purchase |
is_single_ticket_preferred |
Bundle multiple vouchers into one |
is_ticket_sent_before_payment |
Send voucher before payment is confirmed |
is_user_redeemable |
Voucher can be redeemed without a QR code |
Computed fields are marked IncludeByDefault=false and are not returned in responses unless explicitly requested. Use the include query parameter to request them:
GET /resources/Product/123?include=computed_active_prices,computed_main_category_id
Multiple fields can be comma-separated. See RestApi-en.md for full documentation on include.
| Field | Notes |
|---|---|
computed_active_prices |
Prices effective for the current user/campaign context |
computed_main_category_id / computed_main_category_title |
Primary category |
computed_is_new |
Whether the product is flagged as new |
computed_price_reductions |
Discount percentages per price group |
computed_number_of_sold |
Sales count (provider-only) |
computed_title |
Resolved title (falls back across localization) |
computed_owner_department_title |
Owner department name |