# Versioning and Changelog

Read first: [Overview & Concepts](01-overview-and-concepts.md) · [General Requirements](02-general-requirements.md)

This page is the version story for the **In-Store Loyalty API**: how the protocol is versioned, which capability arrived in which version, what changed at each step, and how to migrate an existing integration forward. New integrations are recommended to build on version **1.5 or later** — the latest is **1.7**, and the rest of this document set describes it. **Versions below 1.5 are deprecated**: integrations built on them keep working for backward compatibility; their operations stay in the specification marked as deprecated, but this documentation set no longer covers them. Field-level detail for the supported versions lives in the [In-Store Loyalty API specification](/in-store/api/).

## 1. How versioning works

The protocol version is part of every URL path: `POST {baseUrl}/qr-code/1.7`, `POST {baseUrl}/order-closed/1.7`, and so on. The pattern is `{baseUrl}/{operation}/{version}`. There is no version header and no content negotiation — the path *is* the version selector.

The OpenAPI specification describes the supported versions **1.5** through **1.7** as distinct paths — all four operation families ([`one-time-salt`](/in-store/api/), [`qr-code`](/in-store/api/), [`order-closed`](/in-store/api/), [`refund`](/in-store/api/)) are present at each supported version. Deprecated paths (below 1.5) are not part of the spec; this Markdown describes only 1.7.

Which version an outlet uses is set in its configuration, not chosen per call: the sales-outlet config carries an `apiVersion` value (alongside fields including `apiUrl`, `establishmentId`, the establishment `password`, and the scan `flow`), and your integration calls the matching versioned path. New outlets are provisioned for **1.7**. See [Kiosk and POS Configuration](06-kiosk-and-pos-configuration.md) for the full config contract.

> **Note:** Use the same version on every operation within one order. The version of `order-closed` and `refund` must match the version used for the `qr-code` validation that opened the order — mixing versions on a single order is not supported.

## 2. Capability × version matrix

Each row is a vendor-visible capability; each column a supported version. "added" marks the version that introduced the capability; "available" marks later versions where it remains in force. A blank cell means the capability does not exist in that version. Deprecated versions (below 1.5) are not shown.

| Capability | 1.5 | 1.6 | 1.7 |
|---|---|---|---|
| QR-code validation (`qr-code`) | available | available | available |
| SHA-256 `AuthorizationToken` auth | available | available | available |
| Structured `messages[]` with numeric `code` | available | available | available |
| `payment` object | available | available | available |
| `hasEverRewardedPoints` in response | available | available | available |
| `maxPointsPercent` in response | available | available | available |
| `customerEmail` in response | added | available | available |
| Structured modifiers + `value`/`fixedPrice` discounts | added | available | available |
| Required `total` (`qr-code`); item `price` expected | added | available | available |
| phone number-based flow (`phoneNumber` et al.) | — | added | available |
| `customerTier` + `customerPointsBalance` | — | — | added |
| `publicClientId` lookup | — | — | added |

## 3. Per-version changelog

Concise, vendor-facing deltas. Each entry states what a vendor would notice on the wire; field-level detail lives in the spec.

- **1.7** (latest) — Added customer **tier** name (`customerTier`) and **points balance** (`customerPointsBalance`) to the `qr-code` response `customer` block (both nullable, best-effort enrichment), and a third identification method: `publicClientId` lookup. Additive and non-breaking over 1.6.
- **1.6** — Added the **phone number-based flow**: identify or register a customer by `phoneNumber` instead of a QR code — the customer enters their number on the self-service kiosk, or the cashier enters it at the cash register / POS terminal — with optional `clientName` and `subscribedToPushNotifications`. The same fields appear on `order-closed`.
- **1.5** — Restructured the order model: **modifiers** changed from string arrays to objects (`itemId`, `title`, `price`, `quantity`); **discounts** moved to a single `value` field with a new `fixedPrice` type; `total` became required on the `qr-code` request, and item `title`/`price` are expected on `qr-code` menu items per the migration contract. Also added `customer/customerEmail` to the `qr-code` response (exposed only when the outlet has the additional-customer-info feature enabled).
- **1.0–1.4** (**deprecated**) — legacy versions predating the 1.5 order-model restructure; versions 1.0–1.3 additionally use the legacy MD5 `Signature` auth scheme. Their operations stay in the specification marked as deprecated, but this documentation set no longer covers them; integrations built on them keep working for backward compatibility. To move to a supported version, see Section 4.1.

## 4. Migration guides

Each guide covers moving *to* the named version. Apply them cumulatively if you skip versions — e.g. moving from a deprecated version to 1.7 means doing 4.1, 4.2, and 4.3 in order.

### 4.1 From a deprecated version (below 1.5) — to 1.5

Versions below 1.5 are **deprecated**; moving off them means two changes — authentication and the order-model structure.

