{
  "info": {
    "_postman_id": "3b9d7c52-1e4f-4a86-9c20-7f5e1d3a8b64",
    "name": "PosFix — публичный API интеграции",
    "description": "API, через которое внешние системы продавца (интернет-магазин, ERP, бухгалтерия) читают и записывают его данные: каталог, остатки, цены, клиентов, заказы, продажи (Z-отчёты и все продажи по дням и каналам), распознавание накладных, программу лояльности и события через webhooks.\n\nДля API на кассовом аппарате (фискальные чеки, наличные, печатные X/Z-отчёты) есть отдельная коллекция **PosFix — API терминала**.\n\n## Ключ\n\nМодуль **«API интеграции»** — платная услуга: PosFix включает его для продавца и выдаёт ключ. Ключ (`rk_…`) показывается **только один раз**; PosFix хранит только его отпечаток, поэтому потерянный ключ заменяют, а не восстанавливают. После замены старый ключ работает ещё 24 часа.\n\nОтправляется в каждом запросе:\n\n```\nX-Posfix-Api-Key: rk_…\n```\n\nПродавец определяется **по ключу**. Параметра продавца нет, поэтому ключ не может читать чужие данные. Ключ можно ограничить определёнными кассовыми аппаратами: тогда он видит только их продажи.\n\nНачните с `GET /api/public/v1/ping`: он подтверждает ключ и показывает его scope.\n\n## Scope\n\nКаждый ключ получает только разрешения, выбранные при выпуске. Scope, который нужен запросу, указан в начале его описания.\n\n| Scope | Что разрешает |\n|---|---|\n| `products:read` | Каталог, категории, атрибуты, блоки контента, прайс-листы, цены. |\n| `inventory:read` | Остаток товара. |\n| `customers:read` / `customers:write` | Чтение / создание клиентов. |\n| `orders:read` / `orders:write` | Чтение / создание заказов, указание данных доставки. |\n| `sales:read` | Кассовые аппараты, Z-отчёты, чеки. |\n| `docai:catalog` | Собственная Номенклатура для распознавания накладных. |\n| `docai:recognize` | Отправка накладных (PDF, фото) на распознавание — платно. |\n| `docai:read` | Чтение распознанных накладных и подтверждение их получения. |\n| `loyalty:read` | Программы лояльности, участники с их балансом, история карты. Нужен также модуль «Программы лояльности». |\n| `loyalty:write` | Регистрирует участников, меняет их имя или email. Нужен также модуль «Программы лояльности». |\n| `loyalty:earn` | Начисляет за оплаченные заказы и отменяет при возврате. Нужен также модуль «Программы лояльности». |\n| `webhooks:manage` | Подписки на события. |\n\nБез нужного scope или при неактивном модуле «API интеграции» ответ — `403`, **без тела**. Scope `loyalty:*` дополнительно требуют активного у продавца модуля «Программы лояльности». «Ping» не требует ни scope, ни модуля: если он работает, а запрос даёт `403`, причина — отсутствующий scope или неактивный модуль.\n\n## Лимиты\n\n- На ключ: запас в **120 запросов**, который пополняется на **2 в секунду** (~120 в минуту при равномерном темпе).\n- При превышении лимита: `429` с `errorCode: RATE_LIMITED`, **без** заголовка `Retry-After`. Повторите через несколько секунд, с нарастающими паузами.\n- Второй лимит, **300 запросов в минуту**, считается по IP-адресу. Сейчас сервис видит только адрес шлюза, поэтому этот лимит общий для всех интеграторов: `429` может прийти, даже когда ваш ключ не превысил свой лимит.\n- Лимит проверяется раньше ключа: `429` может прийти и на запрос с неверным ключом.\n\n## Ошибки\n\nОтветы с ошибкой бывают нескольких видов. **Ориентируйтесь на HTTP-статус**; `errorCode` и `errors` помогают там, где они есть, а `detail` — текст для людей, он может меняться.\n\n| HTTP | Тело | Когда |\n|---|---|---|\n| `401`, `403` | **без тела** | Ключ отсутствует или недействителен; нет scope или модуль неактивен. Оба случая `403` выглядят одинаково: проверьте их через «Ping». |\n| `404` | без тела или простой формат | Ресурс не существует (или принадлежит другому продавцу). |\n| `400` | `{\"statusCode\":400,\"message\":\"…\",\"errors\":{\"câmp\":[\"…\"]},\"errorCodes\":{\"câmp\":[{\"code\":\"COD\",\"params\":{…}}]}}` | Параметр или JSON неверного типа, некорректный GUID, валидация параметров (формат привязки). |\n| `400`, `404`, `409` | `{\"status\":…,\"title\":\"…\",\"detail\":\"…\",\"errorCode\":\"COD\",\"params\":{…}}` | Отказ бизнес-логики с сообщением в `detail` (простой формат). |\n| `400`, `422`, `5xx` | `{\"type\":…,\"title\":\"COD\",\"status\":…,\"detail\":\"…\",\"errorCode\":\"COD\",\"params\":{…},\"errors\":{…},\"errorCodes\":{…}}` | `VALIDATION` (с `errors` и `errorCodes` по полям), отказ бизнес-правила с его собственным кодом (`422`, например недостаточный остаток или изменённая цена), `SERVICE_UNAVAILABLE` (503), `INTERNAL_ERROR` (500). |\n| `429` | `{\"status\":429,\"title\":\"Too Many Requests\",\"detail\":\"…\",\"errorCode\":\"RATE_LIMITED\"}` | Превышен лимит. |\n\n`503` означает, что внутренний сервис не отвечает: повторите запрос. `401`/`403` может появиться и тогда, когда недоступен сервис, проверяющий ключ или модуль, хотя с вашей стороны всё в порядке.\n\n## Соглашения\n\n- JSON с именами полей в **camelCase**. Поля без значения приходят как `null`, а не отсутствуют — кроме тех, про которые в описании сказано, что они отсутствуют (остатки `reserved` / `available`, новые поля вебхуков).\n- Календарные даты имеют формат `yyyy-MM-dd`; моменты времени — ISO 8601 UTC, с `Z`.\n- Суммы — числа JSON, в леях, если не указано иное.\n- **НДС:** `price` в каталоге — цена на кассе, **с НДС**. В заказе `lines[].unitPrice` считается **без НДС**, и НДС начисляется сверху. Не копируйте цену из каталога прямо в заказ: передавайте `price / (1 + vatRate/100)` с 4 знаками после запятой или не передавайте цену вовсе — PosFix возьмёт её с кассы.\n- Версия указана в пути (`/api/public/v1/`). В v1 добавляются только новые поля и события, существующие не меняются: **игнорируйте незнакомые поля**.\n- Параметр-список повторяется (`?categoryCode=A&categoryCode=B`); логические значения пишутся как `true` / `false`.\n\n## Пагинация\n\n- **С курсором** — товары, клиенты, участники лояльности и их история: вы задаёте `limit`, получаете `nextCursor`; следующую страницу запрашиваете с `cursor=<nextCursor>`, пока не придёт `null`.\n- **По страницам** — заказы, Z-отчёты, чеки: `page` (с 1) и `pageSize`; в ответе есть общее количество.\n\n## Webhook: как вы получаете события\n\nPosFix отправляет JSON-запрос `POST` на ваш адрес, когда у продавца что-то происходит. Вы подписываетесь через «Создать вебхук» и выбираете события.\n\n| Событие | Когда | `data` |\n|---|---|---|\n| `product.created` | Новый товар в номенклатуре. | `posfixId`, `code`, `name`, `description`, `barcode`, `price` (цена по умолчанию из номенклатуры, а не цена на кассе), `currency`, `vatRate`, `isService` |\n| `product.updated` | Товар изменён. | `posfixId`, `code`, `name`, `description`, `currency`, `vatRate`, `isActive` (без цены и штрихкода), `occurredAt` (момент изменения); `nameTranslations`, `descriptionTranslations` — переводы на активные языки, ключ = код языка, без основного языка (он в `name` / `description`); непереведённый язык отсутствует |\n| `inventory.updated` | Изменился остаток или резерв товара на складе; может прийти с неизменным `quantity`. `available` отсутствует у блюд с рецептом и у позиций-услуг; позиция-услуга без рецепта не отправляет `inventory.updated`. | `posfixId`, `warehouseId`, `quantity` (товар на складе), `value` и `averageCost` (стоимость и средняя себестоимость позиции на момент `changedAt`; пересчёт себестоимости тоже отправляет `inventory.updated`, с неизменным `quantity`), `changedAt`, `reserved` (обещано по подтверждённым заказам), `available` (`quantity − reserved`, что ещё можно продать; отрицательно, когда зарезервировано больше остатка) |\n| `order.created` | Новый заказ (в том числе из панели или с кассы). | `id`, `number`, `status`, `changeType`, `total`, `paidSum`, `currency`, `occurredAt` (момент изменения); для заказа, пришедшего через API, также `externalOrderId`, `externalNumber`, `externalSourceChannel`; после подготовки `readyForPickupAt`, после передачи перевозчику `shippedAt` и `trackingNumber` |\n| `order.confirmed` | Продавец подтвердил заказ. | то же |\n| `order.ready_for_pickup` | Заказ на самовывоз готов: покупатель может прийти за ним. Статус остаётся `Confirmed`. | то же; `readyForPickupAt` = момент подготовки |\n| `order.shipped` | Товар передан перевозчику (Nova Post или другой курьер); приходит снова, если номер отправления исправлен. Статус остаётся `Confirmed` до доставки. Если передача отменяется, приходит `order.confirmed` (или `order.fulfilling`) без `shippedAt`. | то же; `shippedAt` = момент передачи, `trackingNumber` = номер отправления (TTN, AWB), если он есть |\n| `order.preparation_started` | Кто-то взял заказ в работу («Iau în lucru»): товар готовится. Статус остаётся `Confirmed`. | то же |\n| `order.fulfilling` | Доставлен частично. | то же |\n| `order.fulfilled` | Доставлен полностью. | то же |\n| `order.closed` | Закрыт вручную. | то же |\n| `order.cancelled` | Отменён. | то же, плюс `reason`, если его отменила система: `payment_deadline` = не оплачен совсем до срока оплаты; `stock_refused` = заказ из API отклонён при получении из-за нехватки остатка (его запрос получил `422`); `confirmation_failed` = подтверждение заказа не прошло по причине, отличной от остатка (из API или с кассы, например когда кассир отказывается); без `reason` = отменён продавцом |\n| `order.reopened` | Отменённый заказ возвращён в черновик. | то же |\n| `order.partially_paid` | Оплаченная сумма выросла, но не покрывает итог. | то же; `paidSum` в леях |\n| `order.paid` | Заказ полностью оплачен. | то же; `paidSum` в леях |\n| `docai.recognition.completed` | Накладная распознана (отправлена через API или загружена в Панели управления). | `id`, `status`, `source`, `externalRef`, `fileName`, `supplierName`, `supplierIdno`, `documentSeries`, `documentNumber`, `issueDate`, `total`, `currency`; полные данные — `GET /api/public/v1/docai/recognitions/{id}` |\n| `docai.recognition.failed` | Распознавание не удалось (нечитаемый файл, слишком длинная накладная, прерывание). | то же, плюс `errorCode` |\n| `docai.recognition.reviewed` | Человек проверил и исправил накладную в Панели управления. | то же; проверенные данные — по id |\n| `sale.created` | Новая продажа: чек, возврат на кассе, счёт-фактура, возврат от клиента или отчёт о розничных продажах. | `id`, `kind` (`receipt`, `refund`, `invoice`, `customer_return`, `retail_report`), `number`, `date`, `soldAt`, `channel` (`id`, `name`, `type`), `customer` (`kind`: `individual`/`company`, `partnerId`, `name`), `terminalId`, `netAmount`, `vatAmount`, `totalAmount`, `currency`, `lines[]` (`productId`, `productCode`, `name`, `quantity`, `netAmount`, `vatAmount`); суммы в леях, без НДС, возвраты с минусом |\n| `sale.updated` | Изменилось то, что видит человек в продаже: сумма, канал, клиент или строки. | то же, с новыми значениями |\n| `sale.deleted` | Продажа исключена из учёта (например, счёт-фактура аннулирована). | то же, с последними значениями |\n| `loyalty.member.enrolled` | Новый участник программы лояльности, зарегистрированный любым путём: через API, в панели, на кассе или на странице регистрации. | текущий участник, с полями из `GET /api/public/v1/loyalty/members/{cardNumber}`, без `wallet`; если участник тем временем удалён, только `cardNumber: null` |\n| `loyalty.balance.changed` | Изменился баланс участника: баллы, штампы, бонус в леях или уровень (начисление, списание, возврат, сгорание). | то же; `balance` и `bonus` — на момент доставки, а не на момент изменения |\n\nТело имеет один и тот же конверт для любого события:\n\n```json\n{ \"id\": \"7c1e9a52-3b4d-4f6e-8a90-1b2c3d4e5f60\", \"type\": \"order.paid\", \"createdAt\": \"2026-09-25T08:12:03.4567891Z\", \"version\": \"1\",\n  \"data\": { \"id\": \"3c9a1f52-7d3e-4b8a-a0c4-6e2b91f0d7a8\", \"number\": \"0000000042\", \"status\": \"Confirmed\", \"changeType\": \"paid\", \"total\": 745.8, \"paidSum\": 745.8, \"currency\": \"MDL\", \"occurredAt\": \"2026-09-25T08:12:02.9180344Z\" } }\n```\n\n- `id` — id доставки: одинаковый при каждой повторной попытке. **Устраняйте дубли по нему** — доставка может прийти дважды.\n- `createdAt` — момент постановки в очередь, а не момент события. Порядок поступления не гарантируется.\n- Доставки могут приходить не по порядку и повторяться часами (см. «Повторные попытки»): применяйте изменение, только если его момент — `changedAt` у остатков, `occurredAt` у товаров и заказов — новее того, что у вас уже есть.\n- Пересчёт себестоимости отправляет `inventory.updated` для каждой переоценённой позиции, а в каскаде одна и та же позиция может получить два события — промежуточное и затем итоговое состояние: для одной позиции верно событие с самым новым `changedAt`.\n- У заказов `paidSum` приходит в любом `order.*`, в леях, так, как PosFix хранит его в заказе на момент события (пересчитывается после каждой оплаты, поэтому может немного запаздывать): применяйте его, только если `occurredAt` новее, чем у последнего применённого события; актуальную сумму даёт `GET /api/public/v1/orders/{id}`.\n- Заказ, пришедший через API, содержит `externalOrderId` (ваш ключ), `externalNumber` и `externalSourceChannel`. **Сопоставляйте заказы по `id`**; `externalOrderId` — только для `id`, которого вы ещё не знаете. Заказ, отклонённый при получении, приходит парой `order.created` → `order.cancelled` (`reason` `stock_refused` или `confirmation_failed`) с одним и тем же `id`, хотя ваш запрос получил `422` без `id`. Если затем вы повторно отправите тот же `externalOrderId`, у нового заказа будет другой `id` (тот, что в ответе `200`/`201`): запоздавший `order.cancelled` отклонённой попытки с `id`, отличным от полученного, игнорируется — иначе вы отмените свой действующий заказ.\n- Отменённая оплата уменьшает оплаченную сумму без события: текущую сумму читайте из `GET /api/public/v1/orders/{id}`.\n- `reserved`, `available`, `nameTranslations`, `descriptionTranslations`, `reason` и поля `external*` **отсутствуют** в `data`, если у события их нет, вместо значения `null`: проверяйте, есть ли они, а не только равны ли `null`.\n\n### Заголовки\n\n| Заголовок | Значение |\n|---|---|\n| `X-Posfix-Signature` | Подпись, base64 |\n| `X-Posfix-Webhook-Id` | ID доставки (= `id` в теле) |\n| `X-Posfix-Timestamp` | Момент отправки, секунды Unix |\n| `X-Posfix-Event` | Тип события |\n\n### Проверка подписи\n\n```\nsignedString       = \"{X-Posfix-Webhook-Id}.{X-Posfix-Timestamp}.{corpul brut}\"\nX-Posfix-Signature = base64( HMAC_SHA256(signingSecret, signedString) )\n```\n\n- Ключ HMAC — это **весь** секрет вместе с префиксом `whsec_`, в виде байтов UTF-8; он не декодируется.\n- Подписывайте **сырое тело** именно в том виде, в каком оно пришло. JSON, разобранный и сериализованный заново, уже не даст ту же подпись.\n- Сравнивайте за постоянное время и отклоняйте доставки с `X-Posfix-Timestamp` старше ~5 минут.\n\n```js\nconst crypto = require(\"crypto\");\n// rawBody: Buffer, citit cu express.raw({ type: \"application/json\" })\nconst signed = Buffer.concat([Buffer.from(`${webhookId}.${timestamp}.`, \"utf8\"), rawBody]);\nconst expected = crypto.createHmac(\"sha256\", signingSecret).update(signed).digest(\"base64\");\nconst valid = expected.length === signature.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));\n```\n\n### Повторные попытки\n\n- Любой ответ **2xx** в течение 15 секунд означает «получено».\n- `5xx`, `408`, `429`, перенаправления и сетевые сбои повторяются через 2, 4, 8, 16, 32, 60, 60 минут. После 8 попыток (~3 часа) доставка прекращается.\n- Любой другой `4xx` останавливает доставку **сразу**, без повторных попыток. Так же и доменное имя, которое не разрешается или ведёт на частный адрес.\n- Перенаправления не выполняются.\n- Пока модуль «API интеграции» неактивен, события не отправляются и не сохраняются.\n\n## Переменные коллекции\n\n- `baseUrl` — `https://api.posfix.md` (рабочая среда) или `https://dev.posfix.md` (тестирование).\n- `apiKey` — ключ, полученный от PosFix.\n- `today`, `monthStart` — вычисляются перед каждым запросом.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "_anchorHeadings": [
      "Cheia",
      "Scope-uri",
      "Limite",
      "Erori",
      "Convenții",
      "Paginare",
      "Webhook-uri: cum primești evenimentele",
      "Variabilele colecției"
    ]
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "X-Posfix-Api-Key",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{apiKey}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "const pad = (n) => String(n).padStart(2, '0');",
          "const fmt = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;",
          "const now = new Date();",
          "pm.collectionVariables.set('today', fmt(now));",
          "pm.collectionVariables.set('monthStart', fmt(now).slice(0, 8) + '01');"
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.posfix.md",
      "description": "Рабочая среда. Для тестов: https://dev.posfix.md"
    },
    {
      "key": "apiKey",
      "value": "",
      "description": "Ключ rk_…, полученный от PosFix вместе с модулем «API интеграции»"
    },
    {
      "key": "today",
      "value": "",
      "description": "Вычисляется автоматически (YYYY-MM-DD)"
    },
    {
      "key": "monthStart",
      "value": "",
      "description": "Вычисляется автоматически (первый день месяца)"
    }
  ],
  "item": [
    {
      "name": "0. Проверка",
      "description": "Начните отсюда: убедитесь, что ключ работает, и узнайте, какие у него scope.",
      "item": [
        {
          "name": "Ping",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/ping",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "ping"
              ]
            },
            "description": "Проверяет ключ. Возвращает продавца, которому принадлежит ключ, и его scope. Начните отсюда.\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `organizationId` | string (uuid) | Продавец, которому принадлежит ключ. |\n| `scopes` | string[] | Scope ключа. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 401 | — | Ключ отсутствует или недействителен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n\n### Обратите внимание\n\n- Ping не требует ни scope, ни модуля «API интеграции». Если ping работает, а другой запрос даёт `403`, у ключа нет scope этого запроса или модуль не активен."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/ping",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "ping"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"organizationId\": \"3f6c2a1e-8b4d-4c7a-9e21-5d0f7b8a9c12\",\n  \"scopes\": [\n    \"products:read\",\n    \"inventory:read\",\n    \"orders:write\",\n    \"sales:read\"\n  ]\n}"
            }
          ],
          "_anchorName": "Ping"
        }
      ],
      "_anchorName": "0. Verificare"
    },
    {
      "name": "1. Каталог и остатки",
      "description": "Торговый ассортимент, карточки товаров, остатки, категории и атрибуты.",
      "item": [
        {
          "name": "Список товаров",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/products?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "products"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50",
                  "description": "1–200, по умолчанию 50."
                },
                {
                  "key": "search",
                  "value": "apa",
                  "description": "Слова из кода или названия.",
                  "disabled": true
                },
                {
                  "key": "categoryCode",
                  "value": "BAUTURI",
                  "description": "Код категории; можно повторять.",
                  "disabled": true
                },
                {
                  "key": "categoryId",
                  "value": "",
                  "description": "Id категории; можно повторять.",
                  "disabled": true
                },
                {
                  "key": "includeSubcategories",
                  "value": "true",
                  "description": "По умолчанию true.",
                  "disabled": true
                },
                {
                  "key": "attribute",
                  "value": "tara:Moldova",
                  "description": "код:значение; можно повторять.",
                  "disabled": true
                },
                {
                  "key": "includeInactive",
                  "value": "false",
                  "description": "По умолчанию false.",
                  "disabled": true
                },
                {
                  "key": "includeUnlisted",
                  "value": "false",
                  "description": "По умолчанию false.",
                  "disabled": true
                },
                {
                  "key": "warehouseId",
                  "value": "",
                  "description": "Остаток одного склада; по умолчанию все.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "nextCursor из предыдущей страницы.",
                  "disabled": true
                }
              ]
            },
            "description": "**Необходимый scope:** `products:read`\n\nТорговый ассортимент продавца, постранично: товары, у которых есть карточка на кассе, с ценой, категорией и остатком.\n\n### Параметры запроса\n\n| Параметр | Тип | По умолчанию | Описание |\n|---|---|---|---|\n| `limit` | integer | 50 | Значение больше 200 уменьшается до 200. |\n| `search` | string | — | Слова из **кода или названия**, в любом порядке: каждое слово должно встречаться. Без учёта регистра и диакритики. По штрихкоду не ищет. |\n| `categoryId`, `categoryCode` | uuid / string | — | Категории кассы; можно повторять, объединяются через ИЛИ: `?categoryCode=APA&categoryCode=SUCURI`. |\n| `includeSubcategories` | boolean | `true` | Включает подкатегории запрошенных категорий. |\n| `attribute` | string | — | `cod:valoare`, можно повторять. Одинаковый код = ИЛИ, разные коды = И: `?attribute=brand:Samyang&attribute=tara:Moldova`. Код — тот, что в разделе «Атрибуты», строчными буквами. |\n| `includeInactive` | boolean | `false` | Включает неактивные товары и карточки. |\n| `includeUnlisted` | boolean | `false` | Включает позиции **без** карточки на кассе (материалы, внутренние услуги). Игнорируется, когда вы фильтруете по категории. |\n| `warehouseId` | string (uuid) | все | Остаток (`onHand`, `onHandByWarehouse`, `inStock`) берётся только с этого склада. Id — из «Склады». Список товаров не сужается. |\n| `cursor` | string | — | Значение `nextCursor` с предыдущей страницы. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[]` | array | Товары; поля — в таблице ниже. `composition` и `contentBlocks` в списке приходят пустыми. |\n| `nextCursor` | string? | Курсор следующей страницы; `null` на последней. |\n\n### Товар\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `posfixId` | string (uuid) | Id товара в PosFix. По нему вы запрашиваете цены. |\n| `code` | string | Код товара (обычно 10 цифр). По нему вы заказываете товар и читаете остатки. |\n| `name` | string | Название из Номенклатуры. |\n| `names` | object | Название на разных языках; правило карт языков — в примечаниях к запросу. |\n| `description` | string? | Описание на румынском. |\n| `barcode` | string | Штрихкод; `\"\"`, если его нет. |\n| `price`, `currency` | number, string | Цена продажи на кассе (с НДС); если карточки на кассе нет — цена по умолчанию из номенклатуры. |\n| `vatRate` | number? | Ставка НДС, %. `null` = ставка не задана, это не то же самое, что `0`. |\n| `unit` | string | Базовая единица (`buc`, `kg`). |\n| `isActive` | boolean | Товар активен в Номенклатуре. |\n| `category`, `categoryPath[]` | object?, array | Категория кассы (`id`, `name`, `code`, `names`) и её путь от корня. |\n| `imageUrl` | string? | **Относительный** адрес изображения; вы получаете его тем же ключом (запрос «Изображение товара»). |\n| `attributes[]` | array | `code` и `value` — значения атрибутов (бренд, страна…); `valueNames` = значение на разных языках. |\n| `onHand[]` | array | Общий остаток, один элемент с `warehouseId: null`: `quantity`, `reserved`, `available` (их смысл — в примечаниях к запросу). С `?warehouseId=` в списке — остаток этого склада, с его id. |\n| `onHandByWarehouse[]` | array | Остаток на каждом складе, где есть товар: `warehouseId`, `code`, `name`, `quantity`, `reserved`, `available`. Отсутствующий склад — 0. |\n| `inStock` | boolean | Общий физический остаток больше нуля, даже если он весь зарезервирован. Можно ли ещё купить, показывает `onHand[].available`. |\n| `descriptions` | object | Описание на разных языках; правило карт языков — в примечаниях к запросу. |\n| `composition[]` | array | Состав: `ingredient`, `percent`, `allergen`. Только в запросе «Товар по коду». |\n| `contentBlocks[]` | array | Блоки контента (очищенный HTML): `labels` и `contents` по языкам, плюс старые поля `labelRo`… `contentEn`. Только в «Товар по коду». |\n| `sku` | string? | Внешний код товара, иначе SKU на кассе. |\n| `shelfLifeDays` | integer? | Срок годности, в днях. |\n| `packaging[]` | array | `unit` и `factor`: «1 `unit` = `factor` базовых единиц». |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | — | Фильтр `attribute` не в форме `cod:valoare`, значение неверного типа (`tree=1`, некорректный GUID). |\n| 400 | `VALIDATION` | «cursor must be a valid product id.» |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Карты языков (`names`, `descriptions`, `valueNames`, `labels`, `contents`) имеют ключ = код языка: `{ \"ro\": …, \"ru\": … }`. Языки берутся из списка платформы и могут добавиться в любой момент — читайте полученные ключи, не рассчитывайте ровно на `ro`/`ru`/`en`. Язык без перевода отсутствует в карте: используйте базовый текст (`name`, `description`).\n- У остатка три числа: `quantity` — товар на складе; `reserved` — часть, обещанная по подтверждённым заказам, которые ещё не отгружены; `available` = `quantity − reserved` — то, что ещё можно продать. В магазине показывайте `available`.\n- `available` отсутствует, когда нет доступного остатка для публикации (блюда с рецептом, позиции-услуги) или когда он неизвестен; отсутствующий `available` не означает, что товар закончился. Не вычисляйте его из `quantity` и не заменяйте на `0`. У товара без остатка — а с `?warehouseId=` без остатка на запрошенном складе — `quantity`, `reserved` и `available` равны 0. Отсутствующий `reserved` означает, что значение неизвестно.\n- Порядок — по id, а не по имени. Проходите страницы до `nextCursor: null`.\n- Список кешируется на **45 секунд**, поэтому остатки в нём могут отставать. Для актуальных остатков используйте «Остаток товара».\n- Товар без карточки на кассе появляется только с `includeUnlisted=true`.\n- Списки передаются повторением параметра (`?a=x&a=y`), а не через запятую. Логическое значение пишется `true`/`false`, а не `1`."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/products?limit=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "products"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    },
                    {
                      "key": "search",
                      "value": "apa",
                      "description": "Cuvinte din cod sau denumire.",
                      "disabled": true
                    },
                    {
                      "key": "categoryCode",
                      "value": "BAUTURI",
                      "description": "Codul categoriei; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "categoryId",
                      "value": "",
                      "description": "Id-ul categoriei; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "includeSubcategories",
                      "value": "true",
                      "description": "Implicit true.",
                      "disabled": true
                    },
                    {
                      "key": "attribute",
                      "value": "tara:Moldova",
                      "description": "cod:valoare; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "includeInactive",
                      "value": "false",
                      "description": "Implicit false.",
                      "disabled": true
                    },
                    {
                      "key": "includeUnlisted",
                      "value": "false",
                      "description": "Implicit false.",
                      "disabled": true
                    },
                    {
                      "key": "warehouseId",
                      "value": "",
                      "description": "Stocul unui singur depozit; implicit toate.",
                      "disabled": true
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "nextCursor din pagina precedentă.",
                      "disabled": true
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"posfixId\": \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n      \"code\": \"0000000123\",\n      \"name\": \"Apă minerală Borjomi 0,5 L\",\n      \"description\": \"Apă minerală naturală carbogazoasă.\",\n      \"barcode\": \"4860019001339\",\n      \"price\": 18.5,\n      \"currency\": \"MDL\",\n      \"vatRate\": 20,\n      \"unit\": \"buc\",\n      \"isActive\": true,\n      \"category\": {\n        \"id\": \"a1b2c3d4-0002-4a5b-8c6d-7e8f9a0b1c02\",\n        \"name\": \"Apă minerală\",\n        \"code\": \"APA\",\n        \"names\": {\n          \"ro\": \"Apă minerală\",\n          \"ru\": \"Минеральная вода\"\n        }\n      },\n      \"imageUrl\": \"/api/public/v1/products/0000000123/image\",\n      \"attributes\": [\n        {\n          \"code\": \"tara\",\n          \"value\": \"Georgia\",\n          \"valueNames\": {\n            \"ro\": \"Georgia\",\n            \"ru\": \"Грузия\"\n          }\n        },\n        {\n          \"code\": \"brand\",\n          \"value\": \"Borjomi\",\n          \"valueNames\": {\n            \"ro\": \"Borjomi\"\n          }\n        }\n      ],\n      \"composition\": [],\n      \"onHand\": [\n        {\n          \"warehouseId\": null,\n          \"quantity\": 144,\n          \"reserved\": 6,\n          \"available\": 138\n        }\n      ],\n      \"onHandByWarehouse\": [\n        {\n          \"warehouseId\": \"e4d3c2b1-0001-4f5e-8d7c-6b5a49382701\",\n          \"code\": \"DEP-1\",\n          \"name\": \"Depozit central\",\n          \"quantity\": 120,\n          \"reserved\": 6,\n          \"available\": 114\n        },\n        {\n          \"warehouseId\": \"e4d3c2b1-0002-4f5e-8d7c-6b5a49382702\",\n          \"code\": \"MAG-2\",\n          \"name\": \"Magazin Botanica\",\n          \"quantity\": 24,\n          \"reserved\": 0,\n          \"available\": 24\n        }\n      ],\n      \"names\": {\n        \"ro\": \"Apă minerală Borjomi 0,5 L\",\n        \"ru\": \"Минеральная вода Borjomi 0,5 л\"\n      },\n      \"descriptions\": {\n        \"ro\": \"Apă minerală naturală carbogazoasă.\",\n        \"ru\": \"Натуральная газированная минеральная вода.\"\n      },\n      \"contentBlocks\": [],\n      \"categoryPath\": [\n        {\n          \"id\": \"a1b2c3d4-0001-4a5b-8c6d-7e8f9a0b1c01\",\n          \"name\": \"Băuturi\",\n          \"code\": \"BAUTURI\",\n          \"names\": {\n            \"ro\": \"Băuturi\",\n            \"ru\": \"Напитки\"\n          }\n        },\n        {\n          \"id\": \"a1b2c3d4-0002-4a5b-8c6d-7e8f9a0b1c02\",\n          \"name\": \"Apă minerală\",\n          \"code\": \"APA\",\n          \"names\": {\n            \"ro\": \"Apă minerală\",\n            \"ru\": \"Минеральная вода\"\n          }\n        }\n      ],\n      \"sku\": \"BRJ-05\",\n      \"shelfLifeDays\": 730,\n      \"packaging\": [\n        {\n          \"unit\": \"bax\",\n          \"factor\": 12\n        }\n      ],\n      \"inStock\": true\n    }\n  ],\n  \"nextCursor\": \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\"\n}"
            },
            {
              "name": "400 — неверный фильтр по атрибуту",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/products?limit=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "products"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    },
                    {
                      "key": "search",
                      "value": "apa",
                      "description": "Cuvinte din cod sau denumire.",
                      "disabled": true
                    },
                    {
                      "key": "categoryCode",
                      "value": "BAUTURI",
                      "description": "Codul categoriei; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "categoryId",
                      "value": "",
                      "description": "Id-ul categoriei; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "includeSubcategories",
                      "value": "true",
                      "description": "Implicit true.",
                      "disabled": true
                    },
                    {
                      "key": "attribute",
                      "value": "tara:Moldova",
                      "description": "cod:valoare; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "includeInactive",
                      "value": "false",
                      "description": "Implicit false.",
                      "disabled": true
                    },
                    {
                      "key": "includeUnlisted",
                      "value": "false",
                      "description": "Implicit false.",
                      "disabled": true
                    },
                    {
                      "key": "warehouseId",
                      "value": "",
                      "description": "Stocul unui singur depozit; implicit toate.",
                      "disabled": true
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "nextCursor din pagina precedentă.",
                      "disabled": true
                    }
                  ]
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/problem+json"
                }
              ],
              "body": "{\n  \"statusCode\": 400,\n  \"message\": \"One or more errors occurred!\",\n  \"errors\": {\n    \"attribute\": [\n      \"Filtrul de atribut „brand” trebuie să aibă forma cod:valoare (de ex. brand:Samyang).\"\n    ]\n  },\n  \"errorCodes\": {\n    \"attribute\": [\n      {\n        \"code\": \"INTEGRATIONS_ATTRIBUTE_FILTER_FORMAT\",\n        \"params\": {\n          \"filter\": \"brand\"\n        }\n      }\n    ]\n  }\n}"
            }
          ],
          "_anchorName": "Listă produse"
        },
        {
          "name": "Товар по коду",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/products/:code",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "products",
                ":code"
              ],
              "variable": [
                {
                  "key": "code",
                  "value": "0000000123",
                  "description": "Код товара"
                }
              ]
            },
            "description": "**Необходимый scope:** `products:read`\n\nПолная карточка товара: всё, что даёт список, плюс состав и блоки контента по языкам. Находит и неактивные товары, и товары без карточки на кассе.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|---|---|---|\n| `code` | string | Точный код товара (с ведущими нулями). |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `posfixId` | string (uuid) | Id товара в PosFix. По нему вы запрашиваете цены. |\n| `code` | string | Код товара (обычно 10 цифр). По нему вы заказываете товар и читаете остатки. |\n| `name` | string | Название из Номенклатуры. |\n| `names` | object | Название на разных языках; правило карт языков — в примечаниях к запросу. |\n| `description` | string? | Описание на румынском. |\n| `barcode` | string | Штрихкод; `\"\"`, если его нет. |\n| `price`, `currency` | number, string | Цена продажи на кассе (с НДС); если карточки на кассе нет — цена по умолчанию из номенклатуры. |\n| `vatRate` | number? | Ставка НДС, %. `null` = ставка не задана, это не то же самое, что `0`. |\n| `unit` | string | Базовая единица (`buc`, `kg`). |\n| `isActive` | boolean | Товар активен в Номенклатуре. |\n| `category`, `categoryPath[]` | object?, array | Категория кассы (`id`, `name`, `code`, `names`) и её путь от корня. |\n| `imageUrl` | string? | **Относительный** адрес изображения; вы получаете его тем же ключом (запрос «Изображение товара»). |\n| `attributes[]` | array | `code` и `value` — значения атрибутов (бренд, страна…); `valueNames` = значение на разных языках. |\n| `onHand[]` | array | Общий остаток, один элемент с `warehouseId: null`: `quantity`, `reserved`, `available` (их смысл — в примечаниях к запросу). С `?warehouseId=` в списке — остаток этого склада, с его id. |\n| `onHandByWarehouse[]` | array | Остаток на каждом складе, где есть товар: `warehouseId`, `code`, `name`, `quantity`, `reserved`, `available`. Отсутствующий склад — 0. |\n| `inStock` | boolean | Общий физический остаток больше нуля, даже если он весь зарезервирован. Можно ли ещё купить, показывает `onHand[].available`. |\n| `descriptions` | object | Описание на разных языках; правило карт языков — в примечаниях к запросу. |\n| `composition[]` | array | Состав: `ingredient`, `percent`, `allergen`. Только в запросе «Товар по коду». |\n| `contentBlocks[]` | array | Блоки контента (очищенный HTML): `labels` и `contents` по языкам, плюс старые поля `labelRo`… `contentEn`. Только в «Товар по коду». |\n| `sku` | string? | Внешний код товара, иначе SKU на кассе. |\n| `shelfLifeDays` | integer? | Срок годности, в днях. |\n| `packaging[]` | array | `unit` и `factor`: «1 `unit` = `factor` базовых единиц». |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 404 | — | Код не существует. Пустое тело. |\n| 400 | `INTEGRATIONS_PRODUCT_CODE_REQUIRED` | „Codul produsului (productCode) este obligatoriu.” (Код товара (productCode) обязателен.) |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Карты языков (`names`, `descriptions`, `valueNames`, `labels`, `contents`) имеют ключ = код языка: `{ \"ro\": …, \"ru\": … }`. Языки берутся из списка платформы и могут добавиться в любой момент — читайте полученные ключи, не рассчитывайте ровно на `ro`/`ru`/`en`. Язык без перевода отсутствует в карте: используйте базовый текст (`name`, `description`).\n- У остатка три числа: `quantity` — товар на складе; `reserved` — часть, обещанная по подтверждённым заказам, которые ещё не отгружены; `available` = `quantity − reserved` — то, что ещё можно продать. В магазине показывайте `available`.\n- `available` отсутствует, когда нет доступного остатка для публикации (блюда с рецептом, позиции-услуги) или когда он неизвестен; отсутствующий `available` не означает, что товар закончился. Не вычисляйте его из `quantity` и не заменяйте на `0`. У товара без остатка — а с `?warehouseId=` без остатка на запрошенном складе — `quantity`, `reserved` и `available` равны 0. Отсутствующий `reserved` означает, что значение неизвестно.\n- Блоки контента приходят только для активных товаров и только для активных определений (см. «Блоки контента»).\n- Старые поля блока (`labelRu`, `contentEn`…) остаются для существующих интеграций и никогда не получат язык, добавленный позже; используйте `labels` и `contents`.\n- Ответ кешируется на 45 секунд."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/products/:code",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "products",
                    ":code"
                  ],
                  "variable": [
                    {
                      "key": "code",
                      "value": "0000000123",
                      "description": "Codul produsului"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"posfixId\": \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n  \"code\": \"0000000123\",\n  \"name\": \"Apă minerală Borjomi 0,5 L\",\n  \"description\": \"Apă minerală naturală carbogazoasă.\",\n  \"barcode\": \"4860019001339\",\n  \"price\": 18.5,\n  \"currency\": \"MDL\",\n  \"vatRate\": 20,\n  \"unit\": \"buc\",\n  \"isActive\": true,\n  \"category\": {\n    \"id\": \"a1b2c3d4-0002-4a5b-8c6d-7e8f9a0b1c02\",\n    \"name\": \"Apă minerală\",\n    \"code\": \"APA\",\n    \"names\": {\n      \"ro\": \"Apă minerală\",\n      \"ru\": \"Минеральная вода\"\n    }\n  },\n  \"imageUrl\": \"/api/public/v1/products/0000000123/image\",\n  \"attributes\": [\n    {\n      \"code\": \"tara\",\n      \"value\": \"Georgia\",\n      \"valueNames\": {\n        \"ro\": \"Georgia\",\n        \"ru\": \"Грузия\"\n      }\n    },\n    {\n      \"code\": \"brand\",\n      \"value\": \"Borjomi\",\n      \"valueNames\": {\n        \"ro\": \"Borjomi\"\n      }\n    }\n  ],\n  \"composition\": [\n    {\n      \"ingredient\": \"Apă minerală naturală\",\n      \"percent\": null,\n      \"allergen\": false\n    },\n    {\n      \"ingredient\": \"Dioxid de carbon\",\n      \"percent\": null,\n      \"allergen\": false\n    }\n  ],\n  \"onHand\": [\n    {\n      \"warehouseId\": null,\n      \"quantity\": 144,\n      \"reserved\": 6,\n      \"available\": 138\n    }\n  ],\n  \"onHandByWarehouse\": [\n    {\n      \"warehouseId\": \"e4d3c2b1-0001-4f5e-8d7c-6b5a49382701\",\n      \"code\": \"DEP-1\",\n      \"name\": \"Depozit central\",\n      \"quantity\": 120,\n      \"reserved\": 6,\n      \"available\": 114\n    },\n    {\n      \"warehouseId\": \"e4d3c2b1-0002-4f5e-8d7c-6b5a49382702\",\n      \"code\": \"MAG-2\",\n      \"name\": \"Magazin Botanica\",\n      \"quantity\": 24,\n      \"reserved\": 0,\n      \"available\": 24\n    }\n  ],\n  \"names\": {\n    \"ro\": \"Apă minerală Borjomi 0,5 L\",\n    \"ru\": \"Минеральная вода Borjomi 0,5 л\"\n  },\n  \"descriptions\": {\n    \"ro\": \"Apă minerală naturală carbogazoasă.\",\n    \"ru\": \"Натуральная газированная минеральная вода.\"\n  },\n  \"contentBlocks\": [\n    {\n      \"key\": \"pastrare\",\n      \"labelRo\": \"Condiții de păstrare\",\n      \"labelRu\": \"Условия хранения\",\n      \"labelEn\": null,\n      \"contentRo\": \"<p>A se păstra la loc uscat, ferit de soare.</p>\",\n      \"contentRu\": null,\n      \"contentEn\": null,\n      \"sortOrder\": 1,\n      \"labels\": {\n        \"ro\": \"Condiții de păstrare\",\n        \"ru\": \"Условия хранения\"\n      },\n      \"contents\": {\n        \"ro\": \"<p>A se păstra la loc uscat, ferit de soare.</p>\"\n      }\n    }\n  ],\n  \"categoryPath\": [\n    {\n      \"id\": \"a1b2c3d4-0001-4a5b-8c6d-7e8f9a0b1c01\",\n      \"name\": \"Băuturi\",\n      \"code\": \"BAUTURI\",\n      \"names\": {\n        \"ro\": \"Băuturi\",\n        \"ru\": \"Напитки\"\n      }\n    },\n    {\n      \"id\": \"a1b2c3d4-0002-4a5b-8c6d-7e8f9a0b1c02\",\n      \"name\": \"Apă minerală\",\n      \"code\": \"APA\",\n      \"names\": {\n        \"ro\": \"Apă minerală\",\n        \"ru\": \"Минеральная вода\"\n      }\n    }\n  ],\n  \"sku\": \"BRJ-05\",\n  \"shelfLifeDays\": 730,\n  \"packaging\": [\n    {\n      \"unit\": \"bax\",\n      \"factor\": 12\n    }\n  ],\n  \"inStock\": true\n}"
            }
          ],
          "_anchorName": "Produs după cod"
        },
        {
          "name": "Изображение товара",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/products/:code/image",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "products",
                ":code",
                "image"
              ],
              "variable": [
                {
                  "key": "code",
                  "value": "0000000123",
                  "description": "Код товара"
                }
              ]
            },
            "description": "**Необходимый scope:** `products:read`\n\nИзображение товара из номенклатуры в виде файла. Обычно миниатюра JPEG не более 320×320.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|---|---|---|\n| `code` | string | Код товара. |\n\n### Ответ\n\n**200** с байтами изображения. `Content-Type` — тип изображения (`image/jpeg` для миниатюры); `Cache-Control: private, max-age=300`.\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 404 | — | Товар не существует, у него нет изображения или изображение недоступно. Пустое тело. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Запрос требует ключ, поэтому `imageUrl` не будет работать напрямую в `<img src>` в браузере вашего клиента. Скачайте изображение на свой сервер и отдавайте его сами."
          },
          "response": [
            {
              "name": "404 — у товара нет изображения",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/products/:code/image",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "products",
                    ":code",
                    "image"
                  ],
                  "variable": [
                    {
                      "key": "code",
                      "value": "0000000123",
                      "description": "Codul produsului"
                    }
                  ]
                }
              },
              "status": "Not Found",
              "code": 404,
              "_postman_previewlanguage": "text",
              "header": [],
              "body": ""
            }
          ],
          "_anchorName": "Imaginea produsului"
        },
        {
          "name": "Остаток товара",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/inventory?productCode=0000000123",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "inventory"
              ],
              "query": [
                {
                  "key": "productCode",
                  "value": "0000000123",
                  "description": "Код товара, обязательно."
                },
                {
                  "key": "warehouseId",
                  "value": "",
                  "description": "Один склад; без него — общий итог.",
                  "disabled": true
                }
              ]
            },
            "description": "**Необходимый scope:** `inventory:read`\n\nТекущий остаток товара на одном складе или на всех. Запрос идёт в обход кеша.\n\n### Параметры запроса\n\n| Параметр | Тип | По умолчанию | Описание |\n|---|---|---|---|\n| `productCode` | string | — | Обязательный. Точный код товара. |\n| `warehouseId` | string (uuid) | все | Склад. Неизвестный склад даёт количество 0, а не ошибку. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `posfixId`, `code`, `name` | string | Товар. |\n| `onHand[]` | array | Один элемент: `warehouseId` (запрошенный или `null` = всего), `quantity`, `reserved` и `available`, в базовой единице. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `INTEGRATIONS_PRODUCT_CODE_REQUIRED` | „Codul produsului (productCode) este obligatoriu.” (Код товара (productCode) обязателен.) |\n| 404 | — | Код не существует. Пустое тело. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- У остатка три числа: `quantity` — товар на складе; `reserved` — часть, обещанная по подтверждённым заказам, которые ещё не отгружены; `available` = `quantity − reserved` — то, что ещё можно продать. В магазине показывайте `available`.\n- `available` отсутствует, когда нет доступного остатка для публикации (блюда с рецептом, позиции-услуги) или когда он неизвестен; отсутствующий `available` не означает, что товар закончился. Не вычисляйте его из `quantity` и не заменяйте на `0`. У товара без остатка — а с `?warehouseId=` без остатка на запрошенном складе — `quantity`, `reserved` и `available` равны 0. Отсутствующий `reserved` означает, что значение неизвестно.\n- Количество может быть **отрицательным**: продан товар, который ещё не был оприходован.\n- `available` бывает отрицательным и тогда, когда зарезервировано больше, чем есть на складе."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/inventory?productCode=0000000123",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "inventory"
                  ],
                  "query": [
                    {
                      "key": "productCode",
                      "value": "0000000123",
                      "description": "Codul produsului, obligatoriu."
                    },
                    {
                      "key": "warehouseId",
                      "value": "",
                      "description": "Un depozit; fără el, totalul.",
                      "disabled": true
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"posfixId\": \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n  \"code\": \"0000000123\",\n  \"name\": \"Apă minerală Borjomi 0,5 L\",\n  \"onHand\": [\n    {\n      \"warehouseId\": null,\n      \"quantity\": 144,\n      \"reserved\": 6,\n      \"available\": 138\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Stoc produs"
        },
        {
          "name": "Склады",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/warehouses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "warehouses"
              ]
            },
            "description": "**Необходимый scope:** `inventory:read`\n\nАктивные склады продавца, основной — первым. Id передаётся в `warehouseId` в «Список товаров» и «Остаток товара».\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[].id` | string (uuid) | Id склада. |\n| `items[].code` | string | Код склада. |\n| `items[].name` | string | Название склада. |\n| `items[].isDefault` | boolean | Основной склад продавца. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Список кешируется на 45 секунд.\n- Отключённого склада нет в списке, но его остаток остаётся в `onHand` и `onHandByWarehouse`, пока товар не перемещён."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/warehouses",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "warehouses"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"e4d3c2b1-0001-4f5e-8d7c-6b5a49382701\",\n      \"code\": \"DEP-1\",\n      \"name\": \"Depozit central\",\n      \"isDefault\": true\n    },\n    {\n      \"id\": \"e4d3c2b1-0002-4f5e-8d7c-6b5a49382702\",\n      \"code\": \"MAG-2\",\n      \"name\": \"Magazin Botanica\",\n      \"isDefault\": false\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Depozite"
        },
        {
          "name": "Атрибуты",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/attributes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "attributes"
              ]
            },
            "description": "**Необходимый scope:** `products:read`\n\nАтрибуты товаров (марка, страна, вкус…) с используемыми значениями. Стройте из них фильтры магазина; код передаётся в `attribute=cod:valoare` в запросе «Список товаров».\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[].code` | string | Код атрибута, строчными буквами. |\n| `items[].name` | string | Название, заданное продавцом. |\n| `items[].names` | object | Название на разных языках (карта языков, как у товара). |\n| `items[].dataType` | string | `string`, `number`, `boolean`, `date` или `enum`. |\n| `items[].values[]` | string[] | Предопределённые значения и значения, используемые в товарах, без дублей. |\n| `items[].valueNames[]` | array | Каждое значение из `values`, в том же порядке: `value` и `names` (значение на разных языках). |\n| `items[].sortOrder` | integer | Порядок отображения. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Значение, используемое только у неактивных товаров, есть в списке, но фильтр по нему может вернуть ноль товаров.\n- Фильтр `attribute=cod:valoare` принимает значение из `values`, никогда не перевод из `valueNames`."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/attributes",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "attributes"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"code\": \"brand\",\n      \"name\": \"Marcă\",\n      \"dataType\": \"string\",\n      \"values\": [\n        \"Borjomi\",\n        \"Franzeluța\",\n        \"Nongshim\"\n      ],\n      \"sortOrder\": 1\n    },\n    {\n      \"code\": \"tara\",\n      \"name\": \"Țara de origine\",\n      \"dataType\": \"enum\",\n      \"values\": [\n        \"Georgia\",\n        \"Moldova\"\n      ],\n      \"sortOrder\": 2,\n      \"names\": {\n        \"ro\": \"Țara de origine\",\n        \"ru\": \"Страна происхождения\"\n      },\n      \"valueNames\": [\n        {\n          \"value\": \"Georgia\",\n          \"names\": {\n            \"ro\": \"Georgia\",\n            \"ru\": \"Грузия\"\n          }\n        },\n        {\n          \"value\": \"Moldova\",\n          \"names\": {\n            \"ro\": \"Moldova\",\n            \"ru\": \"Молдова\"\n          }\n        }\n      ]\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Atribute"
        },
        {
          "name": "Категории",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/categories?tree=true",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "categories"
              ],
              "query": [
                {
                  "key": "tree",
                  "value": "true",
                  "description": "true = дерево; по умолчанию плоский список."
                }
              ]
            },
            "description": "**Необходимый scope:** `products:read`\n\nАктивные категории кассы (по которым товары группируются при продаже). Бухгалтерские категории из номенклатуры здесь не отображаются.\n\n### Параметры запроса\n\n| Параметр | Тип | По умолчанию | Описание |\n|---|---|---|---|\n| `tree` | boolean | `false` | `true` = корневые категории с заполненным `children`; иначе все категории одним списком, с `children: null`. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[].id`, `name`, `code` | string | Категория; код может быть пустым. |\n| `items[].names` | object | Название на разных языках (карта языков, как у товара). |\n| `items[].parentId` | string (uuid)? | Родительская категория. |\n| `items[].sortOrder` | integer | Порядок отображения. |\n| `items[].children` | array? | Подкатегории, только при `tree=true`. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | — | `tree` имеет значение, отличное от `true`/`false`. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- В плоском списке `parentId` может указывать на неактивную категорию, которой нет в списке: считайте эту категорию корневой."
          },
          "response": [
            {
              "name": "200 — дерево",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/categories?tree=true",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "categories"
                  ],
                  "query": [
                    {
                      "key": "tree",
                      "value": "true",
                      "description": "true = arbore; implicit listă plată."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"a1b2c3d4-0001-4a5b-8c6d-7e8f9a0b1c01\",\n      \"name\": \"Băuturi\",\n      \"code\": \"BAUTURI\",\n      \"parentId\": null,\n      \"sortOrder\": 1,\n      \"names\": {\n        \"ro\": \"Băuturi\",\n        \"ru\": \"Напитки\"\n      },\n      \"children\": [\n        {\n          \"id\": \"a1b2c3d4-0002-4a5b-8c6d-7e8f9a0b1c02\",\n          \"name\": \"Apă minerală\",\n          \"code\": \"APA\",\n          \"parentId\": \"a1b2c3d4-0001-4a5b-8c6d-7e8f9a0b1c01\",\n          \"sortOrder\": 1,\n          \"names\": {\n            \"ro\": \"Apă minerală\",\n            \"ru\": \"Минеральная вода\"\n          },\n          \"children\": []\n        }\n      ]\n    },\n    {\n      \"id\": \"a1b2c3d4-0004-4a5b-8c6d-7e8f9a0b1c04\",\n      \"name\": \"Panificație\",\n      \"code\": \"PANIFICATIE\",\n      \"parentId\": null,\n      \"sortOrder\": 2,\n      \"names\": {\n        \"ro\": \"Panificație\"\n      },\n      \"children\": []\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Categorii"
        },
        {
          "name": "Блоки контента",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/content-blocks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "content-blocks"
              ]
            },
            "description": "**Необходимый scope:** `products:read`\n\nОпределения блоков контента продавца (ингредиенты, хранение…). Они задают места в шаблоне страницы товара; содержимое каждого товара приходит в запросе «Товар по коду».\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[].key` | string | Ключ; тот же, что в `contentBlocks[].key` товара. |\n| `items[].labels` | object | Метка на разных языках (карта языков, как у товара). |\n| `items[].labelRo` | string | Подпись на румынском. |\n| `items[].labelRu`, `labelEn` | string? | Метки на русском и английском — старые поля; язык, добавленный позже, появляется только в `labels`. |\n| `items[].sortOrder` | integer | Порядок отображения. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/content-blocks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "content-blocks"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"key\": \"ingrediente\",\n      \"labelRo\": \"Ingrediente\",\n      \"labelRu\": \"Состав\",\n      \"labelEn\": null,\n      \"sortOrder\": 1,\n      \"labels\": {\n        \"ro\": \"Ingrediente\",\n        \"ru\": \"Состав\"\n      }\n    },\n    {\n      \"key\": \"pastrare\",\n      \"labelRo\": \"Condiții de păstrare\",\n      \"labelRu\": \"Условия хранения\",\n      \"labelEn\": \"Storage\",\n      \"sortOrder\": 2,\n      \"labels\": {\n        \"ro\": \"Condiții de păstrare\",\n        \"ru\": \"Условия хранения\",\n        \"en\": \"Storage\"\n      }\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Blocuri de conținut"
        }
      ],
      "_anchorName": "1. Catalog și stoc"
    },
    {
      "name": "2. Цены",
      "description": "Прайс-листы и действующая цена товаров по прайс-листу.",
      "item": [
        {
          "name": "Прайс-листы",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/price-lists",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "price-lists"
              ]
            },
            "description": "**Необходимый scope:** `products:read`\n\nПрайс-листы продавца, первым идёт прайс-лист по умолчанию. Их id передаётся в «Цены на товары».\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[].id`, `code`, `name` | string | Прайс-лист. |\n| `items[].currencyCode` | string | Валюта прайс-листа. |\n| `items[].isDefault` | boolean | Прайс-лист продавца по умолчанию. |\n| `items[].isActive` | boolean | Прайс-лист активен. Неактивные тоже возвращаются. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Прайс-лист с условиями (аудитория, объём) или вне срока действия отображается здесь, но «Цены на товары» его игнорирует и выдаёт цену по умолчанию.\n- Списки магазинов здесь не показываются: их цена действует только в выбранных магазинах, не через публичный API."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/price-lists",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "price-lists"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a01\",\n      \"code\": \"PL-RETAIL\",\n      \"name\": \"Preț cu amănuntul\",\n      \"currencyCode\": \"MDL\",\n      \"isDefault\": true,\n      \"isActive\": true\n    },\n    {\n      \"id\": \"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a02\",\n      \"code\": \"ANGRO\",\n      \"name\": \"Angro HoReCa\",\n      \"currencyCode\": \"MDL\",\n      \"isDefault\": false,\n      \"isActive\": true\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Liste de prețuri"
        },
        {
          "name": "Цены на товары",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/products/prices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "products",
                "prices"
              ]
            },
            "description": "**Необходимый scope:** `products:read`\n\nДействующая цена выбранных товаров по прайс-листу — например, оптовые цены для клиента HoReCa.\n\n### Тело запроса\n\n| Поле | Тип | Обязательное | Правила |\n|---|---|---|---|\n| `productIds[]` | string (uuid)[] | да | Значения `posfixId` товаров, от 1 до 1000. Пустой список → `400` „Lista de produse (productIds) este obligatorie.” (список товаров обязателен); больше 1000 → `400` „Cel mult 1.000 produse pe un apel.” (не более 1000 товаров за один вызов) |\n| `priceListId` | string (uuid) | нет | Прайс-лист. Не передан → прайс-лист продавца по умолчанию. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[].posfixId` | string (uuid) | Товар. |\n| `items[].price` | number | Действующая цена. |\n| `items[].currency` | string | Валюта товара. |\n\n### Как выбирается цена\n\n1. Явная цена товара в прайс-листе, если она есть и действует сегодня.\n2. Для прайс-листа типа «скидка»: цена по умолчанию минус процент листа, округлённая до 2 знаков после запятой.\n3. Иначе — цена товара по умолчанию из номенклатуры.\n\nПрайс-лист игнорируется (все товары получают цену по умолчанию), если он неактивен, принадлежит другому продавцу, имеет условия по аудитории или объёму, является списком магазина или вне срока действия. В этих случаях ошибки нет.\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `INTEGRATIONS_PRICE_PRODUCTS_REQUIRED`, `INTEGRATIONS_PRICE_PRODUCTS_TOO_MANY` | Список отсутствует или содержит более 1000 id (простой формат); один из id не является GUID (формат привязки). |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Неизвестные товары **пропускаются** в ответе без ошибки; повторяющиеся id дают одну строку. Сопоставляйте строки по `posfixId`, а не по порядку.\n- Цены не конвертируются: прайс-лист в другой валюте применяется только к товарам в его валюте.\n- Прайс-лист клиента (`defaultPriceListId` в «Клиентах») не применяется автоматически: передайте его сами в `priceListId`.\n- Тело читается только при `Content-Type: application/json`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"productIds\": [\n    \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n    \"1c9a6f3b-7d2e-4f80-8b4c-3e5d7f9a2b02\"\n  ],\n  \"priceListId\": \"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a02\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/products/prices",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "products",
                    "prices"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"productIds\": [\n    \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n    \"1c9a6f3b-7d2e-4f80-8b4c-3e5d7f9a2b02\"\n  ],\n  \"priceListId\": \"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a02\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"posfixId\": \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n      \"price\": 15.9,\n      \"currency\": \"MDL\"\n    },\n    {\n      \"posfixId\": \"1c9a6f3b-7d2e-4f80-8b4c-3e5d7f9a2b02\",\n      \"price\": 7.11,\n      \"currency\": \"MDL\"\n    }\n  ]\n}"
            },
            {
              "name": "400 — пустой список",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/products/prices",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "products",
                    "prices"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"productIds\": [\n    \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n    \"1c9a6f3b-7d2e-4f80-8b4c-3e5d7f9a2b02\"\n  ],\n  \"priceListId\": \"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a02\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/400\",\n  \"title\": \"Validation Failed\",\n  \"status\": 400,\n  \"detail\": \"Lista de produse (productIds) este obligatorie.\",\n  \"errorCode\": \"INTEGRATIONS_PRICE_PRODUCTS_REQUIRED\"\n}"
            }
          ],
          "_anchorName": "Prețuri pe produse"
        }
      ],
      "_anchorName": "2. Prețuri"
    },
    {
      "name": "3. Клиенты",
      "description": "Клиенты продавца. Клиент определяется по фискальному коду.",
      "item": [
        {
          "name": "Создать клиента",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/customers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "customers"
              ]
            },
            "description": "**Необходимый scope:** `customers:write`\n\nСоздаёт клиента по фискальному коду. Если партнёр с таким кодом уже существует, возвращает его **без изменений** — запрос ничего не обновляет.\n\n### Тело запроса\n\n| Поле | Тип | Обязательное | Правила |\n|---|---|---|---|\n| `name` | string | да | Название. Пустое → `400` «Numele clientului este obligatoriu.» (имя клиента обязательно). |\n| `fiscalCode` | string | да | IDNO или IDNP, ровно 13 цифр. Иначе → `400` «Codul fiscal (IDNO/IDNP, 13 cifre) este obligatoriu pentru un client distinct.» |\n| `email` | string | нет | Не проверяется. |\n| `phone` | string | нет | Сохраняется в том виде, в каком пришёл. |\n| `address` | string | нет | Сохраняется и как юридический, и как фактический адрес. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `posfixId` | string (uuid) | Id партнёра в PosFix. В заказах он указан как `partnerId`. |\n| `code` | string | Код контрагента, 10 цифр (`0000000318`). |\n| `name` | string | Нормализованное название: «Societate cu Răspundere Limitată „FLOAREA”» становится `SRL FLOAREA`. |\n| `fiscalCode` | string? | IDNO или IDNP. |\n| `email`, `phone` | string? | Контакты. |\n| `address` | string? | Фактический адрес; если его нет — юридический. |\n| `isActive` | boolean | Партнёр активен. |\n| `defaultPriceListId` | string (uuid)? | Прайс-лист клиента, если он есть. Список магазина не показывается: поле пустое. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `INTEGRATIONS_CUSTOMER_NAME_REQUIRED`, `INTEGRATIONS_CUSTOMER_FISCAL_CODE_REQUIRED` | Пустое имя или неверный фискальный код (простой формат). |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Ответ — `200` и при создании, и когда клиент уже существовал. Поля, которое сообщало бы, какой из случаев, нет.\n- Партнёр, который был только поставщиком, становится и клиентом.\n- Код, начинающийся с `2` (IDNP), создаёт физическое лицо; остальные — юридические лица.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"SRL Floarea Soarelui\",\n  \"fiscalCode\": \"1012600034567\",\n  \"email\": \"contabil@floarea.md\",\n  \"phone\": \"+37322123456\",\n  \"address\": \"mun. Chișinău, str. Ismail 45\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 — клиент создан или найден",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/customers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "customers"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"name\": \"SRL Floarea Soarelui\",\n  \"fiscalCode\": \"1012600034567\",\n  \"email\": \"contabil@floarea.md\",\n  \"phone\": \"+37322123456\",\n  \"address\": \"mun. Chișinău, str. Ismail 45\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"posfixId\": \"b4e7c1a2-9d3f-4e61-8a0b-2f5c7d9e1a34\",\n  \"code\": \"0000000318\",\n  \"name\": \"SRL Floarea Soarelui\",\n  \"fiscalCode\": \"1012600034567\",\n  \"email\": \"contabil@floarea.md\",\n  \"phone\": \"+37322123456\",\n  \"address\": \"mun. Chișinău, str. Ismail 45\",\n  \"isActive\": true,\n  \"defaultPriceListId\": null\n}"
            },
            {
              "name": "400 — неверный фискальный код",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/customers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "customers"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"name\": \"SRL Floarea Soarelui\",\n  \"fiscalCode\": \"1012600034567\",\n  \"email\": \"contabil@floarea.md\",\n  \"phone\": \"+37322123456\",\n  \"address\": \"mun. Chișinău, str. Ismail 45\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/400\",\n  \"title\": \"Validation Failed\",\n  \"status\": 400,\n  \"detail\": \"Codul fiscal (IDNO/IDNP, 13 cifre) este obligatoriu pentru un client distinct.\",\n  \"errorCode\": \"INTEGRATIONS_CUSTOMER_FISCAL_CODE_REQUIRED\"\n}"
            }
          ],
          "_anchorName": "Creează client"
        },
        {
          "name": "Клиент по id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/customers/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "customers",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "posfixId клиента"
                }
              ]
            },
            "description": "**Необходимый scope:** `customers:read`\n\nКлиент продавца по `posfixId`.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|---|---|---|\n| `id` | string (uuid) | ID клиента. Не GUID → `400`. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `posfixId` | string (uuid) | Id партнёра в PosFix. В заказах он указан как `partnerId`. |\n| `code` | string | Код контрагента, 10 цифр (`0000000318`). |\n| `name` | string | Нормализованное название: «Societate cu Răspundere Limitată „FLOAREA”» становится `SRL FLOAREA`. |\n| `fiscalCode` | string? | IDNO или IDNP. |\n| `email`, `phone` | string? | Контакты. |\n| `address` | string? | Фактический адрес; если его нет — юридический. |\n| `isActive` | boolean | Партнёр активен. |\n| `defaultPriceListId` | string (uuid)? | Прайс-лист клиента, если он есть. Список магазина не показывается: поле пустое. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 404 | — | Не существует, удалён или является только поставщиком. Пустое тело. |\n| 400 | — | `id` не является GUID (формат привязки). |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/customers/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "customers",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "posfixId-ul clientului"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"posfixId\": \"b4e7c1a2-9d3f-4e61-8a0b-2f5c7d9e1a34\",\n  \"code\": \"0000000318\",\n  \"name\": \"SRL Floarea Soarelui\",\n  \"fiscalCode\": \"1012600034567\",\n  \"email\": \"contabil@floarea.md\",\n  \"phone\": \"+37322123456\",\n  \"address\": \"mun. Chișinău, str. Ismail 45\",\n  \"isActive\": true,\n  \"defaultPriceListId\": null\n}"
            }
          ],
          "_anchorName": "Client după id"
        },
        {
          "name": "Список клиентов",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/customers?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "customers"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "floarea",
                  "description": "Поиск по названию, коду и фискальному коду.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "1–200, по умолчанию 50."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "nextCursor из предыдущей страницы.",
                  "disabled": true
                }
              ]
            },
            "description": "**Необходимый scope:** `customers:read`\n\nКлиенты продавца, постранично. Включает неактивных клиентов и сводного партнёра «Comenzi online» (онлайн-заказы).\n\n### Параметры запроса\n\n| Параметр | Тип | По умолчанию | Описание |\n|---|---|---|---|\n| `search` | string | — | Слова из названия, кода или фискального кода, в любом порядке: каждое слово должно встречаться. Без учёта регистра и диакритики («faina» находит «Făină»). |\n| `limit` | integer | 50 | Значение больше 200 уменьшается до 200; 0 или отрицательное значение означает 50. |\n| `cursor` | string (uuid) | — | Значение `nextCursor` с предыдущей страницы. Не GUID → `400`. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[]` | array | Клиенты, с полями из «Клиент по id». |\n| `nextCursor` | string? | Курсор следующей страницы; `null` на последней странице. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `VALIDATION` | Некорректный курсор. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Порядок — по id, а не по имени. Проходите все страницы до `nextCursor: null`; не полагайтесь на порядок."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/customers?limit=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "customers"
                  ],
                  "query": [
                    {
                      "key": "search",
                      "value": "floarea",
                      "description": "Caută în denumire, cod și cod fiscal.",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "nextCursor din pagina precedentă.",
                      "disabled": true
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"posfixId\": \"0a1f2b3c-4d5e-4f60-8a71-92b3c4d5e6f7\",\n      \"code\": \"0000000011\",\n      \"name\": \"Comenzi online\",\n      \"fiscalCode\": null,\n      \"email\": null,\n      \"phone\": null,\n      \"address\": null,\n      \"isActive\": true,\n      \"defaultPriceListId\": null\n    },\n    {\n      \"posfixId\": \"b4e7c1a2-9d3f-4e61-8a0b-2f5c7d9e1a34\",\n      \"code\": \"0000000318\",\n      \"name\": \"SRL Floarea Soarelui\",\n      \"fiscalCode\": \"1012600034567\",\n      \"email\": \"contabil@floarea.md\",\n      \"phone\": \"+37322123456\",\n      \"address\": \"mun. Chișinău, str. Ismail 45\",\n      \"isActive\": true,\n      \"defaultPriceListId\": null\n    }\n  ],\n  \"nextCursor\": null\n}"
            }
          ],
          "_anchorName": "Listă clienți"
        }
      ],
      "_anchorName": "3. Clienți"
    },
    {
      "name": "4. Заказы",
      "description": "Заказы из вашего магазина. Заказ не создаёт проводок: их несёт отгрузка. С картой лояльности покупателя (`loyalty`) PosFix сам ведёт и лояльность заказа: использованные бонусы, баллы и кэшбэк при завершении, возврат бонусов при отмене.",
      "item": [
        {
          "name": "Создать заказ",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "orders"
              ]
            },
            "description": "**Необходимый scope:** `orders:write`\n\nЗаказ из вашего магазина становится заказом клиента в PosFix. Со складом он сразу подтверждается и товар резервируется; без склада остаётся `Draft`. Заказ **информационный**: он не оформляет фискальный чек, счёт-фактуру или бухгалтерскую проводку. Они появляются только тогда, когда продавец отгружает товар.\n\n### Тело запроса\n\n| Поле | Тип | Обязательное | Правила |\n|---|---|---|---|\n| `externalOrderId` | string | да | ID заказа в вашей системе, до 200 символов. Это **ключ идемпотентности**: то же значение не создаёт второй заказ. |\n| `externalNumber` | string | нет | Номер заказа в вашей системе (`#1001`), до 100 символов. Только копия: номер PosFix выделяется отдельно. |\n| `orderDate` | string | нет | `yyyy-MM-dd`. День — молдавский; ISO-момент берётся по его дню в Молдове. Не указана **или нечитаема** → сегодня по времени Молдовы, без ошибки. Год определяет серию нумерации. |\n| `currency` | string | нет | Пока только `MDL` (и по умолчанию). Другая валюта отклоняется с `TRADE_ORDER_CURRENCY_NOT_SUPPORTED`. |\n| `customer.verified` | boolean | нет | По умолчанию `false`. `true` = магазин проверил своего клиента: вход в аккаунт, подтверждённый телефон или IDNO. Проверенный → заказ относится на существующего клиента, с его ценами. Непроверенный → заказ не привязывается к компании по IDNO и не получает цену клиента; самое большее он привязывается к розничному клиенту, найденному по телефону, с публичной ценой. |\n| `customer.idno` | string | нет | IDNO/IDNP из 13 цифр, до 20 символов. Проверенный → заказ относится на компанию с этим кодом, которая при необходимости создаётся. Непроверенный → IDNO не связывает заказ с компанией; он остаётся в заказе вместе с именем, телефоном и email как заявленный покупатель, которого продавец видит и проверяет. |\n| `customer.phone` | string | нет | Телефон покупателя, до 50 символов. Молдавский номер (`+373 69 123 456`, `069123456`) относит заказ на клиента с этим телефоном, который при необходимости создаётся: проверенный → существующий клиент с его ценами; непроверенный → только розничный клиент (без фискального кода), с публичной ценой. Без молдавского телефона и без проверенного IDNO заказ относится на общего контрагента «Comenzi online». |\n| `customer.name`, `.email`, `.address` | string | нет | До 500 / 200 / 500 символов. Используются при создании нового контрагента (по проверенному IDNO или по телефону). Для непроверенного покупателя имя и email также остаются в заказе как заявленный покупатель. Адрес клиента — это не адрес доставки: для доставки используйте `delivery` или `deliveryAddress`. |\n| `warehouseId` | string (uuid) | нет | Склад, с которого резервируется товар. Не указан → склад ключа по умолчанию, если он есть. Со складом заказ подтверждается по настройке остатка ключа (ниже). |\n| `delivery.method` | string | нет* | `pickup` (самовывоз), `own_courier` (курьер продавца) или `nova_post`. *Обязательно, если вы передаёте `delivery`. Другое значение → `400`. |\n| `delivery.recipientName`, `.recipientPhone` | string | нет | Получатель, до 200 / 50 символов. Для `nova_post` обязательны, а телефон должен быть молдавским. |\n| `delivery.novaPostDivisionId`, `.novaPostSettlementId`, `.novaPostDivisionLabel` | number, number, string | нет | Пункт Nova Post (их идентификаторы, > 0) и его название, до 300 символов. С пунктом адрес ниже не используется. |\n| `delivery.city`, `.street`, `.building`, `.flat`, `.postCode` | string | нет | Адрес, до 100 / 200 / 20 / 20 / 10 символов. Для `nova_post` без пункта обязательны все, кроме `flat`. |\n| `deliveryAddress`, `deliveryContact` | string | нет | Адрес и контакт свободным текстом, для старых интеграций. С `delivery` PosFix сам составляет текст из её полей. |\n| `paymentMethod` | string | нет | `online`, `on_pickup` (при получении), `cash_on_delivery` (наложенный платёж) или `bank_transfer`. Другое значение → `400`. |\n| `loyalty.cardNumber` | string | нет* | Карта лояльности покупателя, 13 цифр (её даёт «Зарегистрировать участника»). Заказ связывается с участником, а при завершении участник получает баллы или кэшбэк за отгруженный товар. Участник, уже связанный с клиентом в PosFix, приводит заказ к этому клиенту, какой бы телефон ни был отправлен (проверенный IDNO важнее: заказ остаётся на компании); без `customer.phone` используется телефон участника. *Обязательно, когда вы отправляете `loyalty`. Требует scope `loyalty:earn`. |\n| `loyalty.bonusAmount` | number | нет | Бонусы в леях, использованные в заказе: больше или равно нулю, не более двух знаков после запятой, не больше `maxRedeemable` из «Сколько бонусов можно использовать» (с `orderAmount` = сумма заказа с НДС). Вычитаются из цены строк, поэтому клиент платит сумму минус бонусы. Дополнительно требует scope `loyalty:redeem`. |\n| `lines[]` | array | да | Хотя бы одна строка. |\n| `lines[].productCode` | string | да | **Код** товара из каталога (`code`, 10 цифр), а не SKU. Неизвестный код → `400`. |\n| `lines[].quantity` | number | да | > 0. |\n| `lines[].unitPrice` | number | нет | Цена за единицу **без НДС**; НДС добавляется по ставке товара. Не указана → цена на кассе, приведённая к цене без НДС. Как она проверяется, зависит от настройки цены ключа (ниже); при цене PosFix её проще не передавать. Для проверенного клиента сравнивается его цена (прайс-лист из карточки или договора): магазин, показывающий публичную цену каталога, может получить отказ для таких клиентов. |\n| `lines[].discountPct` | number | нет | Скидка в строке, 0–100, не более 2 знаков после запятой (иначе `400` `COMMON_PRECISION` по полю; не округляется). На ключах с ценой PosFix скидка, отличная от 0, → `400`. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `orderId` | string (uuid) | ID заказа в PosFix. Его номер возвращает запрос «Заказ по id». |\n| `idempotentReplay` | boolean | `true`, если `externalOrderId` уже существовал. |\n| `status` | string | `created` или `replayed`. Это результат запроса, а не статус заказа. |\n| `orderStatus` | string? | Статус заказа: `Draft` или `Confirmed` (при повторе — текущий статус). |\n| `paymentDueAt` | string? | Срок оплаты, ISO 8601 UTC, если у ключа есть срок, заказ подтверждён и оплата не при получении и не при доставке; иначе `null`. |\n\n### Настройки ключа\n\nИх задаёт PosFix на ключе по запросу продавца; в запросе они не передаются.\n\n| Настройка | Что делает |\n|---|---|\n| Цена строк | **Цена PosFix** (по умолчанию для новых ключей): цену ставит PosFix, поэтому `unitPrice` можно не передавать. Если передана, она проходит, когда это цена на кассе без НДС, округлённая до бана или до 4 знаков, либо когда `unitPrice × (1 + cota/100)`, округлённая до бана, точно равна цене на кассе; иначе заказ отклоняется (`422`), и вы получаете текущие цены. Сохраняется в любом случае цена PosFix: для проверенного клиента — его цена (прайс-лист из карточки или договора), иначе публичная цена. **Цена интегратора** (ключи, выданные до этих настроек): отправленная цена остаётся как есть. В обоих случаях строка без `unitPrice` получает цену на кассе, приведённую к цене без НДС. При цене PosFix скидка в строке (`discountPct`) отклоняется: скидки берутся из прайс-листов продавца. |\n| Недостаточный остаток | **Отклонить заказ** (по умолчанию для новых ключей): заказ подтверждается, только если весь остаток доступен; иначе `422`, и заказ не остаётся. **Принять заказ** (ключи, выданные до этих настроек): он подтверждается и резервирует сколько может. |\n| Склад по умолчанию | Склад для заказов, которые не передают `warehouseId`. Без какого-либо склада заказ остаётся `Draft`, без резерва. |\n| Срок оплаты | Выключен или 1–10 080 минут с момента получения. Подтверждённый и совсем не оплаченный к сроку заказ отменяется автоматически (вебхук `order.cancelled`); частично оплаченный остаётся у продавца. Срок не применяется к оплате при получении или при доставке (`on_pickup`, `cash_on_delivery`): заказ, который нельзя оплатить заранее, не отменяется за неоплату. |\n\n### Лояльность в заказе (`loyalty`)\n\nТак вы работаете, когда заказы проходят через PosFix: платформа сама делает всё, что касается лояльности, вы отправляете только карту и бонусы. Сравнение с вариантом, в котором магазин напрямую вызывает API лояльности, — в «8. Лояльность».\n\n| Когда | Что делает PosFix |\n|---|---|\n| При создании | Проверяет карту, связывает заказ с участником и, при `bonusAmount`, резервирует бонусы по правилам программы (как на кассе) и вычитает их из цены строк. Зарезервированные бонусы нельзя потратить в другом месте, но с карты они ещё не списаны. Отказ в резерве отклоняет заказ. |\n| Заказ полностью отгружен или закрыт | Списывает с карты бонусы за отгруженный товар, а остаток (за неотгруженный) возвращает на карту. Начисляет баллы или кэшбэк на стоимость отгруженного, после скидок. Один раз, сколько бы событий ни пришло. |\n| Заказ отменён | Возвращает все бонусы на карту. Если часть была отгружена, рассчитывается как при закрытии: бонусы и начисление только за отгруженное. |\n| Заказ отгружен, затем возвращён (посылка не принята, отгрузка отменена продавцом) | Возвращает списанные бонусы и снова резервирует их на заказ; забирает баллы или кэшбэк. При следующем завершении рассчитывается по тому, что отгружено тогда. |\n| Отменённый заказ возвращён в черновик | Заказ теряет бонусы (они вернулись на карту при отмене) и возвращается к полной цене; карта остаётся, поэтому начисление будет при завершении. |\n| Участник, зарегистрированный через API, не связан с клиентом | Связывается с клиентом заказа при первом заказе с картой (не со сводным партнёром «Comenzi online»). С этого момента его заказы идут на того же клиента, даже если сменился телефон. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `VALIDATION` | Обязательное поле отсутствует, неверно или слишком длинное; см. `errors`. Неизвестное значение в `delivery.method` или `paymentMethod`: „Valoarea „card” nu e acceptată. Valori posibile: online, on_pickup, cash_on_delivery, bank_transfer.” (Значение „card“ не допускается. Возможные значения: online, on_pickup, cash_on_delivery, bank_transfer.) |\n| 400 | `INTEGRATIONS_ORDER_PRODUCT_UNKNOWN`, `INTEGRATIONS_ORDER_VAT_UNRESOLVED` | „Produs necunoscut: codul „X” nu există în catalogul comerciantului.” (Неизвестный товар: кода „X“ нет в каталоге продавца.) или „Cota TVA a produsului „X” e nerezolvată, iar comanda nu se înregistrează cu 0%. Verificați cota produsului în catalog.” (Ставка НДС товара „X“ не определена, и заказ не регистрируется с 0%. Проверьте ставку товара в каталоге.) (простой формат; код товара — в `params.code`). |\n| 400 | `TRADE_ORDER_DELIVERY_RECIPIENT_REQUIRED`, `TRADE_ORDER_DELIVERY_DESTINATION_REQUIRED`, `TRADE_ORDER_DELIVERY_PHONE_INVALID` | Доставка Nova Post без получателя или без пункта назначения, либо телефон получателя — не молдавский номер. |\n| 400 | `INTEGRATIONS_ORDER_DISCOUNT_NOT_ALLOWED` | Цена PosFix: в строке есть `discountPct`. „Linia cu produsul „X” are reducere, dar cheia folosește prețul de la casă: reducerile vin din listele de prețuri ale comerciantului. Trimiteți linia fără discountPct.” (В строке с товаром „X“ есть скидка, но ключ использует цену на кассе: скидки берутся из прайс-листов продавца. Отправьте строку без discountPct.) |\n| 409 | `INTEGRATIONS_ORDER_UNRESOLVED` | Более ранний запрос с тем же ключом прервался до того, как PosFix стал хранить ключ на заказе, поэтому заказ уже может существовать. Не отправляйте повторно автоматически: продавец проверяет; если заказа у него нет, отправьте заказ с другим `externalOrderId`. |\n| 422 | `INTEGRATIONS_ORDER_PRICE_CHANGED` | Цена PosFix: отправленная цена не даёт цену на кассе. Заказ не создан; текущие цены — в `priceMismatches` (`productCode`, `sentUnitPrice`, `currentUnitPrice`, все без НДС). |\n| 422 | `INSUFFICIENT_STOCK` | Отказ в заказе при недостаточном остатке: „Stoc insuficient pentru „X” în „Y”: sunt necesare 5, disponibile 2, lipsesc 3. Înregistrați întâi intrarea mărfii sau micșorați cantitatea.” (Недостаточно остатка „X“ в „Y“: требуется 5, доступно 2, не хватает 3. Сначала оформите поступление товара или уменьшите количество.) Заказ не создан; тем не менее вы можете получить по нему вебхуки `order.created` и `order.cancelled` (с `reason: stock_refused`) — истиной остаётся ответ API. |\n| 400 | `INTEGRATIONS_LOYALTY_CARD_NUMBER_FORMAT`, `INTEGRATIONS_LOYALTY_AMOUNT_INVALID` | В карте не 13 цифр или бонусы — не сумма в леях (отрицательная, больше двух знаков после запятой). |\n| 400 | `TRADE_ORDER_LOYALTY_CARD_UNKNOWN` | «Карта лояльности 2000000012345 не принадлежит этой компании. Проверьте номер или зарегистрируйте клиента в программе.» Заказ не создан. |\n| 400 | `TRADE_ORDER_LOYALTY_BONUS_EXCEEDS_TOTAL` | «Бонус 300,00 лей больше суммы заказа 243,38 лей. Отправьте бонус не больше суммы заказа.» |\n| 403 | — | В заказе есть `loyalty`, но у ключа нет `loyalty:earn` (или `loyalty:redeem`, с бонусами), либо у продавца не активен модуль «Программы лояльности». Пустое тело. |\n| 422 | `LOYALTY_BONUS_HOLD_EXCEEDS_MAX`, `LOYALTY_QUOTE_NOTHING_AVAILABLE` и другие причины расчёта | Запрошенные бонусы нельзя использовать в заказе (сверх лимита программы, недостаточный баланс, карта без кошелька). «На этом чеке можно использовать не более 120,50 MDL.» Заказ не создан: повторите с тем же `externalOrderId` и меньшими бонусами или без бонусов. |\n| 422 | `TRADE_ORDER_PARTNER_INACTIVE` | Контрагент, на которого был бы оформлен заказ, деактивирован в PosFix: как правило, компания, найденная по IDNO проверенного клиента (`customer.verified: true`). Заказ не создан; продавец заново активирует контрагента, после чего вы повторно отправляете заказ с тем же `externalOrderId`. При поиске по телефону этого не происходит: деактивированный контрагент пропускается, и заказ оформляется на нового розничного клиента. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Ответ — `200`, а не `201`, и при создании, и при повторе.\n- `replayed` может прийти даже на ваш первый запрос: если внутренняя попытка дошла до PosFix, но её ответ потерялся, повторная попытка получает уже созданный заказ. Обрабатывайте `created` и `replayed` одинаково: заказ существует, и `orderId` — его идентификатор.\n- При повторе тело **не сравнивается**: вы получите существующий заказ, даже если изменили строки. Для нового заказа нужен новый `externalOrderId`.\n- При повторе `orderStatus` и `paymentDueAt` — текущие значения заказа. Если продавец тем временем удалил заказ, тот же запрос создаёт новый.\n- Повтор безопасен: если запрос сорвался по дороге (тайм-аут, `503`), отправьте его снова с тем же `externalOrderId` — второй заказ не создаётся. Исключение — ключи, оставшиеся невыясненными после запроса, прерванного до того, как PosFix стал хранить ключ на заказе: они получают `409` `INTEGRATIONS_ORDER_UNRESOLVED`, и продавец сначала проверяет, нет ли у него уже этого заказа; если нет, заказ отправляется с другим `externalOrderId`.\n- После отказа по цене или по остатку заказа не существует: отправьте его снова с тем же `externalOrderId` — с ценами из `priceMismatches` или после пополнения остатка.\n- Цена без НДС из цены на кассе: `price / (1 + vatRate/100)` (`7,90 / 1,08 = 7,3148`). При цене PosFix она проходит с 4 знаками после запятой (`7,3148`), округлённой до бана (`7,31`) или как любая цена, которая с НДС и округлением до бана даёт цену на кассе. Округление до бана не принимается, если цена без НДС, округлённая до бана, равна 0.\n- Строка: нетто = количество × цена × (1 − скидка/100); НДС = нетто × ставка товара; итого = нетто + НДС. Нетто и НДС округляются до 2 знаков, от нуля (2,345 → 2,35).\n- Новый заказ запускает webhook `order.created`, а его подтверждение — `order.confirmed`.\n- С `loyalty` не вызывайте для того же заказа «Использовать бонусы в заказе», «Начислить за заказ» или «Отменить заказ (возврат)» из API лояльности: это делает PosFix, а повторный вызов начислит участнику дважды.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 — заказ создан и подтверждён",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"orderId\": \"3c9a1f52-7d3e-4b8a-a0c4-6e2b91f0d7a8\",\n  \"idempotentReplay\": false,\n  \"status\": \"created\",\n  \"orderStatus\": \"Confirmed\",\n  \"paymentDueAt\": \"2026-10-16T10:15:00Z\"\n}"
            },
            {
              "name": "400 — чужая карта лояльности",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/400\",\n  \"title\": \"TRADE_ORDER_LOYALTY_CARD_UNKNOWN\",\n  \"status\": 400,\n  \"detail\": \"Cardul de fidelitate 2000000012345 nu e al acestei firme. Verificați numărul sau înscrieți clientul în program.\",\n  \"errorCode\": \"TRADE_ORDER_LOYALTY_CARD_UNKNOWN\"\n}"
            },
            {
              "name": "422 — бонусы сверх лимита программы",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"LOYALTY_BONUS_HOLD_EXCEEDS_MAX\",\n  \"status\": 422,\n  \"detail\": \"Pe bonul acesta se pot folosi cel mult 120,50 MDL.\",\n  \"errorCode\": \"LOYALTY_BONUS_HOLD_EXCEEDS_MAX\"\n}"
            },
            {
              "name": "200 — повтор с тем же externalOrderId",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"orderId\": \"3c9a1f52-7d3e-4b8a-a0c4-6e2b91f0d7a8\",\n  \"idempotentReplay\": true,\n  \"status\": \"replayed\",\n  \"orderStatus\": \"Confirmed\",\n  \"paymentDueAt\": \"2026-10-16T10:15:00Z\"\n}"
            },
            {
              "name": "422 — цена отличается от цены на кассе",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"INTEGRATIONS_ORDER_PRICE_CHANGED\",\n  \"status\": 422,\n  \"detail\": \"Prețul unor linii diferă de prețul de la casă, așa că comanda nu s-a înregistrat. Prețurile curente, fără TVA, sunt în priceMismatches: retrimiteți comanda cu ele.\",\n  \"errorCode\": \"INTEGRATIONS_ORDER_PRICE_CHANGED\",\n  \"priceMismatches\": [\n    {\n      \"productCode\": \"0000000124\",\n      \"sentUnitPrice\": 6.9444,\n      \"currentUnitPrice\": 7.3148\n    }\n  ]\n}"
            },
            {
              "name": "422 — недостаточно остатка",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"INSUFFICIENT_STOCK\",\n  \"status\": 422,\n  \"detail\": \"Stoc insuficient pentru „Apă minerală Borjomi 0,5 L” în „Depozit central”: sunt necesare 12, disponibile 5, lipsesc 7. Înregistrați întâi intrarea mărfii sau micșorați cantitatea.\",\n  \"errorCode\": \"INSUFFICIENT_STOCK\",\n  \"params\": {\n    \"product\": \"Apă minerală Borjomi 0,5 L\",\n    \"warehouse\": \"Depozit central\",\n    \"requested\": \"12\",\n    \"available\": \"5\",\n    \"missing\": \"7\"\n  }\n}"
            },
            {
              "name": "400 — неизвестный товар",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/400\",\n  \"title\": \"Validation Failed\",\n  \"status\": 400,\n  \"detail\": \"Produs necunoscut: codul „0000000999” nu există în catalogul comerciantului.\",\n  \"errorCode\": \"INTEGRATIONS_ORDER_PRODUCT_UNKNOWN\",\n  \"params\": {\n    \"code\": \"0000000999\"\n  }\n}"
            },
            {
              "name": "400 — валидация",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/400\",\n  \"title\": \"VALIDATION\",\n  \"status\": 400,\n  \"detail\": \"Câmpul este obligatoriu.\",\n  \"errorCode\": \"VALIDATION\",\n  \"errors\": {\n    \"externalOrderId\": [\n      \"Câmpul este obligatoriu.\"\n    ]\n  },\n  \"errorCodes\": {\n    \"externalOrderId\": [\n      {\n        \"code\": \"COMMON_REQUIRED\",\n        \"params\": {}\n      }\n    ]\n  }\n}"
            }
          ],
          "_anchorName": "Creează comandă"
        },
        {
          "name": "Заказ по id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/orders/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "orders",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "orderId из «Создать заказ»"
                }
              ]
            },
            "description": "**Необходимый scope:** `orders:read`\n\nЗаказ клиента со строками, оплатами и сформированными из него документами продажи. Возвращает любой заказ продавца, включая созданные в Панели управления или на кассе.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|---|---|---|\n| `id` | string (uuid) | ID заказа. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `id`, `number` | string | ID и номер заказа (10 цифр). |\n| `status` | string | Состояние; см. таблицу ниже. |\n| `partnerId` | string (uuid) | Клиент. Может быть сводным партнёром «Comenzi online» (онлайн-заказы). |\n| `orderDate` | string | `yyyy-MM-dd`. |\n| `expectedShipDate` | string? | Плановая дата доставки, указанная продавцом. |\n| `currencyCode` | string | Валюта заказа. |\n| `subTotal`, `vatTotal`, `total` | number | Без НДС, НДС, с НДС — в валюте заказа. |\n| `reservedQtyTotal`, `shippedQtyTotal` | number | Зарезервированное и отгруженное количество по всему заказу. |\n| `invoicedSum` | number | Сумма проведённых продаж по заказу, в леях. |\n| `paidSum` | number | Сколько оплачено (продажи, авансы, счёт на оплату), в леях. |\n| `deliveryAddress`, `deliveryContact`, `trackingNumber` | string? | Доставка. |\n| `paymentDueAt` | string? | Срок оплаты, ISO 8601 UTC; `null` = без срока. По сроку подтверждённый и совсем не оплаченный заказ отменяется. |\n| `paymentMethod` | string? | `online`, `on_pickup`, `cash_on_delivery` или `bank_transfer`, как при создании; `null` = не указан. |\n| `loyalty` | object? | `{cardNumber, bonusAmount}`: карта лояльности заказа и бонусы в леях, сейчас вычтенные из цены строк (0 = только карта). `null` = заказ без карты. |\n| `cancellationReason` | string? | Почему заказ отменён без продавца: `payment_deadline` (совсем не оплачен к сроку) или `external_cancelled` (его отменили вы, через «Отменить заказ»); `null` = отменён человеком или не отменён. Заказы, отклонённые при создании (склад, неудачное подтверждение), удаляются, поэтому здесь не появляются; их видно только в вебхуке `order.cancelled`. |\n| `description`, `notes` | string? | Заполняются продавцом в Панели управления. |\n| `lines[]` | array | `lineNumber`, `productId` (`posfixId` из каталога), `productName`, `unitOfMeasure`, `quantity`, `reservedQty`, `shippedQty`, `unitPrice` (без НДС), `discountPct`, `vatRatePct`, `lineTotal` (с НДС). |\n| `generatedSales[]` | array | Документы продажи, созданные из заказа: `id`, `number`, `status` (`Draft`/`Posted`), `date`, `total`. |\n\n### Статусы заказа\n\n| Статус | Что означает |\n|---|---|\n| `Draft` | Получен, не подтверждён, без резерва. Таким остаётся заказ из API без склада или если его подтверждение не удалось. |\n| `Confirmed` | Подтверждён; товар резервируется. Заказ из API со складом (своим или складом ключа по умолчанию) подтверждается при создании. |\n| `Fulfilling` | Доставлен частично. |\n| `Fulfilled` | Доставлен полностью: по фактуре, курьерской партией доставки или самовывозом с кассы. |\n| `Closed` | Закрыт вручную; остаток резерва снимается. |\n| `Cancelled` | Отменён продавцом или автоматически по сроку оплаты ключа. Продавец может открыть его снова. |\n\nСтатус меняют продавец и процессы доставки; API изменить его не может.\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 404 | — | Заказ не существует или удалён. Пустое тело. |\n| 400 | — | `id` не является GUID. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- При самовывозе с кассы или доставке курьером заказ может перейти в `Fulfilled` без записей в `generatedSales`: бухгалтерскую проводку несёт Z-отчёт или партия доставки."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "orderId din „Creează comandă”"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"3c9a1f52-7d3e-4b8a-a0c4-6e2b91f0d7a8\",\n  \"number\": \"0000000042\",\n  \"status\": \"Draft\",\n  \"partnerId\": \"0a1f2b3c-4d5e-4f60-8a71-92b3c4d5e6f7\",\n  \"orderDate\": \"2026-09-25\",\n  \"expectedShipDate\": null,\n  \"currencyCode\": \"MDL\",\n  \"subTotal\": 204.79,\n  \"vatTotal\": 38.59,\n  \"total\": 243.38,\n  \"reservedQtyTotal\": 0,\n  \"shippedQtyTotal\": 0,\n  \"invoicedSum\": 0,\n  \"paidSum\": 0,\n  \"description\": null,\n  \"notes\": null,\n  \"deliveryAddress\": \"mun. Chișinău, bd. Dacia 27, ap. 4\",\n  \"deliveryContact\": \"Ana Rusu, +373 69 123 456\",\n  \"trackingNumber\": null,\n  \"paymentDueAt\": null,\n  \"paymentMethod\": \"cash_on_delivery\",\n  \"cancellationReason\": null,\n  \"lines\": [\n    {\n      \"lineNumber\": 1,\n      \"productId\": \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n      \"productName\": \"Apă minerală Borjomi 0,5 L\",\n      \"unitOfMeasure\": \"buc\",\n      \"quantity\": 12,\n      \"reservedQty\": 0,\n      \"shippedQty\": 0,\n      \"unitPrice\": 15.42,\n      \"discountPct\": 0,\n      \"vatRatePct\": 20,\n      \"lineTotal\": 222.05\n    },\n    {\n      \"lineNumber\": 2,\n      \"productId\": \"1c9a6f3b-7d2e-4f80-8b4c-3e5d7f9a2b02\",\n      \"productName\": \"Pâine Franzeluța albă feliată 400 g\",\n      \"unitOfMeasure\": \"buc\",\n      \"quantity\": 3,\n      \"reservedQty\": 0,\n      \"shippedQty\": 0,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10,\n      \"vatRatePct\": 8,\n      \"lineTotal\": 21.33\n    }\n  ],\n  \"generatedSales\": []\n}"
            }
          ],
          "_anchorName": "Comandă după id"
        },
        {
          "name": "Список заказов",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/orders?from={{monthStart}}&to={{today}}&page=1&pageSize=20",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "orders"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "Draft",
                  "description": "Draft · Confirmed · Fulfilling · Fulfilled · Closed · Cancelled",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "{{monthStart}}",
                  "description": "yyyy-MM-dd, включительно."
                },
                {
                  "key": "to",
                  "value": "{{today}}",
                  "description": "yyyy-MM-dd, включительно."
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": "Начиная с 1."
                },
                {
                  "key": "pageSize",
                  "value": "20",
                  "description": "1–200, по умолчанию 20."
                }
              ]
            },
            "description": "**Необходимый scope:** `orders:read`\n\nЗаказы продавца, сначала самые новые (по дате, затем по номеру).\n\n### Параметры запроса\n\n| Параметр | Тип | По умолчанию | Описание |\n|---|---|---|---|\n| `status` | string | все | Статус из таблицы в разделе «Заказ по id». Другое значение → `400`. |\n| `from` / `to` | string | — | Дата заказа, включительно. Передавайте `yyyy-MM-dd`: сервер принимает и другие форматы и может прочитать их неверно (`01.09.2026` становится 9 января). Текст, который не является датой, → `400`. |\n| `page` | integer | 1 | Номер страницы, начиная с 1. |\n| `pageSize` | integer | 20 | 1–200. **Значение больше 200 сбрасывается на 20**, а не на 200. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[]` | array | `id`, `number`, `status`, `orderDate`, `expectedShipDate`, `total`, `currencyCode`, `paidSum` (в леях), `shippedQtyTotal`, `deliveryAddress`, `deliveryContact`, `trackingNumber`, `paymentDueAt`, `paymentMethod`, `cancellationReason`. |\n| `total` | integer | Сколько заказов соответствует фильтру, на всех страницах. |\n| `page`, `pageSize` | integer | Применённые значения. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `VALIDATION` | Некорректный статус или дата. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders?from={{monthStart}}&to={{today}}&page=1&pageSize=20",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ],
                  "query": [
                    {
                      "key": "status",
                      "value": "Draft",
                      "description": "Draft · Confirmed · Fulfilling · Fulfilled · Closed · Cancelled",
                      "disabled": true
                    },
                    {
                      "key": "from",
                      "value": "{{monthStart}}",
                      "description": "yyyy-MM-dd, inclusiv."
                    },
                    {
                      "key": "to",
                      "value": "{{today}}",
                      "description": "yyyy-MM-dd, inclusiv."
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "De la 1."
                    },
                    {
                      "key": "pageSize",
                      "value": "20",
                      "description": "1–200, implicit 20."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"3c9a1f52-7d3e-4b8a-a0c4-6e2b91f0d7a8\",\n      \"number\": \"0000000042\",\n      \"status\": \"Draft\",\n      \"orderDate\": \"2026-09-25\",\n      \"expectedShipDate\": null,\n      \"total\": 243.38,\n      \"currencyCode\": \"MDL\",\n      \"paidSum\": 0,\n      \"shippedQtyTotal\": 0,\n      \"deliveryAddress\": \"mun. Chișinău, bd. Dacia 27, ap. 4\",\n      \"deliveryContact\": \"Ana Rusu, +373 69 123 456\",\n      \"trackingNumber\": null,\n      \"paymentDueAt\": null,\n      \"paymentMethod\": \"cash_on_delivery\",\n      \"cancellationReason\": null\n    }\n  ],\n  \"total\": 1,\n  \"page\": 1,\n  \"pageSize\": 20\n}"
            }
          ],
          "_anchorName": "Listă comenzi"
        },
        {
          "name": "Обновить доставку",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/orders/:id/delivery",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "orders",
                ":id",
                "delivery"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "orderId"
                }
              ]
            },
            "description": "**Необходимый scope:** `orders:write`\n\nЗадаёт номер отслеживания и при необходимости адрес или контакт доставки — после того как вы отправили заказ.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|---|---|---|\n| `id` | string (uuid) | ID заказа. |\n\n### Тело запроса\n\n| Поле | Тип | Обязательное | Правила |\n|---|---|---|---|\n| `trackingNumber` | string | нет | Трек-номер посылки. |\n| `deliveryAddress` | string | нет | Адрес доставки. |\n| `deliveryContact` | string | нет | Контактное лицо. |\n\n### Ответ\n\n**204** без тела.\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 404 | — | Заказ не существует или удалён. Пустое тело. |\n| 400 | — | `id` не является GUID. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Записываются только переданные непустые поля. Поле **нельзя очистить** через API.\n- Работает при любом статусе заказа и не отправляет webhook.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"trackingNumber\": \"RB123456789MD\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "204 — обновлено",
              "originalRequest": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders/:id/delivery",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders",
                    ":id",
                    "delivery"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "orderId"
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"trackingNumber\": \"RB123456789MD\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "No Content",
              "code": 204,
              "_postman_previewlanguage": "text",
              "header": [],
              "body": ""
            }
          ],
          "_anchorName": "Actualizează livrarea"
        },
        {
          "name": "Отменить заказ",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/orders/:id/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "orders",
                ":id",
                "cancel"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "orderId из «Создать заказ»"
                }
              ]
            },
            "description": "**Необходимый scope:** `orders:write`\n\nОтменяет заказ, отправленный через API, пока товар не ушёл: он в статусе `Draft` или `Confirmed` и ничего не отгружено и не выдано. Это та же отмена, что и кнопка в панели продавца: резерв товара освобождается, ещё не переданные курьерские партии отзываются, а бонусы лояльности, зарезервированные на заказ, возвращаются на карту. Используйте, когда клиент передумал или онлайн-оплата не прошла.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|---|---|---|\n| `id` | string (uuid) | ID заказа. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `id` | string (uuid) | Заказ. |\n| `status` | string | `Cancelled`. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 404 | — | Заказ не существует, удалён или пришёл не через API (его создал продавец в панели или на кассе). Пустое тело. |\n| 400 | — | `id` не является GUID. |\n| 422 | `TRADE_ORDER_API_CANCEL_GOODS_LEFT` | «Заказ 0000000042 больше нельзя отменить через API: товар уже начал уходить (часть отгружена, выдана или заказ закрыт). Возврат оформляет компания.» |\n| 422 | `TRADE_ORDER_CANCEL_HANDED_TO_CARRIER` | Посылка уже передана курьеру; возврат запрашивает продавец. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Повтор по уже отменённому заказу отвечает `200` с `status: Cancelled`, без ошибки: его можно повторить после таймаута.\n- Отмена вызывает вебхук `order.cancelled` с `reason: external_cancelled`.\n- Продавец может вернуть заказ в черновик из панели; тогда он возвращается без бонусов (бонусы вернулись на карту при отмене).\n- Отгруженный заказ, даже частично, больше нельзя отменить через API: возврат оформляет продавец."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders/:id/cancel",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders",
                    ":id",
                    "cancel"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "orderId din „Creează comandă”"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"3c9a1f52-7d3e-4b8a-a0c4-6e2b91f0d7a8\",\n  \"status\": \"Cancelled\"\n}"
            },
            {
              "name": "422 — товар уже ушёл",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders/:id/cancel",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders",
                    ":id",
                    "cancel"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "orderId din „Creează comandă”"
                    }
                  ]
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"TRADE_ORDER_API_CANCEL_GOODS_LEFT\",\n  \"status\": 422,\n  \"detail\": \"Comanda 0000000042 nu se mai poate anula din API: marfa a început să plece (o parte e livrată, ridicată sau comanda e închisă). Returul îl face firma.\",\n  \"errorCode\": \"TRADE_ORDER_API_CANCEL_GOODS_LEFT\"\n}"
            }
          ],
          "_anchorName": "Anulează comanda"
        }
      ],
      "_anchorName": "4. Comenzi"
    },
    {
      "name": "5. Продажи",
      "description": "Продажи продавца: Z-отчёты с кассы, день за днём, и все продажи по дням и каналам.",
      "item": [
        {
          "name": "Кассовые аппараты",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/terminals",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "terminals"
              ]
            },
            "description": "**Необходимый scope:** `sales:read`\n\nКассовые аппараты продавца. Связывайте свои кассы по `id` — он стабилен. Ключ, ограниченный определёнными кассами, видит только их.\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[].id` | string (uuid) | ID кассы. |\n| `items[].name` | string | Название; может быть пустым. |\n| `items[].code` | string | Код кассы (`0000-00124-QWERT`); может быть пустым. |\n| `items[].model` | string? | Модель. |\n| `items[].factoryNumber` | string? | Заводской номер. |\n| `items[].fiscalRegistrationNumber` | string? | Регистрационный номер в SFS. |\n| `items[].address` | string? | Адрес установки. |\n| `items[].warehouseId` | string (uuid)? | Склад кассы. |\n| `items[].isFiscalRegistered` | boolean | Касса зарегистрирована в SFS. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Список включает и деактивированные кассы (но не удалённые). Пагинации нет."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/terminals",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "terminals"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"5b2d6f3e-8c1a-4f7e-9a2b-3c4d5e6f7a81\",\n      \"name\": \"Casa 1 — Magazin Centru\",\n      \"code\": \"0000-00124-QWERT\",\n      \"model\": \"POSfix\",\n      \"factoryNumber\": \"000000124QWERT\",\n      \"fiscalRegistrationNumber\": \"J403001234\",\n      \"address\": \"mun. Chișinău, str. București 45\",\n      \"warehouseId\": \"0d9e2a8b-1f3c-4b6e-8a7d-2c5f9e1b3a46\",\n      \"isFiscalRegistered\": true\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Case de marcat"
        },
        {
          "name": "Z-отчёты",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/z-reports?from={{monthStart}}&to={{today}}&page=1&pageSize=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "z-reports"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "{{monthStart}}",
                  "description": "yyyy-MM-dd, обязательно."
                },
                {
                  "key": "to",
                  "value": "{{today}}",
                  "description": "yyyy-MM-dd, обязательно, ≥ from."
                },
                {
                  "key": "terminalId",
                  "value": "",
                  "description": "Id кассы; можно повторять.",
                  "disabled": true
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": "Начиная с 1."
                },
                {
                  "key": "pageSize",
                  "value": "50",
                  "description": "1–200, по умолчанию 50."
                }
              ]
            },
            "description": "**Необходимый scope:** `sales:read`\n\nZ-отчёты, закрытые за период, в порядке закрытия. С них начинается импорт продаж в вашу бухгалтерию: один Z = один день продаж на одной кассе.\n\n### Параметры запроса\n\n| Параметр | Тип | По умолчанию | Описание |\n|---|---|---|---|\n| `from` / `to` | string | — | Обязательны, `yyyy-MM-dd`, включительно; `to` ≥ `from`. Фильтр по **моменту закрытия**, в сутках UTC. |\n| `terminalId` | string (uuid) | все | Можно повторять: `?terminalId=a&terminalId=b`. |\n| `page` | integer | 1 | ≥ 1. |\n| `pageSize` | integer | 50 | 1–200. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[]` | array | Z-отчёты; поля — в таблице ниже. |\n| `totalCount` | integer | Сколько отчётов соответствует фильтру. |\n| `page`, `pageSize` | integer | Применённые значения. |\n| `totalPages` | integer | Количество страниц; 0, если ничего нет. |\n| `hasNextPage`, `hasPreviousPage` | boolean | Навигация по страницам. |\n| `summary`, `filterableColumns`, `ignoredFilters` | null | Всегда `null`. |\n\n### Z-отчёт\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `id` | string (uuid) | ID Z-отчёта. Используйте его как ключ идемпотентности в своей системе. |\n| `terminalId` | string (uuid) | Касса. |\n| `number` | integer | Номер Z на кассе. |\n| `reportDateTime` | string | Момент закрытия, ISO 8601 UTC. |\n| `tradingDay` | string | Торговый день, `yyyy-MM-dd`: день первой продажи в Z-отчёте. Датируйте по нему бухгалтерские документы. |\n| `warehouseId` | string (uuid)? | Склад. |\n| `cashierName` | string? | Кассир. |\n| `totalSales`, `totalReturns` | number | Продажи и возвраты, в леях, с НДС. |\n| `totalDiscounts` | number | Скидки, в леях. |\n| `netRevenue` | number | `totalSales − totalReturns`. За вычетом возвратов, **но не** НДС. |\n| `totalVat` | number | НДС за день, в леях. |\n| `cashAmount`, `cardAmount`, `otherAmount` | number | Поступления: наличные, карта, остальные формы оплаты. |\n| `receiptsCount`, `returnsCount` | integer | Чеки и возвраты. |\n| `sealedAt` | string? | Момент запечатывания Z-отчёта, ISO 8601 UTC. |\n| `fiscalSeal` | string? | Хеш SHA-256 (64 hex-символа) Z-отчёта. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `INTEGRATIONS_API_DATE_REQUIRED`, `INTEGRATIONS_API_DATE_INVALID`, `COMMON_DATE_ON_OR_AFTER`, `COMMON_BETWEEN` | „Parametrul este obligatoriu: o dată în formatul yyyy-MM-dd.” (Параметр обязателен: дата в формате yyyy-MM-dd.), „Data trebuie să fie cel mai devreme 01.09.2026.” (Дата должна быть не ранее 01.09.2026.), „Valoarea trebuie să fie între 1 și 200.” (Значение должно быть от 1 до 200.) — в `errors` и `errorCodes`, по полю (формат привязки). |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Молдова находится в UTC+2/+3: Z, закрытый в 01:30 по местному времени, при фильтрации попадает на предыдущий день.\n- Ключ, ограниченный определёнными кассами, видит только их отчёты. Фильтр по неразрешённой кассе возвращает пустую страницу, а не `403`."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/z-reports?from={{monthStart}}&to={{today}}&page=1&pageSize=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "z-reports"
                  ],
                  "query": [
                    {
                      "key": "from",
                      "value": "{{monthStart}}",
                      "description": "yyyy-MM-dd, obligatoriu."
                    },
                    {
                      "key": "to",
                      "value": "{{today}}",
                      "description": "yyyy-MM-dd, obligatoriu, ≥ from."
                    },
                    {
                      "key": "terminalId",
                      "value": "",
                      "description": "Id de casă; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "De la 1."
                    },
                    {
                      "key": "pageSize",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"8f1c2b7a-3d4e-4f5a-9b6c-7d8e9f0a1b2c\",\n      \"terminalId\": \"5b2d6f3e-8c1a-4f7e-9a2b-3c4d5e6f7a81\",\n      \"number\": 67,\n      \"reportDateTime\": \"2026-09-24T17:47:31Z\",\n      \"tradingDay\": \"2026-09-24\",\n      \"warehouseId\": \"0d9e2a8b-1f3c-4b6e-8a7d-2c5f9e1b3a46\",\n      \"cashierName\": \"Ana Rusu\",\n      \"totalSales\": 282,\n      \"totalReturns\": 89.9,\n      \"totalDiscounts\": 0,\n      \"netRevenue\": 192.1,\n      \"totalVat\": 22.56,\n      \"cashAmount\": 102.2,\n      \"cardAmount\": 89.9,\n      \"otherAmount\": 0,\n      \"receiptsCount\": 4,\n      \"returnsCount\": 1,\n      \"sealedAt\": \"2026-09-24T17:47:33Z\",\n      \"fiscalSeal\": \"3f9a0c1e5b7d2a4f6e8c0b1d3f5a7c9e1b3d5f7a9c0e2b4d6f8a0c2e4b6d8f0a\"\n    }\n  ],\n  \"totalCount\": 1,\n  \"page\": 1,\n  \"pageSize\": 50,\n  \"summary\": null,\n  \"filterableColumns\": null,\n  \"ignoredFilters\": null,\n  \"totalPages\": 1,\n  \"hasNextPage\": false,\n  \"hasPreviousPage\": false\n}"
            },
            {
              "name": "400 — не указан период",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/z-reports?from={{monthStart}}&to={{today}}&page=1&pageSize=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "z-reports"
                  ],
                  "query": [
                    {
                      "key": "from",
                      "value": "{{monthStart}}",
                      "description": "yyyy-MM-dd, obligatoriu."
                    },
                    {
                      "key": "to",
                      "value": "{{today}}",
                      "description": "yyyy-MM-dd, obligatoriu, ≥ from."
                    },
                    {
                      "key": "terminalId",
                      "value": "",
                      "description": "Id de casă; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "De la 1."
                    },
                    {
                      "key": "pageSize",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    }
                  ]
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/problem+json"
                }
              ],
              "body": "{\n  \"statusCode\": 400,\n  \"message\": \"One or more errors occurred!\",\n  \"errors\": {\n    \"from\": [\n      \"Parametrul este obligatoriu: o dată în formatul yyyy-MM-dd.\"\n    ]\n  },\n  \"errorCodes\": {\n    \"from\": [\n      {\n        \"code\": \"INTEGRATIONS_API_DATE_REQUIRED\",\n        \"params\": {}\n      }\n    ]\n  }\n}"
            }
          ],
          "_anchorName": "Rapoarte Z"
        },
        {
          "name": "Z-отчёт",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/z-reports/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "z-reports",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "id из «Z-отчёты»"
                }
              ]
            },
            "description": "**Необходимый scope:** `sales:read`\n\nZ-отчёт с продажами, сгруппированными по товарам, формами оплаты и НДС по ставкам. Это всё, что нужно, чтобы провести день в бухгалтерии.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|---|---|---|\n| `id` | string (uuid) | ID Z-отчёта. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `report` | object | Z-отчёт с полями, как в разделе «Z-отчёты». |\n| `lines[]` | array | Продажи за день, сгруппированные по товару × ставке НДС × направлению (продажа/возврат). Один и тот же товар может встречаться в нескольких строках; у возврата количество и итог отрицательные. |\n| `lines[].productCode`, `productName` | string | Код и название; могут быть пустыми. |\n| `lines[].productId` | string (uuid)? | Товар из Номенклатуры; `null`, если позиция на кассе не связана с Номенклатурой. |\n| `lines[].posProductId` | string (uuid)? | Карточка товара на кассе. |\n| `lines[].unit` | string? | Единица на чеке (`buc`, `kg`). |\n| `lines[].quantity`, `lines[].total` | number | Количество и итог **с НДС**, в леях. |\n| `lines[].vatCode` | string | 20% → `A`, 8% → `B`, иначе буква на чеке (`C`, `D`, `_` = без НДС). |\n| `lines[].vatAmount`, `lines[].vatPercent` | number | Фактически полученный НДС и ставка. Не пересчитывайте НДС по букве. |\n| `lines[].discountAmount` | number | Скидка, предоставленная по чекам для этой строки, уже вычтена из `total`. `total` + `discountAmount` = сумма до скидки. |\n| `tenders[]` | array | Поступления по формам оплаты: `code`, `amount`, `isCash`. По ним проводятся расчёты по оплатам. |\n| `vat` | object | `totalA`…`totalD` (с НДС) и `vatA`…`vatD` по ставкам; `total` = сумма строк. Буква `_` и неизвестные буквы попадают в `D`. |\n| `cardVoidsTotal` | number | Оплаты картой, отменённые до расчёта банка: банк перечислит меньше, чем показывает `tenders`. |\n| `cardRefundsTotal` | number | Возвраты на карту после расчёта банка; относятся к более позднему периоду. |\n\n### Коды в `tenders[].code`\n\n`cash` · `card` · `voucher` · `check` · `ticket` · `mealTicket` · `subscription` · `transfer` · `credit` · `leasing` · `advance` · `deposit` · `pledge` · `compensation` · `otherCredit`. `other` встречается только в старых отчётах без разбивки. `isCash` равно `true` только для `cash`.\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 404 | — | Z-отчёт не существует, принадлежит другому продавцу или его касса не разрешена для ключа. Пустое тело. |\n| 400 | — | `id` не является GUID (формат привязки). |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/z-reports/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "z-reports",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "id din „Rapoarte Z”"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"report\": {\n    \"id\": \"8f1c2b7a-3d4e-4f5a-9b6c-7d8e9f0a1b2c\",\n    \"terminalId\": \"5b2d6f3e-8c1a-4f7e-9a2b-3c4d5e6f7a81\",\n    \"number\": 67,\n    \"reportDateTime\": \"2026-09-24T17:47:31Z\",\n    \"tradingDay\": \"2026-09-24\",\n    \"warehouseId\": \"0d9e2a8b-1f3c-4b6e-8a7d-2c5f9e1b3a46\",\n    \"cashierName\": \"Ana Rusu\",\n    \"totalSales\": 282,\n    \"totalReturns\": 89.9,\n    \"totalDiscounts\": 0,\n    \"netRevenue\": 192.1,\n    \"totalVat\": 22.56,\n    \"cashAmount\": 102.2,\n    \"cardAmount\": 89.9,\n    \"otherAmount\": 0,\n    \"receiptsCount\": 4,\n    \"returnsCount\": 1,\n    \"sealedAt\": \"2026-09-24T17:47:33Z\",\n    \"fiscalSeal\": \"3f9a0c1e5b7d2a4f6e8c0b1d3f5a7c9e1b3d5f7a9c0e2b4d6f8a0c2e4b6d8f0a\"\n  },\n  \"lines\": [\n    {\n      \"productCode\": \"4840167001234\",\n      \"productId\": \"a1b2c3d4-0000-4000-8000-000000000001\",\n      \"posProductId\": \"b1c2d3e4-0000-4000-8000-000000000001\",\n      \"productName\": \"Lapte Fabrikant 2,5% 1L\",\n      \"unit\": \"buc\",\n      \"quantity\": 3,\n      \"total\": 59.7,\n      \"vatCode\": \"B\",\n      \"vatAmount\": 4.42,\n      \"vatPercent\": 8,\n      \"discountAmount\": 0\n    },\n    {\n      \"productCode\": \"4840001000057\",\n      \"productId\": \"a1b2c3d4-0000-4000-8000-000000000002\",\n      \"posProductId\": \"b1c2d3e4-0000-4000-8000-000000000002\",\n      \"productName\": \"Pâine albă 500 g\",\n      \"unit\": \"buc\",\n      \"quantity\": 5,\n      \"total\": 42.5,\n      \"vatCode\": \"B\",\n      \"vatAmount\": 3.15,\n      \"vatPercent\": 8,\n      \"discountAmount\": 0\n    },\n    {\n      \"productCode\": \"8714599101230\",\n      \"productId\": \"a1b2c3d4-0000-4000-8000-000000000003\",\n      \"posProductId\": \"b1c2d3e4-0000-4000-8000-000000000003\",\n      \"productName\": \"Cafea Jacobs Monarch 250 g\",\n      \"unit\": \"buc\",\n      \"quantity\": 2,\n      \"total\": 179.8,\n      \"vatCode\": \"A\",\n      \"vatAmount\": 29.97,\n      \"vatPercent\": 20,\n      \"discountAmount\": 0\n    },\n    {\n      \"productCode\": \"8714599101230\",\n      \"productId\": \"a1b2c3d4-0000-4000-8000-000000000003\",\n      \"posProductId\": \"b1c2d3e4-0000-4000-8000-000000000003\",\n      \"productName\": \"Cafea Jacobs Monarch 250 g\",\n      \"unit\": \"buc\",\n      \"quantity\": -1,\n      \"total\": -89.9,\n      \"vatCode\": \"A\",\n      \"vatAmount\": -14.98,\n      \"vatPercent\": 20,\n      \"discountAmount\": 0\n    }\n  ],\n  \"tenders\": [\n    {\n      \"code\": \"cash\",\n      \"amount\": 102.2,\n      \"isCash\": true\n    },\n    {\n      \"code\": \"card\",\n      \"amount\": 89.9,\n      \"isCash\": false\n    }\n  ],\n  \"vat\": {\n    \"totalA\": 89.9,\n    \"totalB\": 102.2,\n    \"totalC\": 0,\n    \"totalD\": 0,\n    \"vatA\": 14.99,\n    \"vatB\": 7.57,\n    \"vatC\": 0,\n    \"vatD\": 0,\n    \"total\": 192.1\n  },\n  \"cardVoidsTotal\": 0,\n  \"cardRefundsTotal\": 0\n}"
            }
          ],
          "_anchorName": "Raport Z"
        },
        {
          "name": "Чеки Z-отчёта",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/z-reports/:id/receipts?page=1&pageSize=100",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "z-reports",
                ":id",
                "receipts"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1",
                  "description": "Начиная с 1."
                },
                {
                  "key": "pageSize",
                  "value": "100",
                  "description": "1–500, по умолчанию 100."
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "id из «Z-отчёты»"
                }
              ]
            },
            "description": "**Необходимый scope:** `sales:read`\n\nИсходные чеки одного Z-отчёта, с позициями и оплатами каждого, в порядке оформления. Для бухгалтерии достаточно запроса «Z-отчёт»; чеки нужны для сверки и для анализа по клиентам или по часам.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|---|---|---|\n| `id` | string (uuid) | ID Z-отчёта. |\n\n### Параметры запроса\n\n| Параметр | Тип | По умолчанию | Описание |\n|---|---|---|---|\n| `page` | integer | 1 | ≥ 1. |\n| `pageSize` | integer | 100 | 1–500. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[]` | array | Чеки. |\n| `items[].id`, `transactionNumber` | string, integer | ID и номер транзакции. |\n| `items[].receiptNumber` | integer? | Номер чека. |\n| `items[].fiscalReceiptNumber`, `sfsFiscalCode`, `sfsReceiptUrl` | string? | Фискальные данные; адрес проверки в SFS. |\n| `items[].dateTime` | string | ISO 8601 UTC. |\n| `items[].cashierName` | string? | Кассир. |\n| `items[].total`, `vat`, `discount`, `change` | number | Итог с НДС, НДС, скидка, выданная сдача. **Отрицательные при возврате.** |\n| `items[].currency` | string | Валюта (`MDL`). |\n| `items[].isRefund`, `isFiscal` | boolean | Возврат; фискальный чек. |\n| `items[].lines[]` | array | `productCode`, `productId`, `posProductId`, `productName`, `unit`, `quantity` (отрицательное при возврате), `unitPrice`, `total`, `discountAmount`, `vatCode` (буква на чеке), `vatAmount`, `vatPercent`. |\n| `items[].payments[]` | array | `code` (код ACPS на чеке: `\"1\"` наличные, `\"2\"` карта…), `name`, `amount` (при оплате наличными: сумма, которую дал клиент), `cardMask`, `reference` (RRN), `voidedAmount`, `refundedAmount`, `reversedAt`. |\n| `totalCount`, `page`, `pageSize` | integer | Пагинация. |\n| `terminalId` | string (uuid) | Касса Z-отчёта. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 404 | — | Z-отчёт не существует, принадлежит другому продавцу или его касса не разрешена для ключа. Пустое тело. |\n| 400 | — | `id` не является GUID, или `page`/`pageSize` вне допустимых пределов. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Коды оплаты здесь — коды ACPS с чека, **другой словарь**, чем `tenders[].code` в «Z-отчёте»."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/z-reports/:id/receipts?page=1&pageSize=100",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "z-reports",
                    ":id",
                    "receipts"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "De la 1."
                    },
                    {
                      "key": "pageSize",
                      "value": "100",
                      "description": "1–500, implicit 100."
                    }
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "id din „Rapoarte Z”"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"c7d8e9f0-1a2b-4c3d-8e4f-5a6b7c8d9e0f\",\n      \"transactionNumber\": 1051,\n      \"receiptNumber\": 41,\n      \"fiscalReceiptNumber\": null,\n      \"dateTime\": \"2026-09-24T07:12:05Z\",\n      \"cashierName\": \"Ana Rusu\",\n      \"total\": 76.7,\n      \"vat\": 5.68,\n      \"discount\": 0,\n      \"change\": 23.3,\n      \"currency\": \"MDL\",\n      \"isRefund\": false,\n      \"isFiscal\": true,\n      \"sfsFiscalCode\": \"7F3A9C21\",\n      \"sfsReceiptUrl\": \"https://mev.sfs.md/receipt-verifier/7F3A9C21\",\n      \"lines\": [\n        {\n          \"productCode\": \"4840167001234\",\n          \"productId\": \"a1b2c3d4-0000-4000-8000-000000000001\",\n          \"posProductId\": \"b1c2d3e4-0000-4000-8000-000000000001\",\n          \"productName\": \"Lapte Fabrikant 2,5% 1L\",\n          \"unit\": \"buc\",\n          \"quantity\": 3,\n          \"unitPrice\": 19.9,\n          \"total\": 59.7,\n          \"discountAmount\": 0,\n          \"vatCode\": \"B\",\n          \"vatAmount\": 4.42,\n          \"vatPercent\": 8\n        },\n        {\n          \"productCode\": \"4840001000057\",\n          \"productId\": \"a1b2c3d4-0000-4000-8000-000000000002\",\n          \"posProductId\": \"b1c2d3e4-0000-4000-8000-000000000002\",\n          \"productName\": \"Pâine albă 500 g\",\n          \"unit\": \"buc\",\n          \"quantity\": 2,\n          \"unitPrice\": 8.5,\n          \"total\": 17,\n          \"discountAmount\": 0,\n          \"vatCode\": \"B\",\n          \"vatAmount\": 1.26,\n          \"vatPercent\": 8\n        }\n      ],\n      \"payments\": [\n        {\n          \"code\": \"1\",\n          \"name\": \"NUMERAR\",\n          \"amount\": 100,\n          \"cardMask\": null,\n          \"reference\": null,\n          \"voidedAmount\": 0,\n          \"refundedAmount\": 0,\n          \"reversedAt\": null\n        }\n      ]\n    }\n  ],\n  \"totalCount\": 1,\n  \"page\": 1,\n  \"pageSize\": 100,\n  \"terminalId\": \"5b2d6f3e-8c1a-4f7e-9a2b-3c4d5e6f7a81\"\n}"
            }
          ],
          "_anchorName": "Bonurile unui Z"
        },
        {
          "name": "Продажи по дням и каналам",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/sales/daily?from={{monthStart}}&to={{today}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "sales",
                "daily"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "{{monthStart}}",
                  "description": "yyyy-MM-dd, обязательно."
                },
                {
                  "key": "to",
                  "value": "{{today}}",
                  "description": "yyyy-MM-dd, обязательно, не более 366 дней после from."
                },
                {
                  "key": "channelId",
                  "value": "",
                  "description": "Канал продаж.",
                  "disabled": true
                },
                {
                  "key": "terminalId",
                  "value": "",
                  "description": "Id кассы; можно повторять.",
                  "disabled": true
                }
              ]
            },
            "description": "**Необходимый scope:** `sales:read`\n\nВсе продажи продавца, сложенные по дням и каналам продаж: кассовые чеки, счета-фактуры, возвраты и отчёты о розничных продажах. Это те же цифры, что продавец видит на экране «Продажи» и что отправляют вебхуки `sale.*`.\n\n### Параметры запроса\n\n| Параметр | Тип | По умолчанию | Описание |\n|---|---|---|---|\n| `from` / `to` | string | — | Обязательны, `yyyy-MM-dd`, включительно; `to` ≥ `from`, не более 366 дней. Фильтр по **дню продажи**, время Молдовы. |\n| `channelId` | string (uuid) | все | Один канал продаж. |\n| `terminalId` | string (uuid) | все | Повторяемый. При выбранных кассах учитываются только их продажи — у счетов-фактур нет кассы, поэтому они не входят. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[]` | array | Одна строка на день и канал, по порядку дней. |\n| `items[].date` | string | День, `yyyy-MM-dd`. |\n| `items[].channelId`, `channelName` | string? | Канал продаж; `null`, если у документа нет канала. |\n| `items[].channelType` | string | `Store`, `DirectSales`, `Online`, `SocialNetwork`, `Phone`, `Marketplace`, `Vending` или `Other`. |\n| `items[].documents` | integer | Сколько документов (чеки, счета-фактуры, возвраты) входит в строку. |\n| `items[].netAmount` | number | Продажи без НДС, леи, за вычетом возвратов. |\n| `items[].vatAmount` | number | НДС, леи. |\n| `items[].totalAmount` | number | `netAmount + vatAmount`. |\n| `currency` | string | Всегда `MDL`. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `INTEGRATIONS_API_DATE_REQUIRED`, `INTEGRATIONS_API_DATE_INVALID`, `COMMON_DATE_ON_OR_AFTER`, `INTEGRATIONS_API_PERIOD_TOO_LONG` | «Период может быть не длиннее 366 дней. Запросите отчёт по частям.» — в `errors` и `errorCodes`, по полю (формат привязки). |\n| 422 | `REPORTS_SALES_LEDGER_DISABLED` | «Общий реестр продаж ещё не включён в этой среде. Продажи остаются в кассовых и бухгалтерских отчётах.» |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- У отчёта нет пагинации: в году не больше 366 дней × число каналов продавца.\n- Продажи последних минут могут отсутствовать: реестр пополняется из событий, обычно за несколько секунд.\n- Ключ, ограниченный определёнными кассами, видит только их продажи. Фильтр по запрещённой кассе возвращает пустой список, а не `403`."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/sales/daily?from={{monthStart}}&to={{today}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "sales",
                    "daily"
                  ],
                  "query": [
                    {
                      "key": "from",
                      "value": "{{monthStart}}",
                      "description": "yyyy-MM-dd, obligatoriu."
                    },
                    {
                      "key": "to",
                      "value": "{{today}}",
                      "description": "yyyy-MM-dd, obligatoriu, cel mult 366 de zile după from."
                    },
                    {
                      "key": "channelId",
                      "value": "",
                      "description": "Canalul de vânzare.",
                      "disabled": true
                    },
                    {
                      "key": "terminalId",
                      "value": "",
                      "description": "Id de casă; repetabil.",
                      "disabled": true
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"date\": \"2026-09-24\",\n      \"channelId\": \"3c1e9a7b-5d2f-4b8e-9a1c-7e5d3b2f1a90\",\n      \"channelName\": \"Magazin\",\n      \"channelType\": \"Store\",\n      \"documents\": 41,\n      \"netAmount\": 9634.26,\n      \"vatAmount\": 1926.85,\n      \"totalAmount\": 11561.11\n    }\n  ],\n  \"currency\": \"MDL\"\n}"
            },
            {
              "name": "400 — слишком длинный период",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/sales/daily?from={{monthStart}}&to={{today}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "sales",
                    "daily"
                  ],
                  "query": [
                    {
                      "key": "from",
                      "value": "{{monthStart}}",
                      "description": "yyyy-MM-dd, obligatoriu."
                    },
                    {
                      "key": "to",
                      "value": "{{today}}",
                      "description": "yyyy-MM-dd, obligatoriu, cel mult 366 de zile după from."
                    },
                    {
                      "key": "channelId",
                      "value": "",
                      "description": "Canalul de vânzare.",
                      "disabled": true
                    },
                    {
                      "key": "terminalId",
                      "value": "",
                      "description": "Id de casă; repetabil.",
                      "disabled": true
                    }
                  ]
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/problem+json"
                }
              ],
              "body": "{\n  \"statusCode\": 400,\n  \"message\": \"One or more errors occurred!\",\n  \"errors\": {\n    \"to\": [\n      \"Perioada poate avea cel mult 366 de zile. Cereți raportul pe bucăți.\"\n    ]\n  },\n  \"errorCodes\": {\n    \"to\": [\n      {\n        \"code\": \"INTEGRATIONS_API_PERIOD_TOO_LONG\",\n        \"params\": {\n          \"days\": 366\n        }\n      }\n    ]\n  }\n}"
            },
            {
              "name": "422 — реестр не включён",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/sales/daily?from={{monthStart}}&to={{today}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "sales",
                    "daily"
                  ],
                  "query": [
                    {
                      "key": "from",
                      "value": "{{monthStart}}",
                      "description": "yyyy-MM-dd, obligatoriu."
                    },
                    {
                      "key": "to",
                      "value": "{{today}}",
                      "description": "yyyy-MM-dd, obligatoriu, cel mult 366 de zile după from."
                    },
                    {
                      "key": "channelId",
                      "value": "",
                      "description": "Canalul de vânzare.",
                      "disabled": true
                    },
                    {
                      "key": "terminalId",
                      "value": "",
                      "description": "Id de casă; repetabil.",
                      "disabled": true
                    }
                  ]
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"REPORTS_SALES_LEDGER_DISABLED\",\n  \"status\": 422,\n  \"detail\": \"Registrul comun de vânzări nu e pornit încă pe acest mediu. Vânzările rămân în rapoartele de casă și de contabilitate.\",\n  \"errorCode\": \"REPORTS_SALES_LEDGER_DISABLED\"\n}"
            }
          ],
          "_anchorName": "Vânzări pe zile și canale"
        }
      ],
      "_anchorName": "5. Vânzări"
    },
    {
      "name": "6. Вебхуки",
      "description": "События, отправляемые на ваш адрес: товары, остатки, заказы, продажи, распознанные накладные, лояльность. Контракт доставки — в описании коллекции.",
      "item": [
        {
          "name": "Создать вебхук",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "webhooks"
              ]
            },
            "description": "**Необходимый scope:** `webhooks:manage`\n\nПодписывает ваш адрес на события. Ответ содержит секрет подписи — это **единственный раз**, когда вы его видите.\n\n### Тело запроса\n\n| Поле | Тип | Обязательное | Правила |\n|---|---|---|---|\n| `url` | string | да | Адрес `https`, не более 2048 символов. Частный IP-адрес отклоняется. |\n| `eventTypes[]` | string[] | да | Хотя бы одно событие из списка в описании коллекции. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `id` | string (uuid) | ID подписки. |\n| `url` | string | Адрес. |\n| `eventTypes[]` | string[] | События, строчными буквами, без повторов. |\n| `signingSecret` | string | `whsec_…` — ключ, которым вы проверяете подпись. Его нельзя прочитать повторно и нельзя изменить: для нового секрета удалите подписку и создайте новую. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `VALIDATION` | „Adresa webhook-ului trebuie să folosească https.” (Адрес webhook должен использовать https.), „Alegeți cel puțin un tip de eveniment.” (Выберите хотя бы один тип события.), „Tip de eveniment necunoscut. Tipurile permise: ….” (Неизвестный тип события. Допустимые типы: ….) — код каждой ошибки в `errorCodes`, по полю. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Ответ — `200`, а не `201`.\n- Один и тот же адрес, зарегистрированный дважды, получает каждое событие дважды.\n- Доменное имя проверяется при каждой отправке, а не при регистрации: домен, ведущий на частный адрес, здесь принимается, но доставки завершаются ошибкой.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://erp.magazin.md/hooks/posfix\",\n  \"eventTypes\": [\n    \"order.created\",\n    \"order.paid\",\n    \"inventory.updated\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/webhooks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "webhooks"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"url\": \"https://erp.magazin.md/hooks/posfix\",\n  \"eventTypes\": [\n    \"order.created\",\n    \"order.paid\",\n    \"inventory.updated\"\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"e3b5c1d2-7a8f-4c6e-9d0b-1a2b3c4d5e6f\",\n  \"url\": \"https://erp.magazin.md/hooks/posfix\",\n  \"eventTypes\": [\n    \"order.created\",\n    \"order.paid\",\n    \"inventory.updated\"\n  ],\n  \"signingSecret\": \"whsec_Q2x4bE9mY2J3aVZ5dGNWa0Q2ZUx0ZlQ5c2h1Tk1jUgA\"\n}"
            },
            {
              "name": "400 — адрес http",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/webhooks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "webhooks"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"url\": \"https://erp.magazin.md/hooks/posfix\",\n  \"eventTypes\": [\n    \"order.created\",\n    \"order.paid\",\n    \"inventory.updated\"\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/400\",\n  \"title\": \"VALIDATION\",\n  \"status\": 400,\n  \"detail\": \"Adresa webhook-ului trebuie să folosească https.\",\n  \"errorCode\": \"VALIDATION\",\n  \"errors\": {\n    \"url\": [\n      \"Adresa webhook-ului trebuie să folosească https.\"\n    ]\n  },\n  \"errorCodes\": {\n    \"url\": [\n      {\n        \"code\": \"INTEGRATIONS_WEBHOOK_URL_HTTPS\",\n        \"params\": {}\n      }\n    ]\n  }\n}"
            }
          ],
          "_anchorName": "Creează webhook"
        },
        {
          "name": "Список вебхуков",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "webhooks"
              ]
            },
            "description": "**Необходимый scope:** `webhooks:manage`\n\nАктивные подписки, сначала самые новые. Секрет не возвращается.\n\n### Ответ 200 — список\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `[].id` | string (uuid) | ID подписки. |\n| `[].url` | string | Адрес. |\n| `[].eventTypes[]` | string[] | События. |\n| `[].isActive` | boolean | Всегда `true`: подписка отключается только удалением. |\n| `[].createdAt` | string | ISO 8601 UTC. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Ответ — сразу JSON-список, без пагинации."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/webhooks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "webhooks"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "[\n  {\n    \"id\": \"e3b5c1d2-7a8f-4c6e-9d0b-1a2b3c4d5e6f\",\n    \"url\": \"https://erp.magazin.md/hooks/posfix\",\n    \"eventTypes\": [\n      \"order.created\",\n      \"order.paid\",\n      \"inventory.updated\"\n    ],\n    \"isActive\": true,\n    \"createdAt\": \"2026-09-25T08:02:11.482913Z\"\n  }\n]"
            }
          ],
          "_anchorName": "Listă webhook-uri"
        },
        {
          "name": "Удалить webhook",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/webhooks/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "webhooks",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "id из «Создать вебхук»"
                }
              ]
            },
            "description": "**Необходимый scope:** `webhooks:manage`\n\nОстанавливает подписку. Доставки из очереди больше не отправляются.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|---|---|---|\n| `id` | string (uuid) | ID подписки. |\n\n### Ответ\n\n**204** без тела.\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 404 | `INTEGRATIONS_RESOURCE_NOT_FOUND` | Id неизвестен, принадлежит другому продавцу или **уже удалён** (простой формат). |\n| 400 | — | `id` не является GUID. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Повторное удаление того же id даёт `404`, а не `204`."
          },
          "response": [
            {
              "name": "204 — удалено",
              "originalRequest": {
                "method": "DELETE",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/webhooks/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "webhooks",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "id din „Creează webhook”"
                    }
                  ]
                }
              },
              "status": "No Content",
              "code": 204,
              "_postman_previewlanguage": "text",
              "header": [],
              "body": ""
            },
            {
              "name": "404 — неизвестен или уже удалён",
              "originalRequest": {
                "method": "DELETE",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/webhooks/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "webhooks",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "id din „Creează webhook”"
                    }
                  ]
                }
              },
              "status": "Not Found",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/404\",\n  \"title\": \"Not Found\",\n  \"status\": 404,\n  \"detail\": \"Resursa cerută nu există sau nu aparține acestui comerciant.\",\n  \"errorCode\": \"INTEGRATIONS_RESOURCE_NOT_FOUND\"\n}"
            }
          ],
          "_anchorName": "Șterge webhook"
        }
      ],
      "_anchorName": "6. Webhook-uri"
    },
    {
      "name": "7. Распознавание накладных",
      "description": "Накладные в PDF или на фото → структурированные данные, сопоставленные с вашей номенклатурой. Шаги: (1) загружаете номенклатуру (один раз, затем только изменения); (2) отправляете файл и получаете `202` с id; (3) ждёте вебхук `docai.recognition.completed` или опрашиваете по id каждые 10 секунд; (4) читаете накладную и импортируете её; (5) подтверждаете через `ack`. Сервис платный: каждый новый файл списывается из пакета продавца.",
      "item": [
        {
          "name": "Номенклатура — загрузка / обновление",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/docai/catalog/items",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "docai",
                "catalog",
                "items"
              ]
            },
            "description": "**Необходимый scope:** `docai:catalog`\n\nНоменклатура **вашей программы** (1C, ERP), с которой сопоставляются строки распознанных накладных. Ваш код (`externalId`) — это ключ: повторная отправка обновляет позицию, а не дублирует её. Отправляйте номенклатуру частями не более 1000 позиций. Ничего не удаляется — позицию, выведенную из использования, отправляйте с `isActive: false`.\n\n### Тело запроса\n\n| Поле | Тип | Обязательное | Правила |\n|---|---|---|---|\n| `items[]` | array | да | 1–1000 позиций; каждый код — не более одного раза в запросе. |\n| `items[].externalId` | string | да | Ваш код позиции, не более 100 символов. |\n| `items[].name` | string | да, для активных | Не более 500 символов. |\n| `items[].unit` | string | нет | Ваша единица измерения, не более 50; не пересчитывается. |\n| `items[].barcodes` | string[] | нет | Штрихкоды. Сохраняются только штрихкоды из 8–14 цифр; остальные учитываются в `ignoredBarcodes`. |\n| `items[].isActive` | boolean | нет | По умолчанию `true`. `false` = выведена (больше не предлагается). |\n| `items[].nomenclatureType` | string | нет | Вид номенклатуры у вас (например, Молочные продукты), не более 200. Без него позиция остаётся без вида. Возвращается под тем же именем в списке, в `match` и в выгрузке. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `created`, `updated`, `unchanged`, `deactivated` | integer | Что произошло с отправленными позициями. |\n| `ignoredBarcodes` | integer | Отброшенные штрихкоды (не из 8–14 цифр). |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `DOCAI_CATALOG_API_TOO_MANY_ITEMS`, `DOCAI_CATALOG_API_DUPLICATE_CODE`, валидация поля | „O cerere poate avea cel mult 1.000 de poziții, iar aceasta are 1.200. Trimiteți nomenclatorul în mai multe cereri.” (Запрос может содержать не более 1000 позиций, а в этом 1200. Отправьте номенклатуру несколькими запросами.) |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Как только ваша программа ведёт номенклатуру через API, загрузка её из Excel в Панели управления отклоняется — иначе следующая синхронизация перезаписала бы то, что загрузил человек.\n- У позиций, созданных людьми в PosFix, коды `DX-…` (они приходят к вам через список, `match` и выгрузку). Запрос, который попадает на код такой позиции, её не перезаписывает — она считается в `unchanged`. Не используйте префикс `DX-` для своих кодов.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"items\": [\n    {\n      \"externalId\": \"LAC-0012\",\n      \"name\": \"Lapte Lactis 2,5% 1l\",\n      \"unit\": \"buc\",\n      \"barcodes\": [\n        \"4840811000181\"\n      ],\n      \"nomenclatureType\": \"Lactate\"\n    },\n    {\n      \"externalId\": \"LAC-0099\",\n      \"isActive\": false\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "PUT",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/catalog/items",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "catalog",
                    "items"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"items\": [\n    {\n      \"externalId\": \"LAC-0012\",\n      \"name\": \"Lapte Lactis 2,5% 1l\",\n      \"unit\": \"buc\",\n      \"barcodes\": [\n        \"4840811000181\"\n      ],\n      \"nomenclatureType\": \"Lactate\"\n    },\n    {\n      \"externalId\": \"LAC-0099\",\n      \"isActive\": false\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"created\": 1,\n  \"updated\": 0,\n  \"unchanged\": 0,\n  \"deactivated\": 1,\n  \"ignoredBarcodes\": 0\n}"
            }
          ],
          "_anchorName": "Nomenclator — încărcare / actualizare"
        },
        {
          "name": "Номенклатура — список",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/docai/catalog/items?includeInactive=false&page=1&pageSize=100",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "docai",
                "catalog",
                "items"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "",
                  "description": "Текст из кода или названия; полный штрихкод ищется точно.",
                  "disabled": true
                },
                {
                  "key": "includeInactive",
                  "value": "false",
                  "description": "Также снятые с продажи позиции."
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": "Начиная с 1."
                },
                {
                  "key": "pageSize",
                  "value": "100",
                  "description": "1–200, по умолчанию 100."
                }
              ]
            },
            "description": "**Необходимый scope:** `docai:catalog`\n\nЧто PosFix получил из вашей номенклатуры, в алфавитном порядке — для проверки синхронизации. В список входят и позиции, созданные людьми в PosFix (коды `DX-…`), чтобы ваша программа их приняла.\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[].externalId`, `name`, `unit`, `barcodes` |  | Как вы их отправили (или как их создал человек — для `DX-…`). |\n| `items[].nomenclatureType` | string? | Вид номенклатуры; `null`, если его нет. |\n| `items[].isActive` | boolean | Позиция предлагается при сопоставлении. |\n| `items[].usageCount` | integer | На скольких строках её выбрали люди при проверке. |\n| `items[].updatedAt` | string | Последнее изменение, ISO 8601 UTC. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/catalog/items?includeInactive=false&page=1&pageSize=100",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "catalog",
                    "items"
                  ],
                  "query": [
                    {
                      "key": "search",
                      "value": "",
                      "description": "Text din cod sau denumire; un cod de bare întreg se caută exact.",
                      "disabled": true
                    },
                    {
                      "key": "includeInactive",
                      "value": "false",
                      "description": "Și pozițiile retrase."
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "De la 1."
                    },
                    {
                      "key": "pageSize",
                      "value": "100",
                      "description": "1–200, implicit 100."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"externalId\": \"DX-000001\",\n      \"name\": \"Chefir Lactis 2,5% 0,5l\",\n      \"unit\": \"buc\",\n      \"barcodes\": [],\n      \"isActive\": true,\n      \"usageCount\": 1,\n      \"updatedAt\": \"2026-11-02T09:12:40Z\",\n      \"nomenclatureType\": \"Lactate\"\n    },\n    {\n      \"externalId\": \"LAC-0012\",\n      \"name\": \"Lapte Lactis 2,5% 1l\",\n      \"unit\": \"buc\",\n      \"barcodes\": [\n        \"4840811000181\"\n      ],\n      \"isActive\": true,\n      \"usageCount\": 7,\n      \"updatedAt\": \"2026-09-27T16:02:11Z\",\n      \"nomenclatureType\": \"Lactate\"\n    }\n  ],\n  \"totalCount\": 2,\n  \"page\": 1,\n  \"pageSize\": 50,\n  \"totalPages\": 1,\n  \"hasNextPage\": false,\n  \"hasPreviousPage\": false,\n  \"summary\": null,\n  \"filterableColumns\": null,\n  \"ignoredFilters\": null\n}"
            }
          ],
          "_anchorName": "Nomenclator — listă"
        },
        {
          "name": "Номенклатура — деактивация",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/docai/catalog/items/:externalId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "docai",
                "catalog",
                "items",
                ":externalId"
              ],
              "variable": [
                {
                  "key": "externalId",
                  "value": "LAC-0099",
                  "description": "Код позиции в вашей системе (URL-encoded)."
                }
              ]
            },
            "description": "**Необходимый scope:** `docai:catalog`\n\nДеактивирует позицию (то же, что `isActive: false`). Не удаляет её: уже сопоставленные с ней накладные остаются привязаны к её коду. Возвращает позицию.\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 404 | — | Кода нет в отправленной номенклатуре. Пустое тело. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "DELETE",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/catalog/items/:externalId",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "catalog",
                    "items",
                    ":externalId"
                  ],
                  "variable": [
                    {
                      "key": "externalId",
                      "value": "LAC-0099",
                      "description": "Codul poziției la tine (URL-encoded)."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"externalId\": \"LAC-0099\",\n  \"name\": \"Chefir 1l\",\n  \"unit\": \"buc\",\n  \"barcodes\": [],\n  \"isActive\": false,\n  \"usageCount\": 0,\n  \"updatedAt\": \"2026-09-28T08:00:00Z\",\n  \"nomenclatureType\": null\n}"
            }
          ],
          "_anchorName": "Nomenclator — retragere"
        },
        {
          "name": "Отправить накладную на распознавание",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/docai/recognitions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "docai",
                "recognitions"
              ]
            },
            "description": "**Необходимый scope:** `docai:recognize`\n\nСтавит файл в очередь распознавания и отвечает сразу, **не** дожидаясь чтения (обычно оно занимает 20–90 секунд). Результат вы получаете вебхуком `docai.recognition.completed` / `docai.recognition.failed` или читаете по id.\n\n### Тело запроса\n\n| Поле | Тип | Обязательное | Правила |\n|---|---|---|---|\n| `file` | файл (multipart) | да | PDF до 12 МБ; фото до 20 МБ. Тип определяется по содержимому (PDF, JPEG, PNG, GIF, WebP); заголовок части учитывается, только если содержимое не распознано. |\n| `documentKind` | string | нет | `supplier_invoice` (накладная, полученная от поставщика, по умолчанию) или `sales_invoice`. |\n| `externalRef` | string | нет | Ваш ключ (например, номер записи в вашей системе). Повторная отправка с тем же ключом возвращает существующее распознавание, без дополнительной оплаты. |\n\n### Ответ\n\n**202** — файл поставлен в очередь. **200** — распознавание уже существовало: тот же `externalRef`, тот же файл ещё в обработке (`existing: true`) или уже распознанный файл, скопированный бесплатно (`duplicate: true`, `status: completed`).\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `id` | string (uuid) | ID распознавания. |\n| `status` | string | `queued` \\| `processing` \\| `completed` \\| `failed`. |\n| `externalRef` | string? | Ваш ключ. |\n| `existing` | boolean | Ничего нового не поставлено в очередь. |\n| `duplicate` | boolean | Результат скопирован с того же файла, уже распознанного ранее. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `DOCAI_FILE_REQUIRED`, `DOCAI_FILE_TYPE_NOT_SUPPORTED`, `DOCAI_FILE_TOO_LARGE`, `DOCAI_PDF_TOO_LARGE`, `DOCAI_DOCUMENT_KIND_UNKNOWN` | „Tipul de fișier „application/zip” nu este acceptat. Se acceptă JPEG, PNG, WebP, GIF și PDF.” (Тип файла „application/zip“ не принимается. Принимаются JPEG, PNG, WebP, GIF и PDF.) |\n| 402 | `DOCAI_FREE_TRIAL_USED`, `DOCAI_MONTHLY_LIMIT_REACHED`, `DOCAI_BATCH_EXCEEDS_MONTHLY_LIMIT`, `DOCAI_LIMIT_DENIED` | Распознавания закончились: без договора — 3 бесплатных; с договором — месячный лимит из него (договор без лимита не отказывает). Простой формат, с `title: \"Payment Required\"`. |\n| 422 | `DOCAI_EXTERNAL_REF_REUSED` | «Внешний ключ „INV-2026-000812“ уже использован для другого документа. Используйте новый ключ для каждого документа.» |\n| 422 | `DOCAI_EXTERNAL_REF_FILE_IN_FLIGHT` | Тот же файл прямо сейчас читается в рамках другого распознавания. Отправьте его повторно с тем же ключом, когда то завершится: вы получите бесплатную копию под своим ключом. |\n| 422 | `DOCAI_DISABLED`, `DOCAI_API_KEY_MISSING` | Распознавание отключено на платформе. Повторите позже. |\n| 503 | `SERVICE_UNAVAILABLE` | Слишком много одновременных отправок или сервис не отвечает. Отправьте повторно с тем же `externalRef` — дважды платить не придётся. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Каждый **новый** файл расходует одно распознавание: первые 3 бесплатны, дальше — столько, сколько предусматривает договор продавца с PosFix (без лимита в договоре = без ограничений). Тот же файл, отправленный повторно, больше не оплачивается, но без `externalRef` он становится новым распознаванием (копией) со своим id и вебхуком — всегда передавайте ключ.\n- Мы **не повторяем отправку автоматически**: при тайм-ауте или `503` отправьте повторно сами, с тем же `externalRef`.\n- Не более двух одновременных отправок на ключ; остальные ждут своей очереди. Не отправляйте десятки файлов параллельно — очередь всё равно обрабатывает их по два.",
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "file",
                  "type": "file",
                  "src": [],
                  "description": "PDF (≤ 12 MB) sau fotografie JPEG/PNG/WebP/GIF (≤ 20 MB)."
                },
                {
                  "key": "documentKind",
                  "type": "text",
                  "value": "supplier_invoice",
                  "description": "supplier_invoice (implicit) | sales_invoice"
                },
                {
                  "key": "externalRef",
                  "type": "text",
                  "value": "INV-2026-000812",
                  "description": "Cheia ta de idempotență, ≤ 100 de caractere."
                }
              ]
            }
          },
          "response": [
            {
              "name": "202",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/recognitions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "recognitions"
                  ]
                },
                "body": {
                  "mode": "formdata",
                  "formdata": [
                    {
                      "key": "file",
                      "type": "file",
                      "src": [],
                      "description": "PDF (≤ 12 MB) sau fotografie JPEG/PNG/WebP/GIF (≤ 20 MB)."
                    },
                    {
                      "key": "documentKind",
                      "type": "text",
                      "value": "supplier_invoice",
                      "description": "supplier_invoice (implicit) | sales_invoice"
                    },
                    {
                      "key": "externalRef",
                      "type": "text",
                      "value": "INV-2026-000812",
                      "description": "Cheia ta de idempotență, ≤ 100 de caractere."
                    }
                  ]
                }
              },
              "status": "Accepted",
              "code": 202,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46\",\n  \"status\": \"queued\",\n  \"externalRef\": \"INV-2026-000812\",\n  \"existing\": false,\n  \"duplicate\": false\n}"
            },
            {
              "name": "200 уже существует",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/recognitions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "recognitions"
                  ]
                },
                "body": {
                  "mode": "formdata",
                  "formdata": [
                    {
                      "key": "file",
                      "type": "file",
                      "src": [],
                      "description": "PDF (≤ 12 MB) sau fotografie JPEG/PNG/WebP/GIF (≤ 20 MB)."
                    },
                    {
                      "key": "documentKind",
                      "type": "text",
                      "value": "supplier_invoice",
                      "description": "supplier_invoice (implicit) | sales_invoice"
                    },
                    {
                      "key": "externalRef",
                      "type": "text",
                      "value": "INV-2026-000812",
                      "description": "Cheia ta de idempotență, ≤ 100 de caractere."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46\",\n  \"status\": \"completed\",\n  \"externalRef\": \"INV-2026-000812\",\n  \"existing\": true,\n  \"duplicate\": false\n}"
            },
            {
              "name": "402",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/recognitions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "recognitions"
                  ]
                },
                "body": {
                  "mode": "formdata",
                  "formdata": [
                    {
                      "key": "file",
                      "type": "file",
                      "src": [],
                      "description": "PDF (≤ 12 MB) sau fotografie JPEG/PNG/WebP/GIF (≤ 20 MB)."
                    },
                    {
                      "key": "documentKind",
                      "type": "text",
                      "value": "supplier_invoice",
                      "description": "supplier_invoice (implicit) | sales_invoice"
                    },
                    {
                      "key": "externalRef",
                      "type": "text",
                      "value": "INV-2026-000812",
                      "description": "Cheia ta de idempotență, ≤ 100 de caractere."
                    }
                  ]
                }
              },
              "status": "Payment Required",
              "code": 402,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/402\",\n  \"title\": \"Payment Required\",\n  \"status\": 402,\n  \"detail\": \"Ați atins limita lunară din contract: 100 din 100 recunoașteri. Pentru mai multe, contactați PosFix ca să mărească limita din contract.\",\n  \"errorCode\": \"DOCAI_MONTHLY_LIMIT_REACHED\",\n  \"params\": {\n    \"used\": 100,\n    \"limit\": 100\n  }\n}"
            },
            {
              "name": "400 тип файла",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/recognitions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "recognitions"
                  ]
                },
                "body": {
                  "mode": "formdata",
                  "formdata": [
                    {
                      "key": "file",
                      "type": "file",
                      "src": [],
                      "description": "PDF (≤ 12 MB) sau fotografie JPEG/PNG/WebP/GIF (≤ 20 MB)."
                    },
                    {
                      "key": "documentKind",
                      "type": "text",
                      "value": "supplier_invoice",
                      "description": "supplier_invoice (implicit) | sales_invoice"
                    },
                    {
                      "key": "externalRef",
                      "type": "text",
                      "value": "INV-2026-000812",
                      "description": "Cheia ta de idempotență, ≤ 100 de caractere."
                    }
                  ]
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/400\",\n  \"title\": \"DOCAI_FILE_TYPE_NOT_SUPPORTED\",\n  \"status\": 400,\n  \"detail\": \"Tipul de fișier „application/zip” nu este acceptat. Se acceptă JPEG, PNG, WebP, GIF și PDF.\",\n  \"errorCode\": \"DOCAI_FILE_TYPE_NOT_SUPPORTED\"\n}"
            }
          ],
          "_anchorName": "Trimite o factură la recunoaștere"
        },
        {
          "name": "Распознавания — список",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/docai/recognitions?status=completed&acknowledged=false&page=1&pageSize=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "docai",
                "recognitions"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "completed",
                  "description": "queued | processing | completed | failed"
                },
                {
                  "key": "acknowledged",
                  "value": "false",
                  "description": "false = только ещё не полученные"
                },
                {
                  "key": "source",
                  "value": "",
                  "description": "api | control_panel",
                  "disabled": true
                },
                {
                  "key": "reviewed",
                  "value": "",
                  "description": "true = только проверенные человеком в Панели управления",
                  "disabled": true
                },
                {
                  "key": "createdFrom",
                  "value": "",
                  "description": "ISO 8601, включительно",
                  "disabled": true
                },
                {
                  "key": "createdTo",
                  "value": "",
                  "description": "ISO 8601, не включительно",
                  "disabled": true
                },
                {
                  "key": "externalRef",
                  "value": "",
                  "description": "Ваш ключ",
                  "disabled": true
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": "Начиная с 1."
                },
                {
                  "key": "pageSize",
                  "value": "50",
                  "description": "1–200, по умолчанию 50."
                }
              ]
            },
            "description": "**Необходимый scope:** `docai:read`\n\nРаспознавания продавца **из любого источника** — и отправленные вами, и выполненные людьми в Панели управления, — сначала самые старые. Обычный порядок получения: `status=completed&acknowledged=false`, детали каждого по id, затем `POST …/ack` после импорта.\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[].id`, `status`, `source`, `externalRef`, `fileName` |  | Как в подробной информации. |\n| `items[].reviewed` | boolean | Человек проверил её в Панели управления. |\n| `items[].acknowledgedAt` | string? | Когда вы подтвердили, что забрали её. |\n| `items[].supplierName`, `supplierIdno`, `documentSeries`, `documentNumber`, `issueDate`, `total`, `currency`, `lineCount` |  | Сводка по накладной для отображения; данные для импорта — в подробной информации. |\n| `totalCount`, `page`, `pageSize`, `totalPages`, `hasNextPage` |  | Пагинация. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `COMMON_INVALID_REQUEST_FIELD`, `COMMON_BETWEEN` | Неизвестное значение фильтра, страница вне диапазона. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- В режиме входящих (`acknowledged=false`) **всегда читайте страницу 1**: после каждого `ack` список сокращается, а распознавание, завершённое позже, может иметь более раннюю дату создания, чем уже прочитанные. С `page++` вы пропустите накладные."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/recognitions?status=completed&acknowledged=false&page=1&pageSize=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "recognitions"
                  ],
                  "query": [
                    {
                      "key": "status",
                      "value": "completed",
                      "description": "queued | processing | completed | failed"
                    },
                    {
                      "key": "acknowledged",
                      "value": "false",
                      "description": "false = doar cele încă nepreluate"
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "api | control_panel",
                      "disabled": true
                    },
                    {
                      "key": "reviewed",
                      "value": "",
                      "description": "true = doar cele verificate de un om în panou",
                      "disabled": true
                    },
                    {
                      "key": "createdFrom",
                      "value": "",
                      "description": "ISO 8601, inclusiv",
                      "disabled": true
                    },
                    {
                      "key": "createdTo",
                      "value": "",
                      "description": "ISO 8601, exclusiv",
                      "disabled": true
                    },
                    {
                      "key": "externalRef",
                      "value": "",
                      "description": "Cheia ta",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "De la 1."
                    },
                    {
                      "key": "pageSize",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46\",\n      \"status\": \"completed\",\n      \"source\": \"api\",\n      \"externalRef\": \"INV-2026-000812\",\n      \"fileName\": \"lactis-1791127.pdf\",\n      \"documentKind\": \"supplier_invoice\",\n      \"createdAt\": \"2026-09-28T09:14:02Z\",\n      \"completedAt\": \"2026-09-28T09:14:41Z\",\n      \"reviewed\": false,\n      \"acknowledgedAt\": null,\n      \"supplierName\": \"Lactis SRL\",\n      \"supplierIdno\": \"1003600012345\",\n      \"documentSeries\": \"AAY\",\n      \"documentNumber\": \"1791127\",\n      \"issueDate\": \"2026-09-21\",\n      \"total\": 410.31,\n      \"currency\": \"MDL\",\n      \"lineCount\": 1\n    }\n  ],\n  \"totalCount\": 1,\n  \"page\": 1,\n  \"pageSize\": 50,\n  \"totalPages\": 1,\n  \"hasNextPage\": false,\n  \"hasPreviousPage\": false,\n  \"summary\": null,\n  \"filterableColumns\": null,\n  \"ignoredFilters\": null\n}"
            }
          ],
          "_anchorName": "Recunoașteri — listă"
        },
        {
          "name": "Распознавание — детали",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/docai/recognitions/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "docai",
                "recognitions",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46",
                  "description": "Id распознавания."
                }
              ]
            },
            "description": "**Необходимый scope:** `docai:read`\n\nСтатус и, когда готово, прочитанная накладная. Если человек проверил её в Панели управления (`reviewed: true`), `invoice` — его вариант, а в строках `match` — выбранная им позиция. Иначе в каждой строке приходят `suggestions` — предложения из вашей номенклатуры, самое надёжное первым.\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `status` | string | `queued` \\| `processing` \\| `completed` \\| `failed`. |\n| `error` | object? | При `failed`: `code` (стабильный) и `message` (текст на румынском). |\n| `confidence` | number? | 0..1, по арифметическим проверкам (строки, итоги, IDNO), а не по модели. Ниже 0,8 стоит проверить. |\n| `duplicateOf` | string? | ID распознавания, из которого скопирован результат. |\n| `invoice` | object? | Только при `completed`: шапка, `supplier`/`buyer`/`carrier`, `lines[]`, `totals`. |\n| `invoice.lines[].match` | object? | `{type, id, name, unit, nomenclatureType}`; `type` = `external` (`id` = ваш код или `DX-…` для позиции, созданной в PosFix) или `platform`. |\n| `invoice.lines[].suggestions[]` | array | `{externalId, name, unit, score, method, unitMismatch}`; `method`: `learned` (уже подтверждена у того же поставщика), `barcode`, `name`. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 404 | — | Отсутствует в данных продавца. Пустое тело. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Распознавание `failed` с `DOCAI_RESPONSE_TRUNCATED` — это накладная, слишком длинная для текущего бюджета; ничего из неё не импортируйте.\n- Поля, которые модель не смогла прочитать, приходят как `null` — не заменяйте их нулём."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/recognitions/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "recognitions",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46",
                      "description": "Id-ul recunoașterii."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46\",\n  \"status\": \"completed\",\n  \"source\": \"api\",\n  \"externalRef\": \"INV-2026-000812\",\n  \"fileName\": \"lactis-1791127.pdf\",\n  \"documentKind\": \"supplier_invoice\",\n  \"createdAt\": \"2026-09-28T09:14:02Z\",\n  \"completedAt\": \"2026-09-28T09:14:41Z\",\n  \"reviewed\": false,\n  \"reviewedAt\": null,\n  \"acknowledgedAt\": null,\n  \"confidence\": 0.96,\n  \"duplicateOf\": null,\n  \"error\": null,\n  \"invoice\": {\n    \"documentType\": \"Factură fiscală\",\n    \"series\": \"AAY\",\n    \"number\": \"1791127\",\n    \"issueDate\": \"2026-09-21\",\n    \"deliveryDate\": \"2026-09-21\",\n    \"currency\": \"MDL\",\n    \"waybillNumber\": null,\n    \"supplier\": {\n      \"name\": \"Lactis SRL\",\n      \"idno\": \"1003600012345\",\n      \"vatCode\": \"0401234\",\n      \"address\": \"mun. Chișinău, str. Uzinelor 1\",\n      \"iban\": \"MD24AG000000022512345678\",\n      \"bank\": \"Moldova Agroindbank\",\n      \"bankCode\": \"AGRNMD2X\"\n    },\n    \"buyer\": {\n      \"name\": \"Aroma Corner SRL\",\n      \"idno\": \"1015600098765\",\n      \"vatCode\": null,\n      \"address\": null,\n      \"iban\": null,\n      \"bank\": null,\n      \"bankCode\": null\n    },\n    \"carrier\": null,\n    \"lines\": [\n      {\n        \"index\": 0,\n        \"barcode\": \"4840811000181\",\n        \"tariffCode\": null,\n        \"name\": \"Lapte 2,5% 1L\",\n        \"unit\": \"buc\",\n        \"quantity\": 24,\n        \"unitPriceExclVat\": 15.83,\n        \"amountExclVat\": 379.92,\n        \"vatRate\": 8,\n        \"vatAmount\": 30.39,\n        \"amountInclVat\": 410.31,\n        \"match\": null,\n        \"suggestions\": [\n          {\n            \"externalId\": \"LAC-0012\",\n            \"name\": \"Lapte Lactis 2,5% 1l\",\n            \"unit\": \"buc\",\n            \"score\": 100,\n            \"method\": \"barcode\",\n            \"unitMismatch\": false\n          },\n          {\n            \"externalId\": \"LAC-0013\",\n            \"name\": \"Lapte Lactis 3,2% 1l\",\n            \"unit\": \"buc\",\n            \"score\": 67,\n            \"method\": \"name\",\n            \"unitMismatch\": false\n          }\n        ]\n      }\n    ],\n    \"totals\": {\n      \"amountExclVat\": 379.92,\n      \"vatAmount\": 30.39,\n      \"total\": 410.31\n    },\n    \"notes\": null\n  }\n}"
            }
          ],
          "_anchorName": "Recunoaștere — detaliu"
        },
        {
          "name": "Распознавание — подтвердить получение",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/docai/recognitions/:id/ack",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "docai",
                "recognitions",
                ":id",
                "ack"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46",
                  "description": "Id распознавания."
                }
              ]
            },
            "description": "**Необходимый scope:** `docai:read`\n\nОтмечает накладную как полученную вашей системой: список `acknowledged=false` её больше не показывает. Идемпотентно — повторное подтверждение сохраняет дату первого. Подтверждение принадлежит продавцу, а не ключу: две системы, забирающие одни и те же накладные, должны договориться между собой.\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 422 | `DOCAI_ACK_NOT_READY` | „Recunoașterea nu s-a terminat încă. Confirmați preluarea după ce ajunge la „completed” sau „failed”.” (Распознавание ещё не завершено. Подтвердите получение после того, как оно перейдёт в „completed“ или „failed“.) |\n| 409 | `CONCURRENCY_CONFLICT` | Накладная была изменена в тот же момент (проверка в панели). Повторите запрос. |\n| 404 | — | Отсутствует в данных продавца. Пустое тело. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 403 | — | У ключа нет требуемого scope или модуль «API интеграции» не активен. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/recognitions/:id/ack",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "recognitions",
                    ":id",
                    "ack"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46",
                      "description": "Id-ul recunoașterii."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46\",\n  \"status\": \"completed\",\n  \"source\": \"api\",\n  \"externalRef\": \"INV-2026-000812\",\n  \"fileName\": \"lactis-1791127.pdf\",\n  \"documentKind\": \"supplier_invoice\",\n  \"createdAt\": \"2026-09-28T09:14:02Z\",\n  \"completedAt\": \"2026-09-28T09:14:41Z\",\n  \"reviewed\": false,\n  \"acknowledgedAt\": \"2026-09-28T09:20:00Z\",\n  \"supplierName\": \"Lactis SRL\",\n  \"supplierIdno\": \"1003600012345\",\n  \"documentSeries\": \"AAY\",\n  \"documentNumber\": \"1791127\",\n  \"issueDate\": \"2026-09-21\",\n  \"total\": 410.31,\n  \"currency\": \"MDL\",\n  \"lineCount\": 1\n}"
            }
          ],
          "_anchorName": "Recunoaștere — confirmă preluarea"
        }
      ],
      "_anchorName": "7. Recunoaștere facturi"
    },
    {
      "name": "8. Лояльность",
      "description": "Программа лояльности продавца из вашей системы: вы регистрируете клиентов, читаете их баланс, начисляете баллы или бонусы за оплаченные заказы, позволяете использовать бонусы в леях в заказах и отзываете их при возврате. Нужны scope `loyalty:*` **и** активный у продавца модуль «Программы лояльности». Участник определяется по номеру карты (13 цифр).\n\nУ каждой программы есть тип (`mechanic`): **баллы** (`points`), **штампы** (`stamps`) или **бонус в леях** (`money`). Правила программы продавец задаёт в Панели управления; вы отправляете только оплаченную сумму, а платформа рассчитывает, что получает участник, — так же, как на кассе. Типы и статусы описаны в разделе «Программы».\n\n### Два способа работать с лояльностью из интернет-магазина\n\nВыберите один из них для заказа, не оба: иначе участник получит начисление дважды.\n\n| | **A. Магазин ведёт свои заказы** | **B. Заказы проходят через PosFix** |\n|---|---|---|\n| Где живёт заказ | В вашей системе; PosFix его не видит. | В PosFix, как любой заказ клиента («4. Заказы»). |\n| Что вы вызываете | Запросы этой папки: расчёт, использование бонусов, начисление, отмена. | Только «Создать заказ» с `loyalty: {cardNumber, bonusAmount}` и, при необходимости, «Отменить заказ». |\n| Использованные бонусы | Списываются с карты сразу, в «Использовать бонусы в заказе». | Резервируются при создании и списываются при завершении заказа, только за отгруженный товар. |\n| Баллы и кэшбэк | Начисляете вы, после оплаты («Начислить за заказ»). | Начисляет PosFix при завершении, на стоимость отгруженного. |\n| Оплата не прошла, клиент передумал | Вы отменяете заказ без суммы («Отменить заказ (возврат)»). | «Отменить заказ» из «4. Заказы»: бонусы возвращаются сами. |\n| Возврат | Вы отменяете возвращённую часть, с суммой. | Оформляет продавец, в панели. |\n| Клиент в PosFix | Участник остаётся только в лояльности. | Участник связывается с клиентом заказа; следующие заказы идут на того же клиента, даже если сменился телефон. |\n| Scope | `loyalty:read`, `loyalty:redeem`, `loyalty:earn`. | `orders:write`, `loyalty:earn` и, с бонусами, `loyalty:redeem`. |\n\nВ обоих случаях клиент определяется по номеру карты, который вы получаете в «Зарегистрировать участника» и храните рядом со своим клиентом. Телефон нужен только для первого сопоставления.\n\n### Вариант A, по шагам\n\nДля магазина, который сам ведёт заказы и оплаты и напрямую вызывает запросы ниже:\n\n1. **Клиент называет себя.** Если карты нет, зарегистрируйте его («Зарегистрировать участника») и сохраните `cardNumber` рядом со своим клиентом. Если карта есть, найдите его по карте («Участник по карте») или по телефону («Участники — список», с `search`).\n2. **Корзина.** Покажите, сколько бонусов можно использовать («Сколько бонусов можно использовать», с суммой заказа). Этот запрос ничего не списывает.\n3. **Оформление заказа.** Если клиент использует бонусы, спишите их («Использовать бонусы в заказе», с `orderId` вашего заказа). Бонусы сразу уходят с карты для всех каналов: на кассе их уже не потратить.\n4. **Оплата прошла.** Начислите за заказ («Начислить за заказ») с суммы, оплаченной деньгами, то есть без части, оплаченной бонусами.\n5. **Оплата не прошла или клиент передумал.** Отмените заказ без суммы («Отменить заказ (возврат)»): бонусы вернутся полностью. В отменённом заказе бонусы больше не используются; новая попытка оплаты — это новый заказ с другим `orderId`.\n6. **Возврат.** Отмените возвращённую часть с `amount`: начисленное за неё списывается, а доля бонусов, использованная на неё, возвращается.\n\nЗапросы, меняющие баланс, идемпотентны: при таймауте или `503` повторите их с тем же ключом (`orderId` или `reversalId`), и операция выполнится один раз. При `409` повторите так же: в тот же момент прошёл другой запрос по этой карте, и новый ответ его учитывает.",
      "item": [
        {
          "name": "Программы",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/programs",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "programs"
              ]
            },
            "description": "**Необходимый scope:** `loyalty:read`\n\nПрограммы лояльности продавца. Отсюда берётся `programId` для регистрации. Правила (сколько баллов за лей, процент бонуса, уровни, награды) продавец задаёт в Панели управления, в ответе их нет: вы отправляете оплаченную сумму, платформа считает, а ответ на начисление показывает, что получил участник.\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[].id` | string (uuid) | Id программы. |\n| `items[].name` | string | Название. |\n| `items[].mechanic` | string | Тип программы — что копит участник: `points` (баллы), `stamps` (штампы) или `money` (бонус в леях). Что означает каждый: первая таблица ниже. |\n| `items[].status` | string | Статус программы: `active`, `draft`, `paused` или `closed`. Что можно делать в каждом: вторая таблица ниже. |\n| `items[].currency` | string | Валюта программы, `MDL`: сумм, отправляемых при начислении, и бонуса в леях. |\n| `items[].acceptsEnrollment` | boolean | `true`, пока программа не `closed` — регистрировать можно и в программу `draft` или `paused`. |\n| `items[].hasBonus` | boolean | `true` = у участников есть кошелёк в леях (поле `bonus` у участника). Всегда `true` для `money`; для `points` — если продавец добавил и бонус в леях, и тогда участник копит баллы **и** леи. |\n\n### Тип программы (`mechanic`)\n\n| Значение | Что копит участник | Сколько приносит заказ |\n|---|---|---|\n| `points` | Баллы, которые обмениваются на награды на кассе. | Оплаченная сумма × баллы за лей по программе × коэффициент уровня, с округлением вниз. При одном балле за каждые 10 леев заказ на 749,90 лея приносит 74 балла. |\n| `stamps` | Штампы, которые обмениваются на награду («10-й кофе бесплатно»). | Одинаковое число штампов за каждый заказ, независимо от суммы, — обычно один. |\n| `money` | Бонус в леях, в кошельке; тратится при оплате. | Процент от оплаченной суммы (кэшбэк), округлённый вниз до бана. При 5% заказ на 749,90 лея приносит 37,49 лея. |\n\n### Статус программы (`status`)\n\n| Значение | Что означает | Регистрация | Начисление |\n|---|---|---|---|\n| `active` | Программа работает. | да | да, по правилам |\n| `draft` | Создана, продавец ещё не запустил. | да | отвечает `201`, но 0 баллов, 0 штампов, 0 леев; учитывается только покупка |\n| `paused` | Остановлена продавцом («Деактивировать» в Панели управления); можно запустить снова. | да | как при `draft` |\n| `closed` | Закрыта окончательно. | нет, `422` | нет, `422`; отмена старого начисления работает |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 403 | — | У ключа нет нужного scope, или у продавца не активен модуль «API интеграции» либо модуль «Программы лояльности». Тело пустое. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Заказ, отправленный, пока программа в статусе `draft` или `paused`, остаётся без баллов и после возобновления: повторная отправка — это повтор (`duplicate: true`), а не новое начисление."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/programs",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "programs"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n      \"name\": \"Aroma Club\",\n      \"mechanic\": \"points\",\n      \"status\": \"active\",\n      \"currency\": \"MDL\",\n      \"acceptsEnrollment\": true,\n      \"hasBonus\": false\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Programe"
        },
        {
          "name": "Зарегистрировать участника",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members"
              ]
            },
            "description": "**Необходимый scope:** `loyalty:write`\n\nРегистрирует клиента в программе и возвращает участника с номером карты и ссылками для сохранения в телефоне. Телефон — ключ: если он уже участник программы, вы получаете существующего участника без изменений.\n\n### Тело запроса\n\n| Поле | Тип | Обязательное | Правила |\n|---|---|---|---|\n| `programId` | string (uuid) | да | Программа из «Программы». |\n| `firstName`, `lastName` | string | да | Не более 100 символов каждое. |\n| `phone` | string | да | 8–15 цифр, с `+` в начале или без, без пробелов: `+37369123456`. Хранится и возвращается только цифрами (`37369123456`). После регистрации не меняется. |\n| `email` | string | нет | Не более 200 символов. |\n\n### Ответ\n\n**201** — новый участник (`created: true`). **200** — телефон уже был в программе (`created: false`).\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `created` | boolean | `false` = участник уже существовал. |\n| `member` | object | Участник, как в «Участник по карте». |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `LOYALTY_MEMBER_PHONE_FORMAT`, валидация поля | «Телефон нужно указать в международном формате (+37369123456) или в национальном, 8–15 цифр.» |\n| 422 | `LOYALTY_PROGRAM_NOT_FOUND` | «Программа лояльности не найдена. Возможно, её уже удалили; обновите страницу.» — `programId` не является программой продавца. |\n| 422 | `LOYALTY_PROGRAM_CLOSED` | «Программа лояльности «Aroma Club» закрыта — новых участников записать нельзя.» |\n| 422 | `DUPLICATE_MEMBER_EMAIL` | «Участник с этим адресом электронной почты уже есть в программе.» |\n| 403 | — | У ключа нет нужного scope, или у продавца не активен модуль «API интеграции» либо модуль «Программы лояльности». Тело пустое. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Новый участник вызывает webhook `loyalty.member.enrolled` — так же, как и зарегистрированный на кассе или в панели.\n- Если программа даёт приветственный бонус, отправляется и `loyalty.balance.changed`, который может прийти раньше `loyalty.member.enrolled`: порядок доставки не гарантируется.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n  \"firstName\": \"Ana\",\n  \"lastName\": \"Rusu\",\n  \"phone\": \"+37369123456\",\n  \"email\": \"ana.rusu@example.md\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n  \"firstName\": \"Ana\",\n  \"lastName\": \"Rusu\",\n  \"phone\": \"+37369123456\",\n  \"email\": \"ana.rusu@example.md\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"created\": true,\n  \"member\": {\n    \"cardNumber\": \"2000000012345\",\n    \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n    \"programName\": \"Aroma Club\",\n    \"firstName\": \"Ana\",\n    \"lastName\": \"Rusu\",\n    \"phone\": \"37369123456\",\n    \"email\": \"ana.rusu@example.md\",\n    \"tier\": null,\n    \"balance\": {\n      \"unit\": \"points\",\n      \"value\": 0,\n      \"lifetime\": 0\n    },\n    \"bonus\": null,\n    \"totalSpent\": 0,\n    \"purchaseCount\": 0,\n    \"enrolledAt\": \"2026-09-01T08:00:00Z\",\n    \"lastActivityAt\": null,\n    \"wallet\": {\n      \"googleSaveUrl\": \"https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiJ9…\",\n      \"applePassUrl\": \"https://cp.posfix.md/loyalty-api/api/v1/loyalty/public/members/3e5f9a1c-…/apple-pass\"\n    }\n  }\n}"
            },
            {
              "name": "200 существующий",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n  \"firstName\": \"Ana\",\n  \"lastName\": \"Rusu\",\n  \"phone\": \"+37369123456\",\n  \"email\": \"ana.rusu@example.md\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"created\": false,\n  \"member\": {\n    \"cardNumber\": \"2000000012345\",\n    \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n    \"programName\": \"Aroma Club\",\n    \"firstName\": \"Ana\",\n    \"lastName\": \"Rusu\",\n    \"phone\": \"37369123456\",\n    \"email\": \"ana.rusu@example.md\",\n    \"tier\": \"Argint\",\n    \"balance\": {\n      \"unit\": \"points\",\n      \"value\": 120,\n      \"lifetime\": 340\n    },\n    \"bonus\": null,\n    \"totalSpent\": 2450.5,\n    \"purchaseCount\": 7,\n    \"enrolledAt\": \"2026-09-01T08:00:00Z\",\n    \"lastActivityAt\": \"2026-10-27T15:42:10Z\",\n    \"wallet\": {\n      \"googleSaveUrl\": \"https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiJ9…\",\n      \"applePassUrl\": \"https://cp.posfix.md/loyalty-api/api/v1/loyalty/public/members/3e5f9a1c-…/apple-pass\"\n    }\n  }\n}"
            },
            {
              "name": "400 телефон",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n  \"firstName\": \"Ana\",\n  \"lastName\": \"Rusu\",\n  \"phone\": \"+37369123456\",\n  \"email\": \"ana.rusu@example.md\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"statusCode\": 400,\n  \"message\": \"One or more errors occurred!\",\n  \"errors\": {\n    \"phone\": [\n      \"Telefonul trebuie scris în format internațional (+37369123456) sau național, din 8–15 cifre.\"\n    ]\n  },\n  \"errorCodes\": {\n    \"phone\": [\n      {\n        \"code\": \"LOYALTY_MEMBER_PHONE_FORMAT\",\n        \"params\": {}\n      }\n    ]\n  }\n}"
            },
            {
              "name": "422 программа закрыта",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n  \"firstName\": \"Ana\",\n  \"lastName\": \"Rusu\",\n  \"phone\": \"+37369123456\",\n  \"email\": \"ana.rusu@example.md\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"LOYALTY_PROGRAM_CLOSED\",\n  \"status\": 422,\n  \"detail\": \"Programul de fidelitate „Aroma Club” este închis — nu se mai pot înscrie membri noi.\",\n  \"errorCode\": \"LOYALTY_PROGRAM_CLOSED\"\n}"
            }
          ],
          "_anchorName": "Înscrie un membru"
        },
        {
          "name": "Участник по карте",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members",
                ":cardNumber"
              ],
              "variable": [
                {
                  "key": "cardNumber",
                  "value": "2000000012345",
                  "description": "Номер карты, 13 цифр."
                }
              ]
            },
            "description": "**Необходимый scope:** `loyalty:read`\n\nУчастник с текущим балансом, бонусом в леях и ссылками для сохранения карты в телефоне.\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `cardNumber` | string | Номер виртуальной карты, 13 цифр — идентификатор участника в API. |\n| `programId`, `programName` | string | Программа, в которой зарегистрирован участник: её id из раздела «Программы» и название. |\n| `firstName`, `lastName`, `phone`, `email` |  | Данные участника; `email` может быть `null`. |\n| `tier` | string? | Уровень участника (например, «Серебро»). Повышается сам, по баллам, накопленным с регистрации (`balance.lifetime`), и может давать больше баллов за лей или скидку на кассе — как настроил продавец. `null`, если в программе нет уровней или участник не дошёл до первого. |\n| `balance.unit` | string | В чём считается баланс, то же значение, что `mechanic` программы: `points` (баллы), `stamps` (штампы) или `money` (леи). |\n| `balance.value` | number | Текущий баланс: сколько участник может потратить. Для `money` — сумма в кошельке, в леях. Никогда не бывает отрицательным: при возврате списывается только то, что ещё есть на карте, а уже потраченное участником не взыскивается. |\n| `balance.lifetime` | number | Сколько участник накопил с момента регистрации. Не уменьшается при списании. Для `points` и `stamps` уменьшается, когда при возврате отменяется начисление; для `money` — весь когда-либо полученный бонус, не уменьшается и при возврате. |\n| `bonus` | object? | Кошелёк в леях, отдельно от баланса — только если в программе есть бонус в леях (`hasBonus: true`), иначе `null`. В программе `money` цифры те же, что в `balance`. |\n| `bonus.amount` | number | Леи в кошельке. Никогда не бывает отрицательным: отзываемый при возврате бонус, если он уже потрачен, не взыскивается. |\n| `bonus.available` | number | Сколько участник может потратить прямо сейчас: `amount` без суммы, зарезервированной кассой на чеке в работе, и без только что полученного бонуса, который ещё несколько часов заблокирован (если программа его блокирует). |\n| `bonus.currency` | string | Валюта кошелька, `MDL`. |\n| `bonus.nextExpiresAt` | string? | Когда истекает ближайший полученный бонус (у бонуса есть срок действия, заданный в программе). `null` = ничего не истекает. |\n| `totalSpent`, `purchaseCount` | number, integer | Сколько куплено по карте — на кассе и через заказы, начисленные через API, после отмен. |\n| `enrolledAt`, `lastActivityAt` | string | ISO 8601 UTC; `lastActivityAt` может быть `null`. |\n| `wallet` | object? | `{googleSaveUrl, applePassUrl}` — ссылки для сохранения карты в телефоне. Приходит только при регистрации и в «Участник по карте»; в списке и после изменения — `null`. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `INTEGRATIONS_LOYALTY_CARD_NUMBER_FORMAT` | «Номер карты состоит ровно из 13 цифр, без пробелов.» |\n| 404 | — | Карта не принадлежит продавцу. Тело пустое. |\n| 403 | — | У ключа нет нужного scope, или у продавца не активен модуль «API интеграции» либо модуль «Программы лояльности». Тело пустое. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n  \"programName\": \"Aroma Club\",\n  \"firstName\": \"Ana\",\n  \"lastName\": \"Rusu\",\n  \"phone\": \"37369123456\",\n  \"email\": \"ana.rusu@example.md\",\n  \"tier\": \"Argint\",\n  \"balance\": {\n    \"unit\": \"points\",\n    \"value\": 120,\n    \"lifetime\": 340\n  },\n  \"bonus\": null,\n  \"totalSpent\": 2450.5,\n  \"purchaseCount\": 7,\n  \"enrolledAt\": \"2026-09-01T08:00:00Z\",\n  \"lastActivityAt\": \"2026-10-27T15:42:10Z\",\n  \"wallet\": {\n    \"googleSaveUrl\": \"https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiJ9…\",\n    \"applePassUrl\": \"https://cp.posfix.md/loyalty-api/api/v1/loyalty/public/members/3e5f9a1c-…/apple-pass\"\n  }\n}"
            },
            {
              "name": "400 карта",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"statusCode\": 400,\n  \"message\": \"One or more errors occurred!\",\n  \"errors\": {\n    \"cardNumber\": [\n      \"Numărul cardului are exact 13 cifre, fără spații.\"\n    ]\n  },\n  \"errorCodes\": {\n    \"cardNumber\": [\n      {\n        \"code\": \"INTEGRATIONS_LOYALTY_CARD_NUMBER_FORMAT\",\n        \"params\": {}\n      }\n    ]\n  }\n}"
            },
            {
              "name": "404",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                }
              },
              "status": "Not Found",
              "code": 404,
              "_postman_previewlanguage": "text",
              "header": [],
              "body": ""
            }
          ],
          "_anchorName": "Membru după card"
        },
        {
          "name": "Участники — список",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members"
              ],
              "query": [
                {
                  "key": "programId",
                  "value": "",
                  "description": "Только участники одной программы; пусто = все.",
                  "disabled": true
                },
                {
                  "key": "search",
                  "value": "",
                  "description": "Телефон, имя или номер карты.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "nextCursor из предыдущей страницы.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "1–200, по умолчанию 50."
                }
              ]
            },
            "description": "**Необходимый scope:** `loyalty:read`\n\nУчастники продавца, постранично с курсором.\n\n### Параметры запроса\n\n| Параметр | Тип | По умолчанию | Описание |\n|---|---|---|---|\n| `programId` | string (uuid) | все | Фильтр по программе. |\n| `search` | string | — | Телефон, имя или номер карты; не более 100 символов. |\n| `cursor` | string | — | Значение `nextCursor` с предыдущей страницы. |\n| `limit` | integer | 50 | 1–200. |\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[]` | array | Участники, с теми же полями, что в «Участник по карте»; `wallet` — `null`. |\n| `nextCursor` | string? | Курсор следующей страницы; `null` на последней странице. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `COMMON_BETWEEN`, `COMMON_MAX_LENGTH` | «Значение должно быть от 1 до 200.» |\n| 403 | — | У ключа нет нужного scope, или у продавца не активен модуль «API интеграции» либо модуль «Программы лояльности». Тело пустое. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Проходите страницы до `nextCursor: null`; курсор не составляется вручную."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members?limit=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members"
                  ],
                  "query": [
                    {
                      "key": "programId",
                      "value": "",
                      "description": "Doar membrii unui program; gol = toate.",
                      "disabled": true
                    },
                    {
                      "key": "search",
                      "value": "",
                      "description": "Telefon, nume sau număr de card.",
                      "disabled": true
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "nextCursor din pagina precedentă.",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"cardNumber\": \"2000000012345\",\n      \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n      \"programName\": \"Aroma Club\",\n      \"firstName\": \"Ana\",\n      \"lastName\": \"Rusu\",\n      \"phone\": \"37369123456\",\n      \"email\": \"ana.rusu@example.md\",\n      \"tier\": \"Argint\",\n      \"balance\": {\n        \"unit\": \"points\",\n        \"value\": 120,\n        \"lifetime\": 340\n      },\n      \"bonus\": null,\n      \"totalSpent\": 2450.5,\n      \"purchaseCount\": 7,\n      \"enrolledAt\": \"2026-09-01T08:00:00Z\",\n      \"lastActivityAt\": \"2026-10-27T15:42:10Z\"\n    }\n  ],\n  \"nextCursor\": null\n}"
            }
          ],
          "_anchorName": "Membri — listă"
        },
        {
          "name": "Изменить участника",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members",
                ":cardNumber"
              ],
              "variable": [
                {
                  "key": "cardNumber",
                  "value": "2000000012345",
                  "description": "Номер карты, 13 цифр."
                }
              ]
            },
            "description": "**Необходимый scope:** `loyalty:write`\n\nМеняет имя или email участника. Отправляйте только изменяемые поля; отсутствующие остаются как есть. Телефон не меняется.\n\n### Тело запроса\n\n| Поле | Тип | Обязательное | Правила |\n|---|---|---|---|\n| `firstName`, `lastName` | string | нет | Если вы их отправляете, они не могут быть пустыми; не более 100 символов. |\n| `email` | string | нет | Не более 200 символов. `\"\"` удаляет email. |\n\n### Ответ\n\n**200** — участник после изменения, как в «Участник по карте», с `wallet: null`.\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `INTEGRATIONS_LOYALTY_CARD_NUMBER_FORMAT`, валидация поля | Неверная карта, пустое имя, неверный email. |\n| 404 | — | Карта не принадлежит продавцу. Тело пустое. |\n| 422 | `DUPLICATE_MEMBER_EMAIL` | «Участник с этим адресом электронной почты уже есть в программе.» |\n| 403 | — | У ключа нет нужного scope, или у продавца не активен модуль «API интеграции» либо модуль «Программы лояльности». Тело пустое. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lastName\": \"Rusu-Ciobanu\",\n  \"email\": \"ana.ciobanu@example.md\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"lastName\": \"Rusu-Ciobanu\",\n  \"email\": \"ana.ciobanu@example.md\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n  \"programName\": \"Aroma Club\",\n  \"firstName\": \"Ana\",\n  \"lastName\": \"Rusu-Ciobanu\",\n  \"phone\": \"37369123456\",\n  \"email\": \"ana.ciobanu@example.md\",\n  \"tier\": \"Argint\",\n  \"balance\": {\n    \"unit\": \"points\",\n    \"value\": 120,\n    \"lifetime\": 340\n  },\n  \"bonus\": null,\n  \"totalSpent\": 2450.5,\n  \"purchaseCount\": 7,\n  \"enrolledAt\": \"2026-09-01T08:00:00Z\",\n  \"lastActivityAt\": \"2026-10-27T15:42:10Z\",\n  \"wallet\": {\n    \"googleSaveUrl\": \"https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiJ9…\",\n    \"applePassUrl\": \"https://cp.posfix.md/loyalty-api/api/v1/loyalty/public/members/3e5f9a1c-…/apple-pass\"\n  }\n}"
            }
          ],
          "_anchorName": "Modifică un membru"
        },
        {
          "name": "История участника",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/transactions?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members",
                ":cardNumber",
                "transactions"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "description": "nextCursor из предыдущей страницы.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "1–200, по умолчанию 50."
                }
              ],
              "variable": [
                {
                  "key": "cardNumber",
                  "value": "2000000012345",
                  "description": "Номер карты, 13 цифр."
                }
              ]
            },
            "description": "**Необходимый scope:** `loyalty:read`\n\nДвижения по карте, сначала самые новые: начисления, списания, отмены, сгорания, бонус в леях.\n\n### Ответ 200\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `items[].id` | string | Id движения. |\n| `items[].occurredAt` | string | ISO 8601 UTC. |\n| `items[].kind` | string | Что произошло; значения — в таблице ниже. Значения с префиксом `bonus_` касаются кошелька в леях; остальные — покупки, возвраты и баллы или штампы. |\n| `items[].source` | string | `pos` (чек на кассе), `api` (заказ через API), `panel` (человек, в панели), `system` (приветствие, сгорание). |\n| `items[].points`, `stamps`, `bonus` | number? | Что изменила строка, со знаком; `null` для того, что она не затрагивает. Леи — только в строках `bonus_`: в программе `money` строка `earn` или `reversal` имеет все три `null` и несёт только сумму, а бонус приходит отдельной строкой. |\n| `items[].purchaseAmount` | number? | Сумма покупки (для `earn`) или возврата (для `reversal`). |\n| `items[].orderId` | string? | Ваш заказ, для движений через API. |\n| `items[].receiptRef` | string? | Код фискального чека, для движений с кассы. |\n| `nextCursor` | string? | Курсор следующей страницы; `null` на последней странице. |\n\n### Вид операции (`kind`)\n\n| Значение | Что означает |\n|---|---|\n| `earn` | Покупка (чек на кассе или заказ через API) с полученными за неё баллами или штампами. Для `money` — только сумма; бонус — строка `bonus_grant`. |\n| `reversal` | Возврат со списанными баллами или штампами. Для `money` — только сумма; списанный бонус — строка `bonus_revoke`. |\n| `redeem` | Баллы или штампы обменяны на награду. |\n| `adjustment` | Корректировка, сделанная человеком в Панели управления (плюс или минус). |\n| `expiry` | Баллы истекли. |\n| `bonus_grant` | Получен бонус в леях: кэшбэк за покупку, приветственный бонус, акция. |\n| `bonus_redeem` | Бонус потрачен при оплате. |\n| `bonus_expiry` | Бонус истёк. |\n| `bonus_restore` | Бонус возвращён в кошелёк: чек, оплаченный бонусом, был возвращён. |\n| `bonus_revoke` | Бонус списан: при возврате или исправлен человеком в Панели управления. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 404 | — | Карта не принадлежит продавцу. Тело пустое. |\n| 403 | — | У ключа нет нужного scope, или у продавца не активен модуль «API интеграции» либо модуль «Программы лояльности». Тело пустое. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/transactions?limit=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "transactions"
                  ],
                  "query": [
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "nextCursor din pagina precedentă.",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    }
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"a71f0c2e-5b3d-4e8a-9c1f-2d4e6f8a0b1c\",\n      \"occurredAt\": \"2026-10-27T15:42:10Z\",\n      \"kind\": \"earn\",\n      \"source\": \"api\",\n      \"points\": 74,\n      \"stamps\": null,\n      \"bonus\": null,\n      \"purchaseAmount\": 749.9,\n      \"orderId\": \"WEB-10482\",\n      \"receiptRef\": null\n    },\n    {\n      \"id\": \"c93e1a4b-7d5f-4a2c-8e0b-1f3a5c7e9d2b\",\n      \"occurredAt\": \"2026-10-20T11:05:33Z\",\n      \"kind\": \"earn\",\n      \"source\": \"pos\",\n      \"points\": 23,\n      \"stamps\": null,\n      \"bonus\": null,\n      \"purchaseAmount\": 231,\n      \"orderId\": null,\n      \"receiptRef\": \"J403001234-0067-0012\"\n    }\n  ],\n  \"nextCursor\": null\n}"
            }
          ],
          "_anchorName": "Istoricul membrului"
        },
        {
          "name": "Сколько бонусов можно использовать",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/bonus-quote?orderAmount=749.90&otherDiscount=0",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members",
                ":cardNumber",
                "bonus-quote"
              ],
              "query": [
                {
                  "key": "orderAmount",
                  "value": "749.90",
                  "description": "Сумма заказа до бонусов, в леях."
                },
                {
                  "key": "otherDiscount",
                  "value": "0",
                  "description": "Скидка, уже предоставленная в заказе из других источников (купон, акция); отсутствует = 0."
                }
              ],
              "variable": [
                {
                  "key": "cardNumber",
                  "value": "2000000012345",
                  "description": "Номер карты, 13 цифр."
                }
              ]
            },
            "description": "**Необходимый scope:** `loyalty:read`\n\nСколько бонусов в леях участник может использовать в заказе по правилам программы — тем же, что и на кассе: лимит на заказ (процент или сумма), минимальная сумма заказа, сочетание с другими скидками. Ничего не списывается; покажите клиенту в корзине, сколько он может использовать.\n\n### Ответ\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `cardNumber` | string | Участник. |\n| `orderAmount` | number | Сумма заказа, по которой выполнен расчёт. |\n| `available` | number | Сколько можно потратить с карты сейчас. |\n| `maxRedeemable` | number | Сколько можно использовать в этом заказе: доступное, ограниченное правилами программы. 0 = ничего. |\n| `currency` | string? | Валюта кошелька, `MDL`. |\n| `refusal` | object? | Почему бонусы нельзя использовать: `{code, message}`. `null`, когда `maxRedeemable` больше нуля. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `INTEGRATIONS_LOYALTY_AMOUNT_INVALID`, проверка поля | «Сумма должна быть больше нуля и иметь не более двух знаков после точки (например, 249.90).» |\n| 404 | — | Карта не принадлежит продавцу. Тело пустое. |\n| 403 | — | У ключа нет нужного scope, или у продавца не активен модуль «API интеграции» либо модуль «Программы лояльности». Тело пустое. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- Расчёт не удерживает деньги: между расчётом и использованием клиент может потратить бонусы на кассе. Итоговую сумму проверяет «Использовать бонусы в заказе»."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/bonus-quote?orderAmount=749.90&otherDiscount=0",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "bonus-quote"
                  ],
                  "query": [
                    {
                      "key": "orderAmount",
                      "value": "749.90",
                      "description": "Suma comenzii înainte de bonus, în lei."
                    },
                    {
                      "key": "otherDiscount",
                      "value": "0",
                      "description": "Reducerea deja dată pe comandă din alte surse (cupon, promoție); lipsă = 0."
                    }
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"orderAmount\": 749.9,\n  \"available\": 150,\n  \"maxRedeemable\": 149.98,\n  \"currency\": \"MDL\",\n  \"refusal\": null\n}"
            },
            {
              "name": "200 без бонусов",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/bonus-quote?orderAmount=749.90&otherDiscount=0",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "bonus-quote"
                  ],
                  "query": [
                    {
                      "key": "orderAmount",
                      "value": "749.90",
                      "description": "Suma comenzii înainte de bonus, în lei."
                    },
                    {
                      "key": "otherDiscount",
                      "value": "0",
                      "description": "Reducerea deja dată pe comandă din alte surse (cupon, promoție); lipsă = 0."
                    }
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"orderAmount\": 749.9,\n  \"available\": 0,\n  \"maxRedeemable\": 0,\n  \"currency\": \"MDL\",\n  \"refusal\": {\n    \"code\": \"LOYALTY_QUOTE_NOTHING_AVAILABLE\",\n    \"message\": \"Clientul nu are bonus disponibil acum.\"\n  }\n}"
            }
          ],
          "_anchorName": "Cât bonus se poate folosi"
        },
        {
          "name": "Использовать бонусы в заказе",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/redemptions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members",
                ":cardNumber",
                "redemptions"
              ],
              "variable": [
                {
                  "key": "cardNumber",
                  "value": "2000000012345",
                  "description": "Номер карты, 13 цифр."
                }
              ]
            },
            "description": "**Необходимый scope:** `loyalty:redeem`\n\nСписывает бонусы в леях с карты участника за заказ: клиент платит `orderAmount` минус `amount`. Те же правила, что и на кассе (лимит, минимум, сочетание), а деньги списываются сразу для всех каналов — одни и те же бонусы нельзя потратить и на кассе, и на сайте. Отправляйте, когда клиент оформляет заказ.\n\n### Тело запроса\n\n| Поле | Тип | Обязательное | Правила |\n|---|---|---|---|\n| `orderId` | string | да | Ваш заказ, не более 100 символов — ключ идемпотентности. |\n| `amount` | number | да | Используемые бонусы, в леях: больше нуля, не более двух знаков после точки, не более `maxRedeemable` из расчёта. |\n| `orderAmount` | number | да | Сумма заказа до бонусов, в леях. |\n| `otherDiscount` | number | нет | Скидка, уже предоставленная из других источников; отсутствует = 0. Важно, когда программа не сочетает бонусы с другими скидками. |\n\n### Ответ\n\n**201** — бонусы использованы сейчас. **200** — заказ уже использовал бонусы (`duplicate: true`), ничего не изменилось.\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `cardNumber`, `orderId` | string | Участник и заказ. |\n| `duplicate` | boolean | `true` = запрос уже выполнялся; сейчас ничего не изменилось. |\n| `amount` | number | Бонусы, списанные с карты, в леях. |\n| `orderAmount` | number | Сумма заказа до бонусов. |\n| `balance`, `bonusBalance` | object, object? | Баланс участника после операции, как у участника. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `INTEGRATIONS_LOYALTY_AMOUNT_INVALID`, `INTEGRATIONS_LOYALTY_CARD_NUMBER_FORMAT`, валидация поля | «Сумма должна быть больше нуля и иметь не более двух знаков после точки (например, 249.90).» |\n| 404 | — | Карта не принадлежит продавцу. Тело пустое. |\n| 409 | `CONCURRENCY_CONFLICT` | Та же карта в тот же момент потратила бонусы в другом канале, или один и тот же заказ отправлен дважды одновременно. Повторите запрос: он увидит новый баланс и получит правильный ответ по заказу. |\n| 422 | `LOYALTY_BONUS_HOLD_EXCEEDS_MAX` | «В этом чеке можно использовать не более 149,98 MDL.» |\n| 422 | `LOYALTY_QUOTE_NOTHING_AVAILABLE` и другие причины расчёта | «У клиента сейчас нет доступных бонусов.» |\n| 422 | `LOYALTY_ORDER_REDEMPTION_AMOUNT_MISMATCH` | «В заказе WEB-10482 уже использовано 100,00 бонусов. Другую сумму по тому же заказу использовать нельзя — отмените заказ и используйте бонусы в новом заказе.» |\n| 422 | `LOYALTY_ORDER_REDEEMED_ON_OTHER_CARD` | «В заказе WEB-10482 уже использованы бонусы другой карты. Бонусы заказа списываются только с одной карты.» |\n| 422 | `LOYALTY_ORDER_CANCELLED_NO_REDEMPTION` | «Заказ WEB-10482 отменён, поэтому использовать в нём бонусы больше нельзя. Новую попытку оплаты отправляйте как новый заказ с другим номером.» |\n| 403 | — | У ключа нет нужного scope, или у продавца не активен модуль «API интеграции» либо модуль «Программы лояльности». Тело пустое. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- При тайм-ауте или `503` отправьте запрос повторно **с тем же** `orderId` и той же суммой: бонусы спишутся только один раз.\n- Если оплата не прошла или клиент передумал, отмените заказ через «Отменить заказ (возврат)» без `amount`: бонусы полностью вернутся на карту. Сами по себе бонусы не возвращаются.\n- В отменённом заказе бонусы больше не используются. Новую попытку оплаты отправляйте как новый заказ с другим `orderId`.\n- После оплаты начислите за заказ («Начислить за заказ») на сумму, оплаченную деньгами, то есть `orderAmount` минус бонусы.\n- Этот запрос — для варианта A (заказ остаётся у вас). Если вы отправляете заказ в PosFix, укажите бонусы в `loyalty.bonusAmount` в «Создать заказ» и не вызывайте этот запрос.\n- Списание вызывает вебхук `loyalty.balance.changed`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 100,\n  \"orderAmount\": 749.9\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/redemptions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "redemptions"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 100,\n  \"orderAmount\": 749.9\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"orderId\": \"WEB-10482\",\n  \"duplicate\": false,\n  \"amount\": 100,\n  \"orderAmount\": 749.9,\n  \"balance\": {\n    \"unit\": \"money\",\n    \"value\": 50,\n    \"lifetime\": 350\n  },\n  \"bonusBalance\": {\n    \"amount\": 50,\n    \"available\": 50,\n    \"currency\": \"MDL\",\n    \"nextExpiresAt\": \"2027-01-31T00:00:00Z\"\n  }\n}"
            },
            {
              "name": "200 повтор",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/redemptions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "redemptions"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 100,\n  \"orderAmount\": 749.9\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"orderId\": \"WEB-10482\",\n  \"duplicate\": true,\n  \"amount\": 100,\n  \"orderAmount\": 749.9,\n  \"balance\": {\n    \"unit\": \"money\",\n    \"value\": 50,\n    \"lifetime\": 350\n  },\n  \"bonusBalance\": {\n    \"amount\": 50,\n    \"available\": 50,\n    \"currency\": \"MDL\",\n    \"nextExpiresAt\": \"2027-01-31T00:00:00Z\"\n  }\n}"
            },
            {
              "name": "422 сверх лимита",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/redemptions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "redemptions"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 100,\n  \"orderAmount\": 749.9\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"LOYALTY_BONUS_HOLD_EXCEEDS_MAX\",\n  \"status\": 422,\n  \"detail\": \"Pe bonul acesta se pot folosi cel mult 149,98 MDL.\",\n  \"errorCode\": \"LOYALTY_BONUS_HOLD_EXCEEDS_MAX\"\n}"
            }
          ],
          "_anchorName": "Folosește bonus pe o comandă"
        },
        {
          "name": "Начислить за заказ",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/accruals",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members",
                ":cardNumber",
                "accruals"
              ],
              "variable": [
                {
                  "key": "cardNumber",
                  "value": "2000000012345",
                  "description": "Номер карты, 13 цифр."
                }
              ]
            },
            "description": "**Необходимый scope:** `loyalty:earn`\n\nНачисляет участнику положенное за заказ, оплаченный в вашей системе (интернет-магазин, приложение), по правилам программы — тем же, что и на кассе. Отправляйте после оплаты заказа.\n\n### Тело запроса\n\n| Поле | Тип | Обязательное | Правила |\n|---|---|---|---|\n| `orderId` | string | да | Ваш заказ, не более 100 символов — ключ идемпотентности. |\n| `amount` | number | да | Оплаченная сумма после скидок, в леях: больше нуля, не более двух знаков после точки. |\n| `occurredAt` | string | нет | Момент покупки, ISO 8601; нет = сейчас. |\n\n### Ответ\n\n**201** — новое начисление. **200** — заказ уже был начислен (`duplicate: true`), ничего не изменилось.\n\n| Поле | Тип | Описание |\n|---|---|---|\n| `cardNumber`, `orderId` | string | Участник и заказ. |\n| `reversalId` | string? | Только при отмене: ссылка на возврат. |\n| `duplicate` | boolean | `true` = операция уже была выполнена с тем же ключом; сейчас ничего не изменилось. |\n| `amount` | number | Сумма, по которой рассчитана операция: оплаченная при начислении, возвращённая при отмене. |\n| `points`, `stamps`, `bonus` | number | Что участник получил за заказ (отрицательное при отмене). Ненулевая только единица программы: `points` для баллов, `stamps` для штампов, `bonus` (леи) для бонуса в леях; для программы с баллами и кошельком — `points` и `bonus`. Все 0, когда программа `draft` или `paused`. |\n| `remainingAmount` | number | Сколько от заказа осталось неотменённым после операции. |\n| `bonusRestored` | number | Только при отмене: бонусы, использованные в заказе (через «Использовать бонусы в заказе»), возвращённые на карту сейчас, в леях. 0 при начислении. |\n| `balance`, `bonusBalance` | object, object? | Баланс участника после операции, как `balance` и `bonus` у участника; `bonusBalance` — `null`, если в программе нет бонуса в леях. |\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `INTEGRATIONS_LOYALTY_AMOUNT_INVALID`, `INTEGRATIONS_LOYALTY_CARD_NUMBER_FORMAT`, валидация поля | «Сумма должна быть больше нуля и иметь не более двух знаков после точки (например, 249.90).» |\n| 404 | — | Карта не принадлежит продавцу. Тело пустое. |\n| 422 | `LOYALTY_ORDER_ACCRUAL_AMOUNT_MISMATCH` | «За заказ WEB-10482 уже начислено на сумму 749,90. Другую сумму по тому же заказу начислить нельзя — для исправления отмените заказ (полностью или частично).» |\n| 422 | `LOYALTY_ORDER_ACCRUED_TO_OTHER_CARD` | «За заказ WEB-10482 уже начислены баллы на другую карту. Заказ начисляется один раз и одному участнику.» |\n| 422 | `LOYALTY_PROGRAM_CLOSED_ACCRUAL` | «Программа лояльности «Aroma Club» закрыта — баллы больше не начисляются.» |\n| 403 | — | У ключа нет нужного scope, или у продавца не активен модуль «API интеграции» либо модуль «Программы лояльности». Тело пустое. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- При тайм-ауте или `503` отправьте запрос повторно **с тем же** `orderId`: начисление произойдёт только один раз.\n- Заказ начисляется только один раз, на одну карту, на одну сумму. Исправление делается отменой (полной или частичной), а не вторым начислением.\n- Начисление вызывает webhook `loyalty.balance.changed`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 749.9,\n  \"occurredAt\": \"2026-10-27T15:40:00Z\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/accruals",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "accruals"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 749.9,\n  \"occurredAt\": \"2026-10-27T15:40:00Z\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"orderId\": \"WEB-10482\",\n  \"reversalId\": null,\n  \"duplicate\": false,\n  \"amount\": 749.9,\n  \"points\": 74,\n  \"stamps\": 0,\n  \"bonus\": 0,\n  \"remainingAmount\": 749.9,\n  \"bonusRestored\": 0,\n  \"balance\": {\n    \"unit\": \"points\",\n    \"value\": 194,\n    \"lifetime\": 414\n  },\n  \"bonusBalance\": null\n}"
            },
            {
              "name": "200 повтор",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/accruals",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "accruals"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 749.9,\n  \"occurredAt\": \"2026-10-27T15:40:00Z\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"orderId\": \"WEB-10482\",\n  \"reversalId\": null,\n  \"duplicate\": true,\n  \"amount\": 749.9,\n  \"points\": 74,\n  \"stamps\": 0,\n  \"bonus\": 0,\n  \"remainingAmount\": 749.9,\n  \"bonusRestored\": 0,\n  \"balance\": {\n    \"unit\": \"points\",\n    \"value\": 194,\n    \"lifetime\": 414\n  },\n  \"bonusBalance\": null\n}"
            },
            {
              "name": "400 сумма",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/accruals",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "accruals"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 749.9,\n  \"occurredAt\": \"2026-10-27T15:40:00Z\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"statusCode\": 400,\n  \"message\": \"One or more errors occurred!\",\n  \"errors\": {\n    \"amount\": [\n      \"Suma trebuie să fie mai mare decât zero, cu cel mult două zecimale (de exemplu 249.90).\"\n    ]\n  },\n  \"errorCodes\": {\n    \"amount\": [\n      {\n        \"code\": \"INTEGRATIONS_LOYALTY_AMOUNT_INVALID\",\n        \"params\": {}\n      }\n    ]\n  }\n}"
            },
            {
              "name": "422 другая сумма",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/accruals",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "accruals"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 749.9,\n  \"occurredAt\": \"2026-10-27T15:40:00Z\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"LOYALTY_ORDER_ACCRUAL_AMOUNT_MISMATCH\",\n  \"status\": 422,\n  \"detail\": \"Comanda WEB-10482 a fost deja acordată pentru 749,90. Pe aceeași comandă nu se poate acorda altă sumă — pentru o corecție anulați comanda (integral sau parțial).\",\n  \"errorCode\": \"LOYALTY_ORDER_ACCRUAL_AMOUNT_MISMATCH\"\n}"
            }
          ],
          "_anchorName": "Acordă pe o comandă"
        },
        {
          "name": "Отменить заказ (возврат)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/accruals/:orderId/reversals",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "accruals",
                ":orderId",
                "reversals"
              ],
              "variable": [
                {
                  "key": "orderId",
                  "value": "WEB-10482",
                  "description": "Начисленный заказ (URL-encoded)."
                }
              ]
            },
            "description": "**Необходимый scope:** `loyalty:earn`\n\nОтзывает из начисления за заказ возвращённую часть, пропорционально, и возвращает на карту долю бонусов, использованных в заказе. Без `amount` отменяется всё, что осталось от заказа. Заказ, в котором только использовались бонусы (оплата не прошла до начисления), отменяется без `amount`: бонусы возвращаются полностью.\n\n### Тело запроса\n\n| Поле | Тип | Обязательное | Правила |\n|---|---|---|---|\n| `reversalId` | string | да | Возврат или отмена на вашей стороне, не более 100 символов, без двоеточия (`:`) — ключ идемпотентности. |\n| `amount` | number | нет | Возвращённая сумма в леях: больше нуля, не более двух знаков после точки, не больше неотменённого остатка. |\n\n### Ответ\n\n**201** — новая отмена. **200** — отмена уже была сделана (`duplicate: true`).\n\nПоля такие же, как в «Начислить за заказ»: отзываемое приходит отрицательным (единица программы), остальное 0; `bonusRestored` — возвращённые бонусы.\n\n### Ошибки\n\n| HTTP | `errorCode` | Когда |\n|---|---|---|\n| 400 | `INTEGRATIONS_LOYALTY_AMOUNT_INVALID`, валидация поля | Неверная сумма, нет `reversalId`. |\n| 400 | `INTEGRATIONS_LOYALTY_REFERENCE_COLON` | «Ссылка на возврат не может содержать двоеточие («:»). Используйте буквы, цифры, дефис или косую черту.» |\n| 404 | — | За заказ у этого продавца ничего не начислялось и бонусы в нём не использовались. Пустое тело. |\n| 422 | `LOYALTY_ORDER_REVERSAL_FULL_ONLY_WITHOUT_ACCRUAL` | «За заказ WEB-10482 ничего не начислялось, в нём только использованы бонусы: его отмена возвращает все бонусы, поэтому отправляется без суммы.» |\n| 422 | `LOYALTY_REVERSAL_EXCEEDS_REMAINING` | «По заказу WEB-10482 осталось неотменённых 500,00; отменить 600,00 нельзя.» |\n| 422 | `LOYALTY_REVERSAL_AMOUNT_MISMATCH` | «Отмена RET-2231 уже проведена на сумму 249,90. Для другой суммы используйте новую ссылку отмены.» |\n| 403 | — | У ключа нет нужного scope, или у продавца не активен модуль «API интеграции» либо модуль «Программы лояльности». Тело пустое. |\n| 401 | — | Ключ отсутствует, неверен, отозван или истёк. Пустое тело. |\n| 429 | `RATE_LIMITED` | Слишком много запросов; см. «Лимиты» в описании коллекции. |\n| 503 | `SERVICE_UNAVAILABLE` | Внутренний сервис не отвечает. Повторите через несколько секунд. |\n\n### Обратите внимание\n\n- При тайм-ауте отправьте повторно с тем же `reversalId`: отмена произойдёт только один раз. Для нового возврата нужен новый `reversalId`.\n- Баланс не уходит ниже нуля: списывается только то, что ещё есть на карте. Уже потраченное участником не взыскивается — ни сейчас, ни из следующих начислений.\n- Использованные в заказе бонусы возвращаются в той же доле, что и возвращённая сумма от оплаченной деньгами: заказ 749,90, бонусы 100, оплачено 649,90 — возврат 324,95 возвращает 50.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reversalId\": \"RET-2231\",\n  \"amount\": 249.9\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/accruals/:orderId/reversals",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "accruals",
                    ":orderId",
                    "reversals"
                  ],
                  "variable": [
                    {
                      "key": "orderId",
                      "value": "WEB-10482",
                      "description": "Comanda acordată (URL-encoded)."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"reversalId\": \"RET-2231\",\n  \"amount\": 249.9\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"orderId\": \"WEB-10482\",\n  \"reversalId\": \"RET-2231\",\n  \"duplicate\": false,\n  \"amount\": 249.9,\n  \"points\": -24,\n  \"stamps\": 0,\n  \"bonus\": 0,\n  \"remainingAmount\": 500,\n  \"bonusRestored\": 0,\n  \"balance\": {\n    \"unit\": \"points\",\n    \"value\": 170,\n    \"lifetime\": 414\n  },\n  \"bonusBalance\": null\n}"
            },
            {
              "name": "422 больше остатка",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/accruals/:orderId/reversals",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "accruals",
                    ":orderId",
                    "reversals"
                  ],
                  "variable": [
                    {
                      "key": "orderId",
                      "value": "WEB-10482",
                      "description": "Comanda acordată (URL-encoded)."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"reversalId\": \"RET-2231\",\n  \"amount\": 249.9\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"LOYALTY_REVERSAL_EXCEEDS_REMAINING\",\n  \"status\": 422,\n  \"detail\": \"Din comanda WEB-10482 au rămas neanulați 500,00; nu se pot anula 600,00.\",\n  \"errorCode\": \"LOYALTY_REVERSAL_EXCEEDS_REMAINING\"\n}"
            }
          ],
          "_anchorName": "Anulează o comandă (retur)"
        }
      ],
      "_anchorName": "8. Fidelitate"
    }
  ]
}
