POS API · для разработчиков касс

Открытый API для кассовых систем

Касса заведения сама узнаёт клиента Unemi, применяет его бонусы и награды к чеку и после оплаты передаёт сумму в Unemi — штамп, бонусы или абонемент проводятся так же, как в кассе Unemi Staff. Для разработчиков кассовых систем и заведений со своей кассой.

Как подключить

  1. Тариф Pro

    Открытый API работает на тарифе Pro. На других тарифах ключ не создать, а запросы ранее созданным ключом получают отказ.

  2. Ключ в кабинете

    Владелец открывает в кабинете раздел «API и интеграции» и создаёт ключ: название — например, по кассе или точке — и права.

  3. Права: точка или всё заведение

    Ключ точки работает только на ней. Ключ на всё заведение называет точку, когда узнаёт клиента и отменяет чек, — id точек есть в том же разделе.

  4. Ключ показывается один раз

    Скопируйте его в настройки кассы на сервере. Unemi хранит только хэш: потерянный ключ не восстановить — создайте новый, а старый отзовите.

Как касса работает с Unemi

Четыре шага на каждый чек. Согласие клиента — на каждый чек: QR или код с его телефона открывает сессию на один чек.

  1. Клиент показывает QR

    Или называет шесть цифр под ним. Касса узнаёт клиента и получает его балансы, награды, купоны и идущие акции.

    posIdentify
  2. Кассир применяет скидку

    Списание бонусов, награду или купон кассир проводит в своей кассе, как обычную скидку.

  3. Чек закрыт — сумма в Unemi

    Касса передаёт номер чека, программу, сумму, списание и погашенные награды, по желанию — кассира и позиции. Клиенту приходит пуш, как после кассы Unemi Staff.

    posApply
  4. Отмена и возврат — 48 часов

    Чек отменили или вернули — касса отменяет его и в Unemi по номеру чека: всё возвращается как было. Частичный возврат касса передаёт с суммой — Unemi покажет его владельцу.

    posUndo

Лимиты

  • 120 запросов в минуту на ключ
  • Сессия узнавания проводит один чек и живёт 2 часа, у ключа кассы — 12 часов
  • Отменить или вернуть чек можно в течение 48 часов
  • В одном чеке — одна программа и до 5 наград и купонов
  • Промахи узнавания за 15 минут: после 5 — пауза 1 минута, после 10 — 5 минут, после 15 — 30 минут
  • До 10 действующих ключей на заведение

Справочник методов

Справочник — на русском: так его читают разработчики касс в Казахстане. Имена методов, полей и кодов ошибок одинаковы на любом языке. Справочник на английском

Общие правила

ПараметрЗначение
Адрес https://api.unemi.kz/<метод>
Методы posIdentify, posApply, posUndo — только POST
Заголовки Authorization: Bearer unemi_<id>_<секрет>, Content-Type: application/json
Тело JSON-объект; ответ — JSON
Время ISO 8601 в UTC: "2026-10-07T09:30:00.000Z"
Деньги и бонусы Целые тенге; 1 бонус = 1 ₸
Id запроса Заголовок ответа X-Unemi-Request-Id (и requestId в ошибке) — его просят у поддержки
Точка Ключ одной точки работает только на ней; ключ на всё заведение передаёт venueId в posIdentify и posUndo, а posApply берёт точку из сессии
Ключ кассы Ключ одной точки с кассовой системой и правилами точки: сессия 12 часов и программа по умолчанию — раздел «Ключ кассы»
Лимит 120 запросов в минуту на ключ
CORS Нет: ключ живёт на сервере кассы, не в браузере
Тексты Названия программ, наград, купонов и акций — как их записало заведение в кабинете; названия уровней и message в ошибках — по-русски