**Authentication.** Versions 1.0–1.3 send a `Signature` header equal to `MD5(requestBody + apiPrivateKey + salt)`, and the server signs its response with the same scheme. From 1.4 on, send an `AuthorizationToken` header equal to `SHA256(apiPrivateKey + salt)` (key first, request body **excluded**), and the response is unsigned.

1. Replace the `Signature` header with `AuthorizationToken` (keep `SaltId`, which is unchanged — it is sent on both legacy and current requests).
2. Compute the token as `SHA256(apiPrivateKey + salt)`, lowercase hex — the request body is no longer part of the hash.
3. Stop verifying the response `Signature` header; responses are not signed anymore.
4. Start handling HTTP **400** validation errors: a JSON object mapping each invalid field's JSON-Pointer path to a reason.

The token mechanics are detailed in the [QR Code Loyalty Guide](04-qr-code-loyalty-guide.md).

**Order model.** 1.5 reshapes the order model. The change is in field structure, not new operations.

| Deprecated (pre-1.5) shape | 1.5+ shape |
|---|---|
| `discount`: `type` (`percent`/`amount`) + `percent`/`amount` field | `discount`: `type` (`percent`/`amount`/`fixedPrice`) + single `value` field |
| `modifiers`: array of strings (`["EXTRA_CHEESE"]`) | `modifiers`: array of objects (`{itemId, title, price, quantity}`) |
| `qr-code` menu item: `title`/`price` optional, no `total` | `qr-code` menu item: `title`/`price` expected (per migration contract); request `total` required |

To migrate: convert every discount's `percent`/`amount` field to a single `value` (e.g. `{"type":"percent","percent":10}` → `{"type":"percent","value":10}`); convert each modifier string into an object with `itemId`, `title`, `price`, `quantity`; add `price` to every order item; and add `total` to the `qr-code` request. The new `fixedPrice` discount type is optional — use it only if needed.

> **Note:** The OpenAPI `MenuItem.required` set is `[itemId, quantity]` — item `title` and `price` are not flagged required by the machine schema. Their requiredness at 1.5+ comes from the migration business contract and server-side validation, not the OpenAPI `required` list. Sending them is always safe.
> **Warning:** The `value`/`fixedPrice` discount shape and modifier objects apply to **both** `qr-code` and `order-closed`. Update the order composition in both requests, or `order-closed` will fail schema validation.

### 4.2 To 1.6 — phone number-based flow

1.6 adds the phone number-based flow and is otherwise unchanged from 1.5 on the wire for existing QR-code traffic — your QR validation keeps working as-is. To adopt the new capability:

1. On `qr-code`, send `phoneNumber` (international format) instead of `qrCode`, optionally with `clientName` and `subscribedToPushNotifications` — the customer enters their number on the self-service kiosk, or the cashier enters it at the cash register / POS terminal.
2. Echo the same `phoneNumber`, `clientName`, and `subscribedToPushNotifications` on `order-closed` for that order.

Full implementation is in the [Phone Number Guide](05-phone-number-guide.md). Migrating from a deprecated version requires doing 4.1 first.

### 4.3 To 1.7 — tier, balance, and publicClientId

1.7 is **additive and non-breaking** over 1.6. A 1.6 client can move to the `/1.7` paths with no request changes and will simply start receiving two extra response fields. To use the new features:

1. Read `customer.customerTier` (tier name, e.g. `"Gold"`) and `customer.customerPointsBalance` (points balance) from the `qr-code` response when present, and surface them at the point of payment.
2. Optionally, identify a customer by `publicClientId` instead of `qrCode`/`phoneNumber`.

> **Warning:** `customerTier` and `customerPointsBalance` are best-effort enrichment and may be `null` in a successful (`accepted: true`) response if the backing lookup is unavailable. Treat them as optional display data; never gate the flow on their presence.
> **Note:** When more than one identifier is sent, the in-store API resolves them in priority order `qrCode` → `phoneNumber` → `publicClientId`; if none is sent, the request is rejected.

## 5. Support and deprecation policy

- **Versions below 1.5 are deprecated.** Integrations built on the `1.0`–`1.4` paths keep working for backward compatibility, but these versions are covered by the specification only as deprecated operations and are no longer described in this documentation set. To move to a supported version, see Section 4.1.
- **New integrations are recommended to build on 1.5 or later.** 1.7 is the latest version, and this documentation set describes it.
- **The legacy Integration Server XML protocol is out of scope.** The older session-based in-store protocol ("Фабрика Лояльности" / Universal Protocol) is a separate, deprecated product and is not documented here; the In-Store Loyalty API is the current in-store API.

> **Note:** No end-of-life dates are published for any version of the in-store API. If LoyaltyPlant schedules the retirement of a legacy version for your integration, it is communicated through your integration contact, not through this document.

---

**Next:** [Message & Error Codes →](08-message-and-error-codes.md)
