Products
The Product
Import Endpoint
https://{BASE_URL}/imports/products
All import integration endpoints use the same authentication parameters, please see Authentication page for more details.
Advanced Product Import Parameters
When importing Products
you can additionally specify which Markets
you wish to import for. The default market is dk
(Denmark) - signifying all Shops
in the Danish market.
The default can be overridden by the query parameter markets
. The value is comma separated lists of markets.
E.g.:
https://{BASE_URL}/imports/products?markets=dk,se,no&...
For further clarification of the Market
concept, please read the Vocabulary.
There are two further query parameters that can be used when importing products. These are related to partial product import.
Importing Products
Send a POST
request to the endpoint with the parameters specified above.
The body of the POST
request must be a JSON object with the following layout:
{
"products": [
[product A],
[product B],
...
]
}
The definition of the Product
model can be found in the Models document. The minimal data required for a regular product is an id
, a retail_price
and a name
:
{
"products": [
{
"id": "0001",
"name": "Coffee",
"retail_price": 25
},
{
"id": "0002",
"name": "Tea",
"retail_price": 20
}
]
}
NOTE
When products are imported into the Ka-ching system, the
id
will be used as a unique key.One limitation in the database we are using (the Firebase real-time database) is that not all characters are valid to use in keys.
This means that the following list of characters may not be used in the "id" field of a product:
.
,/
,#
,$
,*
,[
and]
Other basic properties of products include:
description
short_description
image_url
barcode
The description
will be shown when displaying product details.
The image_url
is a URL for an image that will be shown in the Product Grid, basket and Product Details.
The barcode
could be either an EAN
number, a SKU
, an ISBN
number or any other means of identifying a product. The only thing that matters for the POS is that this is the bar code that you would scan in the shop in order to add the product to the basket.
Advanced product models
Importing products with prices for different markets
All price information on a product will be interpreted to be in the currency of the market in which they are imported.
If you are importing for multiple markets, then you naturally need to be able to specify prices for each of these markets. This can be done by supplying the pricing information as objects keyed by the market. Here is an example of importing a few products in the markets: Denmark, Norway and Sweden:
{
"products": [
{
"id": "0001",
"name": "Coffee",
"retail_price": {
"dk": 25,
"no": 35,
"se": 30
}
},
{
"id": "000"",
"name": "Tea",
"retail_price": {
"dk": 20,
"no": 32,
"se": 28
}
},
...
]
}
Localizing product names and other details
Throughout the Ka-ching system, data can be localized into multiple languages. This could for instance be used to display a chinese version of product names on a secondary, customer facing display.
The way this is modelled is that all string data can either be a simple, non-localized string -or- an object containing localizations for various language codes.
Localizing the product name could for instance be done as follows:
{
"products": [
{
"id": "0001",
"name": {
"da": "Kaffe",
"en": "Coffee",
"nb": "Kaffe"
},
"retail_price": 25
}
]
}
Variable priced products
The Ka-ching system allows for variable priced products
. These are products that do not have an explicit retail price, but rather need the product price to be entered when the product is added to the basket on the POS.
Variable priced products
can be created simply by not specifying a retail price
.
{
"products": [
{
"id": "0001",
"name": "Variable priced coffee"
}
]
}
Unit priced products
The Ka-ching system allows for unit priced products
. These are products that do not have an explicit retail price, but rather need the product price by weight, length, area or volume - where the unit countis entered when the product is added to the basket on the POS.
Unit priced products
can be created simply by not specifying a retail price
, but specifying a unit pricing
entry instead.
{
"products": [
{
"id": "0001",
"name": "Candy",
"unit_pricing": {
"unit": "mass/g",
"multiplicity": 100
"retail_price_per_unit": 7.5,
}
}
]
}
The (optional) multiplicity
above means that the unit price is per 100 * of the unit. In the example above, the candy costs 7.50 per 100 grams.
The available units are:
mass/g
mass/kg
volume/l
volume/dl
volume/cl
volume/ml
length/m
length/cm
length/mm
area/m2
area/cm2
area/mm2
You can also speciy an optional cost_price_per_unit
, which will allow unit priced products to also count the costs.
Examples:
{
"products": [
{
"id": "0001",
"name": "Candy",
"unit_pricing": {
"unit": "mass/kg",
"retail_price_per_unit": 100
}
}
]
}
Will show the product: Candy
with the price description: 100,00 per kg
.
Adding 2.5 kg
of candy will result in the following line item:
Candy (2.5 kg) - price: 250
You can also use multiplicities with decimal values - if appropriate for your use case.
{
"products": [
{
"id": "0002",
"name": "Hummus",
"unit_pricing": {
"unit": "volume/dl",
"retail_price_per_unit": 15,
"cost_price_per_unit": 5,
"multiplicity": 4.5
}
}
]
}
Will show the product: Hummus
with the price description: 15,00 per 4.5 dl
.
Adding 2 dl
of hummus will result in the following line item:
Hummus (2 dl) - price: 6.67
The cost price will be 2.22 (2 * 5 / 4.5)
Taxes
The market that your shop operates in defines a set of one or more default taxes. This means that in countries like Denmark, you never need to specify other taxes directly on the product.
But in special situations you have the option to override the set of taxes for individual products. This can be done as follows:
{
"products": [
{
"id": "0001",
"name": "Coffee",
"retail_price": 25,
"taxes": [
{
"id": "coffee_tax",
"name": "Coffee tax",
"rate": 0.1,
"type": "vat"
}
]
}
]
}
Taxes can be either vat
or sales_tax
. This determines how the base price is calculated from the retail price. See the Vocabulary for more on this.
Notice that the taxes are specified as an array. This is because in some countries, multiple taxes can be in effect at the same time. For instance you could imagine taxation to be split into two rates defining 'city tax' and 'state tax'. Again the Vocabulary has more details on this concept.
Creating simple, fixed sale
discounts
Our Discount Campaign
engine can be used to model powerful discount concepts in a very expressive manner, but sometimes this complexity is unneeded.
So if a product
is currently on sale, this sale_price
can be added directly to the product
.
When a product
with a sale_price
is added to the basket, a new price discount
is automatically applied to the item.
{
"products": [
{
"id": "0001",
"name": "Coffee on sale",
"retail_price": 25,
"sale_price": 15
}
]
}
Unlocking Contribution Ratio
functionality in the POS
In the Ka-ching Back Office, you can configure a desired Contribution Ratio
for your sales. In order for this to work, you need to supply a cost price for all products.
When a desired Contribution Ratio
is configured, the Action Button
in the bottom right of the POS will show a status indicating wether the Contribution Ratio
is met (green) or not (orange) or if money are actually lost by performing the sale (red). This could be the case if too much of a discount is being applied to the sale.
Note that retail prices in Ka-ching always represent the price that you would print on a price-tag (meaning including VAT, but excluding american style sales tax), but cost prices are always represented without any taxes.
{
"products": [
{
"id": "0001",
"name": "T-shirt",
"retail_price": 150,
"cost_price": 40
}
]
}
Purchase types
The concept of purchase types exists to let pricing and tax details of a product vary with an aspect of the sales situation. Most often this would be used to model varying prices and taxes for 'eat in' and 'dine out' sales. For instance taxation in many states in the US varies exactly based on this choice.
Currently the setup for the available purchase types needs to be performed by Ka-ching, but when the desired purchase types are added, you may specify price or tax variations for each purchase type in the manner shown below. Note that you can default to the base pricing and taxing, so if you are modelling 'eat in' vs. 'dine out', you only need to use once explicit purchase type.
For the example below, imagine that we have defined the purchase type 'dine_out', and that the absence of 'dine_out' means 'eating in'.
{
"products": [
{
"id": "0001",
"name": "Pie",
"retail_price": 25,
"retail_price_map": {
"dine_out": 20
}
}
]
}
Similarly you can vary taxes by having both a 'taxes' and a 'taxes_map':
{
"products": [
{
"id": "0001",
"name": "Pie",
"retail_price": 25,
"taxes": [
{
"id": "coffee_tax",
"name": "Coffee tax",
"rate": 0.1,
"type": "vat"
}
],
"taxes_map": {
"dine_out": [
{
"id": "coffee_tax",
"name": "Coffee tax",
"rate": 0.07,
"type": "vat"
}
]
}
}
]
}
Product Groups
Product Groups
can be defined in the Ka-ching Back Office and are used to group sold goods on the X- and Z-reports. A Product Group
is defined as an identifier and a display name. By adding the product group identifier to a product on import, this will make the grouping appear on the reports.
A Product
can belong to 1 or 0 Product Groups
{
"products": [
{
"id": "0001",
"name": "Pie",
"retail_price": 25,
"product_group": "deserts"
}
]
}
More functionality may be added around the Product Group
concept in the future.
Tags
Tags
can be defined in the Ka-ching Back Office and are informal identifiers that can be added to a Product
. A Tag
is defined as an identifier and a display name.
In the Ka-ching POS, the tags can be used to create the tab bar and a folder hierarchy.
Tags can also be used in conjunction with Discount Campaigns as a means of triggering discounts in case a certain number of products bearing the same tag are in the shopping basket.
Any number og tags can be added to one Product
.
{
"products": [
{
"id": "0001",
"name": "Pie",
"retail_price": 25,
"tags": {
"deserts": true,
"food": true,
"popular": true
}
}
]
}
More functionality may be added around the Tag
concept in the future.
Attributes
Attributes
can be defined in the Ka-ching Back Office. They are similar to tags, but allow you to define stronger semantics between values.
Consider the tags merlot
, cabernet
, denmark
and france
. While all of these may be used for filtering purposes, there is nothing that binds merlot
and cabernet
to each other - and the same for denmark
and france
. One product could easily contain all four tags.
Instead you may wish to define a more formal relationship where you let the system know that merlot
and cabernet
are both types of grapes
- and denmark
and france
are both countries
.
From Ka-ching release 11 you may now specify an Attribute
called Grape
that can list all of the available grapes - and similarly for Country
.
For each of these two attributes, a product can only have a single value.
From Ka-ching release 12 we've introduced two new attribute types, text
and number
, where the syntax is almost the same as for option references described above. Take a look at Attributes import for details on how to import definitions for all possible attribute types in Ka-ching. The value for a number
attribute is just the number. The value for a text
attribute is localizable string wrapped in an object keyed by text
. Take a look below for an example.
With these added semantics, each attribute can now be represented in Ka-ching POS as a search facet.
Any number of attributes can be added to one Product
.
{
"products": [
{
"id": "0001",
"name": "2016, Grand Sud",
"retail_price": 305,
"attributes": {
"grape": "merlot",
"country": "france",
"length": 1,
"misc": {
"text": {
"en": "Store up to 10 years"
}
}
}
}
]
}
More functionality may be added around the Attribute
concept in the future.
For instance, attributes can only be 'option sets' today. In the future they might also embrace numerical values and other data types.
Product Variants
In the Ka-ching system, a product can either be a stand-alone, sellable entity, but it can also be used to model the concept of Product Variants
. If, for instance, a product
can be sold in a big
and a small
variant, then this can be modelled as a product
that has two variants
. This basically means that the product itself is not a sellable entity (you cannot sell this product without knowing whether you are selling the big or the small variant), but each variant becomes a sellable entity instead.
The variant can have it's own name
, barcode
, image_url
and can also specify price variations. If it does not specify a retail_price
, the retail_price
of the product
will be used.
In the POS, tapping a product with variants will display the variant selector. Variants are displayed in a list unless dimensions
are also specified (read more about dimensions below).
If all variants of a product have the same price, this price will be displayed in the Product Grid. If the prices of the variants vary, then the prices will only be shown when the product is tapped.
{
"products": [
{
"id": "0001",
"name": "Pie",
"retail_price": 25,
"variants": [
{
"id": "small",
"name": "Small"
},
{
"id": "big",
"name": "Big",
"retail_price": 50
}
]
}
]
}
Dimensions and dimension values
Imagine the situation where you sell a shirt in sizes: S, M and L and colors: red, yellow and blue. This basically gives rise to 9 variants, so one way of modelling this would just be to include 9 variants with the names "S, red", "S, yellow", etc., etc.
This would of course give a list of 9 variants to choose from in the UI, but conceptually, the cashier just needs to make one choice of the size and one choice for the color. These two choices will then uniquely identify one of the 9 variants.
In the Ka-ching system we model this by a concept called dimensions
. A product
can contain any number of dimensions
- and each of these dimensions
can define a number of available dimension values
. In the example above we could define the dimension: size
with the values S
, M
and L
and the dimension: color
with the values red
, yellow
and blue
.
By defining these, and adding a dimension value for each dimension to each of the variants, the POS can now display a much nicer UI - namely a list of dimensions, each having a number of possible values to select from.
In order to make the experience for the cashier even nicer, the dimension values can define both a color and also an image.
Example of a product with variants and dimensions:
{
"products": [
{
"id": "0001",
"name": "T-shirt",
"retail_price": 150,
"dimensions": [
{
"id": "size",
"name": "Size",
"values": [
{
"id": "s",
"name": "S"
},
{
"id": "l",
"name": "L"
}
]
},
{
"id": "color",
"name": "Color",
"values": [
{
"id": "red",
"name": "Red",
"color": "#FF0000",
"image_url": "https://url.for/red_cloth.image"
},
{
"id": "blue",
"name": "Blue",
"color": "#0000FF",
"image_url": "https://url.for/blue_cloth.image"
}
]
}
]
"variants": [
{
"id": "a",
"dimension_values": {
"color": "red",
"size": "s"
}
},
{
"id": "b",
"dimension_values": {
"color": "red",
"size": "l"
}
},
{
"id": "c",
"retail_price": 200,
"dimension_values": {
"color": "blue",
"size": "s"
}
},
{
"id": "d",
"retail_price": 200,
"dimension_values": {
"color": "blue",
"size": "l"
}
}
]
}
]
}
Notice how the blue t-shirts in this example are more expensive than the red ones.
Product quantity bundles
In some cases, the most natural quantity of a product to sell is not one. If so, you can define 'quantity bundles' for your product.
Example of a product with quantity bundles:
{
"products": [
{
"id": "0001",
"name": "Domaine d'Angayrac Cuvée Prestige",
"retail_price": 150,
"bundles": [
{
"id": "box",
"name": "Kasse",
"quantity": 6,
"is_default": true
},
{
"id": "pallet",
"name": "Palle",
"quantity": 90
}
]
}
]
}
In POS, the default quantum will be used throughout when adding products or changing the quantity. With the product above, tapping the product will add 6 to the basket. Tapping "+" on the quantity picker, will add 6 more.
Under Product Details you get a picker similar to the dimension picker that allows you to select the non-default quantums too.
Note that the quantum '1' will always implicitly be added even if it's not in the defined list, as an escape hatch to allow adding a single product. If no products have the 'is_default' flag set, then '1' will be the default.
Since the dimension picker UI is used, each quantum can also include a color and an image url - just like dimension values:
Importing products to specific shops
If the imported products should not be shared between all shops, you can specify a list of shop identifiers as follows:
{
"products": [
{
"id": "0001",
"name": "Pie",
"retail_price": 25
}
],
"shops": {
"shop_a": true,
"shop_b": true,
"shop_c": true
}
}
{
"products": [
{
"id": "0001",
"name": "Domaine d'Angayrac Cuvée Prestige",
"retail_price": 150,
"bundles": [
{
"id": "box",
"name": "Kasse",
"quantity": 6,
"is_default": true,
"image_url": "https://url.to.a.box.image.jpg"
},
{
"id": "pallet",
"name": "Palle",
"quantity": 90,
"color": "#328139"
}
]
}
]
}
You may optionally add a name property to be shown in the basket and on the receipt for a nicer layout:
{
"products": [
{
"id": "0001",
"name": "Domaine d'Angayrac Cuvée Prestige",
"retail_price": 150,
"bundles": [
{
"id": "box",
"name": "Kasse",
"name_per_quantity": "kasser af",
"quantity": 6,
"is_default": true,
"image_url": "https://url.to.a.box.image.jpg"
},
{
"id": "pallet",
"name": "Palle",
"name_per_quantity": "paller af",
"quantity": 90,
"color": "#328139"
}
]
}
]
}
Without the 'name_per_quantity', adding two boxes will show: Quantity: 12, Domaine d'Angayrac Cuvée Prestige (2 x Kasse)
With the 'name_per_quantity' set to 'kasser af', the same will show: Quantity: 12, Domaine d'Angayrac Cuvée Prestige (2 kasser af 6)
Deleting Products
Send an HTTP DELETE request to the endpoint with a body containing a JSON object containing product ids to delete.
{
"ids": ["0001", "0002"]
}
In order to delete shop specific products you may additionally specify an array of shop ids in the DELETE
request:
{
"ids": ["0001", "0002"],
"shops": ["xxx", "yyy"]
}
Partial product import
In some situations there's not a single source of truth that defines all the properties for a product.
In such a situation it can be relevant with multiple product imports that each deal with specific aspects of the product.
In order to support this use case, we can add the query parameter partial=true
to the product import. This means that the product data in the import will be merged with the existing product (if any). The merge is done at leaf value level, meaning that any properties not specified will be left untouched.
If one import is marked as partial, and another is a regular import, then it follows that the latter will still override the products entirely. This is of course not desirable, so you could mark both imports as partial.
This, however, has another complication, since partial imports are only additive. This means that it's not easy to delete values, like for instance removing a variant of a product.
In order to solve this situation, there's a variation of the partial import that first filters the existing product by specified keys before merging. This means that this import will be overriding all values just like regular imports, except for a list of specific keys to ignore. These properties can be specified on the import using a sequence of ignore
query parameters. For instance: &ignore=cost_price&ignore=retail_price
.
Example:
Say that you have a product import (a) defining all values for a product except for the retail_price
and the cost_price
. And then another product import (b) that only defines retail_price
and cost_price
.
Import a
would use query parameters &ignore=cost_price&ignore=retail_price
and send data like:
{
"products": [
{
"id": "AmeliaShellJacketSS20",
"variants": [
{
"id": "dark_grey-122_128",
"barcode": "A89119",
"dimension_values": {
"color": "dark_grey",
"size": "122_128"
}
},
{
"id": "dark_purple-122_128",
"barcode": "A89113",
"dimension_values": {
"color": "dark_purple",
"size": "122_128"
}
},
{
"id": "dark_grey-134_140",
"barcode": "A89120",
"dimension_values": {
"color": "dark_grey",
"size": "134_140"
}
}
]
}
]
}
Import b
would use query parameter &partial=true
and send data like:
{
"products": [
{
"id": "AmeliaShellJacketSS20",
"variants": [
{
"id": "dark_grey-122_128",
"retail_price": 120,
"cost_price": 80
},
{
"id": "dark_purple-122_128",
"retail_price": 125,
"cost_price": 82
},
{
"id": "dark_grey-134_140",
"retail_price": 150,
"cost_price": 90
}
]
}
]
}
And the final merged product would contain the values from both sources.