FORMAT: 1A HOST: https://pos-playground-eu.qnips.com/api/pos/v3 # qnips Web API v3 This page describes how you can integrate the qnips functions into your software. The API is roughly divided into 5 areas, which can be implemented independently of each other, so that a partial implementation of the qnips functionality is also possible. Area | Description ---|--- **Registering New API Clients** | This describes how a new client registers with the API and automatically joins the virtual organizational structure of a merchant. **Basket handling** | Real-time calculation and reporting of discount and loyalty campaigns based on a basket are topics in this area. **Sortiment maintenance** | This allows product data to be imported into the qnips system, which enables the following functional areas: product and material group-based loyalty campaigns and customizable immediate discounts, digital menus and menus, ordering functionality. **Customer/credit/gift cards** | In order not to have to deprive smartphone users exclusively of the advantages of qnips functions, they can also be implemented with any type of customer card (magnetic card, NFC/RFID transponder, barcode/QR code cards etc.) as a means of identifying a customer profile and providing the qnips offers. Customer cards can be loaded with credit in our system, which can then be used as a means of payment. **Mobile Payment** | qnips allows convenient mobile payments at your POS via various established payment services.

Let's get started! # Basics Our API is implemented as REST API. You should therefore use an HTTP client library to create the corresponding GET, POST, PUT and DELETE Perform operations on individual resources of our API. The available resources can be accessed via unique URLs. Example: `GET https://{https://pos-playground-eu.qnips.com}/api/{v3}/{brands}/{123}` Values in curly brackets have the following meaning: | Code | Description | | --- | --- | |`{https://pos-playground-eu.qnips.com}` This is the main domain of our API. Change this value on productive systems to 'https://pos-live-eu.qnips.com'. |`{v3}` |Version of the interface. Currently 'v3' is to be used. | |`{brands}` |Name of the resource you want to access. The following resources are available: merchants, trackingUnits, baskets, sortiments, products, tokens | |`{123}` |`id` of the object specified by the resource name to be delivered. | ## Requests Each resource of our API is protected with two special mandatory HTTP headers: | Header Name| Description | | --- | --- | |`DevKey`| Here you have to enter your DeveloperKey, which you can get from us. | |`trackingUnitId`|Each individual client that wants to call the qnips API must first register with the qnips system (see *[ Client registration](#introduction/client-registrierung)*), which gives it a unique `trackingUnitId` that must be specified here for each subsequent call.| ### Optional headers Furthermore, there are some optional headers with which, for example, the desired format (JSON or XML) for the data exchange can be specified. | Header Name| Description | | --- | --- | |`Accept`| Here you specify whether you expect the results of a call in XML or JSON format. Allowed are 'application/xml' and 'application/json'. Default is `application/xml`.| |`Content-Type`| This specifies whether the content in the body of your POST and PUT requests is XML or JSON. Allowed are 'application/xml' and 'application/json'. Default is `application/xml`.| |`Accept-Encoding`| To reduce the traffic and to speed up the interaction with the qnips API it supports the zipped transmission of the content. If this header is set in the request with **'gzip'**, the content is zipped in the response. |`Content-Encoding`| For some POST calls it is useful to also transfer the content of the request body in zipped form. In this case set this header with **'gzip'**. This way the backend notices that the request is zipped and unzips it before processing| |`SecurityToken`| Some resources are additionally protected with a security token that is to be calculated dynamically in order to protect particularly sensitive parts of the API. The parts of the request from which such a token is to be formed are described directly in the specification of the resource. The calculation algorithm is always the same and is described [here](#introduction/basic/security-token). Please always place an XML/JSON encoded in 'utf-8' in the body of requests. Note that certain characters are also not allowed as values for XML tags in XML, so that a URL escaping of the values may have to take place *Example: `Drinks&Food` would have to be converted at least to `Drinks%26Food`.

## Responses API-responses are normal HTTP-responses with an HTTP status code, response headers and, if applicable, a response body The following HTTP status codes are used: | Code| Description | | --- | --- | |`200`| **Success** - Marks the successful execution. | |`400`| **Bad Request** - Request cannot be executed due to failed validations or unfulfilled preconditions. Please process the ErrorId in the response body for such errors, since in some cases it makes sense to repeat the request. | |`401`| **Unauthorized** - DevKey, trackingUnitId or Security-Token is/are not or incorrectly specified. | |`500`| **Server error** - Something went wrong during processing. The body usually contains more details about the bug, which are intended for logs and developers, but are not suitable for output to the UI.| If successful, the response body may contain an XML or JSON (depending on the 'Content-Type'-header) serialized object

## Security token Since parameters of qnips WebAPI resources are transmitted in plain text, even sufficient SSL encryption cannot protect completely against man-in-the-middle attacks. Therefore, qnips secures the transmission of safety-relevant parameters by means of a so-called 'security token', which is to be dynamically calculated before the request, added to the request as a header and validated in the qnips backend before the request is processed. This SecurityToken is secured by: + one, the third unknown, calculation algorithm + an individual secret key for each trackingUnit (checksumSecret), which is assigned to the trackingUnit when it is registered (see [GET trackingUnits](#reference/trackingunits/registrieren/get-trackingunits) Each resource protected with a SecurityToken defines for which parts of the input parameters a 'SecurityToken' is to be formed. The method for calculating the security token therefore takes a string and also returns the token as a string. Here is the algorithm for this method: + form a string with 'input' + `„qPnsS14“` + 'checksumSecret', with: + 'input': the value for which the checksum should be calculated + `„qPnsS14“`: a constant string + `checksumSecret`: key assigned to the `trackingUnit` at registration Example with `input`=„123456“ und `checksumSecret`=„af87b1“: **„123456qPnsS14af87b1“** + Create an MD5 hash for the result from the first step. For the value from the example shown above, the MD5 hash would be the following **„1283ff82ae8e9ace636449a519d99fa9“** + This hash is the 'SecurityToken'. Now pack it into the 'SecurityToken' header of the request so that the request can be processed at our backend.


# Definition of terms This API description uses some terms that are understood as follows: * **merchant (merchant)** - a merchant is a person identified by their unique qnips username (e.g. 'max@mustermann.de') differentiable carrier of one or more sales locations (*outlet*). The *outlets* can be used in one or more trademark (*brand*) must be classified. * **brand (private label)** - a private label is usually a combination of several locations (*outlet*), which are operated under a uniform brand or corporate identity (e.g. 'Pizza Hut') A *merchant* can be the owner of several such *brands*. and manage them as independent units in qnips. * **outlet (sales location)** - a sales location is usually a branch, which is distinguished from other branches by its unique address stores (or sales locations) is distinguished. * **trackingUnit (POS terminal)** - are there several places in an *outlet* where invoices with independent invoice number ranges can be processed, each such location must be registered independently in our system as a so-called *tracking unit*. Will the Invoicing also centrally processed for several terminals, it is sufficient to register only the central unit as *trackingUnit*. * **sortiment (Product master data)** - A product master data is an organizational unit for the products marked as reward-enabled by the *merchant*. Sales articles that can be booked in a *basket* at the POS. Such articles must be uploaded to the qnips system by an upload so that the *merchant* can define a reward on these articles. * **basket** - this is an object containing the data about the items purchased by the consumer, as have been entered in a *trackingUnit* in the context of an invoice * **consumer** - this refers to an end customer who purchases a *basket* in a *store* of a *merchant*. and links this purchase event ( *purchase* ) either via a scan of the QR code or via identification at the POS with his consumer profile in the qnips system * **consumerIdentToken** - this refers to a 'token' of any type stored on any medium, which is uniquely is linked to a *consumer* profile and is therefore suitable for uniquely identifying the consumer. This token can e.g. be stored in a customer card, or exist in the form of a printed or virtual barcode or QR code, which can be read by the POS system can either be scanned or read in via another carrier medium.


# Client registration Each instance in your system that can independently generate and complete an invoice must have a unique 'trackingUnitId' from the qnips system. For this, if the client does not yet have a trackingUnitId, a one-time *[GET trackingUnits](#reference/trackingunits/registrieren/get-trackingunits)*-Call to be executed and the result of the call in to persist permanently in the case of the offending entity. It is important to make a reference to the `outlet' for each *trackingUnit* to be registered. To do this, the *[GET trackingUnits](#reference/trackingunits/registrieren/get-trackingunits)*-Call an 'outletId' must be specified. This may are fetched in two ways: * **[GET merchants-info](reference/merchants/merchant-info-abrufen/get-merchants-info)** - Use this call to retrieve the 'outlets' available for a 'merchant' in our system and let the desired Select 'outlet' on the UI * **[POST outlet]((#reference/merchants/outlet-erstellen/post-outlet)** - If your system has the address of the location where it is used, an 'outlet' can also be can be newly created via an API call. In the response to this call you will find the required 'outletId'. If the cash register is moved to another location at some point, the new 'outletId' should be added to our system via Change of location either via a *[POST trackingUnits](#reference/trackingunits/registrieren/post-trackingunits)* call.