Сессия и согласие клиента

  • Согласие клиента — на каждый чек: QR или шесть цифр с экрана клиента открывают сессию, она проводит один чек и живёт 2 часа, у ключа кассы — 12 часов. Следующий чек — новый QR.
  • QR и код одноразовые: posIdentify гасит их, как скан кассира. Повтор того же запроса тем же ключом (ответ не дошёл) отдаёт ту же сессию — пока по ней не проведён чек и она не истекла; кассе Unemi и другому ключу — code_used.
  • У ключа кассы тот же код или номер телефона в пределах 12 часов отдаёт ту же открытую сессию и после того, как сам код устарел (живёт около минуты): касса спрашивает карту клиента несколько раз за заказ. Свежий код другого клиента с теми же цифрами важнее — он гасится и открывает новую сессию.
  • Чек отменили (posUndo) — согласие на него не израсходовано: та же сессия, пока жива, проводит его заново.
  • По номеру телефона находятся только клиенты этого заведения (у них уже есть карта, бонусный счёт или абонемент), а сессия — только для начислений: accrualOnly: true, штампы и бонусы без списания.
  • Номер ничем не подтверждён: списать бонусы, оформить или выдать абонемент, погасить награду можно только по QR или коду с телефона клиента — иначе consent_required. Нового клиента по номеру API не заводит.

Повторы и идемпотентность

  • Ответ не дошёл — повторите тот же запрос: второй раз операция не проводится.
  • posIdentify с тем же QR или кодом от того же ключа отдаёт ту же сессию, пока по ней не проведён чек и она не истекла.
  • posApply с тем же receiptId и теми же данными отдаёт сохранённый ответ с replayed: true — без второй операции и второго пуша клиенту. Тот же receiptId с другими данными или по другой сессии — receipt_conflict. Кассир и позиции (cashier, items) — справка: в сравнение не входят.
  • receiptId уникален в пределах точки: если на точке несколько касс, их номера чеков не должны совпадать — например, с префиксом кассы. После отмены (posUndo) тот же receiptId можно провести заново.
  • Повтор posUndo отдаёт тот же ответ с replayed: true.
  • rate_limited и too_many_attempts — подождите details.seconds секунд; internal — повторите позже.

Ответ с ошибкой

Ошибка — HTTP-статус и тело:

{
  "error": {
    "code": "cooldown",
    "message": "Слишком рано: следующий штамп можно через 12 мин",
    "details": { "minutes": 12 },
    "requestId": "Qk2v9XyZaB7cD1eF3gH5"
  }
}

Касса смотрит на error.code — коды стабильны (таблицы ниже). message — по-русски, как в кассе Unemi; details — числа к коду (минуты, лимит, баланс).

posIdentify — узнать клиента

POST https://api.unemi.kz/posIdentify

QR из приложения Unemi, шесть цифр под ним или номер телефона → сессия на один чек, клиент, балансы, награды и купоны, идущие акции.

Запрос

ПолеОписание
qr Строка QR-кода с экрана клиента: unemi:1:st:… — или шесть цифр цифрового QR (см. «Ключ кассы»)
code Шесть цифр под QR — когда сканера нет
phone Казахстанский номер клиента — только начисления (см. «Сессия и согласие клиента»)
venueId Id точки — для ключа на всё заведение; у ключа точки необязателен

Ровно одно из qr, code, phone.

По QR
{ "qr": "unemi:1:st:Xk2pQ9LmNb7Tz1Rw4Ycd", "venueId": "v8Kp2LmQx1" }
По шести цифрам
{ "code": "482913" }
По номеру телефона
{ "phone": "+7 701 123 45 67" }

Ответ