# Basket Handling Basket Handling defines a real-time calculation of possible discounts and loyalty points defined in the qnips system for a specific basket. This process consists of (multiple) calls on two resources of our API: * **[POST basket](#reference/baskets/basket-hochladen/post-basket)** - this will create the basket using a unique ID submitted. Possible discounts and loyalty points are then calculated and returned to the POS system in response. A basket can and should be renewed several times and each new submission restarts the calculation of discounts and loyalty points. * **[POST basket-grant](#reference/baskets/rewards-als-granted-makieren/post-basket-grant)**- this will mark a basket as completed. Shopping baskets marked this way cannot be re-calculated. qnips offers the following (and more) types of rewards: + Turnover-based coupons (e.g. 10% discount starting at 20€ turnover) + Product-based coupons (e.g. 20% on all men's boots) + coupons on product combinations (e.g. laces to men's shoes at half price) + quantity coupons (e.g. 3 for the price of 2) + Sales and/or product-based point collection systems with flexibly configurable discounts The complete process of reward crediting is as follows: * *1. enter the basket* * *2. carry out consumer identification if necessary*
To do this, read from a customer card or smartphone presented by the consumer at the POS using a suitable reader (for example imager, magnetic card reader, NFC/RFID reader etc. the customer identifier stored in it and save it as a so-called *consumerIdentToken*. * *3. call [POST basket](#reference/baskets/basket-hochladen/post-basket) The response to this POST could look like this: { "Rewards":[ { "ProductId": "20019", "Name": "30% off large coffee drink and a donut", "GrossRewardValue": 0.72, "CouponId": 683, "RewardTriggeringPositionId": 1, "RewardType": 1 }, { "ProductId": "151" "Name": "30% off large coffee drink and a donut", "GrossRewardValue": 0.53, "CouponId": 683, "RewardTriggeringPositionId": 2, "RewardType": 1 } ], "LoyaltyInfo": [ { "Name": "One point for each hot beverage." "LoyaltyId": 171, "Details": [ { "PreviousPoints": 7, "NewPoints": 2, "PointsInScheme": 10, "GrossRewardValue": 0, "RewardTriggeringPositionId": 1 } ] } ] } * *4. Process 'rewards' in the response*
You can now incorporate the rewards directly into the invoice and reduce the invoice amount accordingly. * *5. mark basket as completed*
As long as a basket is not closed (`granted`), it can be resubmitted as often as desired. With each new submission the rewards are recalculated and replace the old ones. However, if, from the consumer's point of view, a final status is reached and the invoice amount is paid, the basket must be marked as 'granted' in our system. This way, our system will send a notification about a purchase to the consumer (by email or push). To do this, call *[POST basket-grant](#reference/baskets/rewards-als-granted-makieren/post-basket-grant)*


# Sortiment Maintenance Sortiment Maintenance describes the initial upload and update of product/article data in our system. Sortiment Maintenance is a prerequisite for the following qnips features: + product related rewards For product-related rewards, it is sufficient to transfer only those products (or product groups) to the qnips system. for which the merchant wants to set up a Reward. Here it is sufficient, if the articles in your minimum form (only PLU and name, and, if applicable, a product group assignment) must be submitted. + Menu card display in the app incl. allergen/nutritional value/additive information Especially for gastronomy and communal catering in particular, we offer the function of a comprehensive, LMIV-compliant meal plans with proprietary allergen/additive management at item level. Does your system have such data, they can also be uploaded to our system, so that the allergen/additive management remains in your software and only the composition of the menu can be done by us *NOTE: Please implement the feature so that the merchant can ultimately decide to what extent an sortiment of qnips will be uploaded, for example, by creating a way in your system to mark the items transferred to qnips as such. ## Sortiments Article data is organized in the qnips system in so-called 'sortiments'. A sortiment can be made up of several 'trackingUnits' which can be used together at one or also at different locations. The only distinguishing feature, whether an article can be maintained by several trackingUnit in the same sortiment, is the uniqueness of its PLU ? If a different article name is recorded under the same PLU on two trackingUnit, these articles must be uploaded into different sortiments. Call [GET sortiments?thirdPartySortimentIdentifier={string1}&name={string2}](#reference/sortiments/sortimentid-besorgen). ## Upload The article data can be uploaded or updated individually or via bulk upload: + [POST / PUT / DELETE sortiments/{qnipsSortimentId}/group](#reference/sortiments/warengruppen-bearbeiten) + [POST / PUT / DELETE sortiments/{qnipsSortimentId}/article](#reference/sortiments/artikel-bearbeiten) + [POST sortiments/{qnipsSortimentId}/bulkUpload?changedArticlesOnly={boolean}](#reference/sortiments/bulkupload)


# Loyalty cards Loyalty cards are another way of linking a 'basket' to a specific consumer profile, in addition to QR code scans. The procedure is quite simple: at the POS, an identifier of any type is generated by any identifier carrier is read out and sent to our system as a 'consumerIdentToken' together with a 'basket'. Therefore it is not important for us whether this token is found + on a magnetic strip or NFC chip of a plastic card + in the barcode or QR code of a paper card + in an RFID transponder + as a virtual token on the consumer's smartphone + or via other means It is just as unimportant by whom such a token has been generated, as long as it is unchangeable and is clear enough to distinguish its owner from other consumers. ## Register customer card A customer card can therefore be as easily implemented by using the card identifier stored in the card as a so-called 'token'. To do this, read the identifier from a card that you send to the consumer as a customer card and execute [POST tokens/{id}](#reference/tokens/token-registrieren/post-tokens%2F%7Bid%7D%3Ftokentype%3D%7Btokentype%7D) with this identifier as `{id}'. ## Block customer card Customer cards can be blocked by the merchant either directly in the web portal or, if your software acts as the administration center for customer cards, it can be communicated to us via an API call. To do this, perform one of the two actions: + [POST tokens/{id}/lock](#reference/tokens/token-sperren/post-tokens%2F%7Bid%7D%2Flock) This action renders a card completely unusable. + or [POST tokens/{id}/dispose](#reference/tokens/token-freigeben/post-tokens%2F%7Bid%7D%2Fdispose) Only releases the card from a consumer profile, but the card itself is still available for a new registration and use by another consumer ## Top up card with credit Loyalty cards can be topped up with a credit balance. The (re-)charge can be carried out by the consumer directly via our apps or consumer portal (e.g. by means of a bank transfer or via PayPal). Or it can be done at the POS. To do this, issue an invoice to a consumer, collect the money and post the credit to his or her customer card by entering the 'token' of his or her customer card and making the following call: [POST tokens/{id}/addBalance?balanceToAdd={balanceToAdd}](#reference/tokens/guthaben-aufladen/post-tokens%2F%7Bid%7D%2Faddbalance%3Fbalancetoadd%3D%7Bbalancetoadd%7D) ## Withdraw credit If there is credit on a customer card and your system sends a 'basket' with the 'token' of this customer card, the response to the [POST basket/{basketId}?consumerIdentToken={token1}](#reference/baskets/basket-hochladen/post-baskets%2F%7Bbasketid%7D%3Fconsumeridenttoken%3D%7Btoken1%7D%26giftcardid%3D%7Btoken2%7D) will contain the amount of credit. If a portion of this credit is to be offset against the invoice total, you call [POST tokens/{id}/withdrawBalance?balanceToWithdraw={balanceToWithdraw}](#reference/tokens/guthaben-abheben/post-tokens%2F%7Bid%7D%2Fwithdrawbalance%3Fbalancetowithdraw%3D%7Bbalancetowithdraw%7D)


# Gift cards A 'gift card' is treated as a 'customer card' as well in our system, with the only difference that the holder of a gift card, unlike the holder of a customer card, does not have public access via the consumer portal to the activities (charging and discharging transactions, 'baskets' purchased and possibly paid for with the card, etc.). *Implementation-wise, however, a 'gift card' is completely identical to the 'customer card'.


# Group merchants As described in the chapter [ Client registration](#introduction/client-registrierung), each client must register with our system as a `trackingUnit` first. For this registration we need a so called 'outletId' which helps us to immediately assign the `trackingUnit` to a real branch/location of a 'merchant'. An 'outletId' can be based on two different ways: + Retrieve existing 'brands' and 'outlets' to place a `trackingUnit` to be registered in an existing 'outlet Install this as follows: * During qnips activation, ask for the username of the qnips account * Execute *[GET merchants/{qnipsUserName}/info](reference/merchants/merchant-info-abrufen/get-merchants/{qnipsusername}/info)* and display the result in the UI * Let the operator select an 'outletId * Register the 'trackingUnit' via *[GET trackingUnits](#reference/trackingunits/registrieren/get-trackingunits)* + If your software has location information, consisting at least of postcode, city, street and street number, it is possible to enable an automatic export of the locations from your system to ours. If necessary, install this as follows: * During qnips activation, ask for the username of the qnips account * Execute *[GET merchants/{qnipsUserName}/info](reference/merchants/merchant-info-abrufen/get-merchants/{qnipsusername}/info)* and display the result in the UI * Let the operator select a 'brandId * Create a new outlet in the Brand via *[POST merchants/{qnipsUserName}/outlets](#reference/merchants/outlet-erstellen/post-merchants/{qnipsusername}/outlets)* * Register the 'trackingUnit' via *[GET trackingUnits](#reference/trackingunits/registrieren/get-trackingunits)* ## Get Merchant Info [/merchants/{qnipsUserName}/info] ### GET merchants info [GET] In the response, you will receive a list of the 'brands' that are available for the 'merchant' with the specified 'qnipsUserName' in our system are deposited. Each brand-object can contain a list of 'outlets', each with an ID and a name. Example: [ { "brandId": 1, "brand name": Caesar's, "outlets": [ { "Id": 1, "Name": "Caesar's Hemmingen - (Hemmingen, Rathausplatz square 6A)" }, { "Id": 2, "Name": "Caesar's Hannover - (Hannover, Weidendamm 8)" } ] } ] | Return | Description | | --- | --- | || ||brandId|||||Id of brand. |`brandName`|Name of the brand, which you could display, for example, in a drop-down box in the UI for selection.| |`outlet.Id`|Id of the location entry (branch) .| |`outlet.name`|Name of the store, which you could display in a drop-down box in the UI for selection, for example. You can now either create a new outlet using a 'brandId' selected by the user (see *[POST merchants/{qnipsUserName}/outlets](#reference/merchants/outlet-erstellen/post-merchants/{qnipsusername}/outlets)*) or continue with direct registration of a tracking unit using a selected outlet ID (see *[GET trackingUnits](#reference/trackingunits/registrieren/get-trackingunits)*) + Parameters + qnipsUserName (string, `hans.m%C3%B6ller@firma.de`) ... Let the merchant enter his 'qnipsUserName' and insert it here. The value should be escaped URL-compliant, as it may contain umlauts, e.g. 'hans.möller@firma.de' -> 'hans.m%C3%B6ller@firma.de'. + Request + Headers Accept: application/json DevKey: {Your Key} + response 200 [ { "brandId": 1, "brand name": Caesar's, "outlets": [ { "Id": 1, "Name": "Caesar's Hemmingen - (Hemmingen, Rathausplatz 6A)" }, { "Id": 2, "Name": "Caesar's Hannover - (Hannover, Weidendamm 8)" } ] } ] + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing + Response 401 // HTTP-Unauthorized: DevKey not specified or not available + response 404 // HTTP-NotFound: qnipsUserName unknown or has no brands ## Create an outlet [/merchants/{qnipsUserName}/outlets?brandId={brandId}] ### POST outlet [POST] The body of the request to create a new outlet can take the following parameters (all optional): { "Name": "Demo Branch." "Description": "Demo Branch." "Street": "Schulenburger Landstrasse 156", "City": "Hannover", "PostalIndex": "30419." "CountryIso": "DE" "PublicTelefon": "0511-12345" "PublicEmail": "example@example.de" "WebsiteUrl": "http://..." "FacebookUrl": "http://..." "TwitterUrl": "http://..." } | Field | Description | | --- | --- | |`Name`|**string : optional** - name of the branch. This value will be visible to the public in the smartphone apps. |`Description`|**string : optional** - Longer description to the branch if available This value will be visible to the public in the smartphone apps. |`Street`|**string : optional** - street name with street number| |`City`|**string : optional** - city name| |`PostalIndex`|**string : optional** - postal code| |`CountryIso`|**string : optional** - 2-letter country abbreviation according to ISO 3166 .| |`PublicTelefon`|**string : optional** - This value will be visible to everyone in the smartphone apps as a contact phone. |`PublicEmail`|**string : optional** - This value will be visible to everyone in the smartphone apps as contact mail. |`WebsiteUrl`|**string : optional** - Url to the website of the branch, if available. |`FacebookUrl`|**string : optional** - Url to the Facebook page of the branch, if available.| |`TwitterUrl`|**string : optional** - Url to the Twitter page of the branch, if available. In the response, an object with an 'ID' and a 'name' is delivered to the newly created 'outlet' object. Example: { Id: 10125, Name: 'Caesar's Hannover - (Hannover, Weidendamm 8)', } This can then be used to register the trackingUnit (see *[GET trackingUnits](#reference/trackingunits/registrieren/get-trackingunits)*) + Parameters + qnipsUserName (string, `hans.m%C3%B6ller@firma.de`) ... Let the merchant enter his 'qnipsUserName' and insert it here. The value should be escaped URL-compliant, as it may contain umlauts, e.g. 'hans.möller@firma.de' -> 'hans.m%C3%B6ller@firma.de'. + brandId (long, `123`) ... Here should be the ID of the selected brand, in which the outlet should be created + Request + Headers Accept: application/json DevKey: {Your Key} + Body { "Name": "Demo Branch." "Description": "Demo Branch." "Street": "Schulenburger Landstrasse 156", "City": "Hannover", "PostalIndex": "30419." "CountryIso": "DE" "Public Telefon": "0511-12345" "PublicEmail": "example@example.de" "WebsiteUrl": "http://..." "FacebookUrl": "http://..." "TwitterUrl": "http://..." } + response 200 { Id: 10125, Name: 'Caesar's Hannover - (Hannover, Weidendamm 8)', } + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing + Response 401 // HTTP-Unauthorized: DevKey not specified or not available + response 404 // HTTP-NotFound: qnipsUserName unknown or has no brands #Group trackingUnits ## Register [/trackingUnits?qnipsUserName={qnipsUserName}&outletId={outletId}&sortimentId={sortimentId}&instanceName={instanceName}] ### GET trackingUnits [GET] Each call to this resource will always be a new identifier different from the previous call, provide the so-called 'trackingUnitId' and a 'checksumSecret' in the response. | Return | Description | | --- | --- | | ``trackingUnitId`|You will have to set this Id in the ``TrackingUnitId Request Header`` for every further call, without which every call would be rejected by our backend.| | `checksumSecret`|You will need this `Secret` to form a check digit, which must be transmitted with some calls of our API as security criterion. Therefore, call this resource only once in each terminal to be registered, persist the two values from the response and make sure that the 'checksumSecret' remains secret. + Parameters + qnipsUserName (`hans.m%C3%B6ller@firma.de`) ... Let the merchant enter his qnips username and enter it here. The value should be escaped URL-compliant, since it can contain umlauts: *hans.möller@firma.de -> hans.m%C3%B6ller@firma.de* + outletId (long, `123`) ... Please enter the ID of the outlet where the trackingUnit is to be registered. + sortimentId (optional, long, `234`) ... Here please optionally specify the ID of the sortiment on which the trackingUnit works + instanceName (optional, string, `Kasse%20im%20Gang%201`) ... During qnips activation, have the merchant enter a name for the instance to be activated and specify it here. The name entered here will be visible to the merchant in the qnips web portal and will help the merchant to correctly classify this instance if necessary. The value should be escaped in accordance with the URL, since it can contain umlauts and spaces, for example: * 'Kasse im Gang 1' -> ' Kasse%20im%20gang%201'* + Request + Headers Accept: application/json DevKey: {Your Key} + response 200 { "trackingUnitId" = "1234567890" "checksumSecret" = "def567" } + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing + Response 401 // HTTP-Unauthorized: DevKey or trackingUnitId not specified or not available + response 400 { "ErrorId":123, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 10001: unknown qnipsUserName // 10002: no instance name specified ### POST trackingUnits [POST] If a cash register is moved to another store, or switched to a different sortiment, this should be reported to our system via a POST call to the trackingUnit resource. With this, we reorganize the trackingUnit accordingly. If successful, an empty HTTP-200 response is delivered + Parameters + qnipsUserName (`hans.m%C3%B6ller@firma.de`) ... Let the merchant enter his qnips username and enter it here. The value should be escaped URL-compliant, since it can contain umlauts: *hans.möller@firma.de -> hans.m%C3%B6ller@firma.de* + outletId (long, `123`) ... Please enter the ID of the outlet where the trackingUnit is to be registered. + sortimentId (optional, long, `234`) ... Here please optionally specify the ID of the sortiment on which the trackingUnit works + instanceName (optional, string, `Kasse%20im%20Gang%201`) ... During qnips activation, have the merchant enter a name for the instance to be activated and specify it here. The name entered here will be visible to the merchant in the qnips web portal and will help the merchant to correctly classify this instance if necessary. The value should be escaped in accordance with the URL, since it can contain umlauts and spaces, for example: * 'Kasse im Gang 1' -> 'Kasse%20im%20Gang%201'* + Request + Headers Content-Type: application/json DevKey: {Your Key} TrackingUnitId: {trackingUnitId} + response 200 + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing + Response 401 // HTTP-Unauthorized: DevKey or trackingUnitId not specified or not available + response 400 { "ErrorId":123, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 1000: Body contains syntactical errors and cannot be deserialized #Group baskets ## Upload basket [/baskets/{basketId}?consumerIdentToken={token1}&giftCardId={token2}] ### POST basket [POST] A basket is the content of an invoice and is the basis for most qnips functions. The result of this call always includes the possible 'rewards' for the products contained in the basket. These calculated 'rewards' are a preliminary result and must be marked as 'granted' by your software, as soon as the invoice amount has been paid in full for the Rewards to take effect and reported to the consumer as having been redeemed (see *[POST baskets/{basketId}/grant](#reference/baskets/rewards-als-granted-makieren/post-baskets%2F%7Bbasketid%7D%2Fgrant%3Fredeemedinpos%3D%7Bredeemedinpos%7D)*) If a *consumerIdentToken* or a *giftCardId* was specified with the call, the response can also contain information about potentially available credit either on the consumer profile or on the gift card As long as a basket is not 'granted', it can be updated as often as you like by calling this resource again, e.g. because another item was added or deleted without further ado (a frequent case in the catering trade). Each new call will trigger and deliver a new calculation of the rewards. #### Parameters in request body Since the meaning of most properties should be clear from the name, here are just a few explanations of the properties that require explanation. | Name | Description | | --- | --- | |`Timestamp`|**DateTime : required** - here you should enter the time of billing in *[ISO8601 format](http://de.wikipedia.org/wiki/ISO_8601)* including time zone. Example: 2015-04-30T08:45:15+02:00 for 08:45:15 on April 30, 2015 in Berlin (CEST - daylight saving time)| |`HasDiscounts`|**bool : required** - if true is specified here, no reward calculation will take place in the qnips system for this basket| |`CurrencyISO`|**string : optional** - Enter the currency in which billing will be performed as a 3-character abbreviation according to *[ISO 4217](http://de.wikipedia.org/wiki/ISO_4217)*| |`WaiterName`|**string : optional** - here should be the name of the operator/cashier who has significantly serviced/advised the customer| |`BillPdf`|**Byte-Array : optional** - In order to be able to offer the features of the digital receipt to consumers without a smartphone, your software can send us a copy of the invoice as a PDF file, so that we can display it on the qnips Consumer Portal on the one hand, but also send it directly to the consumer via email. To do this, send the contents of the PDF file as a base64-encoded byte array here| |`ContextIdentifiers`|**String-Array : optional** - If Coupon-Identifiers or a Profile-Identifier have been added to the basket (see *[Context-Identifiers](https://qnipsapi1draft.docs.apiary.io/#reference/contextidentifiers)*), then they should be specified here | |`RegularGrossPrice`|**double : optional** - here the normal selling price for the article should be given| |`CurrentGrossPrice`|**double : optional** - here should be the price under which this item is sold in this invoice, e.g. if any discounts (promotions, employee discounts etc.) have been applied to this item. |`PositionId`|**int** - enter a unique number here, which makes the position referenceable This is required to reference any discount calculated by our system to the item that triggers the inh. | |`ProductId`|**string : optional** - enter here the PLU of the product under which the product was submitted to our system so that we can correctly recognize it for the calculation of the product-based rewards. If you have not implemented support for product-based rewards in your software, you can omit this field| |`OrderTime`|**DateTime : optional** - in the catering trade it is often the case that an invoice is open for a long period of time and items are posted one by one. If this is also the case for you, enter the exact booking time of the respective position here in *[ISO8601 format](http://de.wikipedia.org/wiki/ISO_8601)* (incl. time zone). This will allow us to calculate so called "Happy Hour" rewards on a position exactly. Example: 2015-04-30T08:45:15+02:00 for 08:45:15 on April 30, 2015 in Berlin (CEST - daylight saving time)| |`Unit`|**int** - This is the unit of measure in Amount. 0 for quantity, 1 for kilogram. | |`Amount`|**decimal** - Quantity of the article. Can be specified as number of pieces or kilograms (see Unit). | |`ProductProps`|**dictionary[string, string] : optional** - If your products have special features that should be displayed in a structured way on the digital receipt, this list of key-value pairs is exactly the right place for these features| #### Response The response body contains a list of the price reductions (rewards) and loyalty points applicable to the submitted basket. The rewards list will be empty, if no reward is applicable. Otherwise, it is a list of items, each of which represents one Reward per item or refers to the total invoice amount. If a Reward was triggered by a combination of articles, this Reward will be split proportionally among all articles triggering it for tax purposes. Each Reward entry has a reference to the position of the basket to which it is assigned. It is now the task of the cash desk to incorporate these price reductions into the invoice in a correct accounting manner. The calculated Reward amount is always based on the gross price of the respective position in the basket. Each object has the following properties: | Name | Description | | --- | --- | |`RewardType`|**int**: 1 = Reward on product. 2 = Reward on total invoice amount. With 1, the 'ProductId' is assigned the PLU of the product for which this reward was determined. With 2 the field 'ProductId' remains empty. |`ProductId`|**string**: the PLU of the article transmitted in the basket data record on which a reward could be determined |`GrossRewardValue`|**decimal**: this is the calculated value of the Reward based on the gross price of the respective position. | |`RewardTriggeringPositionId`|**int**: PositionId of the position of the transmitted basket triggering the current reward. |||||| CouponId|||**int**: Id of the coupon in the qnips system for documentation purposes. |`Name`|**string**: Name of the coupon in the qnips system for documentation purposes.| And here is an explanation of the properties of a LoyaltyInfo object: | Name | Description | | --- | --- | ||||| LoyaltyId|||**int**: Id of the loyalty point scheme in the qnips system for documentation purposes. |`Name`|**string**: Name of the loyalty point scheme in the qnips system for documentation purposes. || || PointsInScheme|||**int**: Number of points that must be collected in order for a reward to be issued. || || PreviousPoints|||**int**: Number of points before the current purchase| || ||NewPoints||||int**: Number of new points generated with this purchase| |`GrossRewardValue`|**decimal**: this is the calculated value of the Reward based on the gross price of the respective position. It is only set if PreviousPoints+NewPoints >= PointsInScheme is set. |`RewardTriggeringPositionId`|**int**: PositionId of the position of the transmitted basket triggering the current reward. + Parameters + basketId (string, `ab12345`) ... A string that can be freely generated by your client, which uniquely identifies the basket. You can use the invoice number as 'basketId' here, for example. + token1 (optional, string, `member123`) ... If the consumer has already identified himself at the POS, e.g. by showing a customer card in which the 'token' linked to his consumer profile is stored, this 'token' should be entered here. + token2 (optional, string, `giftCardABC`) ... If the consumer had presented a gift card when making the purchase and this was recorded by your system, the ID stored in the card should be entered here + Request + Headers Content-Type: application/json DevKey: {Your Key} TrackingUnitId: {trackingUnitId} + Body { "BillNumber": "1234." "Timestamp": "2015-04-21T08:15:45,000+02:00" "TotalValue":159.90, "HasDiscounts":false, "TableId": "123." "WaiterName": "Max Mustermann", "CurrencyISO": "EUR", "Positions": [ { "PositionId":1, "ProductId": "20019", "ProductName": "Coffee Large", "Amount":1, "Unit":0, "HasDiscounts":false, "CurrentGrossPrice":2.39, "RegularGrossPrice":2.39, "VatInPercent":19.0, "OrderTime": "2015-04-21T08:15:45,000+02:00" "ImageUrl": "http://abc.de/xyz" "ProductProps":{ "Bean": "100% Arabica." "Milk": "soya" } }, { "PositionId":2, "ProductId": "151" "ProductName": "Vanilla Donut." "Amount":1, "Unit":0, "HasDiscounts":false, "CurrentGrossPrice":1.79, "RegularGrossPrice":1.79 "VatInPercent":19.0, "OrderTime": "2015-04-21T08:15:45,000+02:00" } ], "BillPdf": "98761238467129803470987809123489798172435980728347578091873645786..." } + response 200 + Headers Content-Type: application/json + Body { "Rewards":[ { "ProductId": "20019", "Name": "30% off large coffee drink and a donut", "GrossRewardValue": 0.72, "CouponId": 683, "RewardTriggeringPositionId": 1, "RewardType": 1 }, { "ProductId": "151" "Name": "30% off large coffee drink and a donut", "GrossRewardValue": 0.53, "CouponId": 683, "RewardTriggeringPositionId": 2, "RewardType": 1 } ], "LoyaltyInfo": [ { "Name": "One point for each hot beverage." "LoyaltyId": 171, "Details": [ { "PreviousPoints": 7, "NewPoints": 2, "PointsInScheme": 10, "GrossRewardValue": 0, "RewardTriggeringPositionId": 1 } ] } ] } + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing + Response 401 // HTTP-Unauthorized: DevKey or trackingUnitId not specified or not available + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + response 400 { "ErrorId":123, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 1000: Body contains syntactical errors and cannot be deserialized // 11004: consumerIdentToken not found Repeat if necessary without this token // 11005: consumerIdentToken locked. Repeat if necessary without this token // 11006: consumerIdentToken expired. Repeat if necessary without this token // 11007: giftCardId not found. Repeat if necessary without this token // 11008: giftCardId locked. Repeat if necessary without this token // 11009: giftCardId expired. Repeat if necessary without this token // 11010: this basket is 'granted' and therefore not changeable ## Mark rewards as 'granted' [/baskets/{basketId}/grant?redeemedInPos={redeemedInPos}] ### POST baskets/{basketId}/grant [POST] Only after marking a baskets as 'granted' does the qnips system consider the rewards as legitimate and write them the consumer good. Exactly those Rewards are marked as 'granted' that have been paid during the last *[POST baskets/{basketId}](#reference/baskets/basket-hochladen/post-baskets%2F%7Bbasketid%7D%3Fconsumeridenttoken%3D%7Btoken1%7D%26giftcardid%3D%7Btoken2%7D)*-Call for the basket with the specified basketId were determined and returned. Enter the value 'true' for the 'redeemedInPos' parameter if your system charges the qnips discounts directly at the current dealer. + Parameters + basketId (string, `1234567890`) ... Enter the ID of the basket you want to mark as closed + redeemedInPos (bool, `true` ) ... Use **true** if your system charges the qnips rebates directly from the current dealer. + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} + response 200 + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing + Response 401 // HTTP-Unauthorized: DevKey or trackingUnitId not specified or not available + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + response 400 { "ErrorId":12000, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 11001: basketId unknown #Group contextIdentifiers It is useful to be able to recognize directly at the cash register after a scan or a keyboard entry whether is a qnips-relevant identifier, to add it to the context of a basket if necessary. This can be achieved via Regular expressions, which the cash register can print out via special API call. These regular expressions are always site-specific and can be extended or adapted on a daily basis, so a nightly update of this information per location is provided at the checkout. The following type distinctions exist for qnips-relevant identifiers: | Type | Description | | --- | --- | |Qnips Profile Identifier|This is usually used as a `consumerIdentToken` for the POST basket| |discount codes|These are simple coupon identifiers that can be added directly to the basket without a 'consumerIdentToken'. If such discount codes are recorded at the cash register without a Qnips profile identifier following, they are so-called generic coupons, which are not personal and therefore no profile identifier is used for calculation Such coupon identifiers must be added to the basket directly in the request body in the property 'ContextIdentifiers'. ## Get context identifiers [/merchants/{qnipsUsername}/contextIdentifiers?outletId={outletId}] ### GET context-identifiers [GET] This resource essentially provides a list of regular expressions that can be used to distinguish whether an input or scan of a barcode (or 2D codes) contains a qnips-relevant identifier and to what Type of identifier it may be. The following types can be distinguished: + Request + Headers Accept: application/json DevKey: {Your Key} Authorization: {qnipsUserName}:{qnipsPwd} Let the merchant enter the username and password of his Qnips account and insert them here, separated by a colon, e.g. `hans.müller@firma.de:geheim` + response 200 [ { "type": 1, "regex": "oT[a-zA-Z0-9]{10}P" "name": "Profiltoken App" }, { "type": 1, "regex": "C[a-zA-Z0-9]{16}" "name": "Profiltoken Kundenkarte" }, { "type": 2, "regex": "VA04[0-9]{9}" "name": "Vattenfall-coupon Lokalzeitung" }, { "type": 2, "regex": "VA05[0-9]{9}" "name": "Vattenfall-Coupon PDF download" } ] // type 1: ConsumerIdentToken // type 2: CouponIdent + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing + Response 401 // HTTP-Unauthorized: DevKey not specified or not available + response 404 // HTTP-NotFound: qnipsUserName or outletId unknown #Group sortiments ## get sortimentId [/sortiments?thirdPartySortimentIdentifier={string1}&name={string2}] ### GET sortiments?thirdPartySortimentIdentifier={string1}&name={string2} [GET] Before an article can be uploaded or updated in the qnips system, the trackingUnit must register with an sortiment. This is done with this call. In the response you will receive a so-called 'qnipsSortimentId', which in future will be displayed on the sortiment must be specified. If no sortiment with the specified parameters exists, one is created and its ID returned. Save the return.

    {
        "qnips sortimentId": "sortiment123"
    }

If the POS system is linked to another sortiment in your system, call up this resource again and save the return again. + Parameters + string1 (string, `1234567890`) ... Here you must enter the unique identifier of the sortiment under which it is managed in your own system + string2 (optional, string, `Sortiment Lounge Bar` -> 'Sortiment%20Lounge Bar') ... a name that is understandable for the merchant, under which he will be able to see the sortiment in the qnips dashboard and edit it if necessary. The value should be escaped according to the URL. + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} + response 200 { "qnips sortimentId": "sortiment123" } + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + Response 401 // HTTP-Unauthorized: DevKey or trackingUnitId not specified or not available + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing { "ErrorMessage": "some explanation" "StackTrace": "text" } ## Edit product groups [/sortiments/{qnipsSortimentId}/group/{Id}] ### POST sortiments/{qnipsSortimentId}/group} [POST] + Parameters + qnipsSortimentId (string, `'sortiment123'`) ... this is the `qnipsSortimentId' returned by the call to `GET sortiments?thirdPartySortimentIdentifier={string1}&name={string2}`. + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} + Body { "Id": "123." "UpGroupId": "234", "Name":[ { "lang": "de-DE", "val": "Getränke" } { "lang": "de-CH", "val": "Getränke" } { "lang": "it-CH", "val": "Bevande" } { "lang": "fr-UK", "val": "Boissons" } ] } + response 200 + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + Response 401 // HTTP-Unauthorized: DevKey or trackingUnitId not specified or not available + response 400 { "ErrorId":123, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 12001: qnips sortimentId unknown // 12002: Id field not set // 12003: No product group found under the specified UpGroupId // 12004: There is already a product group under the specified Id // 12005: Name was not specified, but is expected in at least one language ### PUT sortiments/{qnipsSortimentId}/group} [PUT] + Parameters + qnipsSortimentId (string, `'sortiment123'`) ... this is the `qnipsSortimentId' returned by the call to `GET sortiments?thirdPartySortimentIdentifier={string1}&name={string2}`. + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} + Body { "Id": "123." "UpGroupId": "234", "Name":[ { "lang": "de-DE", "val": "Getränke" } { "lang": "de-CH", "val": "Getränke" } { "lang": "it-CH", "val": "Bevande" } { "lang": "fr-UK", "val": "Boissons" } ] } + response 200 + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing { "ErrorMessage": "some explanation" "StackTrace": "text" } + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + Response 401 // HTTP-Unauthorized: DevKey or trackingUnitId not specified or not available + response 400 { "ErrorId":123, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 1000: Body contains syntactical errors and cannot be deserialized // 12001: qnips sortimentId unknown // 12002: Id field not set // 12003: No product group found under the specified UpGroupId // 12005: Name was not specified, but is expected in at least one language // 12006: No item found under given Id // 12007: UpGroupId must not be changed. Delete the item instead and create it in the other material group ### DELETE sortiments/{qnipsSortimentId}/group/{Id}} [DELETE] Material groups are structured as follows:

    {
       "Id": "123."
       "UpGroupId": "234",
       "Name":[
            { "lang": "de-DE", "val": "Getränke" }
            { "lang": "de-CH", "val": "Getränke" }
            { "lang": "it-CH", "val": "Bevande" }
            { "lang": "fr-UK", "val": "Boissons" }
        ]
    }

| Property | Description | | --- | --- | |`Id`|**string**: This is the unique identifier for this product group. It can be referenced as an 'UpGroupId' from another material group, which allows you to map a material group hierarchy of any depth. | |`UpGroupId`|**string**: If this material group is a top-level group, this entry is not necessary. Otherwise, enter the identifier of the material group to which this unit is to be subordinated here. |``Name`|||translatable**: The name of the product group can be entered here in any number of translations in the format described below. Please always enter the name in at least one language. | #### Translatable properties Some string properties of some entities, such as the name of an item in the sortiment, can be received by our system in several translations. In this documentation such properties are marked as 'translatable'. The values for such properties are always passed as a list of Translatable objects. In the 'lang' property of the Translatable object, the language tag is set to [RFC 5646]() and the translation of the value in the appropriate language in the 'val' field. JSON example:

    "Name":[
        { "lang": "de-DE", "val": "Getränke" }
        { "lang": "de-CH", "val": "Getränke" }
        { "lang": "it-CH", "val": "Bevande" }
        { "lang": "fr-UK", "val": "Boissons" }
    ]

+ Parameters + qnipsSortimentId (string, `'sortiment123'`) ... this is the `qnipsSortimentId' returned by the call to `GET sortiments?thirdPartySortimentIdentifier={string1}&name={string2}`. + Id (string, `'A12'`) ... enter the PLU of the article to be deleted or the ID of the product group to be deleted. + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} + response 200 + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing { "ErrorMessage": "some explanation" "StackTrace": "text" } + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + Response 401 // HTTP-Unauthorized: DevKey or trackingUnitId not specified or not available + response 400 { "ErrorId":12000, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 12001: qnips sortimentId unknown // 12006: No item found under given Id ## Edit article [/sortips/{qnipsSortimentId}/article/{PLU}?groupId={groupId}] ### POST sortiments/{qnipsSortimentId}/article} [POST] + Parameters + qnipsSortimentId (string, `'sortiment123'`) ... this is the `qnipsSortimentId' returned by the call to `GET sortiments?thirdPartySortimentIdentifier={string1}&name={string2}`. + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} + Body { "PLU": "123." "Name": [{ "lang": "de-DE", "val": "Bier 0.5L" } "GroupId": "234", "Rateable":true, "ExternalName": [{ "lang": "de-DE", "val": "Bier vom Fass 0.5L" }], "Description": [{ "lang": "de-DE", "val": "Frisch gezapft" }] "Ingredients": [{ "lang": "de-DE", "val": "Malz, Hopfen, Wasser" }], "SoldOut":false, "PictureUrl": "http://..." "Prices":[ { "Value":9.95, "CurrencyIso": "EUR", "Tag":{ "Name":[ { "long": "de-DE", "val": "Mitarbeiter" } ] } }, { "Value":8.95, "CurrencyIso": "EUR", "Tag":{ "Name":[ { "lang": "de-DE", "val": "Studenten" } ] } } ], "Allergen": [1, 5, 11], "Traces": [2, 4], "Additives": [1, 8], "Tags":[ "Vegan." "frisch gezapft" ], "WeightInGrams":125.0, "NutritionFacts":{ "Fats":20, "Carbs":20, "Protein":20, "KJoule":200, "KCal":200 } } + response 200 + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing { "ErrorMessage": "some explanation" "StackTrace": "text" } + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + Response 401 // HTTP-Unauthorized: DevKey or trackingUnitId not specified or not available + response 400 { "ErrorId":123, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 1000: Body contains syntactical errors and cannot be deserialized // 12001: qnips sortimentId unknown // 12003: No product group found under the specified UpGroupId // 12005: Name was not specified, but is expected in at least one language // 12008: PLU field not set // 12009: An article with the specified PLU already exists in the specified product group ### PUT sortiments/{qnipsSortimentId}/article} [PUT] + Parameters + qnipsSortimentId (string, `'sortiment123'`) ... this is the `qnipsSortimentId' returned by the call to `GET sortiments?thirdPartySortimentIdentifier={string1}&name={string2}`. + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} + Body { "PLU": "123." "Name": [{ "lang": "de-DE", "val": "Bier 0.5L" } "GroupId": "234", "Rateable":true, "ExternalName": [{ "lang": "de-DE", "val": "Bier vom Fass 0.5L" }], "Description": [{ "lang": "de-DE", "val": "Frisch gezapft" }] "Ingredients": [{ "lang": "de-DE", "val": "Malz, Hopfen, Wasser" }], "SoldOut":false, "PictureUrl": "http://..." "Prices":[ { "Value":9.95, "CurrencyIso": "EUR", "Tag":{ "Name":[ { "lang": "de-DE", "val": "Mitarbeiter" } ] } }, { "Value":8.95, "CurrencyIso": "EUR", "Tag":{ "Name":[ { "lang": "de-DE", "val": "Studenten" } ] } } ], "Allergen": [1, 5, 11], "Traces": [2, 4], "Additives": [1, 8], "Tags":[ "Vegan." "Frisch gezapft" ], "WeightInGrams":125.0, "NutritionFacts":{ "Fats":20, "Carbs":20, "Protein":20, "KJoule":200, "KCal":200 } } + response 200 + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing { "ErrorMessage": "some explanation" "StackTrace": "text" } + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + Response 401 // HTTP-Unauthorized: DevKey or trackingUnitId not specified or not available + response 400 { "ErrorId":123, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 1000: Body contains syntactical errors and cannot be deserialized // 12001: qnips sortimentId unknown // 12005: Name was not specified, but is expected in at least one language // 12006: No item found under specified Id/PLU. Try a POST // 12007: (Up)GroupId must not be changed. Delete the item instead and create it in the other material group // 12008: PLU field not set ### DELETE sortiments/{qnipsSortimentId}/article/{PLU}?groupId={groupId} [DELETE] + Articles can be organized in merchandise categories. To do this, the field GroupId of the article must be set with the ID of the group and the product group must have been created in the qnips system beforehand. + The two minimum required details for an article are PLU and name + all other fields are optional. So here is a minimal variant:

    {
       "PLU": "123."
       "Name": [{ "lang": "de-DE", "val": "Bier 0.5L" }
    }

And here the maximum variant. Any properties subset in between is of course also possible:

    {
       "PLU": "123."
       "Name": [{ "lang": "de-DE", "val": "Bier 0.5L" }
       "GroupId": "234",
       "Rateable":true,
       "ExternalName": [{ "lang": "de-DE", "val": "Bier vom Fass 0.5L" }],
       "Description": [{ "lang": "de-DE", "val": "Frisch gezapft" }]
       "Ingredients": [{ "lang": "de-DE", "val": "Malz, Hopfen, Wasser" }],
       "SoldOut":false,
       "PictureUrl": "http://..."
       "Prices":[
          {
             "Value":9.95,
             "CurrencyIso": "EUR",
             "Tag":{
                "Name":[
                   {
                      "lang": "de-DE",
                      "val": "Mitarbeiter"
                   }
                ]
             }
          },
          {
             "Value":8.95,
             "CurrencyIso": "EUR",
             "Tag":{
                "Name":[
                   {
                      "lang": "de-DE",
                      "val": "Studenten"
                   }
                ]
             }
          }
       ],
       "Allergen": [1, 5, 11],
       "Traces": [2, 4],
       "Additives": [1, 8],
       "Tags":[
          "Vegan."
          "Frisch gezapft"
       ],
       "WeightInGrams":125.0,
       "NutritionFacts":{
          "Fats":20,
          "Carbs":20,
          "Protein":20,
          "KJoule":200,
          "KCal":200
       }
    }

| Property | Description | | --- | --- | || ||PLU||**string**: What the 'ID' is for the product group is the PLU for the article. | |``Name`|||translatable**: The name as well as some other properties of an article can be given in any number of translations in the format described below. Please specify at least one language. | || ||GroupId|||**string**: This is the ID of the product group under which the article is to be classified. The product group must already have been created, otherwise the call is rejected. If this information is omitted, the article is placed at the top level, ungrouped, in the sortiment. || ||rateable|||||bool**: If the merchant can specify the rateability of an item in your software, you can transmit this setting to qnips here to keep certain items out of the rating sheets. If your system does not offer a possibility to record the valuability of the items, enter 'zero' in this field or leave it out completely. |``Description`|**translatable**: If a description exists for the article, it can be transmitted here in several languages. In certain cases this can be displayed to consumers in the app| || ||Ingredients||||translatable**: If this information exists, it can be transmitted here in several languages and may be visible to consumers in the app. || ||oldOut|||**string**: If an item in your software can be marked as sold out, you should transmit this change of state to qnips via this field. This enables us to hide the article dynamically and promptly in the menu, e.g. in public electronically displayed menus.| || ||pictureUrl||||string**: If there is a publicly accessible image for the article via a URL, its URL can be entered here. || ||Prices||||List of Price**: Any number of price levels can be specified here, whereby a price level always consists of a 'tag' as a publicly visible identifier and the actual price as a decimal value.| || ||List of ints** Here you can specify a list of allergen IDs that are included in the article. See the allergen listing below. | |``Traces`|||List of ints**: A list of the allergen traces contained in the article can be given here. List here the allergen IDs whose traces may be contained in the article. See the allergen listing below. | || ||Additives|||List of ints**: A list of the additives that are required to be identified as a minimum under the LMIV Regulation and that are contained in the article can be given here. See the list of additives below. | ||||||List of strings**: If a merchant has implemented his own individual article labels, which could not be transferred via previous properties, but are still important for one of the qnips functions (e.g. individual article labels in public menus), they can be specified via this list property. | || ||WeightInGrams|||**decimal**: If the information exists in your software, you can pass it on to us here. | || || Fats'||||**decimal**: Indication of the fat content in grams per 100 grams article weight. | || || Carbs|||**decimal**: Indication of the carbohydrate content in grams per 100 grams article weight. | || || protein|||**decimal**: Indication of the protein content in grams per 100 grams article weight. | || || KJoule|||||decimal**: KJoule per 100 grams article weight. | |||||| KCal||||**decimal**: KCal per 100 grams article weight. | #### allergen list Id|allergens ---|--- 0|Gluten-containing grain 1|Crustaceans and products thereof 2| Eggs and egg products 3| Fish and fish products 4| Peanuts and peanut products 5| Soya and soya products 6| Milk and milk products 7| Nuts and nut products 8| Celery and celery products 9| Mustard and mustard products 10| Sesame and sesame products 11| sulphur dioxide and sulphites 12| Lupines and lupine products 13| Molluscs and mollusc products #### Additives list Id|additive name ---|--- 0| with dye 1| with preservative 2| with antioxidant 3| with flavour enhancer 4| sulphurized 5| blackened 6| waxed 7| with phosphate 8| with sweetener(s) 9| contains a phenylalanine source 10| with nitrate 11| with nitrite pickling salt 12| caffeinated + Parameters + qnipsSortimentId (string, `'sortiment123'`) ... this is the `qnipsSortimentId' returned by the call to `GET sortiments?thirdPartySortimentIdentifier={string1}&name={string2}`. + PLU (string, `A12`) ... enter the PLU of the article to be deleted here. + groupId (string, `123`) ... enter the PLU of the product group in which the article to be deleted is located or a '0' if the article is not assigned to a product group + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} + response 200 + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing { "ErrorMessage": "some explanation" "StackTrace": "text" } + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + Response 401 // HTTP-Unauthorized: DevKey or trackingUnitId not specified or not available + response 400 { "ErrorId":123, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 12001: qnips sortimentId unknown // 12006: No item found under specified Id/PLU. Try a POST ## BulkUpload [/sortiments/{qnipsSortimentId}/bulkUpload?changedArticlesOnly={changedArticlesOnly}] ### POST sortiments/{qnipsSortimentId}/bulkUpload?changedArticlesOnly={changedArticlesOnly}} [POST] + In BulkUpload you can create and update several articles and product groups in one go + Using the boolean parameter 'changedArticlesOnly' you can specify whether the body contains a complete trunk or just the articles updated on your system since the last upload. + With 'changedArticlesOnly=true' our system will independently determine the newly added, changed or deleted articles and product groups compared to the previous upload and only save these changes historically. + With 'changedArticlesOnly=false', our system interprets the information in the body as new articles and product groups or those to be updated. In this case, deletion must be carried out by means of individual deletions, as described above. The body must be specified for this request as follows:

        {
            "Groups": [
                {
                   "Id": "1",
                   "UpGroupId": ""
                   "Name": [{ "lang": "de-DE", "val": "Getränke" }
                },
                {
                   "Id": "1_2"
                   "UpGroupId": "1"
                   "Name": [{ "lang": "de-DE", "val": "Biers" }
                }
            ],
            "Products": [
                {
                   "PLU": "123."
                   "GroupId": "1_2"
                   "Name": [{ "lang": "de-DE", "val": "Bier 0.5L" }
                }
            ]
        }
    
| Property | Description | | --- | --- | |`Groups`| List of product groups as specified under [warengruppen-bearbeiten](#reference/sortiments/edit product groups). | |`Products`| List of articles as specified under [Edit article](#reference/sortiments/artikel-bearbeiten). | + Parameters + qnipsSortimentId (string, `'sortiment123'`) ... this is the `qnipsSortimentId' returned by the call to `GET sortiments?thirdPartySortimentIdentifier={string1}&name={string2}`. + changedArticlesOnly (boolean, `true'` ) ... indicates whether the upload is the entire sortiment to be shared with qnips or whether this request only contains the changes to the previous successful upload. + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} Content-encoding: gzip (optional) // add the body of this request in zipped form if this header is set. + response 200 + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing { "ErrorMessage": "some explanation" "StackTrace": "text" } + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + Response 401 // HTTP-Unauthorized: DevKey or trackingUnitId not specified or not available + response 400 { "ErrorId":123, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 1000: Body contains syntactical errors and cannot be deserialized // 12001: qnips sortimentId unknown # Group tokens Tokens are arbitrary, but always different, non-repeatable strings, which can be used for identification purposes as well as for of a consumer ('consumer') as well as for the identification of a credit balance ('balance'). As a rule these tokens are stored on any carrier medium and sent to a consumer in the form of a 'customer card' or 'gift card handed over. The consumer will have to present this carrier medium at the POS with his next purchases in order to access the to be able to access credit or to be credited with qnips-Rewards. ## Register token [/tokens/{id}] ### POST tokens/{id} [POST] This call creates a new consumer profile for a given token. *Please form a checksum (see [Security-Token](#introduction/basic/security-token)) for the ** **{id}** *and specify it in the 'SecurityToken' header. + Parameters + id (string, `cardId123`) ... Enter the token read from a carrier medium here. + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} SecurityToken: {checksum over {id} Token: optional {the token read from a carrier medium as customer identifier} + Body { "UserGroup": { "Key": "ThirdPartyId", "Name": "UserGroupName" } } + response 200 + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing { "ErrorMessage": "some explanation" "StackTrace": "text" } + Response 401 // HTTP-Unauthorized: DevKey, trackingUnitId or SecurityToken not specified/wrong + response 400 { "ErrorId":13000, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 13026: Feature is not supported by the brand. // 13025: Profile already exist. ## Get token info [/tokens/{id}/info] ### GET tokens/{id}/info [GET] Hereby it can be queried, whether there is already credit on the token, which recharge limits for the token If necessary, there may be a PIN query, how many loyalty points the user of this token may already have and much more. This info can be used for the visual display must be relevant for the cashier. *Please form a checksum (see [Security-Token](#introduction/basic/security-token)) for the ** **{id}** *and specify it in the 'SecurityToken' header. + Parameters + id (string, `'cardId123'`) ... Enter the token read from a carrier medium here. If the token is longer than 50 bytes, it should be transmitted via the token request header instead of the URL. + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} SecurityToken: {checksum over {id}} Token: optional {the token read from a carrier medium as customer identifier} + response 200 { "TokenType": "consumerIdentifier", "Balance": 25.99, "MaxBalance": 99999.0, "RemainingBalanceToAdd": 99974.01, "IsWithdrawBalanceAllowed": false, "IsAddBalanceAllowed": true, "RelatedGiftCards": [], "IdentAliases": [ { "IdentString": "6088514929546754", "Source": 4 } ], "IdentString": "6088514929546754", "ExternalIdentString": "12398730984", "ExternalIdentStringSource": "Company", "ExternalCardNumber": "12356473", "RequiresPin": false, "InfoUrl": "http://https://pos-playground-eu.qnips.com/TokenInfo?token=4BZC2R1iAqmappIzAMbOkQ0LZCm0b%2fmc0v7nhSITEV7snTxI%2f%2bELsNd6n1Z2fyXtMp%2fuwlg%2fgU%2be9r7bIQwSiA%3d%3d", "ConsumerIdentityInfo": { "LoyaltySchemeProgressInfos": [ { "CurrentPoints": 2, "MaxPoints": 10, "LoyaltySchemeId": 123, "LoyaltySchemeName": "Every 10th coffee drink for free" } ], "QrCodeContent": "", "Allowances": [ { "Id": 180, "Name": "Allowances Daily", "ThirdPartyId": "AD123", "RemainingValue": 2.0, "Type": 1 } ] }, "TagInfos": [ { "ThirdPartyId": "ThirdPartyId2", "Name": "UserGroupName2" } ] } + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing { "ErrorMessage": "some explanation" "StackTrace": "text" } + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + Response 401 // HTTP-Unauthorized: DevKey, trackingUnitId or SecurityToken not specified/wrong + response 400 { "ErrorId":13000, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 13001: token unknown ## Lock token [/tokens/{id}/lock] ### POST tokens/{id}/lock [POST] With this you lock a token so that no further 'basket' is submitted with this token and the credit can neither be revalued nor withdrawn. Possible remaining credit balance behind this token expires without replacement. *Please form a checksum (see [Security-Token](#introduction/basic/security-token)) for the ** **{id}** *and specify it in the 'SecurityToken' header. + Parameters + id (string, `'cardId123'`) ... Enter the token read from a carrier medium that is to be blocked. + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} SecurityToken: {checksum over {id}} + response 200 + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing { "ErrorMessage": "some explanation" "StackTrace": "text" } + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + Response 401 // HTTP-Unauthorized: DevKey, trackingUnitId or SecurityToken not specified/wrong + response 400 { "ErrorId":13000, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 13001: token unknown ## Release token [/tokens/{id}/dispose] ### POST tokens/{id}/dispose [POST] Releases the token for reuse by a new consumer. Possible credit balance behind this token expires without replacement. *Please form a checksum (see [Security-Token](#introduction/basic/security-token)) for the ** **{id}** *and specify it in the 'SecurityToken' header. + Parameters + id (string, `'cardId123'`) ... Enter the token read from a carrier medium that you want to release for reuse. + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} SecurityToken: {checksum over {id}} + response 200 + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing { "ErrorMessage": "some explanation" "StackTrace": "text" } + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + Response 401 // HTTP-Unauthorized: DevKey, trackingUnitId or SecurityToken not specified/wrong + response 400 { "ErrorId":13000, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 13001: token unknown ## Query credit [/tokens/{id}/balance] ### GET tokens/{id}/balance [GET] This allows you to check the credit balance on the token, if there is any. *Please form a checksum (see [Security-Token](#introduction/basic/security-token)) for the ** **{id}** *and specify it in the 'SecurityToken' header. + Parameters + id (string, `'cardId123'`) ... Enter here the token read from a carrier medium whose credit you want to query. + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} SecurityToken: {checksum over {id}} + response 200 { Balance = 50.0, CurrencyIso = "EUR" } + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing { "ErrorMessage": "some explanation" "StackTrace": "text" } + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + Response 401 // HTTP-Unauthorized: DevKey, trackingUnitId or SecurityToken not specified/wrong + response 400 { "ErrorId":123, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 13001: token unknown // 13004: token is temporarily blocked, no further action possible // 13008: token is finally locked, no further action possible ## Top up credit [/tokens/{id}/addBalance?balanceToAdd={balanceToAdd}] ### POST tokens/{id}/addBalance?balanceToAdd={balanceToAdd} [POST] This allows you to increase the credit on the token by any amount. *Please form a checksum (see [Security-Token](#introduction/basic/security-token)) for the ** **'{id}_{balanceToAdd}'** *(e.g. 'card123_1.99') and enter it in the 'SecurityToken' header. + Parameters + id (string, `'cardId123'`) ... Enter the token read from a carrier medium here. + balanceToAdd (decimal, `'1.99'`) . Enter the amount that should be topped up on the credit of this token. Negative amounts are not allowed + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} SecurityToken: {checksum over '{id}_{balanceToWithdraw}'} (in this example via 'card123_1.99') + response 200 { "oldBalance":1.5 "newBalance":3.49 } + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing { "ErrorMessage": "some explanation" "StackTrace": "text" } + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + Response 401 // HTTP-Unauthorized: DevKey, trackingUnitId or SecurityToken not specified/wrong + response 400 { "ErrorId":123, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 13001: token unknown // 13004: token is locked, no further action possible // 13005: Only positive values are allowed for charging or lifting // 13006: Limit for lift-off/recharge exceeded // 13009: adding balance not possible - current charge amount would exceed monthly charge limit // 13010: adding balance not possible // 13020: adding balance not possible - not registered user ## Draw credit [/tokens/{id}/withdrawBalance?balanceToWithdraw={balanceToWithdraw}&pin={pin}] ### POST tokens/{id}/withdrawBalance?balanceToWithdraw={balanceToWithdraw}&pin={pin} [POST] This allows you to withdraw an amount from the credit of the token. If the amount to be withdrawn is greater than the credit balance, the call is rejected. *Please create a checksum (see [Security-Token](#introduction/basic/security-token)) for the* **'{id}_{balanceToWithdraw}'** ** (e.g. 'card123_1.99') and enter it in the 'SecurityToken' header. + Parameters + id (string, `'cardId123'`) ... Enter the token read from a carrier medium here. + balanceToWithdraw (decimal, `'1.99'`) ... Specify the amount to be withdrawn. Negative amounts are not allowed + pin (optional, string, `'PQ123'`) ... Enter the optional PIN here in case the field `RequiresPin` of the response of GET tokens/{id}/info is set to `true`. The PIN will usually be printed on the gift card + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} SecurityToken: {checksum over '{id}_{balanceToWithdraw}'} (in this example via 'card123_1.99') + response 200 { "oldBalance":3.49 "newBalance":1.5 } + response 500 // HTTP-InternalServerError: An unknown problem occurred during processing { "ErrorMessage": "some explanation" "StackTrace": "text" } + response 403 // HTTP-Forbidden: trackingUnitId is not yet activated by the merchant + Response 401 // HTTP-Unauthorized: DevKey, trackingUnitId or SecurityToken not specified/wrong + response 400 { "ErrorId":123, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 13001: token unknown // 13004: token is locked, no further action possible // 13005: Only positive values are allowed for charging or lifting // 13006: Limit for lift-off/recharge exceeded // 13010: withdrawing balance not yet allowed in current store // 13012: not enugh balance // 13015: PIN is wrong, only if PIN is provided as query parameter ## Draw allowances [/tokens/{id}/Allowances?pin={pin}] ### POST tokens/{id}/Allowances?pin={pin} [POST] This allows you to withdraw an amount from the allowances of the token. *Please form a checksum (see [Security-Token](#introduction/basic/security-token)) for the ** **{id}** *and specify it in the 'SecurityToken' header. + Parameters + id (string, `'cardId123'`) ... Enter the token read from a carrier medium here. If the token is longer than 50 bytes, it should be transmitted via the token request header instead of the URL. + pin (optional, string, `'PQ123'`) ... Enter the optional PIN here in case the field `RequiresPin` of the response of GET tokens/{id}/info is set to `true`. The PIN will usually be printed on the gift card + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} SecurityToken: {checksum over {id}} Token: optional {the token read from a carrier medium as customer identifier} + Body { "UserGroup": { "Key": "123", "Name": "Internal Customers" }, "Allowances": [ { "Id": 1, "ThirdPartyId": "9999", "Amount": 5.99 } ] } + response 200 [ { "Id": 1, "ThirdPartyId": "9999", "Amount": 5.99, "Success": true } ] ## Post basket [/tokens/{id}/ExternalBasket?pin={pin}] ### POST tokens/{id}/ExternalBasket?pin={pin} [POST] This allows you to post a basket for a given token without triggering internal processes. *Please form a checksum (see [Security-Token](#introduction/basic/security-token)) for the ** **{id}** *and specify it in the 'SecurityToken' header. + Parameters + id (string, `'cardId123'`) ... Enter the token read from a carrier medium here. If the token is longer than 50 bytes, it should be transmitted via the token request header instead of the URL. + pin (optional, string, `'PQ123'`) ... Enter the optional PIN here in case the field `RequiresPin` of the response of GET tokens/{id}/info is set to `true`. The PIN will usually be printed on the gift card + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} SecurityToken: {checksum over {id}} Token: optional {the token read from a carrier medium as customer identifier} + Body { "BillNumber": "727e085b-91e9-4383-92b6-b53e04ea1615", "TimestampUtc": "2024-02-16T08:51:01.216Z", "Positions": [ { "PositionId": 0, "ProductId": "QNP-1699877891354", "Amount": 1, "Name": "Hamburger", "OrderTimeUtc": "2024-02-16T08:51:01.216Z", "ProductProps": {} } ] } + response 200 ## Post UserGroups [/tokens/{id}/UserGroups?pin={pin}] ### POST tokens/{id}/UserGroups?pin={pin} [POST] This allows you to manage usergroups for a given token. If a given usergroup(key) is present in UserGroupsToAdd and UserGroupKeysToRemove it will be deleted and not added. *Please form a checksum (see [Security-Token](#introduction/basic/security-token)) for the ** **{id}** *and specify it in the 'SecurityToken' header. + Parameters + id (string, `'cardId123'`) ... Enter the token read from a carrier medium here. If the token is longer than 50 bytes, it should be transmitted via the token request header instead of the URL. + pin (optional, string, `'PQ123'`) ... Enter the optional PIN here in case the field `RequiresPin` of the response of GET tokens/{id}/info is set to `true`. The PIN will usually be printed on the gift card + Request + Headers DevKey: {Your Key} TrackingUnitId: {trackingUnitId} SecurityToken: {checksum over {id}} Token: optional {the token read from a carrier medium as customer identifier} + Body { "UserGroupsToAdd": [ { "Key": "ThirdPartyId", "Name": "UserGroupName" }, { "Key": "ThirdPartyId2", "Name": "UserGroupName2" } ], "UserGroupKeysToRemove": [ "ThirdPartyId2", "ThirdPartyId3" ] } + response 200 + response 400 { "ErrorId":123, "ErrorText": "Details of the error" } // here all possible error codes incl. texts for this resource // 13027: At least one UserGroupsToAdd or UserGroupKeysToRemove have to be provided