Ответ
{
  "session": "Gm3kQ8rT2vXw9LpZa1Yc",
  "expiresAt": "2026-10-07T11:30:00.000Z",
  "identifiedBy": "qr",
  "accrualOnly": false,
  "customer": {
    "id": "cus_4f1c9a0b2d7e8f3a6b5c1d2e",
    "name": "Айгерим"
  },
  "level": {
    "level": 3,
    "name": "Друг",
    "metric": "visits",
    "value": 18
  },
  "selectedProgramId": "p8Fh2kLm",
  "programs": [
    {
      "id": "p8Fh2kLm",
      "type": "stamps",
      "name": "Штамп-карта",
      "locked": false,
      "minLevel": 1,
      "stamps": {
        "count": 3,
        "required": 6,
        "rewardTitle": "Подарок",
        "rewardPercent": 100,
        "steps": [
          { "stamp": 4, "kind": "discount", "percent": 20, "title": "" }
        ]
      }
    },
    {
      "id": "q2Nw7xRt",
      "type": "bonus",
      "name": "Бонусная карта",
      "locked": false,
      "minLevel": 1,
      "bonus": {
        "balance": 1250,
        "percent": 5,
        "percentBp": 500,
        "levelPercent": 7,
        "levelBp": 700,
        "welcomeBonusPending": false
      }
    },
    {
      "id": "s5Kd1vBn",
      "type": "subscription",
      "name": "Абонемент",
      "locked": false,
      "minLevel": 1,
      "subscription": {
        "item": "Позиция дня",
        "price": 15000,
        "durationDays": 30,
        "lifetime": false,
        "perDay": 1,
        "pass": {
          "expiresAt": "2026-10-20T07:00:00.000Z",
          "expired": false,
          "leftToday": 1,
          "lifetime": false
        }
      }
    }
  ],
  "rewards": [
    {
      "id": "rW7yT2pQ",
      "kind": "reward",
      "title": "Подарок",
      "programId": "p8Fh2kLm",
      "percent": 100,
      "item": null,
      "expiresAt": "2026-10-30T09:00:00.000Z"
    },
    {
      "id": "cP3mN8sD",
      "kind": "coupon",
      "title": "−20% на заказ",
      "programId": null,
      "percent": 20,
      "item": null,
      "expiresAt": "2026-10-08T18:00:00.000Z"
    }
  ],
  "promos": [
    {
      "title": "Акции по расписанию",
      "description": "Бонусы ×2",
      "until": "2026-10-07T11:00:00.000Z",
      "effect": { "kind": "bonus_multiplier", "value": 2 }
    }
  ]
}
ПолеОписание
session, expiresAt Сессия для posApply и до какого времени она действует — 2 часа, у ключа кассы — 12 часов.
identifiedBy, accrualOnly Как узнали клиента: qr (и цифровой QR), code или phone. accrualOnly: true — по номеру: только начисления.
customer id — постоянный id клиента для этого заведения (в другом заведении у того же человека другой); name — имя, как его видит кассир Unemi Staff. Телефона, почты и внутреннего id Unemi касса не получает.
level Уровень клиента, если у заведения включены уровни; иначе null. value — сколько набрано за всё время: дни с визитами (metric: "visits") или сумма покупок в тенге ("spend"); заработанный уровень не понижается.
stamps.steps Награды на промежуточных штампах карты по возрастанию номера: stamp — номер штампа (от 1 до required; на последнем штампе гость получает и награду шага, и главную награду rewardTitle), kind — discount (скидка percent%) или item (товар в подарок, percent — 100), title — название, пусто — назовите по скидке. Нет наград — пустой список.
locked, minLevel locked: true — программа «с уровня», а уровень клиента ниже minLevel: операция по ней ответит level_required (кроме выдачи по уже оплаченному абонементу).
bonus.balance Сколько бонусов можно списать. welcomeBonusPending: true — первый визит: приветственные бонусы начислятся этой же операцией, и их уже можно списать.
bonus.percent, bonus.levelPercent percent — процент программы. levelPercent — процент этого клиента по его уровню, если заведение задало проценты по уровням и он выше percent: по нему posApply и начислит; иначе null.
bonus.percentBp, bonus.levelBp Те же проценты точно, в базисных пунктах: 250 — 2,5%, 10 000 — 100%. Процент бывает дробным (до двух знаков после запятой, от 0 до 100); percent и levelPercent — целые, округлены половиной вверх (2,5% → 3), для касс, подключённых раньше. «Начислится» — по bp: floor((amount − spend) × bp / 10000).
subscription.pass Абонемент клиента; null — не оформлен.
subscription.lifetime Бессрочная программа: durationDays у неё 36 500. pass.lifetime — бессрочный абонемент клиента: дата окончания через сто лет, продлевать его не нужно (pass_lifetime_active).
selectedProgramId Программу клиент выбрал в приложении перед показом QR; null — выбирает кассир.
rewards Непогашенные награды штамп-карт и купоны рассылок в сроке: percent — скидка (100 — бесплатно), item — товар купона.
promos Акции по расписанию на точке сейчас. Эффект (bonus_multiplier, bonus_extra_percent, double_stamps) сервер применит сам при posApply; без эффекта (effect: null) — напоминание кассиру провести скидку.
defaultProgram Только у ключа кассы: по какой программе пройдёт чек без programId и action — { "programId": "q2Nw7xRt", "action": "bonus" }: выбранная клиентом в приложении штамп-карта или бонусная карта, иначе программа по умолчанию ключа; null — ни того, ни другого. У обычного ключа поля нет.

posApply — провести закрытый чек

POST https://api.unemi.kz/posApply

Чек закрыт и оплачен → операция по программе и погашение наград. Операция, погашения наград, визит для уровней, подарки «Приведи друга» и подарки за новый уровень — одной транзакцией: либо всё, либо ничего.

Запрос

ПолеОписание
session Из ответа posIdentify.
receiptId Id чека в кассе: 1–64 печатных знака ASCII без пробелов или целое неотрицательное число. Ключ идемпотентности — в пределах точки.
programId, action Операция по программе — парой (действия ниже). Без пары — только погашение наград, а у ключа кассы чек с amount — по программе по умолчанию.
amount Сумма чека, ₸. Для bonus обязательна (1 … 10 000 000); у остальных необязательна (целое 0 … 10 000 000) и хранится для разборов.
spend Сколько бонусов списано в этом чеке; только для bonus (у ключа кассы без программы — тоже, тогда программа по умолчанию должна быть бонусной картой), не больше amount и баланса.
rewardIds До 5 наград и купонов из posIdentify, применённых к чеку.
cashier Необязательно: кто провёл чек — { "id": "1234", "name": "Вдовин Б.А." }; id — код сотрудника в кассе (до 64 знаков без пробелов), имя — до 100 знаков. Хранится в чеке для разборов и антифрода.
items Необязательно: до 100 позиций — { "code": "7644", "name": "Позиция дня", "qty": 2, "amount": 2400 }; код товара кассы — до 40 знаков, количество больше нуля (весовой товар — дробное), сумма после скидок — целые тенге. Нужны правилу ключа кассы «штамп только за товары».

В одном чеке — одна программа и до 5 наград и купонов.

Чек по бонусной карте: списание и купон
{
  "session": "Gm3kQ8rT2vXw9LpZa1Yc",
  "receiptId": "A-1024",
  "programId": "q2Nw7xRt",
  "action": "bonus",
  "amount": 4500,
  "spend": 500,
  "rewardIds": ["cP3mN8sD"]
}

Действия по программам

actionПрограммаЧто делает
stamp Штамп-карта Штамп на карту клиента
bonus Бонусная карта Бонусы с оплаченного деньгами и списание spend
pass_purchase Абонемент Оформить или продлить абонемент
pass_issue Абонемент Выдать по абонементу

Бонусы начисляются с оплаченного деньгами (amount − spend) по проценту программы — вниз до целого бонуса, floor((amount − spend) × bp / 10000), — а клиенту, чей уровень даёт больше, — по проценту его уровня до этого чека (bonus.levelBp из posIdentify); с эффектом акций по расписанию, если он идёт. Прибавки уровня и акции складываются, а не умножаются, и вместе не больше оплаченного.

Ключ кассы без программы. Чек с amount, но без пары programId и action проходит по программе по умолчанию: штамп-карта или бонусная карта, которую клиент выбрал в приложении перед показом QR, иначе программа и действие из правил ключа.

Пороги правил: оплачено деньгами (amount − spend) меньше порога — без начисления; у штампа программы по умолчанию с кодами товаров нужна хотя бы одна позиция с таким кодом (без items — штампа нет). Списание бонусов порог не останавливает, а без бонусной программы оно невозможно — action_mismatch.

Не начислено — ответ всё равно успешный: operation: null и причина в skipped; чек записан, сессия закрыта. У обычного ключа поля skipped нет, а чек без пары и без наград — invalid_request.

Ответ

Ответ
{
  "receiptId": "A-1024",
  "operationId": "bonus:Zx81LmQa0pTt7uVv3wYb",
  "operation": {
    "type": "bonus",
    "programId": "q2Nw7xRt",
    "programName": "Бонусная карта",
    "amount": 4500,
    "spent": 500,
    "earned": 480,
    "welcome": 0,
    "balance": 1230,
    "promo": {
      "title": "Акции по расписанию",
      "kind": "bonus_multiplier",
      "value": 2,
      "extra": 200
    },
    "levelPercent": 7,
    "levelBp": 700,
    "levelExtra": 80
  },
  "redeemed": [
    { "id": "cP3mN8sD", "kind": "coupon", "title": "−20% на заказ" }
  ],
  "referral": null,
  "levelUp": null,
  "replayed": false
}
operation штампа
{
  "type": "stamp",
  "programId": "p8Fh2kLm",
  "programName": "Штамп-карта",
  "stamps": 4,
  "required": 6,
  "added": 1,
  "rewardIssued": false,
  "rewardTitle": null,
  "stepRewards": [{ "stamp": 4, "title": "Скидка 20%", "percent": 20 }],
  "promo": null
}
operation оформления абонемента
{
  "type": "pass_purchase",
  "programId": "s5Kd1vBn",
  "programName": "Абонемент",
  "item": "Позиция дня",
  "price": 15000,
  "expiresAt": "2026-11-19T07:00:00.000Z",
  "renewed": true,
  "lifetime": false
}
operation выдачи по абонементу
{
  "type": "pass_issue",
  "programId": "s5Kd1vBn",
  "programName": "Абонемент",
  "item": "Позиция дня",
  "leftToday": 0,
  "perDay": 1,
  "expiresAt": "2026-10-20T07:00:00.000Z",
  "lifetime": false
}
levelUp с подарками: крупная покупка подняла клиента сразу через уровень (уровни по сумме покупок)
{
  "from": 1,
  "to": 3,
  "name": "Друг",
  "gifts": [
    {
      "level": 2,
      "kind": "coupon",
      "rewardId": "cL4vR9kTq2",
      "title": "−15% на заказ",
      "percent": 15,
      "item": null,
      "expiresAt": "2026-11-05T18:59:59.000Z"
    },
    {
      "level": 3,
      "kind": "bonus",
      "programId": "q2Nw7xRt",
      "programName": "Бонусная карта",
      "amount": 500
    }
  ]
}
ПолеОписание
operationId, operation Проведённая операция; null — в чеке только погашение наград или, у ключа кассы, начислять было нечего (см. skipped).
promo Акции по расписанию, сработавшие в операции: extra — сколько бонусов или штампов добавила акция.
levelPercent, levelBp, levelExtra У бонусной операции: процент уровня клиента, если начислено по нему (levelBp — точно, в базисных пунктах; levelPercent — целым), и сколько бонусов он добавил сверх процента программы (earned — уже с ними); иначе null, null и 0. В примере: Друг — 7% вместо 5% и «бонусы ×2» с 4 000 ₸: 280 по уровню + 200 от акции = 480, а не 560.
rewardIssued true — штамп закрыл карту, у клиента новая награда (rewardTitle); погасить её можно следующим чеком.
stepRewards Награды промежуточных штампов, которые выдал этот чек: номер штампа stamp, название title (без названия — «Скидка 20%» или «Подарок»), скидка percent (100 — товар в подарок). Обычно пусто или одна. Это обычные награды: придут в rewards при узнавании, погасить можно следующим чеком.
redeemed Погашенные награды и купоны из rewardIds.
referral Первый визит приглашённого друга: { "gift": { "kind": "bonus", "amount": 300, "programName": "Бонусная карта" } }; иначе null.
levelUp Клиент поднялся на уровень: from, to, name и gifts — подарки за новый уровень, выданные этим чеком (пусто — подарков нет; через уровень — за каждую ступень); иначе null. Купон: rewardId — id награды (придёт и в rewards при узнавании, погасить можно следующим чеком), title, скидка percent или товар item, срок expiresAt. Бонусы: programId, programName, amount — уже на счёте клиента (если на карте этого чека — и в operation.balance).
skipped Только у ключа кассы, в чеке без программы: почему ничего не начислено — no_program (у ключа нет программы по умолчанию, клиент её не выбрал), below_minimum (оплачено меньше порога), no_qualifying_items (в чеке нет товаров из правила); null — начислено.
replayed true — повтор чека: ответ сохранённый, второй операции не было.

posUndo — отменить чек

POST https://api.unemi.kz/posUndo

Отмена возвращает всё как было до чека: штамп, бонусы, абонемент, погашенные награды и купоны, визит уровня, подарки «Приведи друга» и подарки за новый уровень (купон-подарок, который клиент уже погасил, остаётся у него; потраченные бонусы подарка снимаются, сколько осталось на счёте). Можно в пределах 48 часов после чека: сторно и возврат бывают и в конце смены, и на следующий день.

Запрос

ПолеОписание
receiptId Id чека, проведённого posApply на этой точке.
venueId Только для ключа на всё заведение.
amount Необязательно: сумма возврата, ₸ (целое 0 … 10 000 000). Меньше суммы чека — частичный возврат; без неё или с полной суммой — отмена чека целиком.
Отмена чека
{ "receiptId": "A-1024", "venueId": "v8Kp2LmQx1" }

Повтор отмены отдаёт тот же ответ с replayed: true. Сессия чека снова открыта, пока жива: тот же чек можно провести заново той же сессией.

Отменить нельзя, если после чека карта клиента уже изменилась (undo_not_last), начисленные бонусы потрачены (undo_bonus_spent), награду за этот штамп уже выдали (undo_reward_redeemed) или по оформленному абонементу уже выдавали (undo_pass_used).

Частичный возврат (amount меньше суммы чека): операция остаётся, ответ успешный — partial: true, ничего не отменено.

Частичный возврат и отказы undo_too_late, undo_not_last, undo_bonus_spent, undo_reward_redeemed, undo_pass_used, operation_not_found записываются владельцу в журнал возвратов кабинета, а если он включил «Подозрительные операции» — ещё и сигналом в утренней сводке. Возврат на кассе по ответу posUndo не блокируйте: код отказа значит, что Unemi не отменил начисление, а решает владелец. Удавшаяся отмена снимает запись из журнала.

Ответ

Ответ
{
  "receiptId": "A-1024",
  "undone": ["bonus:Zx81LmQa0pTt7uVv3wYb", "redeem:cP3mN8sD"],
  "undoneAt": "2026-10-07T09:41:12.000Z",
  "replayed": false
}
Частичный возврат
{
  "receiptId": "A-1024",
  "undone": [],
  "undoneAt": null,
  "partial": true,
  "replayed": false
}
ПолеОписание
undone Что отменено: id операции и погашений (redeem:<id награды>); пусто — частичный возврат.
undoneAt Время отмены; null — частичный возврат.
partial true — частичный возврат: ничего не отменено, запись владельцу. При отмене целиком поля нет.
replayed true — повтор отмены: ответ сохранённый.

Коды ошибок

Доступ и запрос

КодHTTPКогдаdetails
unauthorized 401 Нет ключа, ключ не похож на ключ Unemi или не подходит
key_revoked 401 Ключ отозван в кабинете
plan_feature 403 Заведение не на Pro (или оплата просрочена больше 7 дней) plan, feature
method_not_allowed 405 Не POST
invalid_request 400 Тело не JSON-объект или поле не того вида field
rate_limited 429 Больше 120 запросов в минуту на ключ или 30 неудачных входов с одного адреса за минуту seconds, limit
venue_required 400 Ключ на всё заведение, а venueId нет field
venue_forbidden 403 Ключ одной точки, а запрос про другую
venue_not_found 404 Точки нет, она чужая или выключена
internal 500 Сбой на стороне Unemi — повторите позже

Узнавание (posIdentify)

КодHTTPКогдаdetails
unknown_qr 400 Не QR Unemi и не шесть цифр
reward_qr 400 Показан QR награды — нужен QR карты; награды приходят в rewards
invalid_phone 400 Номер не казахстанский
code_not_found 404 Кода нет (опечатка или чужой QR)
code_used 409 Код уже погашен
code_expired 409 Код устарел (живёт около минуты) — клиент обновит экран
customer_not_found 404 По номеру не нашёлся клиент заведения
customer_unknown 400 В коде нет клиента
too_many_attempts 429 Пауза после промахов узнавания seconds

Чек (posApply)

КодHTTPКогдаdetails
session_not_found 404 Сессии нет, она чужая или с точки, недоступной ключу
session_expired 409 Сессии больше 2 часов (у ключа кассы — 12)
session_used 409 По сессии уже проведён другой чек
receipt_conflict 409 Этот чек уже проведён с другими данными operationId
program_not_found 404 Программы нет или она чужая
program_paused 409 Программа выключена
action_mismatch 409 Действие не подходит типу программы; у ключа кассы — списание в чеке без бонусной программы по умолчанию type
level_required 409 Программа с уровня, уровень клиента ниже minLevel, level
cooldown 409 Штамп этой карте на точке был недавно minutes
daily_limit 409 Дневной лимит штампов limit
invalid_amount 400 Сумма или списание не целое в границах max
bonus_insufficient 409 Списание больше баланса balance
pass_not_found 409 Выдача без абонемента
pass_expired 409 Абонемент истёк expiredAtMillis
pass_day_limit 409 Сегодня по абонементу уже выдано limit
pass_lifetime_active 409 У клиента действует бессрочный абонемент — оформлять его снова не нужно
reward_not_found 404 Награды нет или она другого клиента
reward_other_business 403 Награда другого заведения
reward_already_redeemed 409 Награда уже погашена
reward_expired 409 Срок награды вышел
coupon_exhausted 409 Купон уже использовали первые N limit

Отмена (posUndo)

КодHTTPКогдаdetails
receipt_not_found 404 Чек через API на этой точке не проводился
invalid_amount 400 Сумма возврата не целое в границах max
undo_too_late 409 Прошло больше 48 часов (minutes — длина окна отмены, 2880) minutes
undo_already 409 Операцию уже отменили на кассе или в кабинете
undo_not_last 409 После чека карта клиента изменилась
undo_reward_redeemed 409 Награду за этот штамп уже выдали
undo_bonus_spent 409 Начисленные бонусы уже потрачены balance
undo_pass_used 409 По абонементу уже выдавали
operation_not_found 404 Операции чека больше нет (награду удалили)

Безопасность и данные

  • Ключ — unemi_<id ключа, 20 знаков>_<секрет, 48 hex>, 192 случайных бита секрета. Unemi хранит только sha256 секрета и последние четыре знака для узнавания в списке; неверный, кривой и несуществующий ключ — один ответ unauthorized.
  • Чужое заведение недоступно: ключ привязан к заведению, сессия и чек ищутся только в нём; ключ точки видит только свою точку. Чужие сессия, программа, награда и чек отвечают «не найдено».
  • Персональные данные: касса получает имя клиента (как кассир Unemi Staff), балансы, награды и уровень. Телефона, почты и внутреннего id Unemi в ответах нет; id клиента — отдельный псевдоним для каждого заведения.
  • Перебор кодов и номеров: промахи узнавания (code_not_found, customer_not_found) копятся на ключ 15 минут; после 5 — пауза 1 минута, после 10 — 5 минут, после 15 — 30 минут (too_many_attempts). Проводить и отменять чеки пауза не мешает.
  • Журнал запросов в кабинете — без данных клиента, хранится 30 дней.
  • Песочницы нет: интеграцию проверяют на тестовом заведении на тарифе Pro.

Ключ кассы

  • Ключ кассы — ключ API одной точки, отмеченный кассовой системой: r_keeper, iiko, Syrve или своя касса сети. Создаёт владелец в кабинете: «API и интеграции» → «Создать ключ» → «Касса r_keeper». Ключа кассы на всё заведение нет: касса стоит в конкретной точке.
  • У ключа — правила точки: программа по умолчанию и действие (штамп — для штамп-карты, бонусы — для бонусной карты), порог суммы (0 … 10 000 000 ₸) и коды товаров кассы (до 100, каждый до 40 знаков без пробелов), — и язык текстов на экране кассы, один из шести языков Unemi. Правила и язык меняются без нового ключа; для другой кассы создают новый ключ.
  • Что иначе в методах: сессия живёт 12 часов, и тот же код или номер в заказе отдаёт ту же сессию; posIdentify отдаёт defaultProgram; чек без программы проходит по программе по умолчанию, а если ничего не начислено — причина в skipped.
  • Цифровой QR: пока у заведения есть действующий ключ кассы r_keeper, в QR карты клиента — те же шесть цифр, что под ним: сканер r_keeper читает их как номер карты. В posIdentify их передают в qr (identifiedBy: "qr"). Общий QR из меню приложения (без карты) остаётся обычным: клиент называет шесть цифр под ним.
Что владелец задаёт в кабинете: название, точка, касса, язык и правила
{
  "name": "r_keeper · Абая",
  "venueId": "v8Kp2LmQx1",
  "pos": {
    "system": "r_keeper",
    "language": "ru",
    "rules": {
      "programId": "q2Nw7xRt",
      "action": "bonus",
      "minAmount": 0,
      "productCodes": []
    }
  }
}

Мост r_keeper для дилеров Скоро

Модуль HttpFarCard (ExtDllHTTP.dll) кассового сервера r_keeper сам шлёт POST с XML — запрос карты (getcardinfoex) и проводки закрытого чека (transactionsex), — а мост posRkeeper проводит их тем же ядром, что и методы выше: узнаёт клиента, проводит чек, отменяет его при откате и возврате.

ПараметрЗначение
Адрес https://api.unemi.kz/posRkeeper — строка Address в ExtDllHttp.ini
Обработчики GetCardInfoEx=getcardinfoex, TransactionsEx=transactionsex
Вход Базовая авторизация: логин unemi_<id ключа>, пароль — секрет ключа; только https, иначе https_required
Ключ Ключ кассы r_keeper на одну точку: адрес, логин и пароль владелец получает в кабинете при создании ключа и передаёт дилеру. Обычный ключ API или ключ другой кассы — pos_key_required
Привязка Первый запрос запоминает объект r_keeper (его коды в запросах кассы); запрос с другого объекта — venue_mismatch
Ответы XML протокола HttpFarCard. Отказ проводок — Result="1" с причиной в TRRESPONSE; карта не принята — Locked="1" с причиной в WhyLock

Проводки отклоняются, только когда нельзя принять оплату бонусами (например, не хватает бонусов, клиент назван по телефону или нет бонусной программы): чек без оплаты бонусами касса закрывает всегда, а если начисление не прошло, причину владелец видит в журнале запросов.

Откат оплаты, возврат и откат возврата кассу не останавливают: возврат отменяет чек в пределах 48 часов, а что Unemi не отменил, владелец видит в журнале возвратов. Подарки и купоны через r_keeper не гасятся — их выдают в кассе Unemi. Тексты на экране кассы — на языке ключа